/* ===========================================================================
   account.hemishthanki.com — the POST OFFICE flows: /signup/, /verify/ and
   /reset/. The screens a person meets while they are still becoming a member,
   or getting back in.

   LOADED AFTER /account.css, and built on its first three sections (tokens,
   base, shared components): .login / .login-card / .btn / .field / .input /
   .msg / .note / .chip come from there untouched. This file adds only what a
   card that CHANGES needs, and never defines an acc-* or gate-* class (those
   belong to the panel and the sign-in screen).

   CLASS NAMES ARE THE OLD ONES ON PURPOSE. signup.js, verify.js and reset.js
   add and remove auth-*, vf-*, row-*, is-in/is-out/is-enter/is-leave,
   is-morphing, is-stamped, is-busy, is-bad, is-full and is-on by name, and the
   flows' logic was deliberately left alone in the rebuild — so the names are an
   interface, and everything new is prefixed flow-.

   §1 THE CARD         widths, the height morph, one screen giving way to the next
   §2 SEALS & MARKS    the seal at rest, the sealed letter, badges, step icon
   §3 FORM PIECES      step line, two-column names, counter, passkey offer,
                       the code box, the strength meter, notes
   §4 IN FLIGHT        spinners and the wrong-field shake
   §5 SIGN-UPS CLOSED  the notice that replaces the form
   §6 THE STAMP        the done screen: the seal coming down, the postmark, the
                       lines rising in behind it, the apps you can reach
   §7 SMALL SCREENS
   §8 REDUCED MOTION

   WHY THERE IS MOTION HERE AND ALMOST NONE IN THE PANEL. This is the one flow
   where somebody waits on a thing they cannot see — a message in the post, a
   decision taken on a server — and where the page answers by replacing
   itself. A card that CUTS from one state to another reads as a reload, and a
   reload is exactly what people fear they have caused when a code or a link
   misbehaves. The motion says "the same card is still here, it is now saying
   something else". The scripts WAIT on some of it (transitionend), which is
   why §8 shortens rather than removes.
   =========================================================================== */

/* Timings, one place. Longer than --dur: 200 ms is right for a control
   answering a finger and far too quick for a screen changing its mind. */
.vf, .auth-card {
  --flow-view: 220ms;                                /* one screen giving way to the next */
  --flow-morph: 300ms;                               /* the card growing or shrinking to fit it */
  --flow-out: cubic-bezier(.12, .78, .26, 1);        /* firmer than --ease: lands and stops */
  /* The seal's own ink — hard-coded, not themed, for the reason the seal is a
     fixed red: a stamp that changes colour with its surroundings stops reading
     as a stamp. Lifted a little in the dark so it still shows on the panel. */
  --flow-ink: rgba(179, 18, 42, .55);
  --flow-ink-soft: rgba(179, 18, 42, .38);
  /* The envelope's paper: the warm canvas in the light, a lifted grey in the
     dark (the dark canvas would read as a hole in the card, not a letter). */
  --flow-paper: var(--bg);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .vf, :root:not([data-theme="light"]) .auth-card {
    --flow-ink: rgba(242, 92, 108, .55); --flow-ink-soft: rgba(242, 92, 108, .36);
    --flow-paper: var(--panel3);
  }
}
:root[data-theme="dark"] .vf, :root[data-theme="dark"] .auth-card {
  --flow-ink: rgba(242, 92, 108, .55); --flow-ink-soft: rgba(242, 92, 108, .36);
  --flow-paper: var(--panel3);
}

/* ============================== §1 THE CARD =============================== */

/* Sign-up's form is seven fields and verify's done screen lists up to twelve
   apps, so those two cards are wider than the sign-in card — enough for the
   names to pair up and the apps to sit two abreast. The same width on every
   screen of a card: a card that changed WIDTH between screens as well as
   height would read as a different card. */
.login-card.flow-wide { max-width: 540px; }

/* HEIGHT IS ANIMATED, not left to snap. The screens differ by hundreds of
   pixels (seven fields against a code box), and the footer beneath the card
   would otherwise jump while the new screen is still fading in — one gesture
   torn into two. signup.js / reset.js / verify.js set the height for the
   length of a swap and then hand it back; they wait for THIS transition's
   transitionend (with a timer behind it), so it must exist. .login-card
   already clips (its airmail stripe needs it), which is all .is-morphing
   would add; the class is kept styled so the intent is on the page. */
.auth-card, .vf-card { transition: height var(--flow-morph) var(--ease); }
.vf-card.is-morphing { overflow: hidden; }

/* One screen inside the card. Both directions are animated — a screen that
   fades out upwards and the next in from below is a page turning; a screen
   that simply vanishes is a page torn out. .is-in / .is-enter are the state a
   screen is shown IN and immediately released from; .is-out / .is-leave the
   state it leaves by. */
.auth-view, .vf-view {
  transition: opacity var(--flow-view) var(--ease), transform var(--flow-view) var(--ease);
}
.auth-view.is-in, .vf-view.is-enter { opacity: 0; transform: translateY(10px); }
/* A screen on its way out is not a screen to press: after a code is accepted,
   "Use a different email" is still there for the length of the fade. signup.js
   refuses the press too (isLive), which also covers the key. */
.auth-view.is-out, .vf-view.is-leave { opacity: 0; transform: translateY(-8px); pointer-events: none; }

/* Every swap hands focus to the new screen's heading (tabindex="-1") so a
   screen reader starts where the page now is. That focus is the script's, not
   the person's; on a flow typed start to finish the browser still reads it as
   keyboard focus and would ring a heading nobody can operate. */
.auth-card [tabindex="-1"]:focus-visible,
.vf-card [tabindex="-1"]:focus-visible { outline: none; }

/* Two paragraphs in a row under a title are one statement in two breaths. */
.login-card .login-sub + .login-sub { margin-top: -12px; }

/* ============================ §2 SEALS & MARKS ============================ */

/* The seal at rest, on every screen that is asking rather than announcing. It
   arrives with a quarter-turn of settle — a seal being set down, not a picture
   appearing. The stamp in §6 is the only place it lands HARD. */
.auth-card .login-seal, .vf-card .login-seal, .auth-seal, .vf-seal {
  display: block; margin: 0 auto 16px; object-fit: contain;
  filter: var(--seal-shadow);
  animation: flowSealIn 520ms var(--ease) 60ms both;
}
.vf-seal { width: 96px; height: 96px; }
.vf-seal-sm { width: 76px; height: 76px; }
@keyframes flowSealIn { from { opacity: 0; transform: scale(.9) rotate(-8deg); } to { opacity: 1; transform: none; } }

/* A seal with a mark on it: what THIS screen is about, in one glyph, on the
   seal's shoulder — a key to choose a password, a tick once it is changed, a
   broken link for a refused one. Cut out of the seal by a ring of the card's
   own colour, so it reads as a badge on top, in either theme. */
.flow-mark { position: relative; width: max-content; margin: 0 auto 16px; }
.flow-mark > img { margin: 0; }
.flow-mark-badge {
  position: absolute; right: -4px; bottom: 0;
  display: grid; place-items: center;
  width: 36px; height: 36px; border-radius: 50%;
  background: var(--accent); color: var(--on-accent);
  box-shadow: 0 0 0 3px var(--panel), var(--e1);
  animation: flowPop 380ms var(--flow-out, var(--ease)) 320ms both;
}
.flow-mark-badge .material-symbols-rounded { font-size: 20px; font-variation-settings: "FILL" 1, "wght" 500, "GRAD" 0, "opsz" 20; }
/* --panel as the glyph colour: white on the light theme's deep green/amber,
   near-black on the dark theme's bright ones — ≥ 4.5:1 both ways. */
.flow-mark-badge.is-ok { background: var(--ok); color: var(--panel); }
.flow-mark-badge.is-warn { background: var(--warn); color: var(--panel); }
@keyframes flowPop { from { opacity: 0; transform: scale(.4); } 70% { transform: scale(1.08); } to { opacity: 1; transform: none; } }

/* THE SEALED LETTER, on "Check your email". Not a picture of an inbox: an
   airmail envelope in the same red-and-blue the card's top edge wears, closed
   with the Post Office seal — the same seal that is inline in the email on its
   way to them, so the two can be matched by eye. Pure CSS around the one image,
   and aria-hidden: the heading says what happened. */
.flow-letter {
  --lw: 212px; --lh: 134px;
  position: relative; width: var(--lw); height: var(--lh); margin: 6px auto 26px;
  border-radius: 12px;
  background: var(--flow-paper);
  box-shadow: inset 0 0 0 1px var(--line-strong), var(--e2);
  transform: rotate(-2.5deg);
  animation: flowLetterIn 560ms var(--ease) both;
}
/* The airmail edge: a striped frame, cut to a 7px ring by a two-layer mask so
   the paper in the middle stays paper. */
.flow-letter::before {
  content: ""; position: absolute; inset: 0; padding: 7px; border-radius: inherit;
  background: repeating-linear-gradient(-45deg, #d64545 0 10px, transparent 10px 15px, #3a64c8 15px 25px, transparent 25px 30px);
  opacity: .85;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask: linear-gradient(#000 0 0) content-box exclude, linear-gradient(#000 0 0);
}
/* The flap: a shaded triangle from the two top corners down to where the seal
   closes it. */
.flow-letter::after {
  content: ""; position: absolute; left: 7px; right: 7px; top: 7px; height: 56%;
  background: color-mix(in srgb, var(--ink) 7%, var(--flow-paper));
  clip-path: polygon(0 0, 100% 0, 50% 100%);
}
.flow-letter-seal {
  position: absolute; z-index: 1; left: 50%; top: calc(7px + 56% - 42px);
  width: 84px; height: 84px; margin-left: -42px;
  filter: var(--seal-shadow);
  animation: flowSealPress 420ms var(--flow-out) 300ms both;
}
@keyframes flowLetterIn { from { opacity: 0; transform: translateY(14px) rotate(-7deg); } to { opacity: 1; transform: rotate(-2.5deg); } }
@keyframes flowSealPress { from { opacity: 0; transform: scale(1.45) rotate(-12deg); } 60% { opacity: 1; } to { opacity: 1; transform: none; } }

/* "Save a passkey": not the post's screen — the new account's first act — so
   it wears the glyph for what it offers, on the same squircle the panel's app
   tiles use, rather than the seal. */
.step-icon {
  display: grid; place-items: center;
  width: 84px; height: 84px; margin: 2px auto 18px;
  border-radius: 28px;
  background: linear-gradient(150deg, var(--accent-tint-strong), var(--accent-tint));
  color: var(--accent-ink);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 16%, transparent), var(--e1);
  animation: flowPop 460ms var(--ease) 60ms both;
}
.step-icon .material-symbols-rounded { font-size: 42px; font-variation-settings: "FILL" 0, "wght" 400, "GRAD" 0, "opsz" 40; }

/* The "account created" chip over the passkey step: the account exists by
   then, and saying so is what makes "Not now" feel like a full answer. */
.flow-badge { display: flex; justify-content: center; margin: 0 0 10px; }

/* ============================= §3 FORM PIECES ============================= */

/* "Step 1 of 2 · About you" — where you are in a flow that will send you away
   to your inbox and back. Two short bars and the words; the words carry it. */
.flow-step {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  margin: 0 0 10px;
  font: 500 12.5px/1.2 var(--font-display); letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent-ink);
}
.flow-step-bars { display: inline-flex; gap: 4px; }
.flow-step-bars > span { width: 18px; height: 4px; border-radius: 999px; background: var(--line-strong); }
.flow-step-bars > span.is-on { background: var(--accent); }

/* First/middle, then last/birthday, side by side where there is room. Source
   order is untouched, so reading and tab order are the form's own. */
.flow-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: 14px; }

/* "optional", as a quiet tag rather than a second label. */
.field-opt {
  display: inline-block; margin-left: 6px; padding: 0 8px;
  border-radius: 999px; background: var(--panel2); color: var(--ink2);
  /* 16px tall inside an 18px label line, so the paired field beside it does
     not sit a pixel lower than its neighbour */
  font: 400 12px/16px var(--font-body); letter-spacing: 0; vertical-align: 1px;
}

textarea.input { display: block; min-height: 100px; resize: vertical; line-height: 1.45; font-family: var(--font-body); }
/* iOS centres a date's value and then disagrees with every other field. */
.input[type="date"]::-webkit-date-and-time-value { text-align: left; }
.input[type="date"] { min-width: 0; }

/* The hint and the live count share a line: the hint on the left, the count
   pinned right. Mono so the digits do not jitter as it climbs. */
.field.flow-count { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 6px 12px; }
.field.flow-count > .field-label, .field.flow-count > .input { grid-column: 1 / -1; }
.auth-count {
  margin: 0; align-self: start; white-space: nowrap;
  font: 400 12.5px/1.45 var(--font-mono); color: var(--ink3);
  transition: color var(--dur) var(--ease);
}
.auth-count.is-full { color: var(--danger-ink); font-weight: 500; }

/* Show/hide inside .field-box — account.css places it; this only gives the
   class a body so the name means something. */
.auth-eye { color: var(--ink2); }

/* THE PASSKEY OFFER: a box, not a bare checkbox — the one thing on the form
   that is a choice rather than a fact about the person. The label is the whole
   row (≥ 48px), so the tap target is the sentence, not a 20px square. */
.auth-check {
  margin: 2px 0 18px; padding: 2px 14px 12px;
  border: 1.5px solid var(--line-strong); border-radius: var(--radius-sm);
  background: var(--panel); text-align: left;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.auth-check label {
  display: flex; align-items: center; gap: 12px; min-height: 48px; cursor: pointer;
  font: 500 15px/1.3 var(--font-display); color: var(--ink);
}
.auth-check input { width: 20px; height: 20px; margin: 0; flex: none; accent-color: var(--accent); cursor: pointer; }
.auth-check .flow-check-ic { margin-left: auto; color: var(--accent-ink); font-size: 24px; }
/* The hint hangs under the sentence, not under the box, so the two read as one. */
.auth-check .field-hint { margin: -4px 0 0 32px; }
/* Ticked, it takes the "selected" look. Only a flourish: where :has() is not
   supported the tick itself still says it. */
.auth-check:has(input:checked) { border-color: var(--accent-tint-strong); background: var(--accent-tint); }
.auth-check:has(input:focus-visible) { outline: 3px solid var(--focus); outline-offset: 2px; }
.auth-check input:focus-visible { outline: none; }

/* The address the code went to, quoted back inside the sentence: this is the
   screen whose one job is to be checked character by character. Breakable —
   an address is one unbroken word and a long one would widen the card. */
.sent-addr {
  padding: 1px 7px; border-radius: 7px;
  background: var(--accent-tint); color: var(--accent-ink);
  font-weight: 500; overflow-wrap: anywhere;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
}

/* THE CODE BOX. Big, mono, centred and spaced: six characters copied across
   from another screen, often one at a time, each checkable against the mail at
   a glance. tabular-nums so no digit is narrower than another. The text-indent
   repays the letter-spacing (added after every glyph, the last included) so the
   group sits truly centred.
   IT KEEPS THE BORDER EVERY OTHER .input HAS AT REST (line-strong, ink3 on
   hover, accent on focus). Tinted AND borderless it read as a disabled slab —
   and a tint with a faint border is the kit's read-only style — so it sat
   under the email field on /verify/ looking like nothing to type into. The
   placeholder in both pages ("Enter the code from the email") says the rest. */
.code-input {
  min-height: 64px; padding: 10px 12px;
  background: var(--panel2);
  font: 500 28px/1.2 var(--font-mono); font-variant-numeric: tabular-nums;
  letter-spacing: .32em; text-indent: .32em; text-align: center;
}
.code-input:focus { background: var(--panel); }
.code-input::placeholder { font: 400 15px/1.2 var(--font-body); letter-spacing: normal; text-indent: 0; }

/* The strength measure on /reset/. FOUR SEGMENTS IN ONE HUE, filling left to
   right; the sentence under them says what the fill means. Nothing is encoded
   in colour: the server's only rule is ten characters, so red or green would
   claim a judgement the rule does not make. Fatter when on as well as darker,
   for anybody who cannot tell the two apart. */
.auth-meter { display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin: 4px 2px 0; }
.auth-meter span {
  height: 5px; border-radius: 999px; background: var(--line);
  transition: background-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.auth-meter span.is-on { background: var(--accent); transform: scaleY(1.3); }

/* The refused-link explanation, between the card's sentence and the form it
   asks you to use. */
.auth-note { margin: -6px 0 20px; text-align: left; }

/* The quiet ways out under a form. */
.auth-alt, .vf-alt { margin: 20px 0 0; text-align: center; color: var(--ink2); font-size: 14.5px; }
.auth-alt a, .vf-alt a {
  display: inline-block; padding: 4px 2px;
  color: var(--accent-ink); font-weight: 500; text-decoration: none;
}
.auth-alt a:hover, .vf-alt a:hover { text-decoration: underline; }
.vf-alt span { color: var(--ink3); padding: 0 4px; }

/* ============================== §4 IN FLIGHT ============================== */

/* verify.js's spinner. account.css's .btn-spin is placed BEFORE the icon and
   hides it with a sibling rule; verify's markup puts its .vf-spin AFTER the
   icon, so it needs its own pair. The spinner stands in for the icon rather
   than joining it, so the button never changes width mid-send. */
.vf-spin {
  display: none; width: 18px; height: 18px; flex: none;
  border: 2.5px solid currentColor; border-right-color: transparent; border-radius: 50%;
  animation: spin .8s linear infinite;
}
.vf .btn.is-busy .vf-spin { display: inline-block; }
.vf .btn.is-busy .material-symbols-rounded { display: none; }

/* A press the finger can feel. */
.auth-card .btn:active:not(:disabled), .vf-card .btn:active:not(:disabled) { transform: scale(.985); }

/* verify.js shakes the INPUT once per submit (sign-up and reset shake the
   .field, via account.css's .field.is-bad). The class comes off on
   animationend, so the next submit can shake again. */
.vf-shake { animation: shake 360ms var(--ease) 1; }

/* =========================== §5 SIGN-UPS CLOSED =========================== */

/* When the owner has closed sign-ups the form is taken away rather than left
   to be filled in and refused. The notice is a fact about the site — the same
   for everybody — and it offers the two things that still work: signing in,
   and finishing a sign-up whose code is already in somebody's inbox. */
.flow-closed { text-align: left; }
.flow-closed .note { margin: 0 0 20px; }
.flow-closed .login-actions { margin-top: 0; }

/* ============================== §6 THE STAMP ============================== */

/* The one moment in the whole flow worth a set piece: the code (or an older
   link) has just been spent and an account now exists — on /signup/ or on
   /verify/, which end on this same screen. The seal comes down the way a stamp
   comes down on a letter — from above, askew, landing square — the ring is the
   ink spreading from the impact, and the wavy postmark bars run out either
   side of it: the letter has been franked. Everything else rises in behind.

   Gated on .is-stamped, which the scripts add exactly once: left ungated this
   would replay on any repaint that re-ran the class list, and a seal that
   stamps itself twice is a seal nobody believes. */
.vf-stamp {
  position: relative; display: grid; place-items: center;
  /* One cell exactly the box's height: an auto row would grow to the glow's
     220px and push the seal down onto the heading. */
  grid-template: minmax(0, 1fr) / minmax(0, 1fr);
  height: 164px; margin: -6px 0 10px;
}
.vf-stamp > *, .vf-stamp::before { grid-area: 1 / 1; }
/* a soft pool of the accent behind the impact */
.vf-stamp::before {
  content: ""; width: 220px; height: 220px; border-radius: 50%;
  background: radial-gradient(closest-side, var(--accent-tint), transparent);
  opacity: 0;
}
.vf-stamp-seal {
  position: relative; z-index: 2;
  width: 136px; height: 136px; border-radius: 50%;
  filter: var(--seal-shadow);
  opacity: 0;
}
.vf-stamp-ring {
  z-index: 1; width: 132px; height: 132px; border-radius: 50%;
  border: 2.5px solid var(--flow-ink); opacity: 0;
}
/* The cancellation bars, drawn as a mask so the ink colour can follow the
   theme. They start under the seal's rim and run outwards. */
.vf-stamp-lines {
  z-index: 1; width: min(340px, 100%); height: 68px;
  background: var(--flow-ink-soft);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 64'%3E%3Cpath d='M4 16q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M204 16q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M4 27q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M204 27q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M4 38q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M204 38q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M4 49q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M204 49q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
          mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 64'%3E%3Cpath d='M4 16q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M204 16q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M4 27q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M204 27q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M4 38q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M204 38q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M4 49q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0M204 49q4 -3.5 8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0t8 0' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
  opacity: 0;
}
.is-stamped .vf-stamp-seal { animation: flowStamp 460ms var(--flow-out) both; }
.is-stamped .vf-stamp-ring { animation: flowPress 620ms var(--ease) 220ms both; }
.is-stamped .vf-stamp-lines { animation: flowFrank 560ms var(--ease) 260ms both; }
.is-stamped .vf-stamp::before { animation: flowGlow 900ms var(--ease) 180ms both; }
@keyframes flowStamp {
  from { opacity: 0; transform: scale(1.4) rotate(-9deg); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}
@keyframes flowPress { from { opacity: .7; transform: scale(.8); } to { opacity: 0; transform: scale(1.55); } }
@keyframes flowFrank { from { opacity: 0; clip-path: inset(0 50% 0 50%); } to { opacity: 1; clip-path: inset(0 0 0 0); } }
@keyframes flowGlow { from { opacity: 0; transform: scale(.6); } to { opacity: 1; transform: none; } }

/* Everything behind the stamp rises in after it, in reading order. --i is the
   place in the queue: set in the markup for the fixed parts and by paintDone()
   for the app rows, capped there so a long list cannot stretch the gesture. */
.vf-rise { opacity: 0; }
.is-stamped .vf-rise { animation: flowRise 320ms var(--ease) both; animation-delay: calc(300ms + var(--i, 0) * 45ms); }
@keyframes flowRise { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* The address they will sign in with — the last cheap moment to spot a typo. */
.vf-user {
  display: table; max-width: 100%; margin: 10px auto 0; padding: 5px 14px;
  border-radius: 999px; background: var(--panel2); color: var(--ink2);
  font: 500 13.5px/1.4 var(--font-mono); overflow-wrap: anywhere;
}
#v-done .login-sub { margin-bottom: 0; }
#v-done .login-actions { margin-top: 22px; }
/* The countdown line and "Stay here" under Continue (see THE WAY BACK in
   signup.js); the scripts hold their room while invisible. */
#done-count { justify-content: center; font-size: 14px; }
#done-count .material-symbols-rounded { font-size: 18px; margin-top: 1px; }

/* "What you can reach" — left-aligned over its list, a section apart. */
#done-apps { margin-top: 26px; }
#done-apps .section-label { margin: 0 0 10px; }

/* The granted apps: tiles two abreast where the card is wide enough, one per
   line on a phone. Each is a link that opens the app. */
.vf-rows {
  display: grid; gap: 8px; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  text-align: left;
}
.row {
  --h: 265; --cs: 1;   /* the house hue until the table below names the app */
  display: flex; align-items: center; gap: 12px; min-width: 0;
  min-height: 60px; padding: 10px;
  border-radius: 14px; background: var(--panel2);
  color: var(--ink); text-decoration: none;
}
.row-click { cursor: pointer; transition: background-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease); }
.row-click:hover { background: var(--panel3); }
.row-main { flex: 1; min-width: 0; }
.row-title { font: 500 15px/1.3 var(--font-display); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row-meta { margin-top: 1px; font: 400 12px/1.35 var(--font-mono); color: var(--ink2); }
.row-chev { display: inline-flex; color: var(--ink3); }
.row-chev .material-symbols-rounded { font-size: 20px; }

/* EACH APP IN ITS OWN COLOUR, as it is in the panel and the console (DESIGN
   §7.4) — the tile is the app's identity, so a new member meets Crossword as
   the tomato one from their first screen. The rows come from the server as
   plain links (appRow() in signup.js / verify.js, left untouched), so the
   colour and glyph are keyed on the href here instead of set from script.
   THIS TABLE MIRRORS account.js HUES + DIRECTORY icons (which mirror
   Root/js/core.js APP_HUES): change one, change all three. An app missing here
   gets the house hue and the generic glyph — still a tile, just not its own.
   Same gradient formula as account.css's .hue --app-grad. */
.row-ic {
  display: grid; place-items: center; flex: none;
  width: 40px; height: 40px; border-radius: 12px;
  background: linear-gradient(150deg, hsl(var(--h) 70% 58%), hsl(calc(var(--h) - 18) 70% 42%));
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .28), inset 0 -1px 0 rgba(0, 0, 0, .12);
}
@supports (color: oklch(0.5 0.1 10)) {
  .row-ic { background: linear-gradient(150deg, oklch(.72 calc(.15 * var(--cs)) calc(var(--h) + 10)), oklch(.54 calc(.17 * var(--cs)) calc(var(--h) - 14))); }
}
/* The glyph: the markup carries a generic "apps" ligature (aria-hidden); it is
   shrunk to nothing and the app's own ligature drawn in its place from --ic.
   Inside the aria-hidden span, so nothing extra reaches a screen reader. */
.row-ic > .material-symbols-rounded { font-size: 0; width: auto; overflow: visible; }
.row-ic > .material-symbols-rounded::before {
  content: var(--ic, "apps");
  font-size: 22px; text-shadow: 0 1px 1px rgba(0, 0, 0, .15);
  font-variation-settings: "FILL" 1, "wght" 400, "GRAD" 0, "opsz" 24;
}
.row[href="https://crypticcrossword.hemishthanki.com" i] { --h: 4; --ic: "grid_4x4"; }
.row[href="https://wordle.hemishthanki.com" i] { --h: 130; --ic: "text_fields"; }
.row[href="https://sudoku.hemishthanki.com" i] { --h: 245; --ic: "grid_on"; }
.row[href="https://geo.hemishthanki.com" i] { --h: 160; --ic: "public"; }
.row[href="https://rails.hemishthanki.com" i] { --h: 28; --ic: "train"; }
.row[href="https://monopoly.hemishthanki.com" i] { --h: 145; --ic: "casino"; }
.row[href="https://library.hemishthanki.com" i] { --h: 35; --cs: .85; --ic: "menu_book"; }
.row[href="https://quotopedia.hemishthanki.com" i] { --h: 275; --ic: "format_quote"; }
.row[href="https://cinema.hemishthanki.com" i] { --h: 330; --ic: "theaters"; }
.row[href="https://study.hemishthanki.com" i] { --h: 215; --ic: "folder_open"; }
.row[href="https://holo.hemishthanki.com" i] { --h: 180; --ic: "school"; }
.row[href="https://cell.hemishthanki.com" i] { --h: 205; --ic: "biotech"; }
.row[href="https://drna.hemishthanki.com" i] { --h: 255; --ic: "genetics"; }
.row[href="https://comp.hemishthanki.com" i] { --h: 280; --ic: "developer_board"; }
.row[href="https://tron.hemishthanki.com" i] { --h: 300; --ic: "bolt"; }
.row[href="https://rothschild.hemishthanki.com" i] { --h: 20; --cs: .8; --ic: "account_tree"; }
.row[href="https://subs.hemishthanki.com" i] { --h: 165; --ic: "subscriptions"; }
.row[href="https://tt.hemishthanki.com" i] { --h: 200; --ic: "calendar_month"; }
.row[href="https://dates.hemishthanki.com" i] { --h: 350; --ic: "cake"; }
.row[href="https://codeground.hemishthanki.com" i] { --h: 190; --ic: "code_blocks"; }
.row[href="https://icon.hemishthanki.com" i] { --h: 48; --ic: "workspaces"; }
.row[href="https://yt.hemishthanki.com" i] { --h: 0; --ic: "download"; }
.row[href="https://x.hemishthanki.com" i] { --h: 220; --cs: .35; --ic: "perm_media"; }
.row[href="https://subdomains.hemishthanki.com" i] { --h: 230; --ic: "dns"; }
.row[href="https://googledoodle.hemishthanki.com" i] { --h: 100; --ic: "sports_esports"; }
.row[href="https://about.hemishthanki.com" i] { --h: 55; --ic: "person"; }
.row[href="https://hemishthanki.com" i], .row[href="https://www.hemishthanki.com" i] { --h: 265; --ic: "home"; }

/* Two abreast, a tile is a tile: the whole thing is the link, and the chevron's
   room is better spent on the host, which otherwise elides. */
@media (min-width: 561px) {
  .flow-wide .vf-rows .row-chev { display: none; }
}

/* ============================= §7 SMALL SCREENS =========================== */
@media (max-width: 560px) {
  /* one column: two dates side by side at phone width are two cramped dates */
  .flow-grid { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 480px) {
  .flow-letter { --lw: 196px; --lh: 124px; margin-bottom: 22px; }
  .flow-letter-seal { width: 76px; height: 76px; margin-left: -38px; top: calc(7px + 56% - 38px); }
  .vf-stamp { height: 150px; }
  .vf-stamp-seal { width: 124px; height: 124px; }
  .vf-stamp-ring { width: 120px; height: 120px; }
  .code-input { font-size: 26px; letter-spacing: .26em; text-indent: .26em; }
  .auth-check .flow-check-ic { display: none; }
}

/* ============================ §8 REDUCED MOTION ===========================
   account.css already takes every animation and transition to 1 ms under
   prefers-reduced-motion (and every keyframe here is written `both`, so each
   still settles on its end state). What is left to do: nothing travels, so
   the 1 ms swap does not flick sideways; and the spinners keep turning, since
   a spinner that stops reads as a hang. The swaps still get their
   transitionend — 1 ms is a transition that fires — which is why this file
   never sets `transition: none` on anything a script waits for. */
@media (prefers-reduced-motion: reduce) {
  .auth-view.is-in, .auth-view.is-out, .vf-view.is-enter, .vf-view.is-leave { transform: none; }
  .vf-spin { animation: spin 1.6s linear infinite !important; }
  .flow-letter { transform: rotate(-2.5deg); }
}
