/* Story 14.1 — the activation landing page.
 *
 * 🛑 EVERY colour, radius, space and type role comes from `src/generated/tokens.css`, which is
 * generated from `contracts/design-tokens/tokens.json` by the SAME generator that produces the
 * Flutter app's Dart tokens and is drift-checked by `pnpm contracts:check`. The first version of
 * this page invented a palette — including a brand red, which DESIGN.md reserves for
 * safety-critical states and explicitly warns against as "a second brand hue competing with
 * green". Nothing here may hard-code a colour.
 */

/* GENERATED CODE - DO NOT MODIFY BY HAND.
 * Source of truth: contracts/design-tokens/tokens.json (AD-2).
 * Generator: tooling/codegen/gen-design-tokens.py (run via `pnpm contracts:generate`).
 * Edit the JSON (and DESIGN.md, the human spec) and regenerate; never hand-edit this file.
 *
 * The CSS projection of the SAME contract the Flutter app is generated from, so the activation
 * pages and the Keycloak sign-in pages cannot drift from the app they hand owners over to.
 * Hand-copying 34 hex values into a stylesheet is exactly the drift this contract exists to prevent.
 *
 * Colors only switch on the OS setting here. The app makes dark a one-tap Profile setting; a
 * web page has no Profile, so it follows `prefers-color-scheme` instead. Same two palettes.
 *
 * NOTE: `--font-sans` is the family NAME, not a stack. Hanken Grotesk carries no Armenian,
 * Georgian or Cyrillic glyphs, so the page composes its own per-script fallback on top -- see
 * `--font-ui` in styles.css.
 */

:root {
  /* Type families (names only -- the page adds per-script fallbacks). */
  --font-mono: 'IBM Plex Mono';
  --font-sans: 'Hanken Grotesk';

  /* Colors -- the light palette. */
  --color-bg: #F4F2ED;
  --color-surface: #FFFFFF;
  --color-surface-2: #EDE9E0;
  --color-ink-surface: #1E1E22;
  --color-text: #161614;
  --color-text-2: #6E685E;
  --color-text-3: #9B958A;
  --color-border: #E6E1D7;
  --color-hairline: #F0ECE3;
  --color-accent: #0FB877;
  --color-accent-bright: #3FCF92;
  --color-accent-ink: #08905A;
  --color-accent-soft: #E4F7EF;
  --color-on-accent: #06160F;
  --color-track: #E8E3D9;
  --color-nav-bg: rgba(255,255,255,0.92);
  --color-warning: #C98A1E;
  --color-warning-soft: #E9C46A;
  --color-danger: #C0142A;
  --color-danger-deep: #9E1020;
  --color-danger-soft: #FBE7E9;

  /* Radii. */
  --radius-xs: 6px;
  --radius-sm: 10px;
  --radius-md: 14px;
  --radius-lg: 18px;
  --radius-xl: 22px;
  --radius-full: 9999px;

  /* Spacing scale. */
  --space-s1: 4px;
  --space-s2: 8px;
  --space-s3: 12px;
  --space-s4: 16px;
  --space-s5: 20px;
  --space-s6: 24px;
  --space-s7: 32px;

  /* Component metrics. */
  --metric-gutter: 20px;
  --metric-header-pad-top: 54px;
  --metric-nav-height: 60px;
  --metric-button-height: 54px;
  --metric-card-pad: 16px;

  /* Type roles. */
  --type-display-family: var(--font-sans);
  --type-display-size: 28px;
  --type-display-weight: 800;
  --type-display-tracking: -0.75px;
  --type-display-leading: 1.1;
  --type-title-family: var(--font-sans);
  --type-title-size: 22px;
  --type-title-weight: 800;
  --type-title-tracking: -0.5px;
  --type-title-leading: 1.15;
  --type-heading-family: var(--font-sans);
  --type-heading-size: 17px;
  --type-heading-weight: 700;
  --type-body-family: var(--font-sans);
  --type-body-size: 15px;
  --type-body-weight: 600;
  --type-body-leading: 1.35;
  --type-meta-family: var(--font-sans);
  --type-meta-size: 13px;
  --type-meta-weight: 500;
  --type-numeric-family: var(--font-mono);
  --type-numeric-size: 28px;
  --type-numeric-weight: 800;
  --type-numeric-tracking: -0.5px;
  --type-label-family: var(--font-mono);
  --type-label-size: 10px;
  --type-label-weight: 600;
  --type-label-tracking: 1.5px;
}

@media (prefers-color-scheme: dark) {
  :root {
    --color-bg: #0B0B0C;
    --color-surface: #171719;
    --color-surface-2: #202023;
    --color-ink-surface: #141416;
    --color-text: #F3F2EF;
    --color-text-2: #A6A2A8;
    --color-text-3: #76737A;
    --color-border: #2C2C30;
    --color-hairline: #252528;
    --color-accent: #15D98C;
    --color-accent-bright: #5BE7B0;
    --color-accent-ink: #46DCA0;
    --color-accent-soft: rgba(21,217,140,0.14);
    --color-track: #2A2A2E;
    --color-nav-bg: rgba(18,18,20,0.86);
  }
}


/* Hanken Grotesk, self-hosted from the same TTF the Flutter app bundles — no Google Fonts, no
 * third-party byte, no request that leaks the invite URL to another origin.
 *
 * 🛑 `unicode-range` is doing real work, not tidiness. This face carries **no Armenian, Georgian
 * or Cyrillic glyphs at all** (measured: 0 of 96, 0 of 96, 0 of 256), so three of the four
 * languages this product ships in cannot be set in it. Bounding it to Latin means a Georgian or
 * Armenian reader never downloads 130 KB they cannot use, and the Latin fragments inside their
 * page — "eAuto", "App Store", "Google Play" — still get the brand face.
 */
@font-face {
  font-family: 'Hanken Grotesk';
  src: url('/fonts/HankenGrotesk-Variable.ttf') format('truetype-variations');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range:
    U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074,
    U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* The UI stack. `--font-sans` is the generated family NAME; the per-script fallbacks are a
     web-only concern and belong here rather than in the token contract. */
  --font-ui:
    var(--font-sans), 'Noto Sans Armenian', 'Noto Sans Georgian', system-ui, -apple-system,
    'Segoe UI', Roboto, sans-serif;
  /* ⚠️ DELIBERATE DEVIATION from DESIGN.md, recorded rather than silent: the `label` role is
     IBM Plex Mono, and this page substitutes a system mono stack. 137 KB of webfont to set one
     ALL-CAPS eyebrow is a bad trade on the one surface an owner opens on a car-park connection.
     The UX spine's own mockup made the same substitution and said so. */
  --font-label: ui-monospace, 'SF Mono', 'IBM Plex Mono', Menlo, Consolas, monospace;
}

* {
  box-sizing: border-box;
}

/* 🛑 Not boilerplate. The UA stylesheet gives `[hidden]` `display: none`, but ANY author
   `display` beats it — and `.store` below is `display: flex`. Without this rule the store button
   platform detection hid stays fully visible, which is the exact failure AC1 exists to prevent:
   an Android owner offered the App Store. Found by rendering the page, not by reading it. */
[hidden] {
  display: none !important;
}

body {
  margin: 0;
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-s6) var(--metric-gutter) calc(var(--space-s6) + env(safe-area-inset-bottom));
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-ui);
  /* Armenian and Georgian run roughly 35% longer than Latin, and that compounds with dynamic
     type. Nothing on this page is a fixed-height single line for that reason. */
  font-size: var(--type-body-size);
  font-weight: var(--type-body-weight);
  line-height: var(--type-body-leading);
  -webkit-font-smoothing: antialiased;
}

.card {
  width: 100%;
  max-width: 26rem;
  text-align: center;
}

/* --- Brand lockup ------------------------------------------------------------------------- */

.lockup {
  margin: 0 0 var(--space-s3);
  font-size: var(--type-display-size);
  font-weight: var(--type-display-weight);
  letter-spacing: var(--type-display-tracking);
  line-height: var(--type-display-leading);
  color: var(--color-text);
}

/* Ownership Green is the ONE chromatic identity colour in this product (DESIGN.md). The wordmark
   uses `accent-ink`, the on-light variant, because `accent` itself is reserved for filled
   surfaces and would not carry the contrast as text on cream. */
.lockup-brand {
  color: var(--color-accent-ink);
}

.tagline {
  margin: 0 0 var(--space-s5);
  font-size: var(--type-heading-size);
  font-weight: var(--type-heading-weight);
  color: var(--color-text-2);
}

/* --- The link notice ---------------------------------------------------------------------- */

/* The slot holds its height while the check is in flight, so the answer arrives without moving
   the store button under the reader's thumb. Sized for the two-line case, which is the common
   one once Armenian and Georgian expansion is counted. */
.note-slot {
  /* 🛑 MEASURED, not guessed — 92px was, and it was 39px short. The tallest notice is
     INDETERMINATE (it is the only one carrying a Retry button): 111px in `en` and 131px in
     `hy`/`ka`/`ru`, which run ~35% longer. A slot shorter than its content does not reserve
     anything; it just shifts later. Sized to the worst case with headroom for dynamic type.
     See `.note-slot` in page-assets.spec.ts for the floor this must not drop below. */
  min-height: 8.5rem; /* 136px at the default root size, but scales with the reader's text size */
  margin-bottom: var(--space-s5);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* The empty state of the live region: invisible and unreachable, but still IN the accessibility
   tree so its first text change is announced. `visibility` (not `display`) is what keeps it
   registered — and the slot above already reserves the space. */
.link-note[data-empty='true'] {
  visibility: hidden;
}

.link-note {
  width: 100%;
  padding: var(--space-s3) var(--metric-card-pad);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  /* Hierarchy from a leading rule rather than a fill — the app's own card language. */
  border-left: 3px solid var(--color-border);
  text-align: left;
}

/* An ANSWER that is good news: the ownership accent, the same signal the app uses for
   connected/secure/ready. */
.link-note[data-tone='answer'] {
  border-left-color: var(--color-accent);
  background: var(--color-accent-soft);
}

/* An ANSWER that needs attention. 🛑 The AMBER tier, never the red one: DESIGN.md reserves
   `danger` for safety-critical and destructive states, and an expired invite is neither — it is
   the system working correctly. Colouring it red would say a fault occurred. */
.link-note[data-tone='attention'] {
  border-left-color: var(--color-warning);
}

/* INDETERMINATE. Deliberately the quietest of the three: we do not know, and the styling must not
   imply we do. */
.link-note[data-tone='muted'] {
  border-left-color: var(--color-text-3);
}

/* 🛑 THE EYEBROW IS `text`, NOT A TONE COLOUR, AND THAT IS A CONTRAST DECISION.
 *
 * Measured against the generated palette, every coloured option FAILS WCAG AA at this size:
 * `text-3` on surface is 2.97:1, `warning` on surface 2.94:1, `accent-ink` on `accent-soft`
 * 3.66:1 — against the 4.5:1 a 10px label needs. The UX run's own accessibility review already
 * recorded `text-3` and `warning` as failing "wherever they are used"; this is one of the places.
 *
 * So the TONE is carried by the leading border and the surface tint, and the MEANING is carried
 * by the words ("Link expired"). That is the same both-channels rule finding C3 imposed on the
 * dead-end screens: never let one perceptual channel be the only one. `contrast.spec.ts` holds
 * every pair on this page to 4.5:1 against the token contract.
 */
.eyebrow {
  margin: 0 0 var(--space-s1);
  font-family: var(--font-label);
  font-size: var(--type-label-size);
  font-weight: var(--type-label-weight);
  letter-spacing: var(--type-label-tracking);
  text-transform: uppercase;
  color: var(--color-text);
}

/* 🛑 Georgian is UNICAMERAL. `text-transform: uppercase` is a no-op in Mkhedruli, so the ALL-CAPS
   eyebrow — a primary hierarchy device across this product — would silently disappear for
   Georgian owners while looking perfectly correct in review. EXPERIENCE.md resolved this for the
   app on 2026-08-19: keep the structural role through increased tracking and a heavier weight at
   the same size, colour and family. Same resolution here. */
html[lang='ka'] .eyebrow {
  text-transform: none;
  letter-spacing: 2.2px;
  font-weight: 700;
}

.link-note-text {
  margin: 0;
  font-size: var(--type-meta-size);
  font-weight: var(--type-meta-weight);
  line-height: 1.5;
  color: var(--color-text-2);
}

.retry {
  margin-top: var(--space-s2);
  min-height: 40px;
  padding: 0 var(--space-s3);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  font-size: var(--type-meta-size);
  font-weight: 700;
  cursor: pointer;
}

/* --- Stores ------------------------------------------------------------------------------- */

.stores {
  display: flex;
  flex-direction: column;
  gap: var(--space-s3);
}

.store {
  display: flex;
  align-items: center;
  justify-content: center;
  /* A MINIMUM, never a fixed height. `--metric-button-height` is 54px in the token contract, and
     DESIGN.md's own note says it should be a floor: a 35%-longer Georgian label must wrap rather
     than truncate, and that compounds with dynamic type. */
  min-height: var(--metric-button-height);
  padding: var(--space-s3) var(--space-s5);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  text-decoration: none;
  font-size: var(--type-body-size);
  font-weight: 700;
}

/* When only ONE store shows, it is the primary action and takes the accent — the app's own
   primary-button treatment, including the soft accent glow that marks a lifted CTA.
   🛑 `:only-child` is not enough: the other store is hidden, not removed, so it is still a child.
   `:has()` asks the real question — "is one of my siblings hidden?" — and the `:only-child` rule
   is kept beside it as the floor for anything that does not support `:has()`. */
/* 🛑 `:is()` is load-bearing, not decoration. A selector LIST is all-or-nothing: a browser that
   cannot parse `:has()` discards the whole rule, taking the `:only-child` fallback with it — so
   the fallback was dead exactly where it was needed. `:is()` contains the parse failure to its own
   argument and leaves the sibling selector standing. */
:is(.stores:has(> .store[hidden]) > .store:not([hidden])):not([aria-disabled='true']),
.store:only-child:not([aria-disabled='true']) {
  background: var(--color-accent);
  color: var(--color-on-accent);
  border-color: transparent;
  /* Derived from the accent token rather than a literal rgba, so a palette change carries. */
  box-shadow: 0 10px 22px color-mix(in srgb, var(--color-accent) 30%, transparent);
}

/* 🛑 A disabled button must never become the accent primary. When `appleAppId` is unset, the sole
   VISIBLE store on an iPhone is the disabled App Store button — the promotion rule above would
   paint the one thing an owner cannot use as the page's main call to action, and `opacity: .45`
   over the accent drops its label to 1.99:1 (2.83:1 dark), against a 4.5:1 requirement. The
   `:not([aria-disabled='true'])` guards above are what prevent it; this rule only dims. */
.store[aria-disabled='true'] {
  opacity: 0.45;
  cursor: not-allowed;
}

.note {
  margin: var(--space-s4) 0 0;
  font-size: var(--type-meta-size);
  color: var(--color-text-2);
}

/* `warning` as TEXT is 2.63:1 on the canvas. The caveat reads in `text-2` and sits directly
   under a button that is already visibly disabled — the state is not carried by colour alone. */
.note-warn {
  color: var(--color-text-2);
}

/* --- Switcher and legal ------------------------------------------------------------------- */

.switcher {
  margin: var(--space-s7) 0 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-s2);
  font-size: var(--type-meta-size);
  color: var(--color-text-2);
}

.lang-switch {
  min-height: 44px;
  padding: var(--space-s1) var(--space-s2);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  font-size: var(--type-meta-size);
}

.legal {
  margin-top: var(--space-s5);
  display: flex;
  gap: var(--space-s4);
  justify-content: center;
}

.legal-link {
  font-size: var(--type-meta-size);
  /* `text-3` is 2.66:1 on the canvas. Legal links are exactly the text nobody may be expected to
     squint at. */
  color: var(--color-text-2);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* --- The account-deletion page (Story 21.6, /delete-account) ------------------------------ */

/* A document to READ before an irreversible step, so it is left-aligned and a little wider than
   the activation card: centred multi-line lists are hard to scan, and this page is mostly lists. */
.card-deletion {
  max-width: 34rem;
  text-align: left;
}

.card-deletion .lockup {
  font-size: var(--type-heading-size);
}

.da-title {
  margin: 0 0 var(--space-s4);
  font-size: var(--type-display-size);
  font-weight: var(--type-display-weight);
  line-height: var(--type-display-leading);
  color: var(--color-text);
}

.da-intro,
.da-text {
  margin: 0 0 var(--space-s4);
  color: var(--color-text);
}

.da-message {
  margin-bottom: var(--space-s4);
}

.da-heading {
  margin: var(--space-s5) 0 var(--space-s2);
  font-size: var(--type-heading-size);
  font-weight: var(--type-heading-weight);
  color: var(--color-text);
}

.da-list {
  margin: 0 0 var(--space-s3);
  padding-left: var(--space-s5);
  color: var(--color-text);
}

.da-list li + li {
  margin-top: var(--space-s2);
}

.da-balances {
  font-weight: 700;
}

/* The provisional-retention line and the "close this page to keep it" line: secondary, never
   `text-3` (2.66:1 on the canvas — see contrast.spec.ts). */
.da-note {
  margin: 0 0 var(--space-s3);
  font-size: var(--type-meta-size);
  color: var(--color-text-2);
}

/* The acknowledgement. The label is the hit target, and the row is at least 44px tall. */
.da-ack {
  margin: var(--space-s4) 0;
  min-height: 44px;
  display: flex;
  align-items: flex-start;
  gap: var(--space-s3);
  color: var(--color-text);
}

.da-ack input {
  flex: none;
  width: 1.5rem;
  height: 1.5rem;
  margin: 0.1rem 0 0;
  accent-color: var(--color-accent-ink);
}

.da-action,
.da-delete {
  display: block;
  width: 100%;
  min-height: var(--metric-button-height);
  padding: var(--space-s3) var(--space-s5);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  font: inherit;
  font-size: var(--type-body-size);
  font-weight: 700;
  cursor: pointer;
}

/* Sign in / Try again: the forward step, so the app's primary treatment. */
.da-action {
  margin-bottom: var(--space-s4);
  background: var(--color-accent);
  color: var(--color-on-accent);
}

/* 🛑 The destructive step is deliberately NOT the green accent — green says "go", and this is the
   one button on the page an owner should hesitate over. It is also not the danger tier, which
   this stylesheet keeps out entirely (page-assets.spec.ts): a solid ink button, labelled in words
   that say exactly what it does, carries the weight without a second alarm colour. */
.da-delete {
  margin-top: var(--space-s2);
  background: var(--color-text);
  color: var(--color-bg);
}

/* Disabled until the acknowledgement is ticked. Disabled controls are exempt from the contrast
   minimum, and the state is carried by the `disabled` attribute, not by colour alone. */
.da-delete:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* --- The re-verification page (Story 27.6, /reverify/<token>) ------------------------------ */

/* Left-aligned like the deletion page: it is a short text to READ before acting, not a CTA. */
.card-reverify {
  max-width: 34rem;
  text-align: left;
}

.card-reverify .lockup {
  font-size: var(--type-heading-size);
}

.rv-title {
  margin: 0 0 var(--space-s4);
  font-size: var(--type-display-size);
  font-weight: var(--type-display-weight);
  line-height: var(--type-display-leading);
  color: var(--color-text);
}

.rv-text {
  margin: 0 0 var(--space-s4);
  color: var(--color-text);
}

.rv-message {
  margin-bottom: var(--space-s4);
}

/* 🛑 ONE button, full width, and the neutral surface treatment rather than the green accent: it
   LOCKS an account, and the person who did ask for the code — most readers of this email — must
   not be drawn to it. They are sent to the app instead (code review, 2026-10-14). */
.rv-report {
  display: block;
  width: 100%;
  margin: var(--space-s2) 0 var(--space-s3);
  /* A MINIMUM: a label ~35% longer in hy/ka/ru (Story 27.5) wraps instead of truncating. */
  min-height: var(--metric-button-height);
  padding: var(--space-s3) var(--space-s4);
  border: 1px solid var(--color-border);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font: inherit;
  font-size: var(--type-body-size);
  font-weight: 700;
  cursor: pointer;
}

/* While the report is in flight. Disabled controls are exempt from the contrast minimum, and the
   state is carried by the `disabled` attribute and the "Sending" message, not by colour alone. */
.rv-report:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

/* "Only a service point can unlock it": secondary, never `text-3` (contrast.spec.ts). */
.rv-note {
  margin: 0 0 var(--space-s3);
  font-size: var(--type-meta-size);
  color: var(--color-text-2);
}

/* --- Accessibility floor ------------------------------------------------------------------ */

/* AC7: the focus ring is VISIBLE and is never removed. This is the one activation surface likely
   to meet a desktop screen reader and a keyboard, and an invisible ring is the single most common
   way a page becomes unusable without a mouse. `accent-ink` rather than `accent` so it holds
   contrast against both the cream canvas and the accent-soft note. */
:focus-visible {
  outline: 3px solid var(--color-accent-ink);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  * {
    transition: none !important;
    animation: none !important;
  }
}

/* --- Desktop ------------------------------------------------------------------------------ */

/* The card metaphor holds on a wide screen — this is a handoff, not a homepage, and widening it
   into columns would invent content the page does not have. What changes is scale: the display
   role steps up, and the card lifts onto a surface so it reads as an object rather than as text
   floating on a large empty canvas. */
@media (min-width: 48rem) {
  .card {
    max-width: 30rem;
    padding: var(--space-s7);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-xl);
    background: var(--color-surface);
  }

  .lockup {
    font-size: 36px;
  }

  .link-note {
    background: var(--color-bg);
  }

  .link-note[data-tone='answer'] {
    background: var(--color-accent-soft);
  }
}
