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
|
|
@ -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