/*
 * Ratelsoft brand tokens.
 * Derived from NewBrand/Brand2.dc.html and Main2.dc.html.
 *
 * Every colour used anywhere in the app must come from a token here. Never declare a
 * colour only inside a media query or [data-theme] block - the light palette on bare
 * :root is the base, and the dark blocks redefine tokens only.
 */

/*
 * The typefaces, served from this application.
 *
 * Every layout used to link fonts.googleapis.com, which meant every visitor's IP address
 * reached Google on every page view - cross-border, with no consent and no necessity.
 * docs/privacy-notice.md S4 records that as a live data-protection finding and the notice
 * had to name Google as a processor because of it. Self-hosting removes the disclosure,
 * and on a Nigerian mobile connection it also removes a third-party DNS lookup, TLS
 * handshake and round trip from the critical path of every page. Do not put a font host
 * back: tests/Feature/Site/SelfHostedFontsTest.php fails if any surface references one.
 *
 * Both are variable fonts, so one file covers 400/500/600/650/700/750/800 - every weight
 * the stylesheets ask for - instead of seven static files. Provenance, the axis surgery
 * and the subsetting decision are in docs/brand.md; the OFL text sits beside the files in
 * public/fonts, as the licence requires.
 *
 * The version is in the filename, not a query string, so public/.htaccess can cache these
 * for a year as immutable and a future upgrade simply lands on a new URL.
 *
 * format("woff2"), not the older format("woff2-variations"): the variations keyword was a
 * 2017 Safari transitional spelling and browsers that never knew it reject the whole src
 * line, which is a silent fall back to the system sans. Every engine that supports variable
 * fonts reads the axes out of the file itself.
 */
@font-face {
  font-family: "Bricolage Grotesque";
  src: url("/fonts/bricolage-grotesque-v1.001.woff2") format("woff2");
  font-weight: 200 800;
  font-stretch: 100%;
  font-style: normal;
  /*
   * swap, not optional: headings are where the brand actually lives, they are few and
   * short, and a reader who never sees the display face has been served a different site.
   * The layouts preload this file so the swap happens as early as the network allows.
   */
  font-display: swap;
}

@font-face {
  font-family: "Instrument Sans";
  src: url("/fonts/instrument-sans-v1.000.woff2") format("woff2");
  font-weight: 400 700;
  font-stretch: 100%;
  font-style: normal;
  /*
   * optional, not swap, and deliberately different from the display face. On a slow
   * Nigerian mobile connection a swap in body copy reflows the paragraph the reader is
   * halfway through; optional gives them stable text immediately in the system sans,
   * fetches the face in the background, and every page after the first in that session is
   * fully branded. The difference between Instrument Sans and a system UI sans in running
   * text is the kind of thing that is not worth a reflow.
   */
  font-display: optional;
}

@font-face {
  font-family: "Instrument Sans";
  src: url("/fonts/instrument-sans-italic-v1.000.woff2") format("woff2");
  font-weight: 400 700;
  font-stretch: 100%;
  font-style: italic;
  font-display: optional;
}

:root {
  /* Core brand */
  --ink:         #0E2A40;   /* deep navy - primary text and dark surfaces */
  --blue:        #1B7FC6;   /* primary action */
  --blue-dark:   #0F6BAB;
  --blue-deeper: #14639C;
  --leaf:        #8DC63F;   /* the flower mark's green */
  --green:       #3FAE49;
  --green-dark:  #2E7D36;
  --gold:        #F6C215;
  --gold-dark:   #8F6B00;

  /* Neutrals - blue-biased, matching the navy rather than pure grey */
  --slate:       #51677A;
  --slate-soft:  #8296A7;
  --slate-faint: #7C93A5;
  --line:        #C9D6E0;
  --line-soft:   #DFE7EE;
  --wash:        #EEF3F7;
  --wash-deep:   #EAF0F5;
  --paper:       #FAFCFE;
  --white:       #FFFFFF;

  /*
   * Semantic - separate from the brand accent.
   *
   * --ok and --warn are each a shade darker than the brand colour they came from
   * (--green-dark #2E7D36 and --gold-dark #8F6B00), and the shade is the whole point: both are
   * used as TEXT on their own wash, in a badge, a pill and a notice, and there they measured
   * 4.29:1 and 4.43:1 — under the 4.5:1 AA asks of body text. On paper they always passed, which
   * is why it survived: the failure is only in the one place the colour sits on its own tint.
   *
   * Lightening the wash instead was the wrong lever. Enough of it to clear 4.5:1 puts the wash
   * within 1.08:1 of the page, so a success badge would have had no visible background left. A
   * 5% and a 3% darkening of the ink is invisible beside the old colour and costs nothing.
   *
   * The brand tokens above are untouched: --green-dark and --gold-dark still mean what
   * CLAUDE.md says they mean. These are the semantic layer, which is what this block is for.
   * SemanticContrastTest measures every pair and is the reason this cannot drift back.
   */
  --ok:          #2C7733;
  --ok-wash:     #E4EFD4;
  --warn:        #8B6800;
  --warn-wash:   #FDF3D3;
  --danger:      #A5301F;
  --danger-wash: #F7DDD8;

  /* Assigned roles */
  --bg:          var(--paper);
  --surface:     var(--white);
  --surface-alt: var(--wash);
  --text:        var(--ink);
  --text-soft:   var(--slate);
  /*
   * The third text tier, and NOT --slate-soft, for the same reason --accent is not --blue.
   *
   * --slate-soft measured 3.06:1 on white and 2.74:1 on --wash. WCAG AA asks 4.5:1 of body text,
   * and this token carries body text in 42 places across four stylesheets: every form-field hint
   * on /register and /account-recovery, the stat labels on the dashboard, table column headers,
   * the words beside an activation code. "Faint" describes the intended weight in the hierarchy;
   * it was never a licence to fall under the floor.
   *
   * Fixed on the token rather than by repainting the 42 surfaces onto --text-soft. Those uses are
   * all correct about wanting the quietest tier — the tier itself was simply too quiet, and
   * moving them would collapse three tiers into two everywhere at once.
   *
   * #5C7082 is 5.13:1 on white, 4.59:1 on --wash and 4.99:1 on --paper: it clears AA on every
   * ground it is used on, with a little headroom on the worst of them. It stays lighter than
   * --text-soft (5.88:1), so the ramp survives — though the gap is narrower than it was, because
   * the floor is where it is and a tier below the floor is not a tier.
   *
   * The dark theme is untouched: #7C93A5 already measures 5.14:1 on --surface and 4.67:1 on
   * --surface-alt. Only the light palette was failing.
   */
  --text-faint:  #5C7082;
  --border:      var(--line);
  --border-soft: var(--line-soft);
  /*
   * The accent is --blue-dark, not --blue, and that is an accessibility fix rather than a taste.
   *
   * --accent is the colour of link text and the background of the primary button under a white
   * --on-accent, so it has to clear 4.5:1 twice over. --blue measures 4.17:1 against --paper and
   * the same against white text — under AA in both directions at once — and dragged the accent
   * pill to 3.85:1 on --wash. --blue-dark is 5.66:1 against paper and 5.06:1 on the wash.
   *
   * --blue is untouched and still right for a large heading or a graphic accent, where 3:1 is
   * the bar. The dark theme audits clean and is not changed.
   *
   * The hover then has to be darker again, and --blue-deeper is: 6.20:1, and still blue, so a
   * link keeps the one cue that says it is a link. Nothing here is a new brand colour.
   *
   * NB: no braces in this comment. BrandPalette reads the tokens out of this file by taking
   * everything between ":root " and the first closing brace, so a CSS example written inside
   * this block truncates the palette — silently, until a mail template asks for a token that
   * came after it and 41 tests fail at once. See BrandPaletteTest.
   */
  --accent:      var(--blue-dark);
  --accent-hover:var(--blue-deeper);
  --on-accent:   var(--white);

  /*
   * Type.
   *
   * Every stack names real families after the webfont, and that is not belt-and-braces:
   * Instrument Sans has no naira sign (U+20A6) at all, so on every price on the site the
   * browser falls through this list glyph by glyph. A stack ending at the bare `sans-serif`
   * keyword would still work, but naming Segoe UI / Roboto / system-ui first is what keeps
   * the naira sign in a face that matches the digits beside it. Bricolage Grotesque does
   * carry U+20A6, which is why prices set in the display face look right today.
   */
  --font-display: "Bricolage Grotesque", "Segoe UI", Roboto, system-ui, "Helvetica Neue", Arial, sans-serif;
  --font-body:    "Instrument Sans", "Segoe UI", Roboto, system-ui, "Helvetica Neue", Arial, sans-serif;
  --font-mono:    ui-monospace, "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;

  /* Scale */
  --step--1: clamp(0.83rem, 0.8rem + 0.15vw, 0.9rem);
  --step-0:  clamp(1rem, 0.96rem + 0.2vw, 1.09rem);
  --step-1:  clamp(1.2rem, 1.12rem + 0.4vw, 1.45rem);
  --step-2:  clamp(1.5rem, 1.35rem + 0.75vw, 2rem);
  --step-3:  clamp(1.95rem, 1.65rem + 1.5vw, 2.9rem);
  --step-4:  clamp(2.5rem, 1.9rem + 3vw, 4.2rem);

  --radius:    10px;
  --radius-lg: 18px;
  --shadow:    0 1px 2px rgba(14, 42, 64, .06), 0 8px 24px rgba(14, 42, 64, .07);
  --shadow-lg: 0 2px 4px rgba(14, 42, 64, .07), 0 20px 50px rgba(14, 42, 64, .11);

  /*
   * What the BROWSER paints, as opposed to what we paint.
   *
   * Checkboxes, radios, scrollbars, select menus, date pickers and the overscroll gutter are
   * drawn by the user agent, and the only thing it consults is `color-scheme`. Our tokens do
   * not reach them.
   *
   * This was missing entirely, and the page carried only
   * `<meta name="color-scheme" content="light dark">` — which says "either is fine, you
   * decide", so the browser decided from the OS. A reader on a dark system who pressed our
   * light toggle got a light page with DARK checkboxes, because the tokens flipped and the
   * user agent did not. The three blocks below move together for exactly that reason: every
   * place the palette switches, this switches with it.
   */
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    /* Native controls follow the palette. See the note on :root. */
    color-scheme: dark;

    --bg:          #08131D;
    --surface:     #0E2130;
    --surface-alt: #14293A;
    --text:        #E6EEF5;
    --text-soft:   #A6BACA;
    --text-faint:  #7C93A5;
    --border:      #21384B;
    --border-soft: #1A2E3F;
    --accent:      #4FA5E4;
    --accent-hover:#6FB8EE;
    --on-accent:   #04121C;
    --ok:          #7FCB6B;
    --ok-wash:     #16301C;
    --warn:        #E0AE55;
    --warn-wash:   #33270F;
    --danger:      #EC8375;
    --danger-wash: #351914;
  }
}

/*
 * A reader whose SYSTEM is dark but who chose our LIGHT theme.
 *
 * The tokens are already right — the media query is written
 * `:root:not([data-theme="light"])`, so it steps aside — but `color-scheme` set inside it
 * does not step aside on its own. Without this one line the page is light and every
 * checkbox, radio and scrollbar on it stays dark. That is the bug this block exists for.
 */
:root[data-theme="light"] {
  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;

  --bg:          #08131D;
  --surface:     #0E2130;
  --surface-alt: #14293A;
  --text:        #E6EEF5;
  --text-soft:   #A6BACA;
  --text-faint:  #7C93A5;
  --border:      #21384B;
  --border-soft: #1A2E3F;
  --accent:      #4FA5E4;
  --accent-hover:#6FB8EE;
  --on-accent:   #04121C;
  --ok:          #7FCB6B;
  --ok-wash:     #16301C;
  --warn:        #E0AE55;
  --warn-wash:   #33270F;
  --danger:      #EC8375;
  --danger-wash: #351914;
}

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-body);
  font-size: var(--step-0);
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  line-height: 1.08;
  letter-spacing: -0.02em;
  text-wrap: balance;
  margin: 0;
}

a { color: var(--accent); }
a:focus-visible, button:focus-visible, input:focus-visible,
select:focus-visible, textarea:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------------------------------------------------------------------------
 * The colour-theme control, and the swap.
 *
 * These two rules live in the TOKEN file rather than in any one area's stylesheet because
 * all three areas now carry the toggle — the public site, the accounts area and the admin
 * area — and brand.css is the only stylesheet all three load. Putting them in site.css, as
 * they were, meant a signed-in customer got a button with no styling and a theme change
 * that crossfaded.
 *
 * Both are written entirely in tokens, so they follow the palette they switch.
 * ------------------------------------------------------------------------- */

/* Transitions are muted for one frame around a theme change.
 *
 * Blink does not restart a transition when only the custom property behind it changes, so
 * anything with a transitioned colour kept painting the OLD theme's value: after a toggle
 * the site header's buttons were left in the previous palette, and "Client login" measured
 * about 1.2:1 — near-white on white. The script adds this class across the swap, which also
 * drops a full-page colour crossfade nobody asked for. */
.theme-switching,
.theme-switching *,
.theme-switching *::before,
.theme-switching *::after {
  transition: none !important;
}

.theme-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border: 1px solid var(--border);
  /* Round, like the pills either side of it in the site header: one shape family. The
   * accounts topbar and the admin sidebar both put it beside their own controls, and a
   * circle reads as "a setting" rather than as another item in the row. */
  border-radius: 999px;
  background: var(--surface);
  color: var(--text-soft);
  cursor: pointer;
  flex: none;
}

.theme-toggle:hover {
  color: var(--text);
  border-color: var(--accent);
}

/*
 * Ratelsoft accounts area.
 *
 * Served with resources/css/brand.css by App\Http\Controllers\Accounts\StylesheetController.
 * Every colour below is a var() onto a token declared in brand.css. There is not one literal
 * colour in this file, and there must never be one: brand.css owns the palette, including
 * both themes, and a hex here would silently opt that element out of dark mode.
 */

/* ------------------------------------------------------------------ shell */

.acc-skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--surface);
  color: var(--text);
  padding: .7rem 1rem;
  z-index: 100;
  border: 1px solid var(--border);
  border-radius: var(--radius);
}
.acc-skip:focus { left: .75rem; top: .75rem; }

.acc-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

.acc-topbar {
  display: flex;
  align-items: center;
  gap: 1rem;
  flex-wrap: wrap;
  padding: .85rem clamp(1rem, 4vw, 2.5rem);
  border-bottom: 1px solid var(--border-soft);
  background: var(--surface);
}

.acc-brand { display: flex; align-items: center; gap: .6rem; text-decoration: none; color: var(--text); }
.acc-brand img { height: 26px; width: auto; display: block; }
.acc-brand span {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--step--1);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-faint);
}

.acc-nav { display: flex; gap: .15rem; flex-wrap: wrap; margin-inline-start: auto; align-items: center; }
.acc-nav a, .acc-nav button {
  font: inherit;
  font-size: var(--step--1);
  color: var(--text-soft);
  text-decoration: none;
  padding: .45rem .7rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  background: none;
  cursor: pointer;
}
.acc-nav a:hover, .acc-nav button:hover { background: var(--surface-alt); color: var(--text); }
.acc-nav a[aria-current="page"] {
  color: var(--text);
  background: var(--surface-alt);
  border-color: var(--border-soft);
  font-weight: 600;
}

.acc-main {
  flex: 1;
  width: 100%;
  max-width: 1120px;
  margin: 0 auto;
  padding: clamp(1.25rem, 4vw, 2.5rem) clamp(1rem, 4vw, 2.5rem) 4rem;
}

.acc-footer {
  border-top: 1px solid var(--border-soft);
  padding: 1.25rem clamp(1rem, 4vw, 2.5rem);
  color: var(--text-faint);
  font-size: var(--step--1);
}

/* ------------------------------------------------------------- typography */

.acc-eyebrow {
  font-size: var(--step--1);
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-weight: 600;
  margin: 0 0 .4rem;
}
.acc-h1 { font-size: var(--step-3); margin: 0 0 .4rem; }
.acc-h2 { font-size: var(--step-1); margin: 0 0 .75rem; }
/* Step headings inside a card. It takes focus after a swap, so it needs a focus ring of its own. */
.acc-h3 { font-size: var(--step-0); margin: 0 0 .5rem; }
.acc-h3:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }
.acc-lede { color: var(--text-soft); margin: 0 0 1.5rem; max-width: 62ch; }
.acc-muted { color: var(--text-soft); }
/* --text-soft, not --text-faint, and the smaller size is what makes it faint.
   This class carries content on 72 templates — "Forgot password?", "Create an account", offer
   summaries, field hints — and --text-faint measures 2.9:1 on --paper, well under the 4.5:1 AA
   asks of text. site.css already draws this line at .vignette__head: --text-faint is chrome, and
   anything a customer has to read is --text-soft (5.8:1). A demotion in weight and size still
   reads as secondary without making the words hard to see. */
.acc-faint { color: var(--text-soft); font-size: var(--step--1); }
.acc-mono { font-family: var(--font-mono); letter-spacing: .06em; }

/* ------------------------------------------------------------------ cards */

.acc-card {
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-lg);
  padding: clamp(1.1rem, 3vw, 1.75rem);
  box-shadow: var(--shadow);
}
.acc-card + .acc-card { margin-top: 1rem; }

/*
 * ...but not inside a grid, where `gap` already does the spacing.
 *
 * The rule above is for cards STACKED in normal flow. In a grid it also matches, so every card
 * except the first got a 1rem top margin inside its own cell while the grid went on stretching
 * all of them to the row height — leaving the first card a full 1rem taller than its
 * neighbours. On the codes screen that read as "Codes owned is bigger than the others", which
 * is the one card that was right: it was the only one without the inset.
 *
 * Reported on /account/codes; it applied to every grid of cards in the accounts area.
 */
.acc-grid > .acc-card + .acc-card { margin-top: 0; }

.acc-grid { display: grid; gap: 1rem; }
.acc-grid--stats { grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); }
.acc-grid--cards { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
.acc-grid--split { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr); align-items: start; }
@media (max-width: 860px) { .acc-grid--split { grid-template-columns: minmax(0, 1fr); } }

.acc-stat { display: flex; flex-direction: column; gap: .2rem; }
.acc-stat__value { font-family: var(--font-display); font-size: var(--step-2); font-weight: 700; }
.acc-stat__label { color: var(--text-faint); font-size: var(--step--1); }

/* ------------------------------------------------------------------ badge */

.acc-badge {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  font-size: var(--step--1);
  font-weight: 600;
  padding: .2rem .6rem;
  border-radius: 999px;
  white-space: nowrap;
}
.acc-badge--ok     { background: var(--ok-wash);     color: var(--ok); }
.acc-badge--warn   { background: var(--warn-wash);   color: var(--warn); }
.acc-badge--danger { background: var(--danger-wash); color: var(--danger); }

/* --------------------------------------------------------- product tiles */

.acc-product { display: flex; gap: 1rem; align-items: flex-start; }
.acc-product img { width: 52px; height: 52px; object-fit: contain; flex: none; }
.acc-product__body { min-width: 0; flex: 1; }
.acc-product__name { font-family: var(--font-display); font-weight: 700; font-size: var(--step-0); margin: 0 0 .15rem; }

.acc-expiry {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: var(--step-1);
  margin: .35rem 0 .1rem;
}
.acc-expiry--warn   { color: var(--warn); }
.acc-expiry--danger { color: var(--danger); }

/* ------------------------------------------------------------------ forms */

.acc-form { display: grid; gap: 1.1rem; max-width: 30rem; }
.acc-form--wide { max-width: none; }
.acc-field { display: grid; gap: .35rem; }
.acc-field label { font-weight: 600; font-size: var(--step--1); }
.acc-field input, .acc-field select, .acc-field textarea {
  font: inherit;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: .65rem .75rem;
  width: 100%;
}
.acc-field input:focus-visible { border-color: var(--accent); }
.acc-field__hint { color: var(--text-faint); font-size: var(--step--1); }
.acc-field__error { color: var(--danger); font-size: var(--step--1); font-weight: 600; }
.acc-check { display: flex; gap: .5rem; align-items: center; font-size: var(--step--1); }
.acc-check input { width: auto; }

.acc-btn {
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .45rem;
  padding: .7rem 1.15rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  text-decoration: none;
  transition: background-color .15s ease, border-color .15s ease;
}
.acc-btn--primary { background: var(--accent); color: var(--on-accent); }
.acc-btn--primary:hover { background: var(--accent-hover); }
.acc-btn--ghost { background: transparent; color: var(--text); border-color: var(--border); }
.acc-btn--ghost:hover { background: var(--surface-alt); }
.acc-btn--quiet { background: transparent; color: var(--text-soft); }

/*
 * The way out of a flow, rendered as what it is: a control that goes somewhere.
 *
 * `--quiet` used to carry `padding-inline: 0` as well, and the result was a button with its
 * affordance deleted — no border, no background, no padding, and a colour softer than the body
 * text. On a phone it was indistinguishable from a paragraph, and it was the control somebody
 * reaches for when they have realised they are signing in to the wrong account on a school's
 * shared desktop. It reads as a link now: body-text colour rather than the soft one, a visible
 * underline, and the .acc-btn padding kept so the hit target clears 44x44 CSS pixels. Both
 * themes come free because every value is a token.
 *
 * Still not a primary, and still a POST on a real <button>: a GET would let any page on the
 * internet cancel a customer's sign-in mid-flow with an <img> tag.
 */
.acc-btn--link {
  background: transparent;
  color: var(--text);
  text-decoration: underline;
  text-underline-offset: .2em;
  text-decoration-thickness: 1px;
}
.acc-btn--link:hover { background: var(--surface-alt); text-decoration-thickness: 2px; }
.acc-btn[disabled] { opacity: .55; cursor: not-allowed; }
.acc-btn-row { display: flex; gap: .6rem; flex-wrap: wrap; align-items: center; }

/* --------------------------------------------------------------- notices */

.acc-notice {
  border-radius: var(--radius);
  border: 1px solid var(--border-soft);
  padding: .85rem 1rem;
  margin-bottom: 1.25rem;
  font-size: var(--step--1);
}
.acc-notice--ok     { background: var(--ok-wash);     border-color: var(--ok);     color: var(--ok); }
.acc-notice--warn   { background: var(--warn-wash);   border-color: var(--warn);   color: var(--warn); }
.acc-notice--danger { background: var(--danger-wash); border-color: var(--danger); color: var(--danger); }
.acc-notice a { color: inherit; }

/* ---------------------------------------------------------------- tables */

.acc-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.acc-table { width: 100%; border-collapse: collapse; font-size: var(--step--1); min-width: 34rem; }
.acc-table th, .acc-table td { text-align: left; padding: .7rem .6rem; border-bottom: 1px solid var(--border-soft); }
.acc-table th { color: var(--text-faint); font-weight: 600; text-transform: uppercase; letter-spacing: .06em; }
.acc-table tbody tr:last-child td { border-bottom: 0; }
.acc-table td.acc-num, .acc-table th.acc-num { text-align: right; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------- auth split page */

.acc-split { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); min-height: 100vh; }
@media (max-width: 900px) { .acc-split { grid-template-columns: minmax(0, 1fr); } }

.acc-split__aside {
  background: var(--ink);
  color: var(--white);
  padding: clamp(1.5rem, 5vw, 3.5rem);
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
  justify-content: center;
}
.acc-split__aside h2 { color: var(--white); font-size: var(--step-2); }
.acc-split__aside ul { list-style: none; margin: 0; padding: 0; display: grid; gap: .9rem; }
.acc-split__aside li { display: flex; gap: .65rem; align-items: flex-start; color: var(--line); }
.acc-split__aside li::before { content: "—"; color: var(--leaf); flex: none; }
.acc-split__aside .acc-brand span { color: var(--line); }
.acc-split__aside a { color: var(--line); }
/* The panel is navy in BOTH themes, so everything on it takes the on-ink ink and never the
   page's body ink. `.acc-faint` resolves to --text-soft, which is a dark slate in the light
   palette: the RC number measured 2.51:1 on the navy, well under the 4.5:1 AA asks of text.
   It was always wrong for a reader whose system is set to light; it is worth naming that it
   only became easy to SEE when the theme control landed on this page. */
.acc-split__aside .acc-faint { color: var(--line); }
@media (max-width: 900px) { .acc-split__aside { display: none; } }

.acc-split__main {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1.5rem, 5vw, 3rem) clamp(1rem, 5vw, 2rem);
  /* The theme control is positioned against this box, so it must establish the
     containing block. It was `static`, which sent the button to the viewport corner and
     out over the navy panel on a wide screen. */
  position: relative;
}
.acc-split__inner { width: 100%; max-width: 26rem; }

/* Top-right of the form half, clear of the sign-in card. Absolute rather than a flex item
   because this column centres its one child vertically, and a second child would move the
   form off-centre by half a button. Under 900px the navy panel is hidden and this column is
   the whole page, so the same corner is still the right corner. */
.acc-split__theme { position: absolute; top: 1rem; right: 1rem; }

/* --------------------------------------------------------------- codes */

.acc-code {
  font-family: var(--font-mono);
  font-size: var(--step-1);
  letter-spacing: .1em;
  background: var(--surface-alt);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  padding: .55rem .8rem;
  display: inline-block;
}
.acc-code-list { display: grid; gap: .5rem; margin: .75rem 0 0; padding: 0; list-style: none; }

/* -------------------------------------------------------------- summary */

.acc-summary { display: grid; gap: .55rem; }
.acc-summary__row { display: flex; justify-content: space-between; gap: 1rem; font-size: var(--step--1); }
.acc-summary__row--total {
  font-family: var(--font-display);
  font-size: var(--step-1);
  font-weight: 700;
  border-top: 1px solid var(--border-soft);
  padding-top: .6rem;
}

/* ------------------------------------------------------- the code inventory
 *
 * Built for a reseller reading fifty rows at once. The one question is "which of
 * these can I still sell?", so the answer is encoded in FORM, not only in colour:
 * an unused row has a solid rail and a filled mark and shows its code at full size;
 * every other row has a hairline rail, a hollow mark, and recedes. A colour-blind
 * reader, a greyscale printout and a screen reader all still get it — the mark is a
 * shape, the state is written out, and the row's accessible name spells out what it
 * means. Colour is the third cue, never the only one.
 *
 * Every value here is a token from brand.css. There is not one literal colour, so
 * both themes come for free.
 */

.acc-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;
}

.acc-plain-list { margin: 0; padding-inline-start: 1.1rem; }

.acc-stat--lead .acc-stat__value { color: var(--ok); }

/* ---------------------------------------------------------------- the tools */

.acc-tools {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
  align-items: flex-end;
  justify-content: space-between;
  margin-top: 1rem;
}
.acc-tools__filter { display: flex; flex-wrap: wrap; gap: .75rem; align-items: flex-end; }
.acc-tools__filter .acc-field { min-width: 11rem; }
.acc-tools__actions { display: flex; gap: .4rem; align-items: center; }
.acc-tools__export { display: flex; flex-direction: column; gap: .35rem; align-items: flex-start; }
@media (max-width: 640px) {
  .acc-tools__filter, .acc-tools__filter .acc-field { width: 100%; }
}

/* --------------------------------------------------------------- the search */

.acc-search { margin-top: 1rem; }
.acc-search__why { max-width: 60ch; margin: 0 0 1rem; }
.acc-search__form { display: flex; gap: .75rem; align-items: flex-end; flex-wrap: wrap; }
.acc-search__form .acc-field { flex: 1 1 20rem; }
.acc-search__verdict { margin: 1.1rem 0 0; font-weight: 600; }
.acc-search__verdict--found { color: var(--ok); }
.acc-search__verdict--missing { color: var(--text-soft); }

/* ----------------------------------------------------------------- the list */

.acc-codes__caption { margin: 1.5rem 0 .6rem; }
.acc-codes__note { margin-top: 1.25rem; max-width: 70ch; }
.acc-codes__pager { margin-top: 1rem; }

.acc-codes { list-style: none; margin: 0; padding: 0; display: grid; gap: .55rem; }
.acc-codes--single { margin-top: .9rem; }

.acc-code-row {
  display: grid;
  grid-template-columns: 8.5rem minmax(0, 1.4fr) minmax(0, 1fr);
  gap: .5rem 1.25rem;
  align-items: center;
  background: var(--surface);
  border: 1px solid var(--border-soft);
  border-inline-start: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: .85rem 1rem;
}
@media (max-width: 720px) {
  .acc-code-row { grid-template-columns: minmax(0, 1fr); align-items: start; }
}

/* The sellable one leads: a solid rail, full contrast, and it sits forward. */
.acc-code-row--unused {
  border-inline-start: 4px solid var(--ok);
  background: var(--surface);
  box-shadow: var(--shadow);
}
/* Everything else recedes — dashed rail, flatter ground, no shadow. */
.acc-code-row--used,
.acc-code-row--expired,
.acc-code-row--withdrawn {
  border-inline-start: 4px dashed var(--border);
  background: var(--surface-alt);
}

.acc-code-row__state {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 0;
  font-size: var(--step--1);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
}
.acc-code-row__word { color: var(--text-faint); }
.acc-code-row--unused .acc-code-row__word { color: var(--ok); }

/* The mark is a drawn shape, so it survives greyscale and a stylesheet-only reading. */
.acc-code-row__mark {
  flex: none;
  position: relative;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  border: 2px solid var(--text-faint);
}
.acc-code-row--unused .acc-code-row__mark { background: var(--ok); border-color: var(--ok); }
.acc-code-row--expired .acc-code-row__mark::after {
  content: "";
  position: absolute;
  inset-block: 50%;
  inset-inline: -3px;
  border-top: 2px solid var(--text-faint);
  transform: rotate(-45deg);
}
.acc-code-row--withdrawn .acc-code-row__mark {
  border-radius: 3px;
  background: var(--text-faint);
}

.acc-code-row__main { min-width: 0; display: flex; flex-direction: column; gap: .35rem; align-items: flex-start; }

.acc-code-row__value { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.acc-code-row__main .acc-code { font-size: var(--step-0); word-break: break-all; }
.acc-code-row--unused .acc-code-row__main .acc-code {
  font-size: var(--step-1);
  font-weight: 600;
  border-style: solid;
  border-color: var(--ok);
  color: var(--text);
}
.acc-code--absent {
  color: var(--text-faint);
  font-family: var(--font-body);
  font-style: italic;
  letter-spacing: normal;
  border-style: dotted;
}

.acc-code-row__product { margin: 0; display: flex; align-items: center; gap: .4rem; font-size: var(--step--1); flex-wrap: wrap; }
.acc-code-row__product img { flex: none; width: 20px; height: 20px; object-fit: contain; }
.acc-code-row__bought { margin: 0; font-size: var(--step--1); }

.acc-code-row__use { font-size: var(--step--1); color: var(--text-soft); min-width: 0; }
.acc-code-row__line { margin: 0 0 .15rem; }

/* ---------------------------------------------------------- the copy button */

.acc-copy {
  font: inherit;
  font-size: var(--step--1);
  font-weight: 600;
  cursor: pointer;
  padding: .3rem .7rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
.acc-copy:hover { background: var(--surface-alt); }
.acc-copy.is-done { border-color: var(--ok); color: var(--ok); }

/* ----------------------------------------------------------- unmatched rows */

.acc-unmatched { margin-top: 1.25rem; }

/* One visible focus ring for everything this screen adds. */
.acc-copy:focus-visible,
.acc-code-row a:focus-visible,
.acc-tools :focus-visible,
.acc-search :focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

/* ------------------------------------------------- two-step sign-in (2FA) */

/*
 * The neutral badge. The three existing modifiers all carry a judgement — ok, warn, danger —
 * and "two-step sign-in is off" is a state, not a fault: colouring it red would nag a customer
 * about a setting they have never been offered before today.
 */
.acc-badge--quiet { background: var(--surface-alt); color: var(--text-soft); }

/*
 * No new visual vocabulary: the panel is an .acc-card, the states are .acc-badge, the
 * secrets are .acc-code and the buttons are .acc-btn. All this block does is lay those out
 * and give the QR a ground of its own — every colour is a token, so both themes come free.
 */

.acc-2fa__rule {
  border: 0;
  border-top: 1px solid var(--border-soft);
  margin: 1.25rem 0;
}

/* The codes flow into as many columns as fit, so eight of them are one glance, not a scroll. */
.acc-2fa__codes {
  list-style: none;
  margin: 1rem 0;
  padding: 0;
  display: grid;
  gap: .5rem;
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
}
.acc-2fa__codes .acc-code {
  font-size: var(--step-0);
  width: 100%;
  text-align: center;
}

.acc-2fa__enrol {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  align-items: start;
  margin-top: 1rem;
}

/*
 * The QR sits on white and stays on white in dark mode. A phone camera reads a dark-on-light
 * square; inverting it for the theme produces a code that half the scanners on the market
 * refuse, which would look like our enrolment being broken rather than like a styling choice.
 */
.acc-2fa__qr {
  margin: 0;
  padding: .75rem;
  background: var(--white);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  display: grid;
  gap: .5rem;
  justify-items: center;
  max-width: 15rem;
}
.acc-2fa__qr svg, .acc-2fa__qr img { display: block; width: 100%; height: auto; max-width: 12rem; }
.acc-2fa__qr figcaption { max-width: 12rem; text-align: center; }

/*
 * The chooser. Two rows, never a two-column grid: they are not equals, the copy says so, and
 * side-by-side columns are exactly how a reader is told two things are interchangeable.
 */
.acc-2fa__choices { list-style: none; margin: 1.25rem 0; padding: 0; display: grid; gap: 1.25rem; }
.acc-2fa__choice {
  display: grid;
  gap: .6rem;
  justify-items: start;
  padding: 1rem;
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  background: var(--surface-alt);
}

/*
 * The register: one row per thing, the status on the left and the control on the right, and
 * nothing expanded. Below 44rem the control drops under its row rather than squeezing beside it.
 */
.acc-2fa__rows { list-style: none; margin: 1.25rem 0 0; padding: 0; display: grid; gap: .25rem; }
.acc-2fa__row {
  display: flex;
  flex-wrap: wrap;
  gap: .75rem 1rem;
  align-items: start;
  justify-content: space-between;
  padding: .9rem 0;
  border-top: 1px solid var(--border-soft);
}
.acc-2fa__rowMain { min-width: 12rem; flex: 1 1 16rem; display: grid; gap: .2rem; }
.acc-2fa__rowName { margin: 0; font-weight: 600; }
.acc-2fa__rowState { margin: 0; color: var(--text-soft); font-size: var(--step--1); }

/*
 * "6 of 8 left" followed by a "Never saved" badge.
 *
 * The recovery row carries two facts that compose rather than replace each other: how many codes
 * remain, and whether anybody ever said they had the list at all. The badge is `inline-flex`, so
 * without this it butts straight against the word before it. `:not(:first-child)` because the
 * "None left" state IS the badge and starts the line — a leading indent there would look like a
 * misaligned row.
 */
.acc-2fa__rowState .acc-badge:not(:first-child) { margin-left: .4rem; }
.acc-2fa__rowMain .acc-faint { margin: .2rem 0 0; }

/*
 * The password-gated disclosures. `<summary>` carries .acc-btn, so it has to lose the marker
 * and the default `display: list-item` or it renders as a triangle followed by a full-width
 * block instead of a button.
 */
.acc-2fa__disclose { flex: none; }
.acc-2fa__disclose > summary { display: inline-flex; cursor: pointer; list-style: none; }
.acc-2fa__disclose > summary::-webkit-details-marker { display: none; }
.acc-2fa__disclose[open] { flex: 1 1 100%; }
.acc-2fa__disclose[open] > summary { margin-bottom: .9rem; }
.acc-2fa__disclose .acc-form { max-width: 24rem; }

/*
 * The challenge screen's button group: the one primary and, beside it, the send. Two forms on
 * one line — the submit is bound to the code form by the HTML `form` attribute — so the row is
 * flex and wraps rather than squeezing on a narrow phone.
 */
.acc-2fa__actions { margin-top: 1.25rem; }
.acc-2fa__cancel { margin-top: .75rem; }

/*
 * The recovery route on the challenge screen: one collapsed disclosure, immediately under the
 * primary action. It is a real <details> so it works with the script gone, and the summary is
 * styled as an obvious control rather than a heading — it is one click from being the only way
 * into the account.
 */
.acc-2fa__escape {
  margin: 1.5rem 0 1rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--border-soft);
}
.acc-2fa__escape > summary {
  cursor: pointer;
  font-weight: 600;
  padding: .5rem 0;
  color: var(--text);
}
.acc-2fa__escape > summary:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.acc-2fa__escape > * + * { margin-top: .75rem; }
.acc-2fa__escape .acc-form { max-width: none; }

.acc-2fa :focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 2px;
}

@media (max-width: 640px) {
  .acc-2fa__enrol { grid-template-columns: minmax(0, 1fr); }
  .acc-2fa__qr { max-width: none; justify-self: start; }
}

/* ------------------------------------------------------------ the price list
 *
 * One card per product; the variants are the choice inside it. The five Network
 * Exam connection tiers are a table rather than five cards for one reason: the
 * only question a school is asking is "which of these prices do I want?", and
 * prices can only be compared when they share a column and a decimal point.
 * Hence `.acc-num` on the price cell — tabular numerals, right-aligned, so
 * ₦15,000 and ₦125,000 line up on the comma instead of on the naira sign.
 *
 * Every value is a token from brand.css, so both themes come for free, and there
 * is no motion here to honour a reduced-motion preference for.
 */

.acc-offer__summary { margin: .15rem 0 .5rem; }
.acc-offer__discount { margin: 0; }
.acc-price__unit { font-size: var(--step--1); }

.acc-offer__form { margin-top: 1rem; }
.acc-offer__actions { align-items: flex-end; }
.acc-offer__qty { flex: 1; min-width: 8rem; max-width: 16rem; }

/* A fieldset with no chrome: the table is the grouping the eye reads, and the
   legend is there for a screen reader rather than for the layout. */
.acc-tiers { border: 0; padding: 0; margin: 0 0 1rem; min-width: 0; }
.acc-tiers__table { min-width: 30rem; }

/* The row header carries the tier name, so it must not read as a column header. */
.acc-tiers__table th[scope="row"] { text-transform: none; letter-spacing: 0; color: var(--text); font-weight: 600; }
.acc-tiers__table th[scope="row"] label { display: block; cursor: pointer; }
.acc-tiers__price { font-family: var(--font-display); font-weight: 700; white-space: nowrap; }

/*
 * The tier a link asked for.
 *
 * A buy link from a product page carries both the SKU and the fragment, so the right radio is
 * already selected when the page opens — but the customer arrives in the middle of a long table
 * with nothing saying which row the page just took them to, and a selected radio a few hundred
 * pixels down reads as a default rather than as an answer. The tint says "this is the one you
 * pressed". It applies only while the fragment is on the URL and goes as soon as they pick
 * something else.
 */
.acc-tiers__table tr:target { background: var(--surface-alt); box-shadow: inset 3px 0 0 var(--blue); }
.acc-tiers__table tr:target th[scope="row"] { color: var(--text); }
.acc-tiers__term { white-space: nowrap; }

/* The term, beside the price.
 *
 * SmartExaminer is sold two ways at once — version 2 and below outright, versions
 * 3 and 4 as a year — and the two land as separate cards on this page with the
 * subscription showing the smaller number. The badge is the first cue and the
 * sentence beside it is the second; never the colour alone, which is why the
 * subscription badge changes its border and its text as well as its wash.
 */
.acc-term {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  align-items: baseline;
  margin: .1rem 0 .5rem;
  font-size: var(--step--1);
}
.acc-term__badge {
  display: inline-block;
  padding: .1rem .5rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--surface-alt);
  color: var(--text-soft);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: .01em;
}
.acc-term__badge--sub {
  border-color: var(--accent);
  background: var(--surface);
  color: var(--accent);
}

/* The worked examples. A rate is not a price, and this table is the difference:
   50 connections at 2,400 is 120,000 a year, and a school should not have to
   reach for a calculator to find that out before it commits. Narrower than the
   tier table because it is three short rows, and scrollable inside its own
   container like every other wide thing here. */
/* Six columns now that both payment routes are broken out — connections, licence, fee,
   card total, transfer total and the "then" note. Wider than it was, so the .acc-scroll
   wrapper around it does real work on a phone rather than being belt and braces. */
.acc-examples { min-width: 38rem; margin-bottom: 1rem; }
.acc-examples caption {
  caption-side: top;
  text-align: left;
  padding: 0 0 .4rem;
  font-size: var(--step--1);
}
.acc-examples th[scope="row"] {
  text-transform: none;
  letter-spacing: 0;
  color: var(--text);
  font-weight: 600;
}

/* How the money travels — the choice that decides whether a gateway fee is added at all.

   Laid out as a stack of full-width options rather than a row of radios, because each one
   carries a sentence: the trade being made is "a fee" against "waiting for a person", and
   that does not fit on a label. The border makes the block read as one question rather
   than as two loose controls between the price table and the quantity field. */
.acc-routes {
  border: 1px solid var(--line-soft);
  border-radius: .6rem;
  padding: .85rem 1rem;
  margin: 0 0 1rem;
  min-width: 0;
}
.acc-routes legend {
  padding: 0 .35rem;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--text);
}
.acc-routes__caption { margin: 0 0 .6rem; }
.acc-routes__option { display: flex; gap: .6rem; align-items: flex-start; padding: .35rem 0; }
.acc-routes__option input[type="radio"] {
  width: 1.15rem;
  height: 1.15rem;
  margin-top: .15rem;
  flex: 0 0 auto;
  accent-color: var(--accent);
}
.acc-routes__option input[type="radio"]:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.acc-routes__option label { display: block; cursor: pointer; }
.acc-routes__label { display: block; font-weight: 600; }
.acc-routes__trade { display: block; font-size: var(--step--1); }

/* The chosen route, and the way back to the other one, on the confirmation screen. */
.acc-routes__chosen { margin: 1rem 0 .5rem; }
.acc-routes__switch { margin: 0 0 .75rem; }
.acc-routes__switch p { margin: 0 0 .4rem; }

.acc-tiers__table input[type="radio"] { width: 1.15rem; height: 1.15rem; accent-color: var(--accent); }
.acc-tiers__table input[type="radio"]:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* Checked row gets a wash as a second cue; the radio itself is the first, and the
   row header's label is the third. Never colour alone. */
.acc-tiers__table tr:has(input[type="radio"]:checked) { background: var(--surface-alt); }

@media (max-width: 640px) {
  .acc-offer__actions { flex-direction: column; align-items: stretch; }
  .acc-offer__qty { max-width: none; }
}

/* The distinguishing half of a single offer's name, under the product heading. */
.acc-offer__variant { margin: 0 0 .25rem; font-size: var(--step--1); }

/* ==========================================================================
   Swapping — what htmx needs from the stylesheet, and nothing more
   ==========================================================================

   The accounts area replaces pieces of a page in place rather than reloading it. Three rules
   below are the whole of what that costs in CSS. Every colour is a token; there is not a
   literal colour anywhere in this file and a test enforces it.

   NOTE ON `.acc-swap`: a swap target has to be one element, but the settings grid needs the
   two-step-sign-in cards to be its own direct children or they stop being grid items and the
   layout collapses to a single column. `display: contents` gives us both — a real element in
   the DOM for htmx to replace, generating no box of its own. A swap must not move furniture
   that did not change. */
.acc-swap { display: contents; }

/* The busy state.

   A page load has a visible progress bar; a swap has nothing, and a customer on a slow
   Nigerian connection stares at a screen that looks dead and presses the button again. So
   every swapping control points `hx-indicator` at one of these. htmx puts `.htmx-request` on
   it for exactly as long as the request is in flight.

   It occupies its space at all times so that appearing does not reflow the line it sits on. */
.acc-busy {
  display: inline-block;
  width: .7rem;
  height: .7rem;
  margin-inline-start: .45rem;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0;
  transition: opacity .12s linear;
}
.acc-busy.htmx-request { opacity: 1; }

/* The pulse is the enhancement on the enhancement, and it is the first thing to go: under
   `prefers-reduced-motion: reduce` the dot simply appears and stays, which says the same thing
   without moving. Written as no-preference rather than as a `reduce` override so that the
   quiet version is the default and the animation is the addition — the same contract the rest
   of the app's motion follows. */
@media (prefers-reduced-motion: no-preference) {
  .acc-busy.htmx-request { animation: acc-pulse 1s ease-in-out infinite; }
  @keyframes acc-pulse {
    0%, 100% { opacity: .25; transform: scale(.8); }
    50%      { opacity: 1;   transform: scale(1); }
  }
}

/* A region whose content is being replaced. Dimmed rather than hidden or blanked: the previous
   answer stays readable and in place while the next one is fetched, which is what stops a
   filter change from looking like a page that broke. */
[aria-busy="true"] { opacity: .55; transition: opacity .12s linear; }

/* `display: contents` generates no box, so opacity on the wrapper itself does nothing — the
   two-step panel would be the one converted surface with no visible stale state. Its children
   carry it instead. */
.acc-swap[aria-busy="true"] { opacity: 1; }
.acc-swap[aria-busy="true"] > * { opacity: .55; transition: opacity .12s linear; }

/* ==========================================================================
   What this order comes to — the live total on the buy page
   ==========================================================================

   Rendered server-side at the offer's minimum, and re-rendered in place when the tier, the
   quantity or the payment route changes. Deliberately the loudest thing inside an offer card
   below the product name: it is the number the bursar came for. */
.acc-total {
  margin: 0 0 1rem;
  padding: .8rem 1rem;
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--accent);
  border-radius: .6rem;
  background: var(--surface-alt);
}
.acc-total__line {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1rem;
  align-items: baseline;
  justify-content: space-between;
  margin: 0;
}
.acc-total__label { font-weight: 600; }
.acc-total__value {
  font-family: var(--font-display, inherit);
  font-size: var(--step-2);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.acc-total__breakdown { margin: .3rem 0 0; font-size: var(--step--1); }

/* ==========================================================================
   Your data — access, portability and erasure
   ==========================================================================

   Two screens, `/account/privacy` and `/account/privacy/delete`, built almost entirely from
   the registers already in this file: cards, tables, badges, notices, fields. What is added
   here is the small amount that had nowhere to live — a destructive button, the two
   what-goes / what-stays lists, and the two panels on the deletion page that must not read
   as ordinary cards.

   Every colour is a var() onto a token in brand.css, so both themes are defined by
   construction. There is not one literal colour below and there must never be one. */

/*
 * The one destructive button in the accounts area.
 *
 * Outlined rather than filled, and that is a deliberate reversal of the usual convention.
 * A solid red button is the loudest thing on a page, and loudest is where the eye goes and
 * where the finger follows — which is precisely wrong for the only control here that cannot
 * be undone. It has to be unmistakably the dangerous one and NOT the obvious one to press.
 * Filling on hover and on focus keeps the commitment visible at the moment it is made.
 */
.acc-btn--danger {
  background: transparent;
  color: var(--danger);
  border-color: var(--danger);
  font-weight: 600;
}
.acc-btn--danger:hover,
.acc-btn--danger:focus-visible {
  background: var(--danger);
  color: var(--on-accent);
}

/*
 * "What we delete" and "What has to stay". A definition list, because that is what it is:
 * a term and the account of it. Grid rather than the browser's default indent so the term
 * and its explanation stay aligned as a register down the card.
 */
.acc-privacy-list { margin: .6rem 0 0; }
.acc-privacy-list dt {
  font-weight: 650;
  margin-top: .9rem;
}
.acc-privacy-list dt:first-child { margin-top: 0; }
.acc-privacy-list dd {
  margin: .15rem 0 0;
  color: var(--text-soft);
  font-size: var(--step--1);
}

/*
 * The statute under a "what stays" row, in the monospaced face the rest of the site uses for
 * anything from the record. It reads as a citation rather than as more prose — which is the
 * point: a reader who wants to check it should be able to see at a glance that there is
 * something to check.
 */
.acc-privacy-law {
  display: block;
  margin-top: .3rem;
  font-family: var(--font-mono, monospace);
  font-size: .74rem;
  line-height: 1.45;
  color: var(--text-faint);
}

/*
 * The scheduled-deletion panel, and the confirm panel. Both carry a heavy left rule in the
 * danger tint so that neither can be mistaken for one of the informational cards beside it.
 * The scheduled one is the more urgent of the two — it is what somebody who did not ask for
 * this is looking for — so it also takes the wash.
 */
.acc-privacy-scheduled,
.acc-privacy-confirm {
  border-left: 3px solid var(--danger);
}
.acc-privacy-scheduled { background: var(--danger-wash); }

/*
 * The optional "why are you leaving" box. `.acc-field` styles inputs and selects; a textarea
 * had no rule anywhere in this file because nothing in the area had one until now.
 * `resize: vertical` and never `none` — a bursar typing three paragraphs into a three-row box
 * should be able to make it bigger, and horizontal resize is what breaks a narrow layout.
 */
.acc-field textarea {
  font: inherit;
  width: 100%;
  min-height: 4.5rem;
  resize: vertical;
  padding: .6rem .75rem;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
}
.acc-field textarea:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/*
 * The exports table holds a timestamp, a badge with a sentence beside it and two links, which
 * is more than fits across 375px. It already sits inside `.acc-scroll`, so the page itself
 * never scrolls sideways; this only stops the state cell from squeezing the date column into
 * one character per line on the way there.
 */
/* Targeted by CLASS, even though the same element carries the id the htmx swap aims at.
   An id selector written out in full here would begin with a hash followed by three letters
   that are all valid hex digits, and a word boundary after them — which is indistinguishable
   from a hard-coded colour to the guard in AccountsChromeTest. That guard reads this file and
   refuses any literal, because a colour here would silently opt the element out of dark mode.
   The rule is worth keeping and the selector is the cheap half to change. */
.acc-privacy-exports .acc-table td:first-child { white-space: nowrap; }
.acc-privacy-exports .acc-btn--link { padding-inline: 0; margin-inline-end: .8rem; }

