/* Sky Portal — base styles
   Adapted from prototype/styles.css for the v2 warm-cream visual language
   (see docs/superpowers/specs/2026-07-08-sky-portal-foundation-design.md §10). */

/* ---------- Brand fonts (self-hosted; CSP is default-src 'self') ---------- */
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/barlow-latin-400-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/barlow-latin-500-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url("fonts/barlow-latin-600-normal.woff2") format("woff2");
}
@font-face {
  font-family: "Barlow";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url("fonts/barlow-latin-700-normal.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");
}
@font-face {
  font-family: "IBM Plex Mono";
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url("fonts/ibm-plex-mono-latin-500-normal.woff2") format("woff2");
}

:root {
  --bg:          #f3eee6;   /* app background — warm cream */
  --bg-warm:     #fbf3e7;   /* soft radial gradient — top-right */
  --bg-cool:     #e9f1f5;   /* soft radial gradient — bottom-left */
  --text:        #2b2622;   /* primary text */
  --muted:       #71706c;   /* secondary text — 4.6:1 on white AND on the #faf6ef table head (WCAG AA) */
  --muted-2:     #787671;   /* faint text — was #a6a49d at 2.49:1, the worst contrast in the app */
  --label:       #5b5b60;   /* form / field labels */
  --line:        #e5ddd0;   /* hairline borders on warm bg */
  --line-2:      #eae1d2;   /* sidebar border */

  --panel:       #ffffff;   /* cards / panels on the warm bg */
  --panel-2:     #faf6ef;   /* subtle panel tint */
  --card-line:   #ece8e0;   /* card border */

  --sidebar-1:   #fffdf9;   /* sidebar gradient start */
  --sidebar-2:   #fcf7ee;   /* sidebar gradient end */
  --sidebar-shadow: rgba(122,92,42,.06);

  --brand:       #1f8fc9;   /* primary action blue — decorative use (focus rings, borders) */
  /* Same blue, darkened until WHITE text on it reaches 4.5:1. The brand stays #1f8fc9 wherever
     nothing sits on top of it; this is only for filled controls carrying label text, which were
     failing at 3.6:1 — the primary button, on every screen in the app. */
  --brand-strong: #1b7eb1;
  --brand-ink:   #ffffff;   /* text on brand blue */
  --brand-hover: #1a7bad;
  --link:        #1f7eae;   /* inline link blue — was 4.45:1, a hair under AA */
  --link-hover:  #185f84;

  --active-bg:   #e8f2f9;   /* active nav background */
  --active-text: #1d6da0;   /* active nav text */

  --dark:        #26262a;   /* dark surfaces — floating "view as" pill, toasts */
  --dark-ink:    #ffffff;   /* text on dark surfaces */

  /* Ink for a control whose own background is a FIXED light overlay — a white alpha painted over
     something that is a photograph or a crew colour in both themes, so the overlay itself cannot
     follow the palette. --text is the wrong token for those: it inverts, and light ink on a
     near-white disc is invisible. This one is pinned identical in both blocks and locked by a
     test. It shipped as the × on every booking card at 1.17:1. */
  --on-light:    #26221c;

  /* Status chips — Delivered (green) / Uploaded (amber) / Created (neutral) */
  --ok:          #1c7a4d;
  --ok-bg:       #e9f5ee;
  --ok-line:     #bfe0cd;
  --warn:        #8a4e12;
  --warn-bg:     #fdf6e9;
  --warn-line:   #eeddb8;
  --neutral:     #6b6a64;
  --neutral-bg:  #efeae0;
  --neutral-line: #ddd5c5;

  --radius:      14px;   /* cards / panels */
  --radius-sm:   8px;    /* inputs / buttons */
  --radius-chip: 6px;    /* small chips / badges */

  --maxw: 1180px;
  --sidebar-w: 236px;
  /* A strip down the right of every calendar day column that a booking card never covers, so
     there is always somewhere to click-and-drag a NEW job — including at a time something is
     already booked. Drag-to-create bails out on pointerdown inside a .cal-card, and a lone card
     used to span the full column, so any minute that was booked had nowhere left to aim. Cards
     only split into side-by-side lanes once two bookings ALREADY overlap, which you could not
     reach, because creating the second one was the thing being blocked. */
  --cal-gutter: 36px;

  --font: Barlow, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Tokens for values that used to be written literally in rules below. A hardcoded colour is
     invisible to the dark palette, so every one of these had to become a token before dark mode
     could be correct rather than merely mostly-correct. */
  --danger:      #b3432e;   /* inline form errors */
  --success:     #2f7a4d;   /* inline form confirmations */
  --btn-hover-line: #cdbfa8;
  --focus-ring:  rgba(31, 143, 201, .16);
  --card-shadow: 0 30px 80px -40px rgba(43, 38, 34, .25);
  /* A QR code must stay dark-on-light to scan — see the dark block. The modules are painted by
     qr.js into an SVG, so the pair lives here and security.js hands both to the encoder rather
     than qr.js keeping its own copy of the same two values. */
  --qr-bg:       #ffffff;
  --qr-fg:       #2b2622;

  /* Alpha families. extra.css tints and scrims with the brand blue and the text brown at a dozen
     opacities; each needs its own token because a colour-mix or opacity trick can't be swapped by
     the dark palette. The suffix IS the opacity, so --brand-a16 is the brand at .16. */
  --brand-a06:   rgba(31, 143, 201, .06);
  --brand-a10:   rgba(31, 143, 201, .10);
  --brand-a12:   rgba(31, 143, 201, .12);
  --brand-a16:   rgba(31, 143, 201, .16);
  --brand-a90:   rgba(31, 143, 201, .90);
  --scrim-a05:   rgba(43, 38, 34, .05);
  --scrim-a08:   rgba(43, 38, 34, .08);
  --scrim-a12:   rgba(43, 38, 34, .12);
  --scrim-a25:   rgba(43, 38, 34, .25);
  --scrim-a30:   rgba(43, 38, 34, .30);
  --scrim-a35:   rgba(43, 38, 34, .35);
  --scrim-a40:   rgba(43, 38, 34, .40);
  --scrim-a50:   rgba(43, 38, 34, .50);
  --scrim-a72:   rgba(43, 38, 34, .72);
  --scrim-a90:   rgba(43, 38, 34, .90);

  /* Typed-in surfaces. In the light theme a white field on a cream page is already distinct, so
     these match --panel/--line exactly and nothing moves. In dark, an input painted --panel on a
     modal that is ALSO --panel disappears: on a phone the booking form read as floating labels
     with no boxes. Dark makes the field DARKER than its panel, so it reads as a recessed well,
     and lifts the border until the edge is visible on both the panel and the page. */
  --input-bg:    #ffffff;
  --input-line:  #e5ddd0;

  --active-line: #cfe4f0;   /* border on info/active chips, matching --active-bg */
  --danger-bg:   #fbe9e6;
  --danger-line: #f0c9c0;
  --danger-ink:  #ffffff;   /* text ON a --danger fill — see the dark block, it cannot stay white */

  /* Unthemed by intent, every one. Changing any of these with the palette is a bug, not an
     omission, so they are tokens purely to say so in one place.
       --on-solid:   ink on a surface that is dark in BOTH themes (the photo lightbox, tile badges)
                     or on a per-crew generated colour, or over a photo. Always white.
       --xero:       Xero's own brand blue, used on the Xero panels. A third party's colour.
       --annot:      the mark-up arrow drawn over delivered photos. Must stay legible on any image,
                     and agents have been reading these for months.
       --annot-pin:  the numbered marker on the same mark-up. It used to be --brand, which inverts,
                     so in dark mode the marker went pale blue under white digits (2.4:1) while the
                     PNG the retoucher received kept the deep blue. The marker sits on a PHOTO, not
                     on a panel, so the reason --brand inverts never applied to it.
       --annot-fill: the wash inside a mark-up box. --annot at .08.
     annotator.js reads these four through SkyTheme.color() so the canvas and the SVG overlay
     cannot drift apart. Give any of them a dark value and the two stop matching. */
  --on-solid:    #ffffff;
  --xero:        #13b5ea;
  --xero-text:   #0d7fa8;   /* the same brand, darkened until it passes AA as text on a panel */
  --annot:       #ff5c39;
  --annot-pin:   #1f8fc9;
  --annot-fill:  rgba(255, 92, 57, .08);

  --brand-a30:   rgba(31, 143, 201, .30);
  --brand-a40:   rgba(31, 143, 201, .40);
  --scrim-a16:   rgba(43, 38, 34, .16);
  --danger-a12:  rgba(192, 64, 59, .12);

  /* Overlays drawn ON a surface that INVERTS with the theme: the floating view-as pill
     (var(--dark)), the active gallery tab and the week-flip (var(--brand)). Those fills are dark
     in the light theme and light in the dark one, so their overlays have to flip with them.
     White at .16 over a light-blue tab is nothing at all. Distinct from the white alphas left
     alone on the photo lightbox and calendar cards, whose surfaces are dark in BOTH themes. */
  --ink-a08:     rgba(255, 255, 255, .08);
  --ink-a10:     rgba(255, 255, 255, .10);
  --ink-a16:     rgba(255, 255, 255, .16);
  --ink-a20:     rgba(255, 255, 255, .20);
  --ink-a22:     rgba(255, 255, 255, .22);
  --ink-a40:     rgba(255, 255, 255, .40);
  --ink-a50:     rgba(255, 255, 255, .50);
  --ink-a55:     rgba(255, 255, 255, .55);
  --ink-a65:     rgba(255, 255, 255, .65);
  --ink-a70:     rgba(255, 255, 255, .70);

  color-scheme: light;
}

/* ---------- Dark palette ----------
   Set as data-theme="dark" on <html> by theme.js, which runs BEFORE first paint (a synchronous
   <script> in <head>, not deferred) so the app never flashes cream and then goes dark.

   COOL NEAR-BLACK NAVY, taken from the Sky Images Uploader, 2026-09-02. Dave: "look at the colour
   palet of the uploader and make the dark theme more like that." It used to be a warm near-black
   on the reasoning that a brown night matched the cream day; his call to drop that and have the
   two products look like one company after dark. The light theme stays warm cream.

   #29abe2 is the uploader's brand blue exactly (the chevron in the logo), and --text is its
   #f4f6f8. What was NOT copied is the uploader's surface ramp: its panel sits 1.06:1 from its
   page and its cards 1.06:1 from the panel, which is flatter than the portal was on the morning
   Dave said he could not read it. The uploader carries its structure on BORDERS instead
   (--line2 1.57:1, --hover-line 1.87:1 against a panel). Here the surfaces keep real steps and
   the borders are strong as well; see the ramp note below.

   Every token above is redefined here; adding a token to :root without adding it here is the bug
   this file is shaped to prevent, and tests/ui/theme.test.ts fails on exactly that. Contrast is
   checked against the surface each token actually sits on, not against pure black, and
   tests/ui/dark-legibility.test.ts holds the floors rather than a comment claiming a number. */
html[data-theme="dark"] {
  /* THE SURFACE RAMP — and the reason it does not look like a ramp.

     --bg, --panel, --panel-2, --text, --muted and --muted-2 are the Sky Images Uploader's own
     values, unchanged. So is --brand. The two products are the same colour after dark on purpose.

     That palette is FLAT: its panel is 1.06:1 from its page and its cards 1.06:1 from the panel.
     Earlier the same day Dave said he could not read a portal whose panel sat at 1.09:1, so
     copying those numbers looks like walking straight back into the complaint. It is not, because
     the uploader does not ask its surfaces to do that job — its BORDERS do. Asked which he
     wanted, he chose to match the method, so:

       - the surfaces are the uploader's, flat
       - --line (1.53:1 on a panel) and --card-line (1.87:1 on a panel, 1.97:1 on the page) are
         stronger than the uploader's equivalents, because they now carry the whole separation.
         --card-line IS the uploader's --hover-line, used one role up.

     tests/ui/dark-legibility.test.ts states this as the design rule rather than a magic number: a
     panel must be separable from the page by its surface OR its edge, and whichever is doing the
     work has to be strong enough to do it. The first attempt at this navy tried to keep a surface
     step and had to be walked up three times (1.13, 1.18, 1.23) before clearing the old floor —
     which is what made it obvious the floor was describing one solution, not the requirement. */
  --bg:          #0c0f14;
  --bg-warm:     #16202b;
  --bg-cool:     #101a24;
  --text:        #f4f6f8;
  --muted:       #8b97a6;
  --muted-2:     #7c8a97;
  --label:       #9fabb9;
  --line:        #2d3846;
  --line-2:      #232b36;

  --panel:       #11161d;
  --panel-2:     #161c25;
  --card-line:   #3a4554;

  --sidebar-1:   #0f141b;
  --sidebar-2:   #0c0f14;
  --sidebar-shadow: rgba(0, 0, 0, .40);

  /* Both themes now put WHITE on a darkened blue; this block used to invert that and draw dark
     ink on a LIGHTER blue, so a primary button looked like a different control by night.

     --brand is the accent blue, unchanged, for everything with NOTHING on top of it: focus rings, borders,
     progress bars, slider thumbs, the logo chevron. Text never lands on it. #29abe2 carries white
     at only 2.62:1.

     --brand-strong is the FILL for anything carrying label text, and follows the light palette's
     rule rather than inverting it: the same blue darkened until WHITE reads on it. #0e7ba8 is
     4.75:1 with white and is the uploader's fill in both of its themes, so a primary button is now
     the same blue in both apps and in both themes. Hover DARKENS, as it already did in light. */
  --brand:       #29abe2;
  --brand-strong: #0e7ba8;
  --brand-ink:   #ffffff;
  --brand-hover: #0a6188;
  --link:        #6cc8f0;
  --link-hover:  #98dbf6;

  --active-bg:   #16394f;
  --active-text: #8ad4f5;

  /* "Dark surfaces" (the view-as pill, toasts) are defined by CONTRAST WITH the page, not by
     being dark. On a dark page they have to invert or they vanish. */
  --dark:        #f4f6f8;
  --dark-ink:    #0d1117;

  /* Pinned, NOT inverted — see the note in :root. The surface under it is a fixed white alpha in
     both themes, so its ink must be too. tests/ui/theme.test.ts locks the pair. */
  --on-light:    #26221c;

  /* The status plates ride the ramp above. They were mixed to sit LIGHTER than a #232019 panel;
     left alone under the lifted panel they would have inverted into dark holes, which reads as a
     disabled control rather than a status. Their ink is lifted to match, so the ratio ON the
     plate is unchanged. */
  --ok:          #7ee2a8;
  --ok-bg:       #14301f;
  --ok-line:     #2f6144;
  --warn:        #ffd27a;
  --warn-bg:     #2e2515;
  --warn-line:   #6b531f;
  --neutral:     #8b97a6;
  --neutral-bg:  #161c25;
  --neutral-line: #2f3a47;

  --danger:      #f2a4a4;
  --success:     #7ee2a8;
  --btn-hover-line: #4a5768;
  --focus-ring:  rgba(41, 171, 226, .26);
  /* Shadow by opacity alone reads as nothing on a dark background; deepen it and let the panel's
     own border carry the edge. */
  --card-shadow: 0 30px 80px -40px rgba(0, 0, 0, .70);
  /* Deliberately NOT inverted, either of them. A QR code is scanned by a camera that expects dark
     modules on a light field; inverting it makes many readers fail. It keeps its white card and
     its dark modules in both themes. */
  --qr-bg:       #ffffff;
  --qr-fg:       #2b2622;

  /* Brand tints track the dark theme's lighter blue, or a hover wash mixed from the light blue
     would read as a muddy stripe. Scrims go to BLACK and lift their opacity: a warm brown at .05
     over a #1a1815 panel is invisible, so every hover row and divider built on one would vanish. */
  --brand-a06:   rgba(41, 171, 226, .08);
  --brand-a10:   rgba(41, 171, 226, .14);
  --brand-a12:   rgba(41, 171, 226, .16);
  --brand-a16:   rgba(41, 171, 226, .22);
  --brand-a90:   rgba(41, 171, 226, .90);
  --scrim-a05:   rgba(0, 0, 0, .22);
  --scrim-a08:   rgba(0, 0, 0, .28);
  --scrim-a12:   rgba(0, 0, 0, .34);
  --scrim-a25:   rgba(0, 0, 0, .48);
  --scrim-a30:   rgba(0, 0, 0, .52);
  --scrim-a35:   rgba(0, 0, 0, .56);
  --scrim-a40:   rgba(0, 0, 0, .60);
  --scrim-a50:   rgba(0, 0, 0, .66);
  --scrim-a72:   rgba(0, 0, 0, .80);
  --scrim-a90:   rgba(0, 0, 0, .92);

  --input-bg:    #080b0f;
  /* WCAG 1.4.11 wants 3:1 for a control BOUNDARY, and the boundary is what tells you a field is
     there — the well itself is only ~1.3:1 from the panel behind it, which is deliberate: a field
     should recede, its edge should not. Lifted with the panel so that 3:1 still holds. */
  --input-line:  #79879a;

  --active-line: #2d5a76;
  --danger-bg:   #331c1c;
  --danger-line: #5d3434;
  /* The light theme puts white on a deep red. Dark inverts it for the same reason the brand does:
     --danger here is a light salmon, and white on it is 2.2:1. Dark ink on it is 8.4:1. */
  --danger-ink:  #1d0f0f;

  --on-solid:    #ffffff;
  --xero:        #13b5ea;
  /* Xero's blue darkened for AA on a LIGHT panel is unreadable on a dark one, so this one token
     does move — it is a contrast correction, not a rebrand. The fill colour above stays exact. */
  --xero-text:   #5cc4ee;
  /* These three are repeated at their light values on purpose — see the note in :root. They are
     drawn over a photograph, where neither the page background nor the panel exists. */
  --annot:       #ff5c39;
  --annot-pin:   #1f8fc9;
  --annot-fill:  rgba(255, 92, 57, .08);

  --brand-a30:   rgba(41, 171, 226, .34);
  --brand-a40:   rgba(41, 171, 226, .44);
  --scrim-a16:   rgba(0, 0, 0, .38);
  --danger-a12:  rgba(242, 164, 164, .16);

  /* The flip that makes the inverting surfaces work. --dark and --brand both go LIGHT in this
     theme, so every overlay drawn on them goes from white to black. Same opacities: the contrast
     they produce against their own fill is unchanged, only the direction is. */
  --ink-a08:     rgba(0, 0, 0, .08);
  --ink-a10:     rgba(0, 0, 0, .10);
  --ink-a16:     rgba(0, 0, 0, .16);
  --ink-a20:     rgba(0, 0, 0, .20);
  --ink-a22:     rgba(0, 0, 0, .22);
  --ink-a40:     rgba(0, 0, 0, .40);
  --ink-a50:     rgba(0, 0, 0, .50);
  --ink-a55:     rgba(0, 0, 0, .55);
  --ink-a65:     rgba(0, 0, 0, .65);
  --ink-a70:     rgba(0, 0, 0, .70);

  color-scheme: dark;
}

/* The Sky mark is drawn in dark ink for a cream background; on #1a1815 it reads as a grey smudge.
   logo-white.png is the same mark inverted and has shipped alongside it since July.

   Swapped with content: rather than by adding a second <img> and hiding one, so the markup stays
   as it is and no page ever downloads both. The url() resolves against THIS stylesheet, which is
   served from /, so the same rule is correct on share.html at /s/{token} where a relative
   "assets/..." in the markup would not be.

   Scoped to these three selectors on purpose: agency logos in Branding & Watermark are customer
   artwork and must never be substituted. */
html[data-theme="dark"] .login-card .logo img,
html[data-theme="dark"] .sidebar__brand img,
html[data-theme="dark"] .mobile-topbar__logo { content: url("assets/logo-white.png"); }

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; height: 100%; }
body {
  background:
    radial-gradient(1100px 700px at 88% -8%, var(--bg-warm), transparent 60%),
    radial-gradient(1100px 700px at -8% 108%, var(--bg-cool), transparent 60%),
    var(--bg);
  background-attachment: fixed;
  color: var(--text);
  font-family: var(--font);
  font-weight: 400;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }
button { font-family: inherit; }

/* ---------- Layout ---------- */
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 28px; }
.section { padding: 40px 0; }
.eyebrow { font-size: 11px; letter-spacing: .22em; text-transform: uppercase; color: var(--muted); margin-bottom: 10px; }
h1.page { font-size: 28px; font-weight: 600; letter-spacing: -.01em; margin: 0; color: var(--text); }
.sub { color: var(--muted); font-size: 14px; margin-top: 6px; }

/* ---------- Page header (shared) — big title + muted subtitle + optional top-right action ---------- */
.page-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.page-head__text { min-width: 0; }
.page-head__sub { color: var(--muted); font-size: 14px; margin-top: 6px; }
.page-head__action { flex-shrink: 0; }

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  font-size: 13px; font-weight: 600; letter-spacing: .01em;
  padding: 10px 18px; border-radius: var(--radius-sm); cursor: pointer;
  border: 1px solid var(--line); background: var(--panel); color: var(--text);
  transition: border-color .15s, background .15s, transform .05s;
}
.btn:hover { border-color: var(--btn-hover-line); background: var(--panel-2); }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .55; cursor: not-allowed; }
.btn--primary { background: var(--brand-strong); color: var(--brand-ink); border-color: var(--brand-strong); }
.btn--primary:hover { background: var(--brand-hover); border-color: var(--brand-hover); }
.btn--ghost { background: transparent; }
.btn--sm { padding: 7px 12px; font-size: 12px; }
.btn--block { width: 100%; }

/* ---------- Forms ---------- */
.field { margin-bottom: 16px; }
.field label { display: block; font-size: 12px; color: var(--label); margin-bottom: 7px; letter-spacing: .02em; font-weight: 500; }
.field input, .field select {
  width: 100%; padding: 12px 14px; border-radius: var(--radius-sm);
  background: var(--input-bg); border: 1px solid var(--input-line); color: var(--text);
  font-size: 14px; font-family: inherit;
}
.field input:focus, .field select:focus { outline: none; border-color: var(--brand); box-shadow: 0 0 0 3px var(--focus-ring); }
.field-error { font-size: 12.5px; color: var(--danger); margin-top: 8px; min-height: 1em; }
.field-notice { font-size: 12.5px; color: var(--success); margin-top: 8px; min-height: 1em; }
.field-hint { font-size: 12px; color: var(--muted); margin-top: 8px; }

/* ---------- Login / set-password ---------- */
.login-shell {
  min-height: 100vh; display: grid; place-items: center; padding: 24px;
  background:
    radial-gradient(1100px 700px at 88% -8%, var(--bg-warm), transparent 60%),
    radial-gradient(1100px 700px at -8% 108%, var(--bg-cool), transparent 60%),
    var(--bg);
  background-attachment: fixed;
}
.login-card {
  width: 100%; max-width: 380px;
  background: var(--panel); border: 1px solid var(--card-line);
  border-radius: 20px; padding: 36px 32px;
  box-shadow: var(--card-shadow);
}
.login-card .logo { display: flex; justify-content: center; margin-bottom: 10px; }
.login-card .logo img { height: 64px; }
.login-card .tag { text-align: center; color: var(--muted); font-size: 12px; letter-spacing: .14em; text-transform: uppercase; margin-bottom: 28px; }
.login-card .btn { width: 100%; margin-top: 6px; }
.login-foot { text-align: center; margin-top: 20px; font-size: 12px; color: var(--muted-2); }
.login-foot a { color: var(--link); text-decoration: underline; text-underline-offset: 3px; }
.login-foot a:hover { color: var(--link-hover); }

.totp-qr {
  display: flex; justify-content: center; margin: 0 0 16px;
}
.totp-qr svg {
  width: 180px; height: 180px; max-width: 100%;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--qr-bg); padding: 8px;
}
.totp-secret {
  font-family: var(--font-mono); font-size: 15px; letter-spacing: .06em;
  background: var(--panel-2); border: 1px dashed var(--line); border-radius: var(--radius-sm);
  padding: 10px 12px; word-break: break-all; margin: 6px 0 14px; text-align: center;
}
.totp-uri-details { margin-bottom: 18px; }
.totp-uri-details summary { font-size: 12px; color: var(--muted); cursor: pointer; }
.totp-uri {
  font-family: var(--font-mono); font-size: 11px; color: var(--muted);
  background: var(--panel-2); border-radius: var(--radius-sm); padding: 8px 10px;
  word-break: break-all; margin-top: 8px;
}

/* ---------- Footer ---------- */
.foot { border-top: 1px solid var(--line); margin-top: 40px; }
.foot .wrap { padding: 26px 28px; display: flex; gap: 18px; align-items: center; color: var(--muted-2); font-size: 12px; }
.foot .spacer { flex: 1; }
.foot .logo img { height: 24px; }

.hidden { display: none !important; }
