/* Lilmint — design tokens.
   A cool, blue-accented interface.

   A mid-tone blue cannot be both a legible background for a white label and a
   decorative tint, and it certainly cannot be read as text on white. So the
   accent is split into roles rather than being one value:

     --accent-strong  the FILL UNDER A WHITE LABEL -- 5.2:1 with --accent-text
     --accent         the brand colour, for everything that carries no label
     --accent-ink     the same colour used AS TEXT, darkened until it clears
                      4.5:1 on white.

   Collapsing these is exactly what produces the classic washed-out blue link.
   Every pair below is measured, not chosen by eye. */

:root {
  /* Cool neutrals. Picked from the same family as the blue, so the accent reads
     as a decision rather than a tint dropped onto an unrelated grey. */
  --bg: #f8fafc;
  --surface: #ffffff;
  --surface-2: #f1f5f9;
  --surface-3: #e2e8f0;

  --text: #0f172a;
  --muted: #64748b;
  --border: #e2e8f0;
  --border-strong: #cbd5e1;

  /* The brand blue in four steps, because one value cannot be both a legible
     background for a white label and a decorative tint:

       --accent         the brand colour: tints, edges, the gradient's light end
       --accent-strong  the FILL UNDER A WHITE LABEL -- 5.2:1 with --accent-text
       --accent-deep    one step deeper, for hover on that fill
       --accent-ink     the accent used AS TEXT -- 6.7:1 on white

     --accent is 3.7:1 against white, which is fine for a mark or a one-pixel
     edge and not for a sentence. Every surface that carries a LABEL therefore
     uses --accent-strong, and --accent is left to the things that carry none. */
  --accent: #3b82f6;
  --accent-strong: #2563eb;
  --accent-deep: #1d4ed8;
  --accent-text: #ffffff; /* the label ON a filled accent */
  --accent-ink: #1d4ed8; /* the accent as text — 6.7:1 on white */
  --accent-soft: #eff6ff;

  --danger: #c0392b;
  --danger-soft: #fdecea;
  /* Success stays green. Nobody reads blue as "it worked". */
  --ok: #15803d;
  --ok-soft: #e7f6ec;
  /* Warning is pushed orange so it cannot be confused with the brand. A blue
     banner on a blue interface teaches people to ignore banners. */
  --warn: #c2410c;
  --warn-soft: #fff3ec;

  --focus: #1d4ed8;

  --radius-sm: 8px;
  --radius: 12px;
  --radius-lg: 18px;

  --shadow-sm: 0 1px 2px rgba(15, 23, 42, .06);
  --shadow-lg: 0 2px 4px rgba(15, 23, 42, .05), 0 18px 40px rgba(15, 23, 42, .12);
  /* A middle shadow used to sit between these two. It was declared in both
     themes and referenced nowhere -- for years, by the look of it. */

  /* The scales that were missing. Colour, radius and shadow were tokenised from
     the start; spacing, type, stacking and motion were not, so each component
     chose its own numbers -- eighteen distinct paddings and thirteen font sizes
     between them. These are the values the file already reaches for, snapped to
     a rhythm rather than invented. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-7: 32px;

  --text-2xs: 11px;
  --text-xs: 12px;
  --text-sm: 13px;
  --text-base: 14px;
  --text-md: 15px;
  --text-lg: 16px;
  --text-xl: 18px;
  --text-2xl: 22px;
  --text-3xl: 26px;

  /* `font-weight: 650` was used in five places. A weight between 600 and 700
     only exists on a variable font: this machine renders it, and most Android
     phones -- which is where these customers are -- snap it to 700. So the same
     heading was two different weights depending on the handset. */
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;

  --radius-pill: 999px;

  /* The rail's width, needed in three places that must agree: the shell's grid
     column, the gradient that paints it, and the drawer it becomes on a narrow
     screen. The drawer was 272px against a 264px column, which is the kind of
     8px that only shows up when the drawer is open next to the layout it
     replaces. */
  --rail: 264px;

  /* The scrim behind the drawer and the purchase sheet: one literal used in two
     places, and the same value in both themes, which is wrong in one of them --
     half-black over a #0f172a page darkens almost nothing. */
  --overlay: rgba(15, 23, 42, .5);

  /* The stacking order was a comment and a set of literals. Putting the ladder
     here is what stops the next panel guessing at it. */
  --z-chart-tip: 5;
  --z-mobilebar: 20;
  --z-scrim: 30;
  --z-sidebar: 40;
  --z-modal: 50;
  --z-gate: 60;
  --z-toast: 70;

  /* Two durations, because the file has two intentions: .15s for a control
     changing state under the pointer, .22s for a panel crossing the screen.
     One token could not hold both, and the drawer is not a hover. */
  --dur-fast: .15s;
  --dur-base: .22s;

  /* Chart chrome, and again NOT --accent. #3b82f6 sits above the lightness band
     a mark wants on a white surface -- it reads as a highlight rather than as
     data -- so these are the chart-sized counterparts, validated against the
     surface for lightness, chroma and contrast. */
  --chart-series: #2563eb;
  --chart-grid: #e2e8f0;
  --chart-baseline: #cbd5e1;
  --chart-hit: rgba(37, 99, 235, .12);
}

/* Dark values are declared TWICE on purpose -- once for the operating system's
   preference and once for an explicit choice -- and the explicit choice has to
   win in both directions. The :not() guard is what lets an explicit light theme
   beat a dark OS setting; the bare scope is what lets an explicit dark theme
   beat a light one. Declaring them once inside the media query would make the
   toggle work in one direction only. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0f172a;
    --surface: #1e293b;
    --surface-2: #27354b;
    --surface-3: #334155;

    --text: #f1f5f9;
    --muted: #94a3b8;
    --border: #334155;
    --border-strong: #475569;

    /* The brand is lifted so it still reads on a dark page, and the ink step
       has to move with it -- #1d4ed8 on a #1e293b surface is unreadable, where
       #93c5fd clears 8.1:1. The FILL does not move: white on #2563eb is 5.2:1
       whatever the page behind the button is, and it is the label that has to
       be legible. */
    --accent: #60a5fa;
    --accent-strong: #2563eb;
    --accent-deep: #1d4ed8;
    --accent-text: #ffffff;
    --accent-ink: #93c5fd;
    --accent-soft: #1e3a5f;

    --danger: #ff7b6b;
    --danger-soft: #2e1a19;
    --ok: #4ade80;
    --ok-soft: #14291f;
    --warn: #fb923c;
    --warn-soft: #2c1a10;

    --focus: #93c5fd;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, .45);
    --shadow-lg: 0 2px 4px rgba(0, 0, 0, .5), 0 18px 40px rgba(0, 0, 0, .55);

    /* Denser than the light scrim, and black rather than slate-black: over a
       #0f172a page the light value barely registers at all. */
    --overlay: rgba(0, 0, 0, .62);

    /* Stepped for the dark surface rather than reused from light. The dark band
       is tighter (L 0.48-0.67 against light's 0.43-0.77), so the deeper blues
       are out of range here and only a lighter step fits. */
    --chart-series: #60a5fa;
    --chart-grid: #334155;
    --chart-baseline: #475569;
    --chart-hit: rgba(96, 165, 250, .18);
  }
}

:root[data-theme="dark"] {
  --bg: #0f172a;
  --surface: #1e293b;
  --surface-2: #27354b;
  --surface-3: #334155;

  --text: #f1f5f9;
  --muted: #94a3b8;
  --border: #334155;
  --border-strong: #475569;

  --accent: #60a5fa;
  --accent-strong: #2563eb;
  --accent-deep: #1d4ed8;
  --accent-text: #ffffff;
  --accent-ink: #93c5fd;
  --accent-soft: #1e3a5f;

  --danger: #ff7b6b;
  --danger-soft: #2e1a19;
  --ok: #4ade80;
  --ok-soft: #14291f;
  --warn: #fb923c;
  --warn-soft: #2c1a10;

  --focus: #93c5fd;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, .45);
  --shadow-lg: 0 2px 4px rgba(0, 0, 0, .5), 0 18px 40px rgba(0, 0, 0, .55);
  --overlay: rgba(0, 0, 0, .62);

  --chart-series: #60a5fa;
  --chart-grid: #334155;
  --chart-baseline: #475569;
  --chart-hit: rgba(96, 165, 250, .18);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--text);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  -webkit-font-smoothing: antialiased;
  /* So form controls, scrollbars and the like follow the theme rather than
     staying stubbornly light inside a dark page. */
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) body { color-scheme: dark; }
}
:root[data-theme="dark"] body { color-scheme: dark; }

body.is-auth {
  display: grid;
  place-items: center;
  padding: 24px;
}

h1, h2, h3 {
  letter-spacing: -.02em;
  /* Tight for headings, loose for reading. The default 1.5 is right for body
     copy and much too airy above 20px -- it is most of the difference between
     type that looks set and type that looks typed. */
  line-height: 1.2;
  /* Keeps a heading from breaking with one word stranded on its own line.
     Silently ignored where unsupported, which is a perfectly good outcome. */
  text-wrap: balance;
}

p { text-wrap: pretty; }

/* Numbers that sit above one another should line up. Only in columns and
   figures -- tabular digits on a large standalone number make 121 look loose. */
.num, .kv__val, .chart__tick, table td, table th { font-variant-numeric: tabular-nums; }

a { color: var(--accent-ink); }

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

[hidden] { display: none !important; }

/* Small caps label used above figures and form fields. */
.eyebrow {
  margin: 0;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
}

.muted { color: var(--muted); }
.small { font-size: 13px; }
.spaced { margin-top: 16px; }

/* ============================================================== auth page */

.card {
  width: 100%;
  max-width: 440px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
  padding: var(--space-7);
}

.card__head { margin-bottom: var(--space-6); }

/* The one lockup, used by every door: the rail, the shop's top bar, the auth
   cards and the account gate. A mark plus the name, so the storefront and the
   dashboard read as one business rather than two products. */
.brand { display: inline-flex; align-items: center; gap: var(--space-3); }

/* Block-level inside a card, where it sits on its own line above the heading
   and has to push it down. */
.card .brand { display: flex; margin-bottom: var(--space-5); }

.card h1 {
  margin: 0 0 var(--space-1);
  font-size: var(--text-3xl);
}

.sub {
  margin: 0;
  color: var(--muted);
  font-size: 14px;
}

/* The same construction as the shop's `.networks` control: one recessed track,
   the checked item lifted onto a raised pill. The two read as one system. */
.tabs {
  display: flex;
  gap: var(--space-1);
  padding: var(--space-1);
  margin-bottom: var(--space-6);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
}

.tabs label {
  flex: 1;
  padding: 9px 12px;
  text-align: center;
  font-size: 14px;
  font-weight: 600;
  color: var(--muted);
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: color var(--dur-fast), background var(--dur-fast);
}

.tabs input { position: absolute; opacity: 0; pointer-events: none; }

.tabs input:checked + label {
  color: var(--accent-ink);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.tabs input:focus-visible + label {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

.field { margin-bottom: 16px; }

.field label {
  display: block;
  margin-bottom: 6px;
  font-size: 14px;
  font-weight: 600;
}

.field__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.hint {
  margin: 6px 0 0;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted);
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="tel"],
input[type="search"] {
  width: 100%;
  padding: 11px 13px;
  font: inherit;
  font-size: 15px;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast);
}

input::placeholder { color: var(--muted); opacity: .65; }

input:focus {
  border-color: var(--accent-ink);
  box-shadow: 0 0 0 3px var(--accent-soft);
  outline: none;
}

input[aria-invalid="true"] { border-color: var(--danger); }

.input-group { position: relative; }
.input-group input { padding-right: 68px; }

.toggle {
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  padding: 5px 9px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.toggle:hover { color: var(--accent-ink); }

.error {
  margin: 6px 0 0;
  font-size: 13px;
  color: var(--danger);
}

.alert {
  margin: 0 0 16px;
  padding: 11px 13px;
  font-size: 14px;
  line-height: 1.45;
  border-radius: var(--radius);
  border: 1px solid transparent;
}
.alert--error { color: var(--danger); background: var(--danger-soft); border-color: var(--danger); }
.alert--ok { color: var(--ok); background: var(--ok-soft); border-color: var(--ok); }
.alert--warn { color: var(--warn); background: var(--warn-soft); border-color: var(--warn); }
.alert--warn strong { font-weight: 700; }
/* Neither a success nor a failure: "we could not check" is an answer, and
   colouring it red would teach the reader to read it as one. */
.alert--note { color: var(--muted); background: var(--surface-2); border-color: var(--border-strong); }

/* =============================================================== buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  font: inherit;
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  color: var(--accent-text);
  background: var(--accent-strong);
  border: 1px solid transparent;
  /* Pill, to match the chips and the segmented controls. Every action on the
     page is the same shape, which is most of what makes the set read as one. */
  border-radius: var(--radius-pill);
  cursor: pointer;
  transition: background var(--dur-fast), box-shadow var(--dur-fast);
}
.btn:hover { background: var(--accent-deep); }
/* One pixel of travel is the whole press feedback, and it is enough. */
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn:disabled:active { transform: none; }

.btn--block { width: 100%; }

.btn--ghost {
  color: var(--text);
  background: var(--surface);
  border-color: var(--border-strong);
}
.btn--ghost:hover { background: var(--surface-2); border-color: var(--muted); }

.btn--small { padding: var(--space-2) var(--space-3); font-size: var(--text-sm); }

/* ============================================================ app shell */

.app { display: grid; grid-template-columns: minmax(0, 1fr); min-height: 100vh; }

.sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-5) var(--space-4);
  background: var(--surface);
  border-right: 1px solid var(--border);
}

.sidebar__brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-1) var(--space-2) var(--space-5);
}

.sidebar__mark {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: var(--radius-sm);
  background: var(--accent-strong);
  color: var(--accent-text);
  font-size: var(--text-md);
  /* The one true 800 in the file, on the logotype glyph and nowhere else -- a
     deliberate one-off, so it stays a literal rather than widening the scale. */
  font-weight: 800;
  letter-spacing: -.02em;
}

.sidebar__name {
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  letter-spacing: -.02em;
}

.nav { display: grid; gap: var(--space-1); }

.nav__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding: var(--space-3);
  font: inherit;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  color: var(--muted);
  background: none;
  border: 0;
  border-radius: var(--radius);
  cursor: pointer;
  text-align: left;
  transition: background var(--dur-fast), color var(--dur-fast);
}
.nav__item:hover { background: var(--surface-2); color: var(--text); }
a.nav__item { text-decoration: none; }

.nav__item[aria-current="page"] {
  color: var(--accent-ink);
  background: var(--accent-soft);
  font-weight: var(--weight-semibold);
}

.nav__icon { flex: none; width: 18px; height: 18px; }
.nav__item[aria-current="page"] .nav__icon { color: var(--accent-ink); }

/* The support links. `overflow-wrap: anywhere` because an email address is one
   unbreakable word, and the rail is a fixed 264px -- without it a longer address
   than this one would push the sidebar wider than the column it lives in. */
.sidebar__contact {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding: 0 var(--space-2);
  overflow-wrap: anywhere;
}
.sidebar__contact a {
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--accent-ink);
  text-decoration: none;
}
.sidebar__contact a:hover { text-decoration: underline; }

.sidebar__foot {
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.sidebar__user {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 12px;
}

.avatar {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent-ink);
  font-size: 13px;
  font-weight: 700;
  text-transform: uppercase;
}

.sidebar__meta { min-width: 0; }
.sidebar__meta strong {
  display: block;
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.sidebar__meta span {
  display: block;
  font-size: 12px;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.mobilebar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: var(--z-mobilebar);
}

.iconbtn {
  display: grid;
  place-items: center;
  width: 38px;
  height: 38px;
  color: var(--text);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;
}

.mobilebar__brand { font-size: 16px; font-weight: 700; letter-spacing: -.02em; }

.scrim {
  position: fixed;
  inset: 0;
  background: var(--overlay);
  z-index: var(--z-scrim);
  border: 0;
  padding: 0;
}

/* ----------------------------------------------------------- purchase popup */

/*
  Above the drawer's scrim (30) and the drawer itself (40), below the account
  gate (60) and the toasts (70) -- so a suspended account still covers it, and
  feedback still shows over it.

  Anchored to the TOP and scrolling itself, rather than centred and clipped.

  100dvh is deliberately not used: dvh tracks the URL bar, not the on-screen
  keyboard. The layout viewport that a fixed overlay is laid out against does
  not shrink when the keyboard opens, so a panel clamped to the viewport height
  is still taller than the part anyone can see -- and clamping it only adds a
  nested scrollport that traps the field under the keyboard. What works is a
  short panel on an overlay that can scroll, so the browser's scroll-into-view
  can bring the focused field up where it can be seen.
*/
.modal {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  align-content: start;
  justify-items: center;
  padding: 16px;
  background: var(--overlay);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.modal__panel {
  width: 100%;
  max-width: 420px;
  padding: 20px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-lg);
}

.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.modal__title { margin: 0; font-size: var(--text-xl); font-weight: var(--weight-semibold); }

/* The one control a thumb has to find without looking, so it is bigger than
   the 38px the toolbar's icon buttons use. */
.modal__head .iconbtn { width: 44px; height: 44px; }

.modal__panel .summary { margin: 0 0 16px; }

/* Every action in the sheet is a full-width thumb target. */
.modal__panel .btn { min-height: 48px; }

@media (max-width: 559px) {
  /* iOS zooms the page in when a field under 16px takes focus, and inside a
     fixed sheet that zoom shifts everything the customer is looking at. The
     house input size is 15px, so the field is nudged up just here. */
  .modal__panel input[type="tel"] { font-size: 16px; }
}

@media (min-width: 560px) {
  /* `safe` keeps a panel taller than the viewport from losing its top edge off
     the scrollport, which is the classic centred-dialog bug. A browser that
     does not know the value drops the line and keeps the `start` above. */
  .modal { align-content: safe center; padding: 24px; }
}

/* ------------------------------------------------------------------ content */

.content {
  padding: 24px;
  max-width: 1080px;
  width: 100%;
  margin: 0 auto;
  display: grid;
  gap: 20px;
  align-content: start;
  min-width: 0;
}

.view { display: grid; gap: 20px; min-width: 0; }

.view__head { display: grid; gap: 2px; }

/* Fluid rather than stepped: a section heading has no business being the same
   size on a phone as on a 1440px screen. */
.view__title { margin: 0; font-size: clamp(20px, 1.2rem + 0.6vw, 25px); }

/* -------------------------------------------------------------- hero card */

/* The gradient's first two stops are the ones that can carry a white label. The
   brand blue is the LAST stop only, at the far corner: white on it is 3.7:1,
   which is fine for a mark and for an edge but not for the label sitting in the
   top-left. Giving it the last 45% keeps that label on a fill that clears 4.5:1
   across its whole width while the brand still shows on the card. */
.balance-card {
  position: relative;
  overflow: hidden;
  padding: var(--space-6);
  border-radius: var(--radius-lg);
  background: linear-gradient(135deg, var(--accent-deep) 0%, var(--accent-strong) 55%, var(--accent) 100%);
  color: var(--accent-text);
  box-shadow: var(--shadow-lg);
}

.balance-card__label {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  /* This pair -- white on the brand blue -- measures 3.7:1. That clears the 3:1
     floor the large amount lives on, but not the 4.5:1 an ordinary label wants,
     so there is NO headroom to soften it to secondary here. It stays full
     strength; opacity was dropped when the fill stopped being a bright yellow
     that had 9.1:1 to spend. */
}

.balance-card__amount {
  margin: 0;
  font-size: clamp(32px, 6vw, 44px);
  font-weight: 700;
  /* The one number the whole page leads with, so it gets more room and a
     tighter fit than anything else. Proportional figures, never tabular --
     equal-width digits make a large number look loose. */
  letter-spacing: -.035em;
  line-height: 1.05;
}

.balance-card__foot {
  margin: var(--space-5) 0 0;
  font-size: var(--text-sm);
}

.balance-card__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-5);
}

.balance-card .btn--onaccent {
  color: var(--accent-ink);
  background: var(--surface);
  border-color: transparent;
}
.balance-card .btn--onaccent:hover { background: var(--surface-2); }

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

.tiles {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
}

/* A well, not a card. These are a stat strip inside a panel, and while they
   carried their own border AND shadow they were three more cards competing with
   the panel holding them -- which is how every surface on the page ended up
   looking exactly as important as every other one. Recessed fill, no shadow, and
   the panel becomes the only raised thing. /admin shares this, and its 4-up tile
   row sits on the same background, so it gains the same reading.

   The border stays. In dark mode --surface-2 against --bg is a clear step, but in
   light these two stone tones are within a couple of percent of each other, and
   a well with no edge simply disappears. */
.tile {
  padding: var(--space-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  min-width: 0;
}

.tile__label {
  margin: 0 0 6px;
  font-size: 13px;
  color: var(--muted);
}

.tile__value {
  margin: 0;
  font-size: var(--text-2xl);
  font-weight: 700;
  letter-spacing: -.02em;
}

/* ------------------------------------------------------------------ panels */

.panel {
  min-width: 0;
  padding: var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

/* The shop's catalogue drops the panel chrome so its cards sit directly on the
   page. That is the whole difference between a wall of tiles and a box of
   cards: with a panel behind them the cards are inside a container, and the
   container is what the eye sees first. */
.panel--flush {
  padding: 0;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

.panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.panel__title { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-semibold); }

.linkish {
  font: inherit;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--accent-ink);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}
.linkish:hover { text-decoration: underline; }

/* ------------------------------------------------------------------- chips */

.chips { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-4); }

.chip {
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: var(--text-base);
  font-weight: var(--weight-medium);
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: border-color var(--dur-fast), color var(--dur-fast), background var(--dur-fast);
}
.chip:hover { border-color: var(--accent-ink); color: var(--accent-ink); background: var(--accent-soft); }

/* ---------------------------------------------------------------- networks */

/* A segmented control, in the same construction as the auth page's `.tabs`: one
   recessed track, the checked item lifted onto a raised pill. `.tabs` itself is
   untouched -- it is shared with the sign-in page, which is not in scope -- but
   the two now read as one system rather than two.

   The DOM is generated by renderNetworks() and is not up for negotiation: the
   radio must stay a sibling BEFORE its label, because the checked and
   focus-visible states are drawn on the label through `+`. */
.networks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  padding: var(--space-1);
  margin-bottom: var(--space-4);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
}
.network { position: relative; }
.network input { position: absolute; opacity: 0; pointer-events: none; }

.network label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--muted);
  border: 0;
  border-radius: var(--radius-pill);
  cursor: pointer;
  background: transparent;
  transition: color var(--dur-fast), background var(--dur-fast), box-shadow var(--dur-fast);
}

.network input:checked + label {
  color: var(--accent-ink);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
}

.network input:focus-visible + label {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}

/* Operator brand colours, used only on the dot -- never as text, where they
   would fail contrast.

   The base colour matters: the dots are generated from whatever networks the
   API returns, so a network with no brand colour rule renders a dot that is
   simply invisible. Grey is at least a dot. */
.dot { width: 9px; height: 9px; border-radius: 50%; display: inline-block; flex: none; background: var(--muted); }
.dot--MTN { background: #ffcc00; }
.dot--Telecel { background: #e60000; }
.dot--AirtelTigo { background: #0033a0; }

/* ----------------------------------------------------------------- bundles */

/* The catalogue is the product, so this is the one thing on the page that lifts
   under the pointer. Everything else stays flat and lets it. */
.bundles {
  display: grid;
  gap: var(--space-3);
  /* Stepped rather than `auto-fill`. Inside the dashboard the grid lives in a
     column that is the viewport minus a 264px rail, so auto-fill has no idea
     how much room it really has and lands six or seven across on a desktop
     where five is as dense as a price list stays legible. Two at 360px, where
     the content box is about 328px: one per row reads as a list, not a shop. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (min-width: 600px) { .bundles { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 880px) { .bundles { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 1120px) { .bundles { grid-template-columns: repeat(5, minmax(0, 1fr)); } }

/* A wall of uniform tiles: white on the page, a hairline edge, the softest
   shadow in the file. Column direction with the call to action on `margin-top:
   auto` is what squares the buttons up along the bottom of a row, which is most
   of what makes a grid of cards look like a shop rather than a list. */
.bundle {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  font: inherit;
  color: inherit;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: border-color var(--dur-fast), box-shadow var(--dur-fast), transform var(--dur-fast);
}
.bundle:hover {
  border-color: var(--accent);
  box-shadow: var(--shadow-lg);
  /* One pixel of travel, the same feedback `.btn:active` gives. */
  transform: translateY(-1px);
}

.bundle[aria-pressed="true"] {
  border-color: var(--accent-ink);
  background: var(--accent-soft);
  /* The ink, not the fill: a #3b82f6 ring on a #eff6ff background is a 3.4:1
     edge, which is the opposite of what a selected state is for. */
  box-shadow: inset 0 0 0 1px var(--accent-ink);
}

/* A dot, not a tick and not a class: a pseudo-element adds no child node, and
   renderBundles() walks the container's children by identity to deselect the
   rest -- a wrapper or an extra element per card would break that. */
.bundle[aria-pressed="true"]::after {
  content: '';
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent-ink);
}

.bundle__size { display: block; font-size: var(--text-lg); font-weight: var(--weight-semibold); }
.bundle__meta { display: block; font-size: var(--text-xs); color: var(--muted); }
.bundle__price {
  display: block;
  font-size: var(--text-xl);
  font-weight: var(--weight-bold);
  color: var(--accent-ink);
  letter-spacing: -.02em;
}

/* The card's own button, drawn rather than nested: the card IS a <button>, and
   a button inside a button is not markup a browser will accept. `margin-top:
   auto` is what squares the buttons along the bottom of a row -- the flex gap
   above is the floor, and the leftover height goes here. */
.bundle__cta {
  margin-top: auto;
  padding: var(--space-2) var(--space-3);
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  text-align: center;
  color: var(--accent-text);
  background: var(--accent-strong);
  border-radius: var(--radius-pill);
}

/* ------------------------------------------------------------------- steps */

.steps {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: minmax(0, 1fr);
  margin: 0;
  padding: 0;
  list-style: none;
}

.step { display: grid; gap: var(--space-2); align-content: start; }

.step__num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  font-size: var(--text-sm);
  font-weight: var(--weight-bold);
  /* The fill carrying the label, which is the pair the whole accent system is
     built around -- the same one the buttons use. */
  color: var(--accent-text);
  background: var(--accent-strong);
  border-radius: 50%;
}

.step__name { margin: 0; font-size: var(--text-md); font-weight: var(--weight-semibold); }
.step__text { margin: 0; font-size: var(--text-base); color: var(--muted); }

/* Three across only once there is room for three. `repeat(3, 1fr)` with no
   guard is the classic way to make a phone scroll sideways, and minmax(0, 1fr)
   stops a long word widening its own column even here. */
@media (min-width: 640px) {
  .steps { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-5); }
}

/* --------------------------------------------------------------------- faq */

/* Full-width rows divided by hairlines, not a stack of cards. Every question is
   a band in one list, which is what an accordion of unrelated answers should
   look like -- cards would hand each one the weight of a headline. */
.faq {
  display: grid;
  gap: 0;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.faq__item { background: none; border: 0; border-radius: 0; }
.faq__item + .faq__item { border-top: 1px solid var(--border); }

.faq__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  cursor: pointer;
  list-style: none;
}
.faq__q::-webkit-details-marker { display: none; }

/* A chevron from two borders rather than an icon: no asset, no extra markup, and
   it turns with the open state. Its transition is covered by the single
   reduced-motion block at the end of the file -- which is the reason that block
   was consolidated rather than left in two pieces. */
.faq__q::after {
  content: '';
  flex: none;
  width: 8px;
  height: 8px;
  border-right: 2px solid var(--muted);
  border-bottom: 2px solid var(--muted);
  transform: rotate(45deg) translateY(-2px);
  transition: transform var(--dur-fast);
}
.faq__item[open] .faq__q::after { transform: rotate(225deg) translateY(-2px); }

.faq__a {
  margin: 0;
  padding: 0 var(--space-4) var(--space-4);
  font-size: var(--text-base);
  color: var(--muted);
  line-height: 1.45;
}

/* ---------------------------------------------------------------- recipient */

.recipient { display: grid; gap: 12px; grid-template-columns: minmax(0, 1fr); align-items: end; }

@media (min-width: 560px) {
  .recipient { grid-template-columns: minmax(0, 1fr) auto; }
}

.saved { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 14px 0 0; }

.summary {
  margin-top: 18px;
  padding: 15px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: 14px;
}

.summary__row { display: flex; justify-content: space-between; gap: 12px; padding: 4px 0; }

.summary__row--total {
  margin-top: 8px;
  padding-top: 11px;
  border-top: 1px solid var(--border-strong);
  font-weight: 700;
}

.summary__label { color: var(--muted); }

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

.table-wrap { overflow-x: auto; max-width: 100%; }

table { width: 100%; border-collapse: collapse; font-size: 14px; }

th, td {
  padding: 11px 12px;
  text-align: left;
  border-bottom: 1px solid var(--border);
  white-space: nowrap;
}

th {
  font-size: var(--text-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
}

tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: var(--surface-2); }

.num { text-align: right; font-variant-numeric: tabular-nums; }
.amount--out { font-weight: 600; }
.amount--in { color: var(--ok); font-weight: 600; }

.badge {
  display: inline-block;
  margin-left: 8px;
  padding: 2px 8px;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  border-radius: var(--radius-pill);
  background: var(--ok-soft);
  color: var(--ok);
}

/* --------------------------------------------------------------- activity */

.activity { display: grid; gap: 2px; }

.activity__row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 11px 10px;
  border-radius: var(--radius);
}
.activity__row:hover { background: var(--surface-2); }

.activity__icon {
  display: grid;
  place-items: center;
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  font-size: 15px;
  font-weight: 700;
  background: var(--surface-3);
  color: var(--muted);
}
.activity__icon--in { background: var(--accent-soft); color: var(--accent-ink); }

.activity__main { min-width: 0; flex: 1; }
.activity__title {
  font-size: 14px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.activity__sub { font-size: 12px; color: var(--muted); }

.activity__amount { font-size: var(--text-base); font-weight: var(--weight-bold); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------ beneficiaries */

.bene-list { display: grid; gap: 8px; margin-top: 14px; }

.bene {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px;
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.bene__name {
  font: inherit;
  font-weight: 600;
  color: var(--text);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-align: left;
}
.bene__name:hover { color: var(--accent-ink); }

.bene__phone { font-size: 13px; color: var(--muted); }

/* ---------------------------------------------------------------- filters */

.filters { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 14px; }

select {
  padding: 8px 11px;
  font: inherit;
  font-size: 14px;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: var(--radius);
  cursor: pointer;
}

/* -------------------------------------------------------------------- misc */

.kv { display: grid; gap: 0; margin: 0; font-size: 14px; }

/* Label left, value right, hairline between -- the receipt shape, which is what
   an order's details are. The first and last rows drop their outer padding so
   the block sits flush in whatever holds it. */
.kv__row {
  display: flex;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) 0;
}
.kv__row + .kv__row { border-top: 1px solid var(--border); }
.kv__row:first-child { padding-top: 0; }
.kv__row:last-child { padding-bottom: 0; }

.kv__key { color: var(--muted); }
.kv__val { text-align: right; font-weight: 500; }

.empty {
  padding: 30px 12px;
  text-align: center;
  color: var(--muted);
  font-size: 14px;
}

/* ============================================================ admin panel */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  padding: 14px 24px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

.topbar__left { display: flex; align-items: center; gap: 10px; }
.topbar__right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

.topbar__brand {
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -.02em;
}

.topbar__user { font-size: 14px; color: var(--muted); }
.topbar__user strong { color: var(--text); }

.pill {
  display: inline-block;
  padding: 2px 9px;
  font-size: var(--text-2xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  white-space: nowrap;
}
.pill--admin { color: var(--accent-ink); background: var(--accent-soft); border-color: var(--accent-ink); }
.pill--on { color: var(--ok); background: var(--ok-soft); border-color: var(--ok); }
.pill--off { color: var(--muted); background: var(--surface-3); border-color: var(--border-strong); }
.pill--warn { color: var(--warn); background: var(--warn-soft); border-color: var(--warn); }

/* Form beside the table on a wide screen, stacked on a narrow one. */
.admin-grid {
  display: grid;
  gap: 20px;
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
  min-width: 0;
}

@media (min-width: 1000px) {
  .admin-grid { grid-template-columns: 340px minmax(0, 1fr); }
  .admin .content { max-width: 1220px; }
}

.rowactions { display: flex; gap: 6px; justify-content: flex-end; }

/* A disabled bundle is still listed -- it has to be, so it can be brought
   back -- so it is dimmed rather than hidden. */
tr.row--off td { opacity: .55; }
tr.row--off .price-now { text-decoration: line-through; }

.conflict-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }

/* A two-up split for panels that are peers (best sellers beside networks). */
.admin-grid--split { grid-template-columns: minmax(0, 1fr); }
@media (min-width: 1000px) {
  .admin-grid--split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/* A section heading inside a panel, where an h2 would be too loud. */
.subhead {
  margin: var(--space-5) 0 var(--space-1);
  padding-top: var(--space-4);
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  border-top: 1px solid var(--border);
}

.balance--sm { font-size: 30px; margin-bottom: 6px; }

/* Covers the whole app for an account that cannot use it -- suspended, or
   holding a temporary password. Not dismissable, because there is nothing
   behind it the server would answer. */
.gate {
  position: fixed;
  inset: 0;
  z-index: var(--z-gate);
  display: grid;
  place-items: center;
  padding: 24px;
  background: var(--bg);
  overflow-y: auto;
}

.rowactions--start { justify-content: flex-start; flex-wrap: wrap; }
.rowactions--start .btn { flex: none; }

.filters input[type="search"] { width: auto; min-width: 200px; flex: 1 1 200px; }

/* ================================================================= charts */

.chart { position: relative; width: 100%; }
.chart svg { display: block; width: 100%; height: auto; }

.chart__bar { fill: var(--chart-series); }
.chart__grid { stroke: var(--chart-grid); stroke-width: 1; }
.chart__baseline { stroke: var(--chart-baseline); stroke-width: 1; }

/* Axis text wears an ink token, never the series colour. Identity comes from
   the mark beside it. */
.chart__tick {
  fill: var(--muted);
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
}

.chart__peak { fill: var(--text); font-size: 11px; font-weight: 600; }

/* One hit area per slot, full height and never narrower than a comfortable
   touch. Landing on a 6px bar should not be a test of aim. */
.chart__hit { fill: transparent; outline: none; }
.chart__hit:hover { fill: var(--chart-hit); }
.chart__hit:focus-visible { fill: var(--chart-hit); stroke: var(--focus); stroke-width: 2; }

.chart__tip {
  position: absolute;
  top: 0;
  transform: translateX(-50%);
  display: grid;
  gap: 1px;
  padding: 6px 10px;
  border-radius: var(--radius-sm);
  background: var(--text);
  color: var(--bg);
  font-size: 12px;
  white-space: nowrap;
  pointer-events: none;
  z-index: var(--z-chart-tip);
}
.chart__tip strong { font-size: 13px; }
.chart__tip span { opacity: .78; }

/* ============================================================== skeletons */

/* Shown only on a first load, never on a refetch -- replacing a render that is
   already on screen with grey blocks is worse than a moment of stale numbers. */
.skeleton {
  border-radius: var(--radius-sm);
  background: linear-gradient(90deg, var(--surface-2) 25%, var(--surface-3) 37%, var(--surface-2) 63%);
  background-size: 400% 100%;
  animation: skeleton 1.4s ease infinite;
}

@keyframes skeleton {
  from { background-position: 100% 0; }
  to { background-position: 0 0; }
}

.skeleton--line { height: 12px; }
.skeleton--value { height: 26px; width: 60%; }
.skeleton--chart { height: 172px; width: 100%; }

/* ================================================================== toasts */

.toasts {
  position: fixed;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-toast);
  display: grid;
  gap: 8px;
  width: min(420px, calc(100vw - 32px));
}

.toast {
  padding: 11px 14px;
  font-size: 14px;
  line-height: 1.45;
  color: var(--text);
  background: var(--surface);
  border: 1px solid var(--border);
  border-left-width: 3px;
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  animation: toast-in .18s ease;
}
.toast--ok { border-left-color: var(--ok); }
.toast--error { border-left-color: var(--danger); }
.toast--info { border-left-color: var(--accent-ink); }

@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
}

/* Anchors dressed as buttons and links need the underline taken off: `.btn` and
   `.linkish` were written for <button>, where the browser supplies none, and the
   storefront is the first page to use them on an <a>. */
a.btn, a.linkish { text-decoration: none; }

/* ------------------------------------------------------------- storefront */

/* The public front door: no rail, no session, no drawer. It borrows the content
   column and the panels -- the catalogue itself is the dashboard's, class for
   class -- and adds what a front door needs: a header strip, a hero, and a
   footer carrying the ways to reach a person. */

/* A thin strip rather than a chunky header, and it stays for the whole scroll,
   so the way in is never more than a glance away on a long price list. */
.sitebar {
  position: sticky;
  top: 0;
  z-index: var(--z-mobilebar);
  background: var(--surface);
  border-bottom: 1px solid var(--border);
}

/* The same measure and gutter as `.content`, so the brand lines up with the
   first panel instead of floating at the window edge. */
.sitebar__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  max-width: 1080px;
  margin: 0 auto;
  padding: var(--space-3) var(--space-6);
}

.shop-actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }

/* The bar's right-hand cluster. The number check sits beside the way in rather
   than inside `#storeActions`, whose children the storefront script replaces. */
.sitebar__right { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); }
.sitebar__link {
  font-size: var(--text-base);
  font-weight: var(--weight-semibold);
  color: var(--text);
  text-decoration: none;
}
.sitebar__link:hover { color: var(--accent-ink); }

/* The brand is a link to the shop on the pages that have one, and a link must
   not arrive underlined simply because it is one. */
a.brand { color: inherit; text-decoration: none; }

/* One field, one answer. A full-width panel on a desktop is a page of emptiness
   around a text box. */
.verify-panel { max-width: 520px; margin: 0 auto; }
.verify-note { text-align: center; }

/* The theme control is a full-width rail button on the dashboard. On a plain
   page it would be a bar right across the content column, so it keeps its own
   width and sits under the panel. */
.content > #themeHost { max-width: 240px; margin: 0 auto; }

/* A sticky header sits over whatever an in-page link lands on, so an anchor
   target has to reserve the bar's height or "Browse bundles" scrolls the first
   row of cards underneath it. */
.anchor { scroll-margin-top: 88px; }

/* The headline block. Deliberately NOT a card: a hero in a box, on a page made
   of boxes, is one container too many. */
.hero { display: grid; gap: var(--space-3); padding: var(--space-4) 0 var(--space-2); }

.hero__title {
  margin: 0;
  max-width: 24ch;
  font-size: clamp(28px, 1.5rem + 2.2vw, 42px);
  font-weight: var(--weight-bold);
  letter-spacing: -.03em;
  line-height: 1.12;
}

.hero__sub { margin: 0; max-width: 62ch; font-size: var(--text-lg); color: var(--muted); }

/* Three short claims that are true of this deployment, NOT a statistics strip.
   A figure nobody measured is a lie with a font on it. */
.hero__points {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-1) 0 0;
  padding: 0;
  list-style: none;
}

.hero__point {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px var(--space-3) 6px 10px;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  color: var(--accent-ink);
  background: var(--accent-soft);
  border-radius: var(--radius-pill);
}

/* The tick is two borders turned 45 degrees -- no asset, no extra markup, and
   the same trick the FAQ chevron uses. */
.hero__point::before {
  content: '';
  flex: none;
  width: 5px;
  height: 9px;
  margin-top: -2px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
}

.hero__cta { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-2); }

/* ----------------------------------------------------------- site footer */

.sitefoot {
  display: grid;
  gap: var(--space-3);
  margin-top: var(--space-5);
  padding-top: var(--space-6);
  border-top: 1px solid var(--border);
}

.sitefoot__text { margin: 0; max-width: 62ch; font-size: var(--text-base); color: var(--muted); }

.sitefoot__links { display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4); }
.sitefoot__links a { font-size: var(--text-base); font-weight: var(--weight-semibold); }

.sitefoot__cta { display: flex; flex-wrap: wrap; gap: var(--space-3); }

/* The theme control is a full-width rail button on the dashboard, where it lives
   in a 264px column. `.btn--block` in the shop's footer would stretch it into a
   bar right across the page, so the host keeps its own width here. */
.sitefoot #themeHost { max-width: 240px; }

/* ----------------------------------------------------------------- layout */

@media (min-width: 960px) {
  .app {
    grid-template-columns: var(--rail) minmax(0, 1fr);
    /* Paints the rail for the full page height. The sidebar itself is only
       100vh (it has to be, to stick), so on a page taller than the viewport
       this stops the column showing a different colour below it. */
    background: linear-gradient(90deg, var(--surface) 0 var(--rail), var(--bg) var(--rail));
  }

  .sidebar {
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
  }

  .mobilebar { display: none; }
  .content { padding: var(--space-7); }
}

@media (max-width: 959px) {
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: var(--rail);
    z-index: var(--z-sidebar);
    transform: translateX(-100%);
    transition: transform var(--dur-base) ease;
    box-shadow: var(--shadow-lg);
    overflow-y: auto;
  }
  .sidebar.is-open { transform: none; }
}

/* One block rather than two, and it lives at the end of the file on purpose:
   `.sidebar`'s transition is declared in the layout section just above, and a
   reduced-motion rule of the same specificity only beats it by coming after it.
   Consolidating this anywhere earlier would quietly stop the drawer obeying it,
   which is the failure mode of a guard that looks present and does nothing. */
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
  .skeleton { animation: none; }
  .sidebar { transition: none; }
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}

@media (max-width: 420px) {
  .card { padding: 24px; }
  .content { padding: 16px; }
  .panel { padding: 16px; }
  /* The header strip's gutter tracks `.content`'s, or the brand sits 8px in
     from the panel below it for no reason anyone could name. */
  .sitebar__inner { padding-left: 16px; padding-right: 16px; }
}
