/* ---------------------------------------------------------------------------
   Ember Live — sign-in / account surface.

   Consumes web/styles/tokens.css for every colour, space, radius, duration and
   easing. The only things declared here are tokens tokens.css does not yet
   carry: semantic status colours and a stacking scale. Both are namespaced
   plainly so they can be lifted into tokens.css verbatim later without a
   rename — see .agent-state/AUTH_DESIGN.md.

   Contrast ratios below were computed with the WCAG 2.x relative-luminance
   formula against the surface each colour actually sits on. The method was
   validated by recomputing three of tokens.css's own published ratios
   (--mint-400 11.07:1, --amber-400 11.55:1, --accent-ink 7.46:1) and matching
   all three exactly.
   --------------------------------------------------------------------------- */

:root {
  /* Status. Hues reused from the tokens.css accent ramp so the palette stays
     closed; only the semantic naming is new. */
  --ok:     var(--mint-400);    /*  9.98:1 on --surface */
  --warn:   var(--amber-400);   /* 10.41:1 on --surface */
  --danger: var(--rose-400);    /*  6.46:1 on --surface */

  /* Error text needs a tinted bed that does not wash out the hairline. Alpha
     over --surface rather than a flat hex, so one value works on both the card
     and the raised card. */
  --danger-bed: rgba(251, 113, 133, 0.10);
  --danger-line: rgba(251, 113, 133, 0.32);
  --ok-bed:      rgba(74, 222, 128, 0.10);
  --ok-line:     rgba(74, 222, 128, 0.30);
  --warn-bed:    rgba(251, 191, 36, 0.10);
  --warn-line:   rgba(251, 191, 36, 0.30);

  --z-raised:    10;
  --z-announce: 200;
}

*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body.auth {
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-body);
  -webkit-font-smoothing: antialiased;
}

/* Every interactive control must show focus. The ring is drawn with outline
   (not box-shadow) so it survives forced-colors mode, and offset so it never
   sits on top of the control's own border. */
:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius-sm);
}
:focus:not(:focus-visible) { outline: none; }

/* Visually hidden, still in the accessibility tree. display:none and the
   hidden attribute both remove an element from that tree, which would silence
   the status region entirely. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Keyboard users land here first; pointer users never see it. */
.skip-link {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  z-index: var(--z-announce);
  padding: var(--space-2) var(--space-4);
  background: var(--surface-raised);
  color: var(--ink);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  transform: translateY(-200%);
  transition: transform var(--dur-fast) var(--ease-out);
}
.skip-link:focus { transform: translateY(0); }

/* ---------------------------------------------------------------------------
   Shell: two panes on desktop, one column on phones. The brand pane is
   decorative and comes SECOND in the DOM so keyboard and screen-reader order
   reaches the form first; `order` puts it on the left visually.
   --------------------------------------------------------------------------- */
.auth-shell {
  display: grid;
  grid-template-columns: 1fr;
  min-height: 100vh;
  min-height: 100dvh;
}

@media (min-width: 60rem) {
  .auth-shell { grid-template-columns: 1.05fr 1fr; }
  .auth-aside { order: -1; }
}

.auth-main {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: var(--space-6) var(--space-4);
  position: relative;
  z-index: var(--z-raised);
}

@media (min-width: 48rem) {
  .auth-main { padding: var(--space-7) var(--space-6); }
}

/* Decorative pane. aria-hidden in the markup, so nothing here is announced.
   Hidden below 60rem rather than reflowed: on a phone it would be a screenful
   of ornament above the form. */
.auth-aside {
  display: none;
  position: relative;
  overflow: hidden;
  padding: var(--space-7) var(--space-6);
  background: var(--surface-sunken);
  border-right: 1px solid var(--line);
  flex-direction: column;
  justify-content: space-between;
}

@media (min-width: 60rem) {
  .auth-aside { display: flex; }
}

/* Two offset radial washes instead of a linear gradient: the ember reads as
   light falling into the corner rather than a painted band. Static — no
   animation loop, because an idle sign-in screen must not hold the GPU awake. */
.auth-aside::before {
  content: '';
  position: absolute;
  inset: -20% -30% auto -30%;
  height: 80%;
  background:
    radial-gradient(60% 60% at 30% 20%, rgba(255, 122, 69, 0.20), transparent 70%),
    radial-gradient(50% 50% at 80% 40%, rgba(122, 162, 255, 0.14), transparent 70%);
  pointer-events: none;
}

.auth-aside > * { position: relative; }

.aside-lede {
  font-size: var(--text-3xl);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
  font-weight: var(--weight-semi);
  max-width: 22ch;
}

.aside-sub {
  margin-top: var(--space-4);
  color: var(--ink-muted);
  max-width: 34ch;
}

.aside-points {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin-top: var(--space-6);
}

.aside-points li {
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
  color: var(--ink-soft);
  font-size: var(--text-sm);
}

/* Hairline rule as the marker, not a glyph: no emoji, no icon font. */
.aside-points li::before {
  content: '';
  flex: none;
  width: var(--space-4);
  height: 1px;
  background: var(--accent);
  transform: translateY(-0.35em);
}

.aside-foot {
  color: var(--ink-faint);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

/* ---------------------------------------------------------------------------
   Card
   --------------------------------------------------------------------------- */
.auth-card {
  width: 100%;
  max-width: 26rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  box-shadow: var(--elev-2);
  padding: var(--space-5) var(--space-4);
}

@media (min-width: 30rem) {
  .auth-card { padding: var(--space-6) var(--space-5); }
}

.auth-brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}

/* The mark is a CSS ember, not an emoji: 🔥 renders as a different glyph on
   every platform and cannot be colour-matched to the palette. */
.brand-mark {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-sm);
  background:
    radial-gradient(70% 70% at 50% 75%, var(--ember-400), var(--ember-600) 70%);
  box-shadow: var(--elev-glow);
}

.brand-name {
  font-size: var(--text-lg);
  font-weight: var(--weight-semi);
  letter-spacing: var(--tracking-tight);
}

/* Chinese sits a touch smaller. No opacity: it composites against whatever is
   behind it, so the same rule measures differently on the card and the aside
   and cannot be reasoned about statically. */
.cn { font-size: 0.9em; }
.brand-name .cn { color: var(--ink-muted); }

.auth-title {
  font-size: var(--text-2xl);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-tight);
  font-weight: var(--weight-semi);
}

.auth-sub {
  margin-top: var(--space-2);
  color: var(--ink-muted);
  font-size: var(--text-sm);
}

/* Language toggle: a real radiogroup, so arrow keys move between options. */
.lang-switch {
  display: inline-flex;
  gap: 2px;
  padding: 2px;
  margin-left: auto;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-radius: var(--radius-pill);
}

.lang-switch button {
  appearance: none;
  border: 0;
  background: none;
  color: var(--ink-faint);
  font: inherit;
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}

.lang-switch button:hover { color: var(--ink); }

.lang-switch button[aria-checked="true"] {
  background: var(--surface-raised);
  color: var(--ink);
}

/* ---------------------------------------------------------------------------
   Form
   --------------------------------------------------------------------------- */
.auth-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-top: var(--space-5);
}

.field { display: flex; flex-direction: column; gap: var(--space-2); }

.field-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.field label {
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--ink-soft);
}

.field input {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--surface-sunken);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  padding: var(--space-3);
  /* 16px minimum on the real control: iOS Safari zooms the viewport on focus
     for anything smaller, which yanks the layout sideways mid-typing. */
  font-size: var(--text-base);
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}

.field input::placeholder { color: var(--ink-faint); }

.field input:hover:not(:disabled) { border-color: var(--ink-faint); }

.field input:focus {
  background: var(--surface-raised);
  border-color: var(--accent);
}

.field input:disabled { cursor: not-allowed; color: var(--ink-faint); }

/* Invalid is driven by aria-invalid, which auth.js sets. Never :invalid — that
   matches on first paint, before the user has typed anything. */
.field input[aria-invalid="true"] {
  border-color: var(--danger);
  background: var(--danger-bed);
}

/* Field-level message. Reserves no height when empty so the card does not
   shift as messages appear; the gap comes from .field's own row gap. */
.field-msg {
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: var(--danger);
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
}

.field-msg:empty { display: none; }

/* Reveal is animated on opacity and a token-scaled offset, so reduced motion
   (--motion-scale: 0) turns travel off and leaves the cross-fade. */
.field-msg {
  animation: msgIn var(--dur-fast) var(--ease-out);
}

@keyframes msgIn {
  from { opacity: 0; transform: translateY(calc(-2px * var(--motion-scale))); }
  to   { opacity: 1; transform: translateY(0); }
}

.hint {
  font-size: var(--text-xs);
  color: var(--ink-faint);
}

.link {
  color: var(--accent);
  font-size: var(--text-xs);
  text-decoration: none;
  border-radius: var(--radius-sm);
  background: none;
  border: 0;
  font-family: inherit;
  cursor: pointer;
  padding: 0;
  /* Text this small leaves a ~15px tall hit box, under the 24px minimum for a
     pointer target. Both links are already wide enough, so only the height
     needs growing: inline-flex centring adds it without moving the text or
     widening the box past its row. */
  display: inline-flex;
  align-items: center;
  min-height: 1.5rem;
}

.link:hover { color: var(--accent-hover); text-decoration: underline; }

/* Password row: the reveal button sits inside the field box, so the input keeps
   its own padding and the button never overlaps typed text. */
.pw-wrap { position: relative; display: flex; }
.pw-wrap input { padding-right: 4.25rem; }

.pw-toggle {
  position: absolute;
  top: 50%;
  right: var(--space-2);
  transform: translateY(-50%);
  appearance: none;
  border: 0;
  background: none;
  color: var(--ink-muted);
  font: inherit;
  font-size: var(--text-xs);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.pw-toggle:hover { color: var(--ink); }

/* ---------------------------------------------------------------------------
   Buttons
   --------------------------------------------------------------------------- */
.btn {
  appearance: none;
  font: inherit;
  font-weight: var(--weight-medium);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  padding: var(--space-3) var(--space-4);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  min-height: 2.75rem;   /* 44px pointer target */
  transition: background-color var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-instant) var(--ease-spring);
}

.btn:active:not(:disabled) { transform: scale(0.99); }

.btn-primary {
  background: var(--accent);
  color: var(--accent-ink);   /* 7.46:1 */
}

.btn-primary:hover:not(:disabled) { background: var(--accent-hover); }

.btn-quiet {
  background: var(--surface-raised);
  color: var(--ink);
  border-color: var(--line-strong);
}

.btn-quiet:hover:not(:disabled) { border-color: var(--ink-faint); }

.btn:disabled { cursor: not-allowed; opacity: 0.55; }

.btn-block { width: 100%; }

/* Busy is aria-busy on the button, not a class, so the visual state and the
   announced state cannot drift apart. */
.btn[aria-busy="true"] { cursor: progress; }

/* Ring spinner. Reduced motion collapses --dur-slower to 120ms, which would
   make this a blur, so the reduced path swaps it for a static arc instead —
   see the block at the end of this file. */
.spinner {
  flex: none;
  width: 1em;
  height: 1em;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-right-color: transparent;
  animation: spin var(--dur-slower) linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------------------
   Form-level alert. One element carries server-error, rate-limited and success;
   the tone comes from a data attribute so auth.js never juggles class lists.
   --------------------------------------------------------------------------- */
.alert {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-3);
  border-radius: var(--radius-sm);
  border: 1px solid var(--danger-line);
  background: var(--danger-bed);
  color: var(--ink);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  animation: alertIn var(--dur-base) var(--ease-out);
}

.alert[hidden] { display: none; }

.alert[data-tone="ok"]   { border-color: var(--ok-line);   background: var(--ok-bed); }
.alert[data-tone="warn"] { border-color: var(--warn-line); background: var(--warn-bed); }

/* A 3px rule in the tone colour, not an icon: it survives forced-colors mode
   and needs no glyph. Colour is never the only signal — the alert always
   carries text, and role="alert" announces it. */
.alert::before {
  content: '';
  flex: none;
  align-self: stretch;
  width: 3px;
  border-radius: 2px;
  background: var(--danger);
}

.alert[data-tone="ok"]::before   { background: var(--ok); }
.alert[data-tone="warn"]::before { background: var(--warn); }

@keyframes alertIn {
  from { opacity: 0; transform: translateY(calc(-4px * var(--motion-scale))); }
  to   { opacity: 1; transform: translateY(0); }
}

.alert-body { min-width: 0; }
.alert-title { font-weight: var(--weight-medium); }
.alert-detail { color: var(--ink-muted); font-size: var(--text-xs); margin-top: var(--space-1); }

/* ---------------------------------------------------------------------------
   Views. Sign-in, sign-up and forgot-password are three panels in one document;
   auth.js toggles [hidden]. hidden (not a class) so the inactive panel leaves
   the accessibility tree AND the tab order — a visually hidden panel that is
   still tabbable is the classic keyboard trap here.
   --------------------------------------------------------------------------- */
.view[hidden] { display: none; }

.view {
  animation: viewIn var(--dur-base) var(--ease-out);
}

@keyframes viewIn {
  from { opacity: 0; transform: translateY(calc(6px * var(--motion-scale))); }
  to   { opacity: 1; transform: translateY(0); }
}

.auth-foot {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--line);
  font-size: var(--text-sm);
  color: var(--ink-muted);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  align-items: baseline;
}

.auth-foot .link { font-size: var(--text-sm); }

/* Pushes the escape hatch to the far end of the row, so it reads as leaving the
   flow rather than as a third step in it. margin-left:auto on a flex-wrap row
   collapses harmlessly to a normal gap once the row wraps on a narrow screen. */
.auth-foot__spacer { margin-left: auto; color: var(--ink-muted); }
.auth-foot__spacer:hover { color: var(--ink); }

.legal {
  margin-top: var(--space-4);
  font-size: var(--text-xs);
  color: var(--ink-faint);
  line-height: var(--leading-snug);
}

.legal a { color: var(--ink-muted); }

/* Reduced motion: tokens.css already collapses the durations and sets
   --motion-scale to 0, which flattens every reveal above into a cross-fade.
   Only the indefinite loop needs a real override — a 120ms rotation is a
   flicker, so it becomes a static arc that still reads as "busy". */
@media (prefers-reduced-motion: reduce) {
  .spinner { animation: none; border-right-color: currentColor; border-top-color: transparent; }
}

/* Forced-colors (Windows high contrast): system colours replace the palette,
   and alpha beds collapse to nothing. Restore the edges with real borders. */
@media (forced-colors: active) {
  .auth-card, .alert, .field input { border: 1px solid CanvasText; }
  .brand-mark { background: Highlight; }
  .alert::before, .aside-points li::before { background: CanvasText; }
  .btn-primary { border-color: CanvasText; }
}
