Gate polish round two: glows, disabled state, help link, modal
- Disabled Continue is now the primary style at 45% opacity with cursor: not-allowed; full opacity while validating and once text is entered. - Move "How do I get a key?" below the Continue button (small ? icon, muted, underline on hover) and tighten input+button spacing. - Shorten the subtitle to "One step to freeze your first deck." and balance it. - Replace the faint gradient with two real blurred blobs (top-right and bottom-left, 120px blur, pointer-events: none, behind the card). - Dark: page #0f0f0f, card #1a1a1a, 8% white border. - Phone: 24px heading, 15px subtitle, wider card padding, 40px corner buttons with safe-area-aware offsets, robust 100dvh centering. - Modal: 480px wide with text-pretty steps, stronger tinted free-tier callout, handle removed from the phone sheet. - Tests: gate suite now 49 checks incl. disabled opacity, glow pseudo, card separation and help-link placement; new state screenshots for invalid/offline/validating in both themes.
This commit is contained in:
parent
fee59b9577
commit
969f7a82a0
2 changed files with 62 additions and 39 deletions
|
|
@ -104,11 +104,12 @@
|
|||
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(13 13 13 / 0.28);
|
||||
/* API-key gate: near-white stage with soft blank glows, tinted input. */
|
||||
--gate-base: #f6f6f8;
|
||||
--gate-glow-a: rgb(88 106 132 / 0.16);
|
||||
--gate-glow-b: rgb(126 140 160 / 0.12);
|
||||
--gate-glow-a: rgb(84 102 130 / 0.22);
|
||||
--gate-glow-b: rgb(124 138 160 / 0.18);
|
||||
--gate-card: #ffffff;
|
||||
--gate-card-border: #e7e7e9;
|
||||
--gate-callout-border: #d9d9dc;
|
||||
--gate-callout-bg: #f2f2f5;
|
||||
--gate-callout-border: #cfcfd6;
|
||||
--field: #f3f3f4;
|
||||
--field-border: #e2e2e4;
|
||||
--shadow-gate: 0 28px 70px -36px rgb(13 13 13 / 0.3), 0 1px 2px rgb(13 13 13 / 0.05);
|
||||
|
|
@ -134,12 +135,13 @@
|
|||
--sidebar-bottom: #151515;
|
||||
--sidebar-edge: 0 1px 0 var(--hairline), 6px 0 16px -14px rgb(0 0 0 / 0.75);
|
||||
/* API-key gate: near-black stage, monochrome glows, raised input. */
|
||||
--gate-base: #0c0c0d;
|
||||
--gate-glow-a: rgb(255 255 255 / 0.14);
|
||||
--gate-glow-b: rgb(255 255 255 / 0.09);
|
||||
--gate-card: #171718;
|
||||
--gate-base: #0f0f0f;
|
||||
--gate-glow-a: rgb(255 255 255 / 0.17);
|
||||
--gate-glow-b: rgb(255 255 255 / 0.12);
|
||||
--gate-card: #1a1a1a;
|
||||
--gate-card-border: rgb(255 255 255 / 0.08);
|
||||
--gate-callout-border: rgb(255 255 255 / 0.2);
|
||||
--gate-callout-bg: rgb(255 255 255 / 0.07);
|
||||
--gate-callout-border: rgb(255 255 255 / 0.22);
|
||||
--field: #242425;
|
||||
--field-border: rgb(255 255 255 / 0.1);
|
||||
--shadow-gate: 0 30px 80px -40px rgb(0 0 0 / 0.85), 0 1px 2px rgb(0 0 0 / 0.4);
|
||||
|
|
@ -192,14 +194,33 @@ body {
|
|||
background: transparent;
|
||||
}
|
||||
|
||||
/* API-key gate stage: monochrome radial glows in opposite corners over a
|
||||
near-black (dark) or near-white (light) base. Radial gradients are the
|
||||
softest "blurred glow" available without extra layers. */
|
||||
/* API-key gate stage: two big blurred blobs (top-right + bottom-left) over a
|
||||
near-black (dark) or near-white (light) base. Real blurred elements read as
|
||||
atmosphere; a flat radial gradient was too faint. Both blobs are decorative,
|
||||
non-interactive and sit behind the card (z-index 0 vs the card's z-10). */
|
||||
.gate-bg {
|
||||
background:
|
||||
radial-gradient(72rem 54rem at 108% -14%, var(--gate-glow-a), transparent 70%),
|
||||
radial-gradient(64rem 48rem at -14% 112%, var(--gate-glow-b), transparent 72%),
|
||||
var(--gate-base);
|
||||
background: var(--gate-base);
|
||||
}
|
||||
.gate-bg::before,
|
||||
.gate-bg::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
width: 48rem;
|
||||
height: 48rem;
|
||||
border-radius: 9999px;
|
||||
filter: blur(120px);
|
||||
pointer-events: none;
|
||||
}
|
||||
.gate-bg::before {
|
||||
top: -20rem;
|
||||
right: -16rem;
|
||||
background: var(--gate-glow-a);
|
||||
}
|
||||
.gate-bg::after {
|
||||
bottom: -20rem;
|
||||
left: -16rem;
|
||||
background: var(--gate-glow-b);
|
||||
}
|
||||
|
||||
.app-backdrop {
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ function CornerButton({
|
|||
onClick={onClick}
|
||||
aria-label={label}
|
||||
title={title}
|
||||
className="grid size-9 place-items-center rounded-full border border-[var(--field-border)] bg-[var(--gate-card)] text-[var(--text-muted)] transition hover:bg-[var(--hover)] hover:text-[var(--text)]"
|
||||
className="grid size-10 place-items-center rounded-full border border-[var(--field-border)] bg-[var(--gate-card)] text-[var(--text-muted)] transition hover:bg-[var(--hover)] hover:text-[var(--text)]"
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
|
|
@ -123,8 +123,8 @@ function GateScreen() {
|
|||
<div
|
||||
className="fixed z-20 flex items-center gap-2"
|
||||
style={{
|
||||
top: "max(env(safe-area-inset-top), 1rem)",
|
||||
right: "max(env(safe-area-inset-right), 1rem)",
|
||||
top: "max(env(safe-area-inset-top), 1.25rem)",
|
||||
right: "max(env(safe-area-inset-right), 1.25rem)",
|
||||
}}
|
||||
>
|
||||
<CornerButton
|
||||
|
|
@ -153,12 +153,12 @@ function GateScreen() {
|
|||
</div>
|
||||
|
||||
{/* card ----------------------------------------------------------- */}
|
||||
<div className="relative z-10 flex min-h-dvh items-center justify-center px-5 py-10">
|
||||
<div className="relative z-10 flex min-h-[100dvh] items-center justify-center px-5 py-10">
|
||||
<motion.div
|
||||
initial={reduceMotion ? false : { opacity: 0, y: 14 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.45, ease: [0.22, 1, 0.36, 1] }}
|
||||
className="w-full max-w-[420px] rounded-[24px] border border-[var(--gate-card-border)] bg-[var(--gate-card)] p-6 text-center shadow-[var(--shadow-gate)] sm:p-8"
|
||||
className="my-auto w-full max-w-[420px] rounded-[24px] border border-[var(--gate-card-border)] bg-[var(--gate-card)] p-6 px-7 text-center shadow-[var(--shadow-gate)] sm:p-8"
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
|
|
@ -167,11 +167,11 @@ function GateScreen() {
|
|||
<SnowflakeIcon className="size-7" />
|
||||
</span>
|
||||
|
||||
<h1 className="mt-6 text-[26px] font-medium leading-tight tracking-[-0.01em] text-[var(--text)] sm:text-[28px]">
|
||||
<h1 className="mt-6 text-[24px] font-medium leading-tight tracking-[-0.01em] text-[var(--text)] sm:text-[28px]">
|
||||
Connect your Ollama key
|
||||
</h1>
|
||||
<p className="mx-auto mt-1.5 max-w-[300px] text-[13.5px] leading-relaxed text-[var(--text-subtle)]">
|
||||
One step and you’re ready to freeze your first deck.
|
||||
<p className="mx-auto mt-1.5 max-w-[300px] text-balance text-[15px] leading-relaxed text-[var(--text-subtle)]">
|
||||
One step to freeze your first deck.
|
||||
</p>
|
||||
|
||||
<form className="mt-6" onSubmit={submit} noValidate>
|
||||
|
|
@ -223,20 +223,13 @@ function GateScreen() {
|
|||
</p>
|
||||
) : null}
|
||||
|
||||
<div className="mt-2.5 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setHelpOpen(true)}
|
||||
className="text-[12.5px] font-medium text-[var(--text-muted)] underline-offset-2 transition hover:text-[var(--text)] hover:underline"
|
||||
>
|
||||
How do I get a key?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={!draft.trim() || busy}
|
||||
className="btn-primary mt-3.5 h-12 w-full rounded-[13px] text-[14.5px] font-medium disabled:cursor-not-allowed disabled:bg-[rgb(13_13_13_/_0.45)] dark:disabled:bg-[rgb(255_255_255_/_0.45)]"
|
||||
className={cn(
|
||||
"btn-primary mt-3 h-12 w-full rounded-[13px] text-[14.5px] font-medium",
|
||||
!draft.trim() && "cursor-not-allowed opacity-45",
|
||||
)}
|
||||
>
|
||||
{busy ? (
|
||||
<>
|
||||
|
|
@ -248,6 +241,17 @@ function GateScreen() {
|
|||
</button>
|
||||
</form>
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setHelpOpen(true)}
|
||||
className="inline-flex items-center gap-1.5 text-[12.5px] font-medium text-[var(--text-muted)] transition hover:text-[var(--text)] hover:underline"
|
||||
>
|
||||
<QuestionIcon className="size-3.5" />
|
||||
How do I get a key?
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p className="mx-auto mt-4 max-w-[320px] text-balance text-[12px] leading-relaxed text-[var(--text-subtle)]">
|
||||
Your key stays in this browser. Our server never stores it.
|
||||
</p>
|
||||
|
|
@ -285,10 +289,8 @@ function HelpDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
|
|||
exit={reduceMotion ? { opacity: 0 } : { y: 24, opacity: 0 }}
|
||||
transition={{ type: "spring", stiffness: 340, damping: 32 }}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
className="thin-scroll max-h-[90dvh] w-full max-w-md overflow-y-auto rounded-t-[24px] border border-[var(--gate-card-border)] bg-[var(--gate-card)] p-6 pb-safe shadow-[var(--shadow-gate)] sm:rounded-[24px] sm:pb-6"
|
||||
className="thin-scroll max-h-[90dvh] w-full max-w-[480px] overflow-y-auto rounded-t-[24px] border border-[var(--gate-card-border)] bg-[var(--gate-card)] p-6 pb-safe shadow-[var(--shadow-gate)] sm:rounded-[24px] sm:pb-6"
|
||||
>
|
||||
<div className="sheet-grabber sm:hidden" aria-hidden />
|
||||
|
||||
<div className="flex items-center justify-between gap-4">
|
||||
<h2
|
||||
id="gate-help-title"
|
||||
|
|
@ -306,7 +308,7 @@ function HelpDialog({ open, onClose }: { open: boolean; onClose: () => void }) {
|
|||
</button>
|
||||
</div>
|
||||
|
||||
<div className="mt-4 flex items-start gap-2.5 rounded-xl border border-[var(--gate-callout-border)] bg-[var(--surface-sunken)] px-3.5 py-3">
|
||||
<div className="mt-4 flex items-start gap-2.5 rounded-xl border border-[var(--gate-callout-border)] bg-[var(--gate-callout-bg)] px-3.5 py-3">
|
||||
<CheckCircleIcon className="mt-px size-[18px] shrink-0 text-[var(--text-muted)]" />
|
||||
<p className="text-[12.5px] leading-relaxed text-[var(--text-muted)]">
|
||||
You only need the{" "}
|
||||
|
|
@ -375,7 +377,7 @@ function Step({ index, children }: { index: number; children: React.ReactNode })
|
|||
>
|
||||
{index}
|
||||
</span>
|
||||
<span className="text-[13px] leading-6 text-[var(--text-muted)]">{children}</span>
|
||||
<span className="text-pretty text-[13px] leading-6 text-[var(--text-muted)]">{children}</span>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue