/* lib/auth.css — component styles for the sign-in / magic-link journey
   (index.html). Ships the reviewed design from issue #40, carried over
   from sign-in-preview.css with the preview-only catalogue chrome
   (jump-nav, frame labels, "needs build" badges, notes-for-review)
   stripped out — none of that belongs in the shipped page.

   Loaded AFTER proposal/design-system/tokens.css and components.css, so
   every value below reads an existing --tf-*, --text-*, --sp-*, --r-*
   custom property. Nothing here is a new token.

   Whole page sits on the dark surface, driven by data-surface="dark" on
   <body> — tokens.css's own surface switch (--surface-bg/--surface-fg/
   --surface-fg-muted/--surface-border/--surface-border-strong/
   --surface-card). Every colour below reads those tokens rather than a
   hardcoded light or dark value. */

/* !important is deliberate: this is a force-hide utility, and it sits on
   elements that also carry their own display value (e.g. .tf-auth-loading's
   display: flex) — without it, source order alone decides the winner. */
.hidden { display: none !important; }

/* The body reset that used to live here moved to lib/base.css in #76, which
   is now this repo's reset sheet and is loaded ahead of this file. */

.tf-auth-page {
  min-height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--sp-6);
  background: var(--surface-bg);
}

/* ─── The component: auth card ─── */

.tf-auth-card {
  width: min(420px, 100%);
  background: var(--surface-card);
  border: 1px solid var(--surface-border);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
  padding: var(--sp-7) var(--sp-6) var(--sp-6);
  font-family: var(--font-ui);
  color: var(--surface-fg);
}

/* #91: links inside the card need a colour stated here. The only
   `a { color }` rule in the vendored design system lives in
   proposal/styles-live.css, which home.html and index.html do not load,
   so an anchor on either page falls back to the UA default #0000EE —
   about 1.3:1 on the dark card. --surface-fg is defined for both the dark
   and light surfaces, so this holds if a card ever renders on light.
   Not --tf-yellow (focus rings and primary buttons own it) and not
   --tf-blue-03 (fails contrast on dark). The underline, not the colour,
   carries the affordance. Scoped to the card so it cannot leak into the
   dashboard shell. */
.tf-auth-card a {
  color: var(--surface-fg);
  text-decoration: underline;
}

.tf-auth-card__brand {
  display: block;
  margin-bottom: var(--sp-5);
}

/* Logo lockup — mark stays brand yellow, wordmark reads --surface-fg,
   which is the asset's own native colouring (white word, yellow mark)
   on this dark surface — no recolour needed. */
.tf-auth-card__brand .tf-logo { height: 20px; width: auto; display: block; }
.tf-auth-card__brand .tf-logo__mark { fill: var(--tf-yellow); }
.tf-auth-card__brand .tf-logo__word { fill: var(--surface-fg); }

.tf-auth-card__title {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: var(--fw-regular);
  letter-spacing: var(--tracking-heading);
  margin: 0 0 var(--sp-2);
}

.tf-auth-card__body {
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--surface-fg-muted);
  margin: 0 0 var(--sp-2);
}
.tf-auth-card__body:last-of-type { margin-bottom: var(--sp-5); }
.tf-auth-card__body strong { color: var(--surface-fg); font-weight: var(--fw-medium); }

/* ─── Field ─── */

.tf-auth-field { margin-bottom: var(--sp-5); }
.tf-auth-field__label {
  display: block;
  font-size: var(--text-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--surface-fg-muted);
  margin-bottom: var(--sp-2);
}
.tf-auth-field__input {
  width: 100%;
  font-family: var(--font-ui);
  font-size: var(--text-base);
  color: var(--surface-fg);
  background: var(--surface-card);
  border: 1px solid var(--surface-border-strong);
  border-radius: var(--r-sm);
  padding: 10px var(--sp-3);
  min-height: 40px;
  transition: border-color var(--dur-quick) var(--ease-out), box-shadow var(--dur-quick) var(--ease-out);
}
.tf-auth-field__input::placeholder { color: var(--surface-fg-muted); }
.tf-auth-field__input:focus-visible {
  outline: none;
  border-color: var(--surface-fg);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tf-yellow) 45%, transparent);
}
.tf-auth-field__input[disabled] {
  background: var(--surface-bg);
  color: var(--surface-fg-muted);
  cursor: not-allowed;
}
.tf-auth-field--invalid .tf-auth-field__input {
  border-color: var(--tf-status-red-fg);
}
.tf-auth-field--invalid .tf-auth-field__input:focus-visible {
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--tf-status-red-fg) 22%, transparent);
}
.tf-auth-field__error {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: var(--sp-2);
  font-size: var(--text-sm);
  color: var(--tf-status-red-fg);
}
/* Match the icon to this field error's own red rather than the
   qualification-taxonomy red .caveat-icon--error carries by default,
   so the icon and its adjacent text read as one consistent error tone. */
.tf-auth-field__error .caveat-icon { color: currentColor; }

/* ─── Buttons: loading affordance on the existing .btn system ───
   Reuses .btn/.btn--primary/.btn--secondary/.btn--block as-is for shape,
   padding and radius; only fill/text are inverted per surface below,
   since the vendored .btn--primary (solid off-black, white text) has
   almost no contrast against the dark surface's own near-black card. */

.tf-auth-card .btn--primary {
  background: var(--surface-fg);
  color: var(--surface-bg);
  border-color: var(--surface-fg);
}
.tf-auth-card .btn--primary:hover { opacity: 0.88; }
.tf-auth-card .btn--secondary {
  color: var(--surface-fg);
  border-color: var(--surface-border-strong);
}
.tf-auth-card .btn--secondary:hover { background: color-mix(in srgb, var(--surface-fg) 6%, transparent); }

.btn[data-loading="true"] {
  cursor: not-allowed;
  opacity: 0.85;
  pointer-events: none;
}

/* ─── Spinner (proposed system addition — none exists today) ───
   Built from tokens only: a neutral mid-grey track works against either
   surface without a conditional, and the indicator arc is currentColor,
   so it automatically matches whatever text colour it sits inside. */

.tf-auth-spinner {
  display: inline-block;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2px solid rgba(128, 128, 128, 0.35);
  border-top-color: currentColor;
  animation: tf-auth-spin 0.7s linear infinite;
  flex: 0 0 auto;
}
.tf-auth-spinner--lg { width: 28px; height: 28px; border-width: 3px; }

@keyframes tf-auth-spin {
  to { transform: rotate(360deg); }
}
@media (prefers-reduced-motion: reduce) {
  .tf-auth-spinner { animation-duration: 1.6s; }
}

.tf-auth-loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-4);
  padding: var(--sp-9) var(--sp-4);
  text-align: center;
}
.tf-auth-loading__text {
  font-family: var(--font-ui);
  font-size: var(--text-base);
  color: var(--surface-fg-muted);
}

/* ─── Alert / banner (proposed system addition — none exists today) ───
   Reuses the existing status-colour tokens rather than inventing new
   ones: amber = --tf-status-amber-*, red = --tf-status-red-*, the
   brand-locked "complete" yellow = --op-complete-* for success. Icon
   slot reuses the existing .caveat-icon mask mechanism from
   components.css (no new SVG asset). */

.tf-auth-alert {
  display: flex;
  gap: var(--sp-3);
  align-items: flex-start;
  padding: var(--sp-3) var(--sp-4);
  border-radius: var(--r-md);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}
.tf-auth-alert__body { flex: 1 1 auto; }

/* Tinted rather than the full-saturation brand fill: --op-complete-bg is
   locked full-strength neon (right for a status badge dot), but a
   full-bleed banner at that saturation reads far louder than the
   amber/red alerts beside it. Same fg token, softened bg only, so the
   three alert variants read as one family. */
.tf-auth-alert--success { background: color-mix(in srgb, var(--tf-yellow) 24%, var(--tf-white)); color: var(--op-complete-fg); }
.tf-auth-alert--warning { background: var(--tf-status-amber-bg); color: var(--tf-status-amber-fg); }
.tf-auth-alert--error   { background: var(--tf-status-red-bg);   color: var(--tf-status-red-fg); }

.tf-auth-alert--success .caveat-icon,
.tf-auth-alert--warning .caveat-icon { color: currentColor; }
.tf-auth-alert--error .caveat-icon { color: currentColor; }

/* ─── Expired / used link state — gets its own composition, not just
   an alert colour swap: this is the most likely real failure a
   customer hits. ─── */

.tf-auth-expired__icon {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--tf-status-amber-bg);
  color: var(--tf-status-amber-fg);
  display: grid;
  place-items: center;
  margin: 0 auto var(--sp-4);
}
.tf-auth-expired__icon svg { width: 20px; height: 20px; }

.tf-auth-expired__reasons {
  margin: 0 0 var(--sp-5);
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sp-2);
}
/* The bullet is positioned, not gridded. See the note above
   .tf-auth-status__list li — the same rule there broke #91 the moment a
   bullet contained a link. These reasons are plain text today; the
   positioned bullet is what keeps that from mattering. */
.tf-auth-expired__reasons li {
  position: relative;
  padding-left: calc(16px + var(--sp-2));
  font-size: var(--text-sm);
  color: var(--surface-fg-muted);
  line-height: var(--leading-normal);
}
.tf-auth-expired__reasons li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 8px;
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--surface-fg-muted);
}

/* ─── Dashboard shell (home.html) — issue #41, corrected per #56 item 1 ───
   home.html previously wrapped the dashboard in a bespoke .tf-dashboard-shell
   (width capped at 960px, its own padding) rather than the vendored
   .tf-page class (design-system/pages.css) that battery-dashboard-preview.html
   (#17) actually uses and that the auth/access design preview (#41) was
   corrected to reuse on 04/08/2026. That mismatch produced the outer-padding
   drift #56 flagged. Fixed by using .tf-page directly, with the same
   [data-surface="dark"] override battery-dashboard-preview.css proved:
   .tf-page's own literal background/color (pages.css) are light-surface
   tokens, so the dark variant must be re-asserted here for the production
   sidecar, the same way the preview's sidecar does it. home.html now also
   loads design-system/pages.css to pick up the base .tf-page rule. */

[data-surface="dark"].tf-page {
  background: var(--surface-bg);
  color: var(--surface-fg);
}

.tf-page__chrome {
  display: flex;
  flex-wrap: wrap;
  row-gap: var(--sp-3);
  align-items: center;
  justify-content: space-between;
  padding-bottom: var(--sp-5);
  margin-bottom: var(--sp-6);
  border-bottom: 1px solid var(--surface-border-strong);
}
.tf-page__chrome-brand {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  white-space: nowrap;
  font-family: var(--font-ui);
  font-weight: var(--fw-semibold);
  color: var(--surface-fg);
}
.tf-page__chrome-brand .tf-logo { height: 22px; width: auto; display: block; }
.tf-page__chrome-brand .tf-logo__mark { fill: var(--tf-yellow); }
.tf-page__chrome-brand .tf-logo__word { fill: var(--surface-fg); }

.tf-page__chrome-account {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--text-xs);
}
.tf-page__chrome-email { color: var(--surface-fg-muted); text-transform: none; letter-spacing: 0; }
.tf-page__chrome-signout {
  font-family: var(--font-ui);
  font-size: var(--text-2xs);
  font-weight: var(--fw-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-eyebrow);
  padding: 4px 10px;
  border-radius: var(--r-pill);
  border: 1px solid var(--surface-border-strong);
  background: transparent;
  color: var(--surface-fg);
  cursor: pointer;
}
.tf-page__chrome-signout:disabled { cursor: default; opacity: 0.6; }

/* Sits below the chrome row rather than replacing it: sign-out failing
   changes nothing about the dashboard content the customer is already
   looking at. */
.tf-page__chrome-alert {
  margin: 0 0 var(--sp-6);
}

/* ─── Chrome at narrow widths ───
   Ported from the approved preview (battery-dashboard-preview.css) in #76.
   Kept beside the chrome's desktop rules above rather than in the dashboard
   stylesheet's narrow block, so the two halves of one component stay
   together. ─── */
@media (max-width: 640px) {
  /* Both header groups get the full width and stack, instead of two short
     columns squeezed against opposite edges. */
  .tf-page__chrome {
    flex-direction: column;
    align-items: flex-start;
  }
  .tf-page__chrome-account {
    flex-wrap: wrap;
    row-gap: var(--sp-2);
  }
}

/* ─── Status icon-circle + reasons list — not-authorised (403) and
   session-expired (401) full-page states. Deliberately NOT the same
   colour or glyph as each other or as sign-in's expired-link icon: see
   the design note in dashboard-auth-preview.html's notes block. ─── */

.tf-auth-status__icon {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  margin: 0 auto var(--sp-4);
}
.tf-auth-status__icon svg { width: 20px; height: 20px; }
.tf-auth-status__icon--amber { background: var(--tf-status-amber-bg); color: var(--tf-status-amber-fg); }
.tf-auth-status__icon--grey  { background: var(--tf-status-grey-bg);  color: var(--tf-status-grey-fg); }

.tf-auth-status__list {
  margin: 0 0 var(--sp-5);
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--sp-2);
  text-align: left;
}
/* #91: the bullet is positioned, not gridded, and that is load-bearing.
   This rule used to be `display: grid; grid-template-columns: 16px 1fr`,
   which works only while the li holds a single run of text. The support
   line holds three children — the text "Contact ", the <a>, and the
   trailing text — and grid wraps each contiguous text run in its own
   anonymous item. Four items into two columns put the anchor in the 16px
   bullet column: a 16px box around 147px of content. Positioning the dot
   lets the li flow as normal text, so inline markup is safe here.
   Geometry is unchanged: dot at offset 0, text indented 24px. */
.tf-auth-status__list li {
  position: relative;
  padding-left: calc(16px + var(--sp-2));
  font-size: var(--text-sm);
  color: var(--surface-fg-muted);
  line-height: var(--leading-normal);
}
.tf-auth-status__list li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 8px;
  width: 4px; height: 4px; border-radius: 50%;
  background: var(--surface-fg-muted);
}
