@charset "UTF-8";
/* ==========================================================================
   HostingMan — Console
   --------------------------------------------------------------------------
   Design direction: an INSTRUMENT PANEL, not a terminal.

   A hosting control panel is literally a control panel, so the reference is
   precision measuring equipment — studio gear, lab instruments, rack hardware
   — rather than the usual "dark IDE with a neon accent" that every hosting
   dashboard reaches for. That means: a cool light chassis, silkscreen-style
   utility labels, indicator LEDs used sparingly for state, and meters that
   read like real gauges with tick marks.

   THE TYPE RULE (the load-bearing idea):
     Monospace = machine-assigned values — domains, IPs, usernames, quotas,
                 invoice numbers, references, ports.
     Sans      = human language — names, descriptions, prose, actions.
   Type therefore encodes provenance. You can tell at a glance whether a value
   was written by a person or issued by a server.

   No web fonts: the Content-Security-Policy blocks external hosts, and
   self-hosting adds weight for little gain. Character comes from treatment —
   weight contrast, tracking, and tabular figures — not from a novelty face.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Tokens
   -------------------------------------------------------------------------- */

:root {
  /* Chassis & readout ---------------------------------------------------- */
  --chassis:        #E6E8EC;  /* panel body — cool mineral grey */
  --chassis-sunk:   #DDE0E6;  /* recessed wells, table headers */
  --readout:        #FAFBFC;  /* instrument face — where data sits */
  --readout-raised: #FFFFFF;
  --rule:           #CDD2DA;
  --rule-soft:      #E1E4EA;

  /* Ink ------------------------------------------------------------------ */
  --ink:      #101419;
  --ink-2:    #4E5765;
  --ink-3:    #7C8695;
  --ink-on-accent: #FFFFFF;

  /* Brand ---------------------------------------------------------------- */
  --accent:       #2440D9;   /* saturated ultramarine */
  --accent-hover: #1B32B3;
  --accent-soft:  #E6EAFD;
  --accent-ring:  rgba(36, 64, 217, 0.32);

  /* Indicator LEDs — state only, never decoration ------------------------ */
  --live:   #0E9F6E;   /* jade — active, paid, healthy */
  --live-soft:  #DDF3EB;
  --warn:   #B45309;   /* burnt amber — approaching a limit, pending */
  --warn-soft:  #FBEEDC;
  --fault:  #BE123C;   /* crimson — suspended, failed, overdue */
  --fault-soft: #FCE4EA;
  --idle:   #7C8695;   /* grey — terminated, cancelled, archived */
  --idle-soft:  #E4E7EC;
  --info:   #0369A1;
  --info-soft:  #DEEEF8;

  /* Elevation — shallow and cool. Instruments sit flat. ------------------- */
  --lift-1: 0 1px 2px rgba(16, 20, 25, .06), 0 1px 1px rgba(16, 20, 25, .04);
  --lift-2: 0 2px 6px rgba(16, 20, 25, .07), 0 1px 2px rgba(16, 20, 25, .05);
  --lift-3: 0 12px 28px rgba(16, 20, 25, .12), 0 2px 6px rgba(16, 20, 25, .06);
  --inset:  inset 0 1px 2px rgba(16, 20, 25, .07);

  /* Type ----------------------------------------------------------------- */
  --sans: Roboto, "Segoe UI", -apple-system, BlinkMacSystemFont,
          "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, "Cascadia Mono", "SF Mono", "JetBrains Mono",
          Menlo, Consolas, "Liberation Mono", monospace;

  --step--2: .6875rem;  /* 11px — silkscreen labels */
  --step--1: .8125rem;  /* 13px — secondary, table cells */
  --step-0:  .9375rem;  /* 15px — body */
  --step-1:  1.125rem;  /* 18px — card titles */
  --step-2:  1.5rem;    /* 24px — section heads */
  --step-3:  2rem;      /* 32px — page titles */
  --step-4:  2.75rem;   /* 44px — display readouts */

  /* Geometry — small radii. Instruments have tight corners. --------------- */
  --r-sm: 4px;
  --r:    7px;
  --r-lg: 11px;
  --r-pill: 999px;

  --rail-w: 244px;
  --topbar-h: 60px;
  --shell-max: 1440px;

  --ease: cubic-bezier(.2, .7, .3, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --dur: 180ms;
  --dur-slow: 420ms;

  color-scheme: light;
}

/* Dark mode = the instrument backlit. Deliberately blue-black and slightly
   luminous rather than pure #000, so meters and LEDs read as emissive. */
:root[data-theme="dark"],
:root:not([data-theme="light"]) {
  --chassis:        #0E1218;
  --chassis-sunk:   #0A0D12;
  --readout:        #161B23;
  --readout-raised: #1C232D;
  --rule:           #2A323E;
  --rule-soft:      #212833;

  --ink:      #E9EDF3;
  --ink-2:    #9AA5B4;
  --ink-3:    #6B7686;

  --accent:       #6E85FF;
  --accent-hover: #8497FF;
  --accent-soft:  #1B2340;
  --accent-ring:  rgba(110, 133, 255, .4);

  --live:  #2DD4A0;  --live-soft:  #0D2A22;
  --warn:  #E8A33D;  --warn-soft:  #2C2113;
  --fault: #FF5C7A;  --fault-soft: #2E1520;
  --idle:  #6B7686;  --idle-soft:  #1D242E;
  --info:  #4CB2E8;  --info-soft:  #10222E;

  --lift-1: 0 1px 2px rgba(0, 0, 0, .4);
  --lift-2: 0 2px 8px rgba(0, 0, 0, .45);
  --lift-3: 0 16px 36px rgba(0, 0, 0, .55);
  --inset:  inset 0 1px 2px rgba(0, 0, 0, .5);

  color-scheme: dark;
}

/* Explicit light override must win over the media-query default above. */
:root[data-theme="light"] { color-scheme: light; }

@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) {
    --chassis: #E6E8EC; --chassis-sunk: #DDE0E6;
    --readout: #FAFBFC; --readout-raised: #FFFFFF;
    --rule: #CDD2DA;    --rule-soft: #E1E4EA;
    --ink: #101419;     --ink-2: #4E5765; --ink-3: #7C8695;
    --accent: #2440D9;  --accent-hover: #1B32B3;
    --accent-soft: #E6EAFD; --accent-ring: rgba(36,64,217,.32);
    --live: #0E9F6E;  --live-soft: #DDF3EB;
    --warn: #B45309;  --warn-soft: #FBEEDC;
    --fault: #BE123C; --fault-soft: #FCE4EA;
    --idle: #7C8695;  --idle-soft: #E4E7EC;
    --info: #0369A1;  --info-soft: #DEEEF8;
    --lift-1: 0 1px 2px rgba(16,20,25,.06), 0 1px 1px rgba(16,20,25,.04);
    --lift-2: 0 2px 6px rgba(16,20,25,.07), 0 1px 2px rgba(16,20,25,.05);
    --lift-3: 0 12px 28px rgba(16,20,25,.12), 0 2px 6px rgba(16,20,25,.06);
    color-scheme: light;
  }
}

/* --------------------------------------------------------------------------
   2. Reset
   -------------------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

* { margin: 0; }

/* The `hidden` attribute is only a user-agent rule (`[hidden] { display:none }`),
   which sits at the LOWEST cascade origin — so any author rule that sets
   `display` silently overrides it. `.btn { display: inline-flex }` was enough to
   resurrect hidden buttons as empty boxes. Restating it here, in author origin,
   makes the attribute mean what everyone assumes it means. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  min-height: 100dvh;
  background: var(--chassis);
  color: var(--ink);
  font: 400 var(--step-0)/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  font-feature-settings: "cv05" 1, "ss01" 1;
  text-rendering: optimizeLegibility;
}

img, svg, video, canvas { display: block; max-width: 100%; }

input, button, textarea, select { font: inherit; color: inherit; }

button { background: none; border: 0; cursor: pointer; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 2px; }

h1, h2, h3, h4 { line-height: 1.18; letter-spacing: -.02em; font-weight: 650; }

table { border-collapse: collapse; width: 100%; }

/* Visible keyboard focus everywhere. Never remove without replacing. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: var(--r-sm);
}
:focus:not(:focus-visible) { outline: none; }

::selection { background: var(--accent); color: var(--ink-on-accent); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --------------------------------------------------------------------------
   3. Typography primitives
   -------------------------------------------------------------------------- */

/* Silkscreen label — the small uppercase text printed on equipment. */
.label {
  font: 600 var(--step--2)/1.2 var(--sans);
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* Machine value: anything a server assigned. Tabular so columns align. */
.mono {
  font-family: var(--mono);
  font-size: .9em;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}

/* Large numeric readout — the instrument display. */
.readout-value {
  font: 700 var(--step-4)/1 var(--mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
  color: var(--ink);
}

.page-title { font-size: var(--step-3); font-weight: 700; letter-spacing: -.03em; }
.section-title { font-size: var(--step-2); font-weight: 650; letter-spacing: -.025em; }
.card-title { font-size: var(--step-1); font-weight: 620; letter-spacing: -.015em; }

.muted { color: var(--ink-2); }
.dim   { color: var(--ink-3); }
.small { font-size: var(--step--1); }
.tnum  { font-variant-numeric: tabular-nums; }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0;
  margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* --------------------------------------------------------------------------
   4. Shell — rail + topbar + content
   -------------------------------------------------------------------------- */

.shell { display: flex; min-height: 100dvh; }

.rail {
  position: fixed;
  inset: 0 auto 0 0;
  width: var(--rail-w);
  display: flex;
  flex-direction: column;
  background: var(--readout);
  border-right: 1px solid var(--rule);
  z-index: 60;
  transition: transform var(--dur-slow) var(--ease-out);
}

.rail__brand {
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: 0 1.125rem;
  border-bottom: 1px solid var(--rule-soft);
  flex-shrink: 0;
}

/* The mark: a stacked-plates glyph — allocation slices in a rack. */
.rail__mark {
  width: 28px; height: 28px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--accent);
  color: var(--ink-on-accent);
  flex-shrink: 0;
}

.rail__name {
  font-weight: 680;
  letter-spacing: -.02em;
  font-size: var(--step-0);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.rail__nav {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: .875rem .625rem 1.5rem;
}

.rail__group + .rail__group { margin-top: 1.375rem; }

.rail__grouplabel {
  padding: 0 .625rem .5rem;
  font: 600 var(--step--2)/1 var(--sans);
  letter-spacing: .13em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.rail__link {
  position: relative;
  display: flex;
  align-items: center;
  gap: .625rem;
  padding: .5rem .625rem;
  border-radius: var(--r);
  color: var(--ink-2);
  font-size: var(--step--1);
  font-weight: 520;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.rail__link:hover { background: var(--chassis-sunk); color: var(--ink); text-decoration: none; }

.rail__link svg, .rail__link i { width: 17px; height: 17px; flex-shrink: 0; opacity: .78; }
.rail__link i { font-size: 14px !important; line-height: 17px; }

.rail__link.is-active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.rail__link.is-active svg, .rail__link.is-active i { opacity: 1; }

/* Active marker: a short bar in the gutter, like a selected channel. */
.rail__link.is-active::before {
  content: "";
  position: absolute;
  left: -.625rem;
  top: 50%;
  width: 3px; height: 18px;
  background: var(--accent);
  border-radius: 0 2px 2px 0;
  transform: translateY(-50%) scaleY(0);
  transform-origin: center;
  animation: markerIn var(--dur-slow) var(--ease-out) forwards;
}
@keyframes markerIn { to { transform: translateY(-50%) scaleY(1); } }

.rail__count {
  margin-left: auto;
  min-width: 20px;
  padding: .0625rem .3125rem;
  border-radius: var(--r-pill);
  background: var(--fault);
  color: #fff;
  font: 650 var(--step--2)/1.5 var(--mono);
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.rail__foot {
  padding: .75rem;
  border-top: 1px solid var(--rule-soft);
  flex-shrink: 0;
}

/* -- Topbar ---------------------------------------------------------------- */

.main {
  flex: 1;
  min-width: 0;
  margin-left: var(--rail-w);
  display: flex;
  flex-direction: column;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  height: var(--topbar-h);
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0 1.5rem;
  background: color-mix(in srgb, var(--chassis) 88%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.6);
  backdrop-filter: blur(12px) saturate(1.6);
  border-bottom: 1px solid var(--rule);
}

.topbar__crumb {
  display: flex;
  align-items: center;
  gap: .4375rem;
  font-size: var(--step--1);
  color: var(--ink-3);
  min-width: 0;
}
.topbar__crumb a { color: var(--ink-3); }
.topbar__crumb a:hover { color: var(--ink); }
.topbar__crumb strong { color: var(--ink); font-weight: 600; }
.topbar__sep { opacity: .5; }

.topbar__spacer { flex: 1; }

.content {
  flex: 1;
  width: 100%;
  max-width: var(--shell-max);
  margin-inline: auto;
  padding: 1.75rem 1.5rem 4rem;
}

/* -- Mobile rail ----------------------------------------------------------- */

.rail__toggle { display: none; }

.scrim {
  position: fixed; inset: 0;
  background: rgba(10, 13, 18, .5);
  opacity: 0; pointer-events: none;
  transition: opacity var(--dur-slow) var(--ease);
  z-index: 55;
}

@media (max-width: 1024px) {
  .rail { transform: translateX(-100%); box-shadow: var(--lift-3); }
  body.rail-open .rail { transform: translateX(0); }
  body.rail-open .scrim { opacity: 1; pointer-events: auto; }
  .main { margin-left: 0; }
  .rail__toggle { display: inline-grid; }
  .content { padding: 1.25rem 1rem 3rem; }
}

/* --------------------------------------------------------------------------
   5. Page header
   -------------------------------------------------------------------------- */

.pagehead {
  display: flex;
  align-items: flex-end;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
}
.pagehead__text { min-width: 0; flex: 1; }
.pagehead__eyebrow { margin-bottom: .375rem; }
.pagehead__sub { color: var(--ink-2); font-size: var(--step--1); margin-top: .3125rem; }
.pagehead__actions { display: flex; gap: .5rem; flex-wrap: wrap; }

/* --------------------------------------------------------------------------
   6. Panel — the base surface
   -------------------------------------------------------------------------- */

.panel {
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
}

.panel__head {
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .875rem 1.125rem;
  border-bottom: 1px solid var(--rule-soft);
}
.panel__head .card-title { flex: 1; min-width: 0; }

.panel__body { padding: 1.125rem; }
.panel__body--flush { padding: 0; }

.panel__foot {
  padding: .75rem 1.125rem;
  border-top: 1px solid var(--rule-soft);
  background: var(--chassis-sunk);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  font-size: var(--step--1);
}

/* --------------------------------------------------------------------------
   7. Readout — the instrument display tile
   -------------------------------------------------------------------------- */

.readouts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: .875rem;
  margin-bottom: 1.5rem;
}

.readout {
  position: relative;
  padding: 1rem 1.125rem 1.125rem;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  overflow: hidden;
}

/* Hairline of state colour along the top edge — the tile's "channel strip". */
.readout::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--tone, var(--accent));
  opacity: .9;
}

.readout__label { margin-bottom: .625rem; }

.readout__value {
  font: 700 var(--step-3)/1 var(--mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.035em;
}
.readout__unit {
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--ink-3);
  margin-left: .25rem;
  letter-spacing: 0;
}

.readout__meta {
  display: flex;
  align-items: center;
  gap: .375rem;
  margin-top: .5rem;
  font-size: var(--step--1);
  color: var(--ink-2);
}

.delta { font-weight: 600; font-variant-numeric: tabular-nums; }
.delta--up   { color: var(--live); }
.delta--down { color: var(--fault); }
.delta--flat { color: var(--ink-3); }

/* Tone modifiers set the accent hairline + value colour. */
.tone-live   { --tone: var(--live); }
.tone-warn   { --tone: var(--warn); }
.tone-fault  { --tone: var(--fault); }
.tone-info   { --tone: var(--info); }
.tone-accent { --tone: var(--accent); }
.tone-idle   { --tone: var(--idle); }

/* --------------------------------------------------------------------------
   8. Indicator LED
   -------------------------------------------------------------------------- */

.led {
  --led-color: var(--idle);
  position: relative;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--led-color);
  flex-shrink: 0;
}

.led--success { --led-color: var(--live); }
.led--warning { --led-color: var(--warn); }
.led--danger  { --led-color: var(--fault); }
.led--info    { --led-color: var(--info); }
.led--neutral { --led-color: var(--idle); }

/* Only a LIVE indicator pulses. A pulsing "suspended" light would be noise. */
.led--success::after {
  content: "";
  position: absolute;
  inset: -3px;
  border-radius: 50%;
  border: 1px solid var(--led-color);
  opacity: 0;
  animation: ledPulse 2.6s var(--ease) infinite;
}
@keyframes ledPulse {
  0%   { opacity: .8; transform: scale(.7); }
  70%  { opacity: 0;  transform: scale(1.5); }
  100% { opacity: 0;  transform: scale(1.5); }
}

/* --------------------------------------------------------------------------
   9. Badge / pill
   -------------------------------------------------------------------------- */

.badge {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  padding: .1875rem .5rem;
  border-radius: var(--r-sm);
  font: 600 var(--step--2)/1.4 var(--sans);
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
  background: var(--idle-soft);
  color: var(--idle);
}
.badge--success { background: var(--live-soft);  color: var(--live); }
.badge--warning { background: var(--warn-soft);  color: var(--warn); }
.badge--danger  { background: var(--fault-soft); color: var(--fault); }
.badge--info    { background: var(--info-soft);  color: var(--info); }
.badge--neutral { background: var(--idle-soft);  color: var(--idle); }
.badge--accent  { background: var(--accent-soft); color: var(--accent); }

/* --------------------------------------------------------------------------
   10. Meter — the signature gauge
   --------------------------------------------------------------------------
   A real instrument meter: a recessed track, a fill, tick marks at 25/50/75,
   and a threshold notch showing where the warning level sits. The fill width
   comes from a --value custom property set inline, so PHP writes one number
   and CSS does the rest.
   -------------------------------------------------------------------------- */

.meter {
  --value: 0%;
  --meter-color: var(--live);
  position: relative;
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--chassis-sunk);
  box-shadow: var(--inset);
  overflow: hidden;
}

.meter__fill {
  height: 100%;
  width: var(--value);
  border-radius: var(--r-pill);
  background: var(--meter-color);
  transform-origin: left center;
  animation: meterFill var(--dur-slow) var(--ease-out) both;
}
@keyframes meterFill { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* Tick marks at the quarter points — what makes it read as an instrument
   rather than a generic progress bar. */
.meter--ticked::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    to right,
    transparent 0 calc(25% - 1px),
    color-mix(in srgb, var(--ink) 22%, transparent) calc(25% - 1px) 25%
  );
  pointer-events: none;
}

.meter--tall { height: 9px; }

.meter-warn   { --meter-color: var(--warn); }
.meter-danger { --meter-color: var(--fault); }

.meterline {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
  margin-bottom: .3125rem;
  font-size: var(--step--1);
}
.meterline__used { font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: 600; }
.meterline__limit { color: var(--ink-3); font-family: var(--mono); font-size: .8125rem; }

/* Grouped meters inside a card */
.meterstack { display: grid; gap: .875rem; }

/* --------------------------------------------------------------------------
   11. Rack strip — the signature component
   --------------------------------------------------------------------------
   Each hosting account renders as a rack-mounted unit: status LED, machine
   identity, live meters, and the server it sits on stamped at the right.
   Stacked, they read as a rack elevation.
   -------------------------------------------------------------------------- */

.rack { display: grid; gap: .5rem; }

.rack__unit {
  display: grid;
  /*
   | FIXED end tracks, not `auto`.
   |
   | Every unit is its own grid, so an `auto` track sizes to that row's own
   | content — and the right-hand column holds either a "cPanel" button or a
   | status badge, next to a plan name that varies in length. The result was a
   | list where no two rows agreed on where their columns began, which reads as
   | sloppiness rather than as a list.
   |
   | Pinning the first and last tracks makes every row resolve to identical
   | geometry, so the meters and actions line up down the whole list. The two
   | middle tracks stay fractional because the domain and the meters SHOULD
   | absorb the remaining width.
   */
  grid-template-columns: 10px minmax(0, 1.6fr) minmax(0, 2fr) 15rem;
  align-items: center;
  gap: 1rem;
  padding: .875rem 1.125rem;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--lift-1);
  transition: transform var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}

.rack__unit:hover {
  transform: translateY(-1px);
  box-shadow: var(--lift-2);
  border-color: color-mix(in srgb, var(--accent) 32%, var(--rule));
}

.rack__id { min-width: 0; }

.rack__domain {
  display: block;
  font: 600 var(--step-0)/1.3 var(--mono);
  letter-spacing: -.02em;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.rack__domain:hover { color: var(--accent); text-decoration: none; }

.rack__meta {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin-top: .1875rem;
  font-size: var(--step--2);
  color: var(--ink-3);
}
.rack__user { font-family: var(--mono); }

.rack__meters { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; }

.rack__meter .label { font-size: 10px; letter-spacing: .1em; margin-bottom: .25rem; display: block; }
.rack__meter-value {
  font: 600 var(--step--1)/1 var(--mono);
  font-variant-numeric: tabular-nums;
  margin-bottom: .3125rem;
  display: block;
}

/* Right-aligned within its fixed track, so the actions form a clean edge
   regardless of which control a given row happens to show. */
.rack__right {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: .875rem;
  min-width: 0;
}

/* The action slot is reserved whether it holds a button or a badge — without
   this, a pending row's narrower badge pulls the plan stamp out of line with
   the rows above and below it. */
.rack__right > form,
.rack__right > .badge {
  flex: none;
  min-width: 6.5rem;
  display: flex;
  justify-content: flex-end;
}

/* Server stamp — silkscreen printing on the chassis. */
.rack__server {
  text-align: right;
  font: 600 var(--step--2)/1.3 var(--mono);
  letter-spacing: .04em;
  color: var(--ink-3);
  text-transform: uppercase;
  white-space: nowrap;
}
.rack__server span { display: block; color: var(--ink-2); text-transform: none; letter-spacing: 0; }

@media (max-width: 900px) {
  .rack__unit { grid-template-columns: 10px 1fr auto; }
  .rack__meters { grid-column: 2 / -1; grid-row: 2; }
  .rack__right { grid-row: 1; }
  .rack__server { display: none; }
}

/* --------------------------------------------------------------------------
   12. Buttons
   -------------------------------------------------------------------------- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4375rem;
  padding: .5rem .875rem;
  border-radius: var(--r);
  border: 1px solid var(--rule);
  background: var(--readout-raised);
  color: var(--ink);
  font-size: var(--step--1);
  font-weight: 570;
  white-space: nowrap;
  box-shadow: var(--lift-1);
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.btn:hover { background: var(--chassis-sunk); text-decoration: none; }
.btn:active { transform: translateY(1px); box-shadow: none; }

.btn svg, .btn i { width: 15px; height: 15px; flex-shrink: 0; }
.btn i { font-size: 13px !important; line-height: 15px; }
.btn--lg i { font-size: 14px !important; }
.btn--sm i { font-size: 11px !important; width: 13px; height: 13px; }

.btn--primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink-on-accent);
}
.btn--primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }

.btn--danger {
  background: var(--fault);
  border-color: var(--fault);
  color: #fff;
}
.btn--danger:hover { filter: brightness(1.08); }

.btn--ghost { background: transparent; border-color: transparent; box-shadow: none; color: var(--ink-2); }
.btn--ghost:hover { background: var(--chassis-sunk); color: var(--ink); }

.btn--sm { padding: .3125rem .625rem; font-size: var(--step--2); }
.btn--lg { padding: .6875rem 1.25rem; font-size: var(--step-0); }
.btn--block { width: 100%; }

.btn--icon { padding: .5rem; aspect-ratio: 1; }

.btn[disabled], .btn[aria-disabled="true"] {
  opacity: .5; pointer-events: none;
}

/* Busy state for async actions */
.btn.is-busy { pointer-events: none; position: relative; color: transparent; }
.btn.is-busy::after {
  content: "";
  position: absolute;
  width: 14px; height: 14px;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  color: var(--ink);
  animation: spin .6s linear infinite;
}
.btn--primary.is-busy::after, .btn--danger.is-busy::after { color: #fff; }
@keyframes spin { to { transform: rotate(360deg); } }

.btnrow { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }

/* --------------------------------------------------------------------------
   13. Forms
   -------------------------------------------------------------------------- */

.field { margin-bottom: 1.125rem; }

.field__label {
  display: block;
  margin-bottom: .375rem;
  font-size: var(--step--1);
  font-weight: 580;
  color: var(--ink);
}
.field__label .req { color: var(--fault); margin-left: .125rem; }

.input, .select, .textarea {
  width: 100%;
  padding: .5625rem .75rem;
  background: var(--readout-raised);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  color: var(--ink);
  font-size: var(--step-0);
  box-shadow: var(--inset);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.input::placeholder, .textarea::placeholder { color: var(--ink-3); }

.input:focus, .select:focus, .textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-ring);
}

/* Machine-value inputs get the mono face — domains, IPs, usernames, tokens. */
.input--mono { font-family: var(--mono); font-size: var(--step--1); letter-spacing: -.01em; }

.textarea { min-height: 100px; resize: vertical; line-height: 1.55; }

.select {
  appearance: none;
  padding-right: 2.25rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%237C8695' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right .75rem center;
}

.field__hint { margin-top: .3125rem; font-size: var(--step--1); color: var(--ink-3); }

.field__error {
  display: flex;
  align-items: center;
  gap: .3125rem;
  margin-top: .3125rem;
  font-size: var(--step--1);
  font-weight: 520;
  color: var(--fault);
}

.field.has-error .input,
.field.has-error .select,
.field.has-error .textarea { border-color: var(--fault); }
.field.has-error .input:focus { box-shadow: 0 0 0 3px color-mix(in srgb, var(--fault) 26%, transparent); }

.field-row { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 1rem; }
.field-row--stacked { grid-template-columns: minmax(0, 1fr); }
@media (max-width: 640px) { .field-row { grid-template-columns: 1fr; } }

/* Input with a leading unit/prefix, e.g. https:// or ₦ */
.inputgroup { display: flex; }
.inputgroup__addon {
  display: inline-flex;
  align-items: center;
  padding: 0 .625rem;
  background: var(--chassis-sunk);
  border: 1px solid var(--rule);
  border-right: 0;
  border-radius: var(--r) 0 0 var(--r);
  font: 500 var(--step--1)/1 var(--mono);
  color: var(--ink-3);
}
.inputgroup .input { border-radius: 0 var(--r) var(--r) 0; }

/* A select styled to read as a prefix — it looks like part of the field, but
   it is the real control rather than a label pretending to be one. */
.inputgroup__select {
  width: auto;
  flex-shrink: 0;
  border-radius: var(--r) 0 0 var(--r);
  border-right: 0;
  background-color: var(--chassis-sunk);
  font-family: var(--mono);
  font-size: var(--step--1);
  color: var(--ink-2);
  padding-right: 1.875rem;
  background-position: right .5rem center;
}
.inputgroup__select:focus { position: relative; z-index: 1; }

/* Checkbox / radio / switch */
.check {
  display: flex;
  align-items: flex-start;
  gap: .5625rem;
  cursor: pointer;
  font-size: var(--step--1);
}
.check input { margin-top: .1875rem; accent-color: var(--accent); width: 15px; height: 15px; flex-shrink: 0; }
.check__text strong { display: block; font-weight: 580; color: var(--ink); }
.check__text span { color: var(--ink-3); }

.switch { display: inline-flex; align-items: center; gap: .625rem; cursor: pointer; }
.switch input { position: absolute; opacity: 0; width: 0; height: 0; }
.switch__track {
  width: 36px; height: 20px;
  border-radius: var(--r-pill);
  background: var(--rule);
  box-shadow: var(--inset);
  position: relative;
  transition: background var(--dur) var(--ease);
  flex-shrink: 0;
}
.switch__track::after {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 16px; height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--lift-1);
  transition: transform var(--dur) var(--ease);
}
.switch input:checked + .switch__track { background: var(--live); }
.switch input:checked + .switch__track::after { transform: translateX(16px); }
.switch input:focus-visible + .switch__track { outline: 2px solid var(--accent); outline-offset: 2px; }

/* --------------------------------------------------------------------------
   14. Tables
   -------------------------------------------------------------------------- */

.tablewrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }

.table { font-size: var(--step--1); }

.table thead th {
  padding: .625rem 1rem;
  background: var(--chassis-sunk);
  border-bottom: 1px solid var(--rule);
  font: 600 var(--step--2)/1.4 var(--sans);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-align: left;
  white-space: nowrap;
}
.table thead th:first-child { border-radius: var(--r) 0 0 0; }
.table thead th:last-child  { border-radius: 0 var(--r) 0 0; }

.table tbody td {
  padding: .75rem 1rem;
  border-bottom: 1px solid var(--rule-soft);
  vertical-align: middle;
}
.table tbody tr:last-child td { border-bottom: 0; }
.table tbody tr { transition: background var(--dur) var(--ease); }
.table tbody tr:hover { background: var(--chassis-sunk); }

.table .num { text-align: right; font-family: var(--mono); font-variant-numeric: tabular-nums; }
.table .nowrap { white-space: nowrap; }
.table .actions { text-align: right; white-space: nowrap; }

/* Sortable header */
.th-sort { display: inline-flex; align-items: center; gap: .3125rem; color: inherit; }
.th-sort:hover { color: var(--ink); text-decoration: none; }
.th-sort svg, .th-sort i { width: 11px; height: 11px; opacity: .45; }
.th-sort i { font-size: 10px !important; }
.th-sort.is-active svg, .th-sort.is-active i { opacity: 1; color: var(--accent); }

/* --------------------------------------------------------------------------
   15. Empty state
   -------------------------------------------------------------------------- */

.empty {
  padding: 3.5rem 1.5rem;
  text-align: center;
}
.empty__icon {
  width: 44px; height: 44px;
  margin: 0 auto 1rem;
  display: grid; place-items: center;
  border-radius: var(--r-lg);
  background: var(--chassis-sunk);
  color: var(--ink-3);
}
.empty__title { font-size: var(--step-1); font-weight: 620; margin-bottom: .375rem; }
.empty__text { color: var(--ink-2); font-size: var(--step--1); max-width: 40ch; margin: 0 auto 1.25rem; }

/* --------------------------------------------------------------------------
   16. Alerts / flash
   -------------------------------------------------------------------------- */

.alert {
  display: flex;
  gap: .75rem;
  padding: .875rem 1rem;
  border-radius: var(--r);
  border: 1px solid;
  font-size: var(--step--1);
  margin-bottom: 1.125rem;
}
.alert__icon { flex-shrink: 0; margin-top: .0625rem; }
.alert__body { flex: 1; min-width: 0; }
.alert__title { font-weight: 620; margin-bottom: .1875rem; }
.alert ul { margin: .375rem 0 0; padding-left: 1.125rem; }
.alert__close { flex-shrink: 0; opacity: .6; padding: 0 .25rem; }
.alert__close:hover { opacity: 1; }

.alert--success { background: var(--live-soft);  border-color: color-mix(in srgb, var(--live) 34%, transparent);  color: var(--live); }
.alert--warning { background: var(--warn-soft);  border-color: color-mix(in srgb, var(--warn) 34%, transparent);  color: var(--warn); }
.alert--danger  { background: var(--fault-soft); border-color: color-mix(in srgb, var(--fault) 34%, transparent); color: var(--fault); }
.alert--info    { background: var(--info-soft);  border-color: color-mix(in srgb, var(--info) 34%, transparent);  color: var(--info); }

/* Toast stack */
.toasts {
  position: fixed;
  right: 1.25rem; bottom: 1.25rem;
  z-index: 200;
  display: grid;
  gap: .625rem;
  max-width: min(380px, calc(100vw - 2.5rem));
}
.toast {
  display: flex;
  gap: .625rem;
  padding: .8125rem 1rem;
  background: var(--readout-raised);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--tone, var(--accent));
  border-radius: var(--r);
  box-shadow: var(--lift-3);
  font-size: var(--step--1);
  animation: toastIn var(--dur-slow) var(--ease-out) both;
}
@keyframes toastIn {
  from { opacity: 0; transform: translateX(16px) scale(.97); }
  to   { opacity: 1; transform: none; }
}
.toast.is-leaving { animation: toastOut 260ms var(--ease) both; }
@keyframes toastOut { to { opacity: 0; transform: translateX(16px); } }

/* --------------------------------------------------------------------------
   17. Layout utilities
   -------------------------------------------------------------------------- */

.grid { display: grid; gap: 1.125rem; }
.grid--2 { grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.grid--3 { grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); }
.grid--sidebar { grid-template-columns: minmax(0, 1fr) 340px; align-items: start; }
@media (max-width: 1100px) { .grid--sidebar { grid-template-columns: 1fr; } }

.stack { display: grid; gap: 1.125rem; }
.stack--sm { gap: .625rem; }
.row { display: flex; align-items: center; gap: .625rem; flex-wrap: wrap; }
.row--between { justify-content: space-between; }
.row--tight { gap: .375rem; }
.spacer { flex: 1; }

.mt-0 { margin-top: 0; } .mt-1 { margin-top: .5rem; } .mt-2 { margin-top: 1rem; } .mt-3 { margin-top: 1.5rem; }
.mb-0 { margin-bottom: 0; } .mb-1 { margin-bottom: .5rem; } .mb-2 { margin-bottom: 1rem; } .mb-3 { margin-bottom: 1.5rem; }

/* --------------------------------------------------------------------------
   18. Toolbar / filters
   -------------------------------------------------------------------------- */

.toolbar {
  display: flex;
  align-items: center;
  gap: .625rem;
  flex-wrap: wrap;
  padding: .75rem 1rem;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  border-bottom: 0;
}

.search {
  position: relative;
  flex: 1;
  min-width: 200px;
  max-width: 380px;
}
.search svg {
  position: absolute;
  left: .625rem; top: 50%;
  transform: translateY(-50%);
  width: 15px; height: 15px;
  color: var(--ink-3);
  pointer-events: none;
}
.search .input { padding-left: 2.125rem; }

/* Segmented filter — reads like a hardware selector switch. */
.segmented {
  display: inline-flex;
  padding: 2px;
  background: var(--chassis-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--inset);
}
.segmented a {
  padding: .3125rem .625rem;
  border-radius: var(--r-sm);
  font-size: var(--step--2);
  font-weight: 580;
  color: var(--ink-2);
  letter-spacing: .02em;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.segmented a:hover { color: var(--ink); text-decoration: none; }
.segmented a.is-active {
  background: var(--readout-raised);
  color: var(--ink);
  box-shadow: var(--lift-1);
}

/* --------------------------------------------------------------------------
   19. Pagination
   -------------------------------------------------------------------------- */

.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .75rem 1rem;
  border-top: 1px solid var(--rule-soft);
  font-size: var(--step--1);
  color: var(--ink-2);
  flex-wrap: wrap;
}
.pager__links { display: flex; gap: .25rem; }
.pager__links a, .pager__links span {
  min-width: 30px;
  padding: .25rem .5rem;
  border-radius: var(--r-sm);
  text-align: center;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
}
.pager__links a:hover { background: var(--chassis-sunk); color: var(--ink); text-decoration: none; }
.pager__links .is-current { background: var(--accent); color: var(--ink-on-accent); font-weight: 600; }
.pager__links .is-disabled { opacity: .35; pointer-events: none; }

/* --------------------------------------------------------------------------
   20. Dropdown menu
   -------------------------------------------------------------------------- */

.menu { position: relative; }

.menu__panel {
  position: absolute;
  right: 0; top: calc(100% + 6px);
  min-width: 208px;
  padding: .3125rem;
  background: var(--readout-raised);
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--lift-3);
  z-index: 90;
  opacity: 0;
  transform: translateY(-4px) scale(.98);
  pointer-events: none;
  transform-origin: top right;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease-out);
}
.menu.is-open .menu__panel { opacity: 1; transform: none; pointer-events: auto; }

.menu__item {
  display: flex;
  align-items: center;
  gap: .5625rem;
  width: 100%;
  padding: .4375rem .625rem;
  border-radius: var(--r-sm);
  font-size: var(--step--1);
  color: var(--ink-2);
  text-align: left;
}
.menu__item:hover { background: var(--chassis-sunk); color: var(--ink); text-decoration: none; }
.menu__item svg, .menu__item i { width: 15px; height: 15px; opacity: .7; flex-shrink: 0; }
.menu__item i { font-size: 13px !important; line-height: 15px; }
.menu__item--danger { color: var(--fault); }
.menu__item--danger:hover { background: var(--fault-soft); color: var(--fault); }

.menu__sep { height: 1px; background: var(--rule-soft); margin: .3125rem 0; }
.menu__head { padding: .5rem .625rem .375rem; }

/* --------------------------------------------------------------------------
   21. Avatar
   -------------------------------------------------------------------------- */

.avatar {
  width: 30px; height: 30px;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  color: var(--accent);
  font: 650 var(--step--2)/1 var(--sans);
  letter-spacing: .02em;
  flex-shrink: 0;
  text-transform: uppercase;
}
.avatar--lg { width: 42px; height: 42px; font-size: var(--step--1); border-radius: var(--r); }

/* --------------------------------------------------------------------------
   22. Modal
   -------------------------------------------------------------------------- */

.modal {
  position: fixed; inset: 0;
  z-index: 150;
  display: grid;
  place-items: center;
  padding: 1.25rem;
  background: rgba(10, 13, 18, .55);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
}
.modal.is-open { opacity: 1; pointer-events: auto; }

.modal__panel {
  width: min(520px, 100%);
  max-height: min(84dvh, 720px);
  display: flex;
  flex-direction: column;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-3);
  transform: translateY(10px) scale(.98);
  transition: transform var(--dur-slow) var(--ease-out);
}
.modal.is-open .modal__panel { transform: none; }

.modal__head {
  display: flex; align-items: flex-start; gap: .75rem;
  padding: 1.125rem 1.25rem .75rem;
}
.modal__body { padding: 0 1.25rem 1.125rem; overflow-y: auto; }
.modal__foot {
  display: flex; justify-content: flex-end; gap: .5rem;
  padding: .875rem 1.25rem;
  border-top: 1px solid var(--rule-soft);
  background: var(--chassis-sunk);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
}
.modal--wide .modal__panel { width: min(760px, 100%); }

/* --------------------------------------------------------------------------
   23. Tabs
   -------------------------------------------------------------------------- */

.tabs {
  display: flex;
  gap: .125rem;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 1.25rem;
  overflow-x: auto;
  scrollbar-width: none;
}
.tabs::-webkit-scrollbar { display: none; }

.tabs a {
  position: relative;
  padding: .625rem .875rem;
  font-size: var(--step--1);
  font-weight: 550;
  color: var(--ink-2);
  white-space: nowrap;
  transition: color var(--dur) var(--ease);
}
.tabs a:hover { color: var(--ink); text-decoration: none; }
.tabs a.is-active { color: var(--accent); font-weight: 620; }
.tabs a.is-active::after {
  content: "";
  position: absolute;
  left: .5rem; right: .5rem; bottom: -1px;
  height: 2px;
  background: var(--accent);
  border-radius: 2px 2px 0 0;
  animation: tabIn var(--dur) var(--ease-out);
}
@keyframes tabIn { from { transform: scaleX(.4); opacity: 0; } }

/* --------------------------------------------------------------------------
   24. Definition list — key/value readouts
   -------------------------------------------------------------------------- */

.dl { display: grid; gap: 0; }
.dl__row {
  display: grid;
  grid-template-columns: minmax(120px, 34%) 1fr;
  gap: 1rem;
  padding: .625rem 0;
  border-bottom: 1px solid var(--rule-soft);
  font-size: var(--step--1);
  align-items: baseline;
}
.dl__row:last-child { border-bottom: 0; }
.dl__key { color: var(--ink-3); }
.dl__val { color: var(--ink); min-width: 0; word-break: break-word; }
.dl__val--mono { font-family: var(--mono); font-size: .8125rem; font-variant-numeric: tabular-nums; }

/* Copy-to-clipboard affordance on machine values */
.copyable {
  display: inline-flex; align-items: center; gap: .375rem;
  cursor: pointer; border-radius: var(--r-sm);
  transition: color var(--dur) var(--ease);
}
.copyable:hover { color: var(--accent); }
.copyable svg, .copyable i { width: 12px; height: 12px; opacity: 0; transition: opacity var(--dur) var(--ease); }
.copyable i { font-size: 11px !important; }
.copyable:hover svg, .copyable:hover i { opacity: .8; }
.copyable.is-copied { color: var(--live); }

/* --------------------------------------------------------------------------
   25. Timeline — activity / audit feed
   -------------------------------------------------------------------------- */

.timeline { display: grid; gap: 0; }
.timeline__item {
  position: relative;
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: .75rem;
  padding-bottom: 1.125rem;
}
/* Connector line between events */
.timeline__item:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 11px; top: 20px; bottom: 0;
  width: 1px;
  background: var(--rule);
}
.timeline__dot {
  width: 23px; height: 23px;
  display: grid; place-items: center;
  border-radius: 50%;
  background: var(--readout);
  border: 1px solid var(--rule);
  color: var(--ink-3);
  z-index: 1;
}
.timeline__dot svg, .timeline__dot i { width: 12px; height: 12px; font-size: 11px !important; }
.timeline__body { min-width: 0; font-size: var(--step--1); padding-top: .1875rem; }
.timeline__time { color: var(--ink-3); font-size: var(--step--2); margin-top: .125rem; }

/* --------------------------------------------------------------------------
   26. Auth screens
   -------------------------------------------------------------------------- */

.auth {
  min-height: 100dvh;
  display: grid;
  grid-template-columns: 1fr;
  background: var(--chassis);
}
@media (min-width: 900px) { .auth { grid-template-columns: 1fr 46%; } }

.auth__form {
  display: grid;
  place-items: center;
  padding: 2rem 1.5rem;
}
.auth__inner { width: min(400px, 100%); }

.auth__brand { display: flex; align-items: center; gap: .625rem; margin-bottom: 2rem; }

/* The right panel: a full-bleed photograph of the thing being sold, with the
   copy laid over it. The photo is dark and blue-dominant, so the same panel
   works in either theme without a second treatment. */
.auth__aside {
  display: none;
  position: relative;
  padding: 3rem;
  overflow: hidden;
  background: #0A1020;
  isolation: isolate;
}
@media (min-width: 900px) { .auth__aside { display: grid; align-content: end; } }

.auth__hero {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
  /* Slow drift, so the panel is alive without pulling attention off the form. */
  animation: authDrift 32s var(--ease) infinite alternate;
}

@keyframes authDrift {
  from { transform: scale(1.06) translate3d(0, 0, 0); }
  to   { transform: scale(1.14) translate3d(-1.5%, -1.5%, 0); }
}

/* Legibility floor for the overlaid copy — the photograph is busy, and text
   over an unmodified photo is a contrast failure waiting to happen. */
.auth__aside::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(180deg, rgba(6, 10, 22, .25) 0%, rgba(6, 10, 22, .82) 62%, rgba(6, 10, 22, .95) 100%),
    linear-gradient(90deg, rgba(6, 10, 22, .55) 0%, transparent 55%);
}

.auth__aside-inner { position: relative; color: #F2F5FB; max-width: 30rem; }

.auth__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .3rem .6rem;
  margin-bottom: 1rem;
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: var(--r-pill);
  background: rgba(255, 255, 255, .08);
  backdrop-filter: blur(6px);
  font-size: var(--step--2);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: #DDE5F5;
}

.auth__stats {
  display: flex;
  flex-wrap: wrap;
  gap: 1.75rem;
  margin-top: 1.75rem;
  padding-top: 1.5rem;
  border-top: 1px solid rgba(255, 255, 255, .16);
}
.auth__stat strong {
  display: block;
  font-size: var(--step-2);
  font-weight: 680;
  letter-spacing: -.02em;
  line-height: 1.1;
}
.auth__stat span {
  font-size: var(--step--2);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: rgba(242, 245, 251, .62);
}

@media (prefers-reduced-motion: reduce) {
  .auth__hero { animation: none; transform: scale(1.06); }
}

.auth__quote {
  font-size: var(--step-2);
  font-weight: 620;
  letter-spacing: -.03em;
  line-height: 1.25;
  max-width: 22ch;
  margin-bottom: 1.75rem;
}

/* Schematic rack units — decorative but on-metaphor. */
.schematic { display: grid; gap: .5rem; max-width: 340px; }
.schematic__unit {
  display: grid;
  grid-template-columns: 8px 1fr auto;
  align-items: center;
  gap: .75rem;
  padding: .625rem .75rem;
  background: var(--readout-raised);
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  animation: unitIn var(--dur-slow) var(--ease-out) backwards;
}
.schematic__unit:nth-child(1) { animation-delay: 60ms; }
.schematic__unit:nth-child(2) { animation-delay: 140ms; }
.schematic__unit:nth-child(3) { animation-delay: 220ms; }
.schematic__unit:nth-child(4) { animation-delay: 300ms; }
@keyframes unitIn { from { opacity: 0; transform: translateY(8px); } }

.schematic__bar {
  height: 4px;
  border-radius: var(--r-pill);
  background: var(--chassis-sunk);
  overflow: hidden;
}
.schematic__bar i {
  display: block;
  height: 100%;
  width: var(--w, 50%);
  background: var(--tone, var(--accent));
  border-radius: inherit;
}
.schematic__label {
  font: 600 10px/1 var(--mono);
  letter-spacing: .08em;
  color: var(--ink-3);
  text-transform: uppercase;
}

/* ==========================================================================
   26b. PUBLIC SITE — landing, storefront, blog, partners
   --------------------------------------------------------------------------
   A different register from the app: wide, airy, editorial. Same tokens
   throughout, so signing up never feels like arriving at a different product.
   The instrument-panel language (rack strips, LEDs, ticked meters) is reused
   in the hero rather than replaced — the marketing shows the actual thing.
   ========================================================================== */

.skip-link {
  position: absolute;
  left: -9999px;
  top: .5rem;
  z-index: 300;
  padding: .625rem 1rem;
  background: var(--accent);
  color: var(--ink-on-accent);
  border-radius: var(--r);
  font-weight: 600;
}
.skip-link:focus { left: .5rem; }

/* -- Site header ----------------------------------------------------------- */

.siteheader {
  position: sticky;
  top: 0;
  z-index: 80;
  background: color-mix(in srgb, var(--chassis) 88%, transparent);
  -webkit-backdrop-filter: blur(12px) saturate(1.6);
  backdrop-filter: blur(12px) saturate(1.6);
  border-bottom: 1px solid var(--rule);
}

.siteheader__inner {
  max-width: var(--shell-max);
  margin-inline: auto;
  padding: .75rem 1.5rem;
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.siteheader__brand {
  display: flex;
  align-items: center;
  gap: .625rem;
  font-weight: 680;
  letter-spacing: -.02em;
  color: var(--ink);
  white-space: nowrap;
}
.siteheader__brand:hover { text-decoration: none; }

.siteheader__nav { display: flex; gap: .25rem; flex: 1; }
.siteheader__nav a {
  padding: .4375rem .75rem;
  border-radius: var(--r);
  color: var(--ink-2);
  font-size: var(--step--1);
  font-weight: 540;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.siteheader__nav a:hover { background: var(--chassis-sunk); color: var(--ink); text-decoration: none; }
.siteheader__nav a.is-active { color: var(--accent); font-weight: 620; }

.siteheader__actions { display: flex; align-items: center; gap: .5rem; }
.siteheader__toggle { display: none; }

@media (max-width: 860px) {
  .siteheader__nav {
    display: none;
    position: absolute;
    inset: 100% 0 auto 0;
    flex-direction: column;
    padding: .75rem 1.5rem 1rem;
    background: var(--readout);
    border-bottom: 1px solid var(--rule);
    box-shadow: var(--lift-2);
  }
  body.site-nav-open .siteheader__nav { display: flex; }
  .siteheader__toggle { display: inline-grid; }
  .siteheader__actions .btn--sm:not(.btn--primary):not([data-theme-toggle]) { display: none; }
}

/* -- Section rhythm -------------------------------------------------------- */

.section {
  max-width: var(--shell-max);
  margin-inline: auto;
  padding: 4.5rem 1.5rem;
}
.section--tight { padding-block: 2.75rem; }
.section--sunk { background: var(--readout); border-block: 1px solid var(--rule); max-width: none; }
.section--sunk > * { max-width: var(--shell-max); margin-inline: auto; }

@media (max-width: 720px) { .section { padding: 3rem 1rem; } }

.sectionhead { max-width: 46ch; margin-bottom: 2rem; }
.sectionhead--center { margin-inline: auto; text-align: center; }
.sectionhead h2 {
  font-size: clamp(1.5rem, 3.4vw, 2.25rem);
  letter-spacing: -.035em;
  font-weight: 700;
  margin-bottom: .5rem;
}
.sectionhead p { color: var(--ink-2); font-size: var(--step-0); }

/* -- Hero ------------------------------------------------------------------
   The hero is an ARGUMENT, performed.

   Most Nigerian "hosts" resell US hardware, so a Lagos business serving Lagos
   customers pays a ~200ms round trip on every request. That is the one claim
   worth leading with, it is provable, and nobody else's page makes it — so the
   hero measures it rather than asserting it. Two ping traces race on load; the
   local one lands almost immediately, the overseas one crawls.

   This is the page's single bold moment. Everything below it stays quiet.
   -------------------------------------------------------------------------- */

.hero {
  max-width: var(--shell-max);
  margin-inline: auto;
  padding: 4rem 1.5rem 3rem;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 3rem;
  align-items: center;
}
@media (min-width: 960px) { .hero { grid-template-columns: 1.08fr .92fr; padding-block: 5.5rem 4rem; } }
@media (max-width: 720px) { .hero { padding: 2.5rem 1rem; gap: 2.25rem; } }

.hero__title {
  font-size: clamp(2.125rem, 5.4vw, 3.5rem);
  line-height: 1.06;
  letter-spacing: -.045em;
  font-weight: 750;
  margin-bottom: 1.125rem;
  text-wrap: balance;
}

/* The type rule doing rhetorical work: the machine half of the sentence is set
   in the machine face. "us-east-1" reads as a place the reader isn't. */
.hero__title code {
  font: 600 .78em/1 var(--mono);
  letter-spacing: -.02em;
  padding: .0625em .28em;
  border-radius: var(--r-sm);
  background: var(--chassis-sunk);
  color: var(--ink-2);
  border: 1px solid var(--rule);
  white-space: nowrap;
}

.hero__lede {
  font-size: clamp(1rem, 1.5vw, 1.125rem);
  line-height: 1.65;
  color: var(--ink-2);
  max-width: 46ch;
  margin-bottom: 1.75rem;
}

/* -- The signature: a latency instrument ----------------------------------- */

.gauge {
  padding: 1.375rem 1.5rem 1.25rem;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-3);
}

.gauge__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding-bottom: .875rem;
  margin-bottom: 1.125rem;
  border-bottom: 1px solid var(--rule-soft);
}

.trace + .trace { margin-top: 1.25rem; }

.trace__line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .75rem;
  margin-bottom: .5rem;
}
.trace__from {
  font-size: var(--step--1);
  font-weight: 560;
}
.trace__from span { color: var(--ink-3); font-weight: 400; }

.trace__ms {
  font: 700 var(--step-1)/1 var(--mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.03em;
  color: var(--tone, var(--ink));
}
.trace__ms small { font-size: .6em; font-weight: 500; color: var(--ink-3); margin-left: .125rem; }

/* Recessed track with quarter ticks — the same instrument as the app's meters. */
.trace__track {
  position: relative;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--chassis-sunk);
  box-shadow: var(--inset);
  overflow: hidden;
}
.trace__track::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    to right,
    transparent 0 calc(25% - 1px),
    color-mix(in srgb, var(--ink) 20%, transparent) calc(25% - 1px) 25%
  );
  pointer-events: none;
}

.trace__fill {
  height: 100%;
  width: var(--w, 100%);
  border-radius: var(--r-pill);
  background: var(--tone, var(--accent));
  transform-origin: left center;
  animation: traceRun var(--run, 1s) var(--ease-out) both;
  animation-delay: var(--delay, 0s);
}
@keyframes traceRun { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.trace--near { --tone: var(--live); }
.trace--far  { --tone: var(--warn); }

.gauge__note {
  margin-top: 1.25rem;
  padding-top: .875rem;
  border-top: 1px solid var(--rule-soft);
  font-size: var(--step--2);
  color: var(--ink-3);
  line-height: 1.5;
}

/* Reduced motion: show the finished measurement immediately. The instrument
   still reads correctly, it just doesn't perform. */
@media (prefers-reduced-motion: reduce) {
  .trace__fill { animation: none; transform: none; }
}

/* -- Setup sequence -------------------------------------------------------
   Numbered because this genuinely IS ordered — you cannot point a domain at a
   server that does not exist yet. Numbering anything else here would be
   decoration pretending to be information.
   -------------------------------------------------------------------------- */

.steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 1.75rem;
  counter-reset: step;
}

.step { position: relative; padding-top: 2.25rem; }
.step::before {
  counter-increment: step;
  content: counter(step, decimal-leading-zero);
  position: absolute;
  top: 0; left: 0;
  font: 700 var(--step--1)/1 var(--mono);
  letter-spacing: .04em;
  color: var(--accent);
}
/* Hairline joining the steps into a sequence, dropped on the last one. */
.step::after {
  content: "";
  position: absolute;
  top: .4375rem; left: 2.25rem; right: -1.75rem;
  height: 1px;
  background: var(--rule);
}
.step:last-child::after { display: none; }
@media (max-width: 640px) { .step::after { display: none; } }

.step h3 { font-size: var(--step-0); font-weight: 640; margin-bottom: .375rem; }
.step p { font-size: var(--step--1); color: var(--ink-2); }

/* -- Honest claims list ---------------------------------------------------- */

.claims { display: grid; gap: 0; max-width: 60rem; }

.claim {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 1.25rem;
  align-items: baseline;
  padding: 1.125rem 0;
  border-bottom: 1px solid var(--rule-soft);
}
.claim:last-child { border-bottom: 0; }

.claim__what { font-size: var(--step-0); font-weight: 570; }
.claim__detail { font-size: var(--step--1); color: var(--ink-2); }
.claim__value {
  font: 600 var(--step--1)/1 var(--mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
  white-space: nowrap;
}
@media (max-width: 640px) {
  .claim { grid-template-columns: auto minmax(0, 1fr); }
  .claim__value { grid-column: 2; }
}

/* -- Plan cards ------------------------------------------------------------ */

.plangrid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
  gap: 1.125rem;
  align-items: start;
}

.plan {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 1.75rem 1.5rem;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.plan:hover { transform: translateY(-3px); box-shadow: var(--lift-3); }

.plan--featured {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), var(--lift-2);
}
.plan__flag {
  position: absolute;
  top: -1px; right: 1.25rem;
  padding: .1875rem .625rem;
  background: var(--accent);
  color: var(--ink-on-accent);
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  font: 650 var(--step--2)/1.5 var(--sans);
  letter-spacing: .06em;
  text-transform: uppercase;
}

.plan__name { font-size: var(--step-1); font-weight: 650; letter-spacing: -.02em; }
.plan__price {
  margin: .875rem 0 .25rem;
  font: 750 clamp(2rem, 4vw, 2.5rem)/1 var(--mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.04em;
}
.plan__period { font: 500 var(--step--1)/1 var(--sans); color: var(--ink-3); letter-spacing: 0; }

.plan__specs { display: grid; gap: .625rem; margin: 1.25rem 0; }
.plan__spec {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--step--1);
  padding-bottom: .625rem;
  border-bottom: 1px solid var(--rule-soft);
}
.plan__spec:last-child { border-bottom: 0; padding-bottom: 0; }
.plan__spec b { font-family: var(--mono); font-weight: 600; font-variant-numeric: tabular-nums; }

.plan__features { display: grid; gap: .4375rem; margin-bottom: 1.5rem; }
.plan__features li {
  display: flex;
  align-items: center;
  gap: .4375rem;
  font-size: var(--step--1);
  color: var(--ink-2);
  list-style: none;
}
.plan__features svg, .plan__features i { color: var(--live); flex-shrink: 0; }

.plan__cta { margin-top: auto; }

/* -- Feature grid ---------------------------------------------------------- */

.features { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 1.75rem; }
.feature__icon {
  width: 38px; height: 38px;
  display: grid; place-items: center;
  border-radius: var(--r);
  background: var(--accent-soft);
  color: var(--accent);
  margin-bottom: .875rem;
}
.feature h3 { font-size: var(--step-0); font-weight: 640; margin-bottom: .375rem; }
.feature p { font-size: var(--step--1); color: var(--ink-2); }

/* -- Article / blog -------------------------------------------------------- */

/*
 | Fixed column counts, not auto-fill.
 |
 | auto-fill creates as many tracks as the container can hold and leaves the
 | surplus ones EMPTY. At the 1440px shell that is four tracks, so a strip of
 | three posts filled three and left a 348px hole on the right — the cards
 | looked shoved to one side for no visible reason. auto-fit would collapse the
 | empty track but then three cards stretch to a third of the page each.
 |
 | Counting the columns explicitly means the strips can be sized to fill their
 | rows: three across the desktop, six posts, two clean rows.
 */
.postgrid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5rem;
}
@media (min-width: 620px)  { .postgrid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .postgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.postcard {
  display: flex;
  flex-direction: column;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  overflow: hidden;
  box-shadow: var(--lift-1);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.postcard:hover { transform: translateY(-2px); box-shadow: var(--lift-2); }

.postcard__cover { aspect-ratio: 16 / 9; object-fit: cover; width: 100%; background: var(--chassis-sunk); }
.postcard__body { padding: 1.125rem; display: flex; flex-direction: column; flex: 1; }
.postcard__title { font-size: var(--step-1); font-weight: 640; letter-spacing: -.02em; margin: .5rem 0 .375rem; }
.postcard__title a { color: var(--ink); }
.postcard__excerpt { font-size: var(--step--1); color: var(--ink-2); flex: 1; }
.postcard__meta {
  display: flex; align-items: center; gap: .5rem;
  margin-top: .875rem; font-size: var(--step--2); color: var(--ink-3);
}

.article { max-width: 68ch; margin-inline: auto; }
.article__title {
  font-size: clamp(1.875rem, 4.5vw, 2.75rem);
  line-height: 1.1;
  letter-spacing: -.04em;
  font-weight: 730;
  margin-bottom: .875rem;
  text-wrap: balance;
}
/* Cover image.
   A fixed aspect ratio, not natural height: a portrait photo uploaded as a
   cover would otherwise run for two screens before the article started, so the
   reader scrolls past it to reach a single word. object-fit crops to the
   centre rather than squashing. */
.article__cover {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  max-height: 460px;
  object-fit: cover;
  object-position: center;
  border-radius: var(--r-lg);
  border: 1px solid var(--rule);
  margin: 1.75rem 0;
  background: var(--chassis-sunk);
}
@media (max-width: 720px) {
  .article__cover { aspect-ratio: 4 / 3; max-height: 300px; }
}

/* Long-form reading measure and rhythm. */
.prose { font-size: 1.0625rem; line-height: 1.75; color: var(--ink); }
.prose > * + * { margin-top: 1.125rem; }
.prose h2 { font-size: var(--step-2); letter-spacing: -.03em; margin-top: 2.25rem; }
.prose h3 { font-size: var(--step-1); margin-top: 1.75rem; }
.prose ul, .prose ol { padding-left: 1.375rem; }
.prose li + li { margin-top: .375rem; }
.prose img { border-radius: var(--r); border: 1px solid var(--rule); }
.prose blockquote {
  padding-left: 1.125rem;
  border-left: 3px solid var(--accent);
  color: var(--ink-2);
  font-style: italic;
}
.prose code {
  padding: .125rem .3125rem;
  background: var(--chassis-sunk);
  border-radius: var(--r-sm);
  font: 500 .875em var(--mono);
}
.prose pre { padding: 1rem; background: var(--chassis-sunk); border-radius: var(--r); overflow-x: auto; }
.prose pre code { padding: 0; background: none; }

/* -- Partner cards --------------------------------------------------------- */

.partnergrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 1.25rem; }

.partner {
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.partner:hover { transform: translateY(-2px); box-shadow: var(--lift-2); }

/* White, with padding, in both themes.
   A partner logo is someone else's asset drawn for whatever ground they had in
   mind — and overwhelmingly that is white. The file this first broke on is
   named "wordmark-dark": dark ink, invisible on the grey this used to use and
   more so on a dark theme. We cannot re-tone a PNG, so we give every one of
   them the ground they were drawn for. */
.partner__logo {
  width: 52px; height: 52px;
  object-fit: contain;
  border-radius: var(--r);
  background: #fff;
  border: 1px solid var(--rule-soft);
  padding: 4px;
  margin-bottom: .875rem;
}
.partner__fallback {
  width: 52px; height: 52px;
  display: grid; place-items: center;
  border-radius: var(--r);
  background: var(--accent-soft);
  color: var(--accent);
  font: 700 var(--step-1)/1 var(--sans);
  margin-bottom: .875rem;
}
.partner__name { font-size: var(--step-0); font-weight: 640; margin-bottom: .25rem; }
.partner__desc { font-size: var(--step--1); color: var(--ink-2); flex: 1; }

/* -- CTA band -------------------------------------------------------------- */

/* A full-bleed slab of brand colour is the default closer, and it fights the
   LED discipline the rest of the site keeps — where ultramarine means "live",
   not "decorative". This keeps the chassis and lets one accent rule carry the
   emphasis instead. */
.ctaband {
  max-width: var(--shell-max);
  margin: 0 auto;
  padding: 3.5rem 1.5rem 4rem;
  text-align: center;
  border-top: 2px solid var(--accent);
  background: var(--readout);
}
.ctaband h2 {
  font-size: clamp(1.5rem, 3.4vw, 2.125rem);
  letter-spacing: -.035em;
  margin-bottom: .625rem;
}
.ctaband p { color: var(--ink-2); max-width: 46ch; margin: 0 auto 1.75rem; }

/* -- Site footer ----------------------------------------------------------- */

.sitefooter { background: var(--readout); border-top: 1px solid var(--rule); margin-top: 4rem; }

.sitefooter__inner {
  max-width: var(--shell-max);
  margin-inline: auto;
  padding: 3rem 1.5rem 2rem;
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
}
@media (min-width: 800px) { .sitefooter__inner { grid-template-columns: 1.2fr 2fr; } }

.sitefooter__links { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 1.75rem; }
.sitefooter__links .label { margin-bottom: .625rem; }
.sitefooter__links a {
  display: block;
  font-size: var(--step--1);
  color: var(--ink-2);
  padding: .1875rem 0;
}
.sitefooter__links a:hover { color: var(--accent); }

.sitefooter__legal {
  border-top: 1px solid var(--rule-soft);
  padding: 1.125rem 1.5rem;
  text-align: center;
}

/* --------------------------------------------------------------------------
   27. Page-load choreography
   --------------------------------------------------------------------------
   One orchestrated entrance rather than scattered effects: content rises in
   sequence, driven by an inline --i index. Fully disabled under
   prefers-reduced-motion by the global rule in section 2.
   -------------------------------------------------------------------------- */

/*
 | `backwards`, NOT `both`, and the distinction is load-bearing.
 |
 | With `both`, the final keyframe keeps applying for the life of the page. Its
 | `transform: none` then computes as the identity matrix — and ANY transform
 | value other than the `none` KEYWORD creates a stacking context. So every
 | .rise element became a permanent stacking context, which trapped the
 | z-index of anything absolutely positioned inside it.
 |
 | That is how the "Buy more" dropdown ended up painting UNDERNEATH the readout
 | tiles below it: the panel's z-index:90 was resolved inside .pagehead's own
 | context, and .pagehead — z-index auto, non-positioned — paints in DOM order,
 | which puts it below the cards that follow.
 |
 | `backwards` applies the `from` values during the delay, runs the animation,
 | and then lets the element revert to its base style. The end state is
 | identical (the `to` keyframe only ever restated the defaults), but nothing
 | lingers to create a context.
 |
 | It fixes a second, quieter bug too: animations outrank normal declarations
 | in the cascade, so a filled `transform: none` was silently overriding the
 | :hover lift on every .rise card that has one.
 */
.rise {
  animation: rise var(--dur-slow) var(--ease-out) backwards;
  animation-delay: calc(var(--i, 0) * 55ms);
}
@keyframes rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------------------
   28. Misc
   -------------------------------------------------------------------------- */

.divider { height: 1px; background: var(--rule-soft); margin: 1.25rem 0; border: 0; }

.kbd {
  display: inline-block;
  padding: .0625rem .3125rem;
  border: 1px solid var(--rule);
  border-bottom-width: 2px;
  border-radius: var(--r-sm);
  background: var(--chassis-sunk);
  font: 600 var(--step--2)/1.4 var(--mono);
  color: var(--ink-2);
}

/* Code block for zone files, API responses, cron lines */
.code {
  padding: .875rem 1rem;
  background: var(--chassis-sunk);
  border: 1px solid var(--rule-soft);
  border-radius: var(--r);
  font: 500 var(--step--1)/1.65 var(--mono);
  color: var(--ink-2);
  overflow-x: auto;
  white-space: pre;
  tab-size: 2;
}

/* Truncation helper */
.trunc { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }

/* Print: invoices and receipts must print cleanly. */
@media print {
  .rail, .topbar, .pagehead__actions, .btn, .toasts, .pager { display: none !important; }
  .main { margin-left: 0; }
  body { background: #fff; color: #000; }
  .panel { border: 1px solid #ddd; box-shadow: none; break-inside: avoid; }
  .content { max-width: none; padding: 0; }
}

/* ==========================================================================
   Rich text editor
   --------------------------------------------------------------------------
   The editable surface is styled to approximate the published article, so what
   the author sees while writing is close to what a reader gets. It is not a
   pixel-perfect preview — that would mean loading the public stylesheet into
   the admin — but headings, quotes and code read the same way.
   ========================================================================== */

.rte {
  border: 1px solid var(--rule);
  border-radius: var(--r);
  background: var(--readout);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
.rte:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 4px var(--accent-ring);
}

.rte__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .125rem;
  padding: .375rem .5rem;
  border-bottom: 1px solid var(--rule);
  background: var(--chassis);
  border-radius: var(--r) var(--r) 0 0;
  position: sticky;
  top: var(--topbar-h);
  z-index: 2;
}

.rte__btn {
  min-width: 30px;
  height: 30px;
  padding: 0 .45rem;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-2);
  font-family: var(--sans);
  font-size: var(--step--1);
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.rte__btn:hover {
  background: var(--accent-soft);
  border-color: var(--accent-ring);
  color: var(--accent);
}
.rte__btn:active { transform: translateY(1px); }

.rte__sep {
  width: 1px;
  height: 18px;
  margin: 0 .3rem;
  background: var(--rule);
  flex: none;
}

.rte__surface {
  min-height: 420px;
  padding: 1.1rem 1.25rem;
  color: var(--ink);
  font-size: var(--step-0);
  line-height: 1.7;
  overflow-wrap: break-word;
}
.rte__surface:focus { outline: none; }

.rte__surface > * + * { margin-top: .9em; }
.rte__surface h2 { font-size: var(--step-2); line-height: 1.25; font-weight: 650; }
.rte__surface h3 { font-size: var(--step-1); line-height: 1.3; font-weight: 650; }
.rte__surface h4 { font-size: var(--step-0); font-weight: 650; }
.rte__surface ul, .rte__surface ol { padding-left: 1.4em; }
.rte__surface li + li { margin-top: .3em; }
.rte__surface blockquote {
  padding-left: 1rem;
  border-left: 3px solid var(--accent);
  color: var(--ink-2);
  font-style: italic;
}
.rte__surface pre {
  padding: .8rem 1rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--chassis-sunk);
  font-family: var(--mono);
  font-size: var(--step--1);
  overflow-x: auto;
  white-space: pre-wrap;
}
.rte__surface code { font-family: var(--mono); font-size: .92em; }
.rte__surface a { color: var(--accent); }
.rte__surface img,
.rte__surface video,
.rte__surface iframe {
  max-width: 100%;
  height: auto;
  border-radius: var(--r-sm);
  border: 1px solid var(--rule);
}
.rte__surface figure { margin: 1.2em 0; }
.rte__surface figcaption {
  margin-top: .4em;
  font-size: var(--step--1);
  color: var(--ink-3);
}
/* Placeholder for a genuinely empty document. */
.rte__surface:empty::before {
  content: attr(data-placeholder);
  color: var(--ink-3);
}

/* Source view: the textarea takes over the same box. */
.rte__source {
  display: block;
  width: 100%;
  min-height: 420px;
  padding: 1.1rem 1.25rem;
  border: 0;
  border-radius: 0 0 var(--r) var(--r);
  background: var(--chassis-sunk);
  color: var(--ink);
  font-family: var(--mono);
  font-size: var(--step--1);
  line-height: 1.6;
  resize: vertical;
}
.rte__source:focus { outline: none; }

.rte__status {
  padding: 0 1.25rem;
  font-size: var(--step--2);
  color: var(--ink-3);
  min-height: 0;
  transition: min-height var(--dur) var(--ease), padding var(--dur) var(--ease);
}
.rte__status:not(:empty) { min-height: 30px; padding: .45rem 1.25rem; }
.rte__status.is-busy { color: var(--accent); }
.rte__status.is-error { color: var(--fault); }

/* Write mode for the source toggle. */
.rte.is-source .rte__surface { display: none; }

/* Image picker — upload-or-paste control shared by post covers and the logo. */
.imagepicker__preview {
  display: block;
  width: 100%;
  max-height: 180px;
  object-fit: contain;
  object-position: left center;
  margin-bottom: .625rem;
  padding: .5rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--chassis-sunk);
}

/* ==========================================================================
   Landing photography
   --------------------------------------------------------------------------
   The hero keeps its latency gauge — that instrument is the page's argument,
   and a stock photo cannot make it. The photograph sits BEHIND as atmosphere,
   heavily veiled so contrast never depends on which part of the image a line
   of text happens to land on.
   ========================================================================== */

.herowrap {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.herowrap__bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: -2;
  opacity: .38;
  animation: heroDrift 40s var(--ease) infinite alternate;
}

@keyframes heroDrift {
  from { transform: scale(1.05); }
  to   { transform: scale(1.13) translate3d(-1.2%, -1%, 0); }
}

/* Veil: fades the photo out toward the content so the gauge and the type sit
   on near-flat ground, then hard-lands on the page background at the bottom. */
.herowrap::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(ellipse 90% 80% at 72% 38%, transparent 0%, var(--chassis) 72%),
    linear-gradient(180deg, transparent 40%, var(--chassis) 96%);
}

/* Photo-led feature band ---------------------------------------------------- */

.photoband {
  max-width: none;
  background: var(--readout);
  border-block: 1px solid var(--rule);
  padding-block: 0;
  overflow: hidden;
}

.photoband__grid {
  max-width: var(--shell-max);
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 0;
  align-items: stretch;
}
@media (min-width: 900px) { .photoband__grid { grid-template-columns: 1fr 1fr; } }

.photoband__media {
  position: relative;
  min-height: 320px;
  overflow: hidden;
}
.photoband__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.2s var(--ease);
}
.photoband:hover .photoband__media img { transform: scale(1.04); }

.photoband__body {
  padding: 3.25rem 1.5rem;
  display: grid;
  align-content: center;
  gap: .9rem;
}
@media (min-width: 900px) { .photoband__body { padding: 4rem 3rem; } }
@media (min-width: 900px) { .photoband--flip .photoband__media { order: 2; } }

.photoband__list { display: grid; gap: .7rem; margin-top: .4rem; }
.photoband__item {
  display: flex;
  gap: .6rem;
  align-items: flex-start;
  font-size: var(--step--1);
  color: var(--ink-2);
  line-height: 1.55;
}
.photoband__item svg, .photoband__item i { flex: none; margin-top: .2em; color: var(--live); }

@media (prefers-reduced-motion: reduce) {
  .herowrap__bg { animation: none; transform: scale(1.05); }
  .photoband:hover .photoband__media img { transform: none; }
}

/* Hero trust points — objection handling placed directly under the CTA, which
   is where the hesitation actually happens. */
.herotrust {
  list-style: none;
  margin: 1.5rem 0 0;
  padding: 0;
  display: grid;
  gap: .5rem;
}
.herotrust li {
  display: flex;
  align-items: flex-start;
  gap: .55rem;
  font-size: var(--step--1);
  color: var(--ink-2);
  line-height: 1.5;
}
.herotrust svg, .herotrust i { flex: none; margin-top: .18em; color: var(--live); }

/* The one emphasised word in the headline. Not italic-for-decoration — it
   carries the whole promise, so it gets the accent. */
.hero__title em {
  font-style: normal;
  color: var(--accent);
}

/* Migration band — replaces a second "See plans" call that sat directly under
   the plans grid and asked for the click the grid had just asked for. */
.ctaband--offer {
  display: grid;
  gap: 1.25rem;
  align-items: center;
}
@media (min-width: 860px) {
  .ctaband--offer { grid-template-columns: 1.2fr auto; text-align: left; }
}
.ctaband--offer .ctaband__points {
  list-style: none;
  margin: .9rem 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.5rem;
}
.ctaband--offer .ctaband__points li {
  display: flex;
  align-items: center;
  gap: .45rem;
  font-size: var(--step--1);
  color: var(--ink-2);
}
.ctaband--offer .ctaband__points svg, .ctaband--offer .ctaband__points i { color: var(--live); flex: none; }

/* ==========================================================================
   Checkout
   --------------------------------------------------------------------------
   Wide, two-column, with the summary pinned. The order form previously ran in
   the auth layout's 400px column, so a five-section form became a long scroll
   with the price and the pay button both below the fold.
   ========================================================================== */

.checkout {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
  background: var(--chassis);
}

.checkout__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: .9rem 1.5rem;
  border-bottom: 1px solid var(--rule);
  background: var(--readout);
}
.checkout__secure {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  font-size: var(--step--2);
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.checkout__secure i { color: var(--live); }

.checkout__main {
  flex: 1;
  width: 100%;
  max-width: 1080px;
  margin-inline: auto;
  padding: 2rem 1.5rem 3rem;
}
@media (max-width: 720px) { .checkout__main { padding: 1.25rem 1rem 2rem; } }

.checkout__foot {
  border-top: 1px solid var(--rule);
  padding: 1.25rem 1.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.5rem;
  justify-content: space-between;
  font-size: var(--step--2);
  color: var(--ink-3);
}
.checkout__foot i { color: var(--live); margin-right: .3rem; }

/* Two columns from 900px: form, then a summary that follows the scroll. */
.checkout__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
}
@media (min-width: 900px) {
  .checkout__grid { grid-template-columns: minmax(0, 1fr) 340px; gap: 2rem; }
  .checkout__aside { position: sticky; top: 1.5rem; }
}

/* Billing cycle cards -------------------------------------------------------
   The price is what is being compared, so it carries the weight: large, dark,
   on its own line. The cycle name is a quiet label above it, and the per-month
   equivalent below makes four unrelated totals comparable at a glance.

   The previous version put the name and total side by side, which made
   "Every 3 months" wrap onto two lines and left the prices ragged. */

.cycles {
  display: grid;
  gap: .625rem;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
}

.cycle {
  position: relative;
  display: block;
  padding: .85rem .9rem 0.9rem;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  background: var(--readout);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur) var(--ease), background var(--dur) var(--ease);
}
.cycle:hover { border-color: var(--accent-ring); transform: translateY(-1px); }
.cycle input { position: absolute; opacity: 0; pointer-events: none; }

.cycle:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 0 3px var(--accent-ring);
}
/* Keyboard users get the same affordance as a focused radio normally would. */
.cycle:has(input:focus-visible) { box-shadow: 0 0 0 3px var(--accent-ring); }

/* Selection tick — only drawn once chosen, so the resting card stays clean. */
.cycle__tick {
  position: absolute;
  top: .6rem;
  right: .6rem;
  width: 17px;
  height: 17px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--accent);
  color: var(--ink-on-accent);
  font-size: 9px;
  opacity: 0;
  transform: scale(.6);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.cycle:has(input:checked) .cycle__tick { opacity: 1; transform: scale(1); }

.cycle__name {
  display: block;
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: .3rem;
  padding-right: 1.2rem;   /* clear of the tick */
}
.cycle:has(input:checked) .cycle__name { color: var(--accent); }

.cycle__price {
  display: block;
  font-family: var(--mono);
  font-size: var(--step-1);
  font-weight: 700;
  letter-spacing: -.02em;
  line-height: 1.15;
  color: var(--ink);
}

.cycle__unit {
  display: block;
  margin-top: .15rem;
  font-size: var(--step--2);
  color: var(--ink-3);
}

.cycle__save {
  display: inline-block;
  margin-top: .5rem;
  padding: .12rem .4rem;
  border-radius: var(--r-pill);
  background: var(--live-soft);
  color: var(--live);
  font-size: var(--step--2);
  font-weight: 650;
  letter-spacing: .01em;
}

@media (prefers-reduced-motion: reduce) {
  .cycle:hover { transform: none; }
  .cycle__tick { transition: none; }
}

/* Order summary ----------------------------------------------------------- */

.summary__art {
  display: block;
  width: 84px;
  height: 84px;
  margin: 0 auto .75rem;
  object-fit: contain;
}
.summary__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: .45rem 0;
  font-size: var(--step--1);
}
.summary__row + .summary__row { border-top: 1px solid var(--rule-soft); }
.summary__row dt { color: var(--ink-2); }
.summary__row dd { font-family: var(--mono); margin: 0; }
.summary__total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin-top: .6rem;
  padding-top: .75rem;
  border-top: 2px solid var(--rule);
  font-weight: 700;
}
.summary__total span:last-child { font-family: var(--mono); font-size: var(--step-1); }

.summary__points { list-style: none; margin: .9rem 0 0; padding: 0; display: grid; gap: .4rem; }
.summary__points li {
  display: flex;
  gap: .45rem;
  align-items: flex-start;
  font-size: var(--step--2);
  color: var(--ink-2);
}
.summary__points i { color: var(--live); margin-top: .15em; }

/* Hero offer card — replaces the latency gauge in the hero's selling slot.
   The gauge still exists, further down, where it argues rather than sells. */
.offercard {
  position: relative;
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  background: var(--readout);
  box-shadow: var(--lift-3);
  overflow: hidden;
}

.offercard__img {
  display: block;
  width: 100%;
  height: 190px;
  object-fit: cover;
}

.offercard__flag {
  position: absolute;
  top: .85rem;
  left: .85rem;
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .32rem .6rem;
  border-radius: var(--r-pill);
  background: rgba(6, 10, 22, .72);
  backdrop-filter: blur(6px);
  color: #fff;
  font-size: var(--step--2);
  font-weight: 640;
  letter-spacing: .02em;
}
.offercard__flag i { color: #FFD24A; }

.offercard__body { padding: 1.25rem 1.35rem 1.4rem; }

.offercard__price {
  font-size: var(--step-3);
  font-weight: 700;
  letter-spacing: -.03em;
  line-height: 1.05;
  margin-top: .15rem;
}
.offercard__price small {
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: 0;
}

.offercard__list {
  list-style: none;
  margin: 1rem 0 1.15rem;
  padding: 0;
  display: grid;
  gap: .5rem;
}
.offercard__list li {
  display: flex;
  align-items: flex-start;
  gap: .55rem;
  font-size: var(--step--1);
  color: var(--ink-2);
  line-height: 1.45;
}
.offercard__list i { color: var(--accent); margin-top: .12em; }

.offercard__note {
  margin-top: .6rem;
  text-align: center;
  font-size: var(--step--2);
  color: var(--ink-3);
}

/* ==========================================================================
   Support tickets
   --------------------------------------------------------------------------
   One vertical timeline carrying three different kinds of entry — customer
   replies, staff replies and system events — because that is what a support
   thread IS. Distinguishing them by alignment alone fails the moment a staff
   member reads the thread on a phone, so each carries a different edge colour
   and an explicit author line.
   ========================================================================== */

.timeline { position: relative; display: grid; gap: 1rem; }
.timeline::before {
  content: "";
  position: absolute;
  left: 15px;
  top: .5rem;
  bottom: .5rem;
  width: 2px;
  background: var(--rule);
}

.tmsg { position: relative; padding-left: 2.75rem; }

.tmsg__dot {
  position: absolute;
  left: 6px;
  top: .55rem;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: var(--readout);
  border: 2px solid var(--rule);
  font-size: 9px;
  color: var(--ink-3);
}
.tmsg--staff .tmsg__dot    { border-color: var(--accent); color: var(--accent); }
.tmsg--customer .tmsg__dot { border-color: var(--live);   color: var(--live); }
.tmsg--note .tmsg__dot     { border-color: var(--warn);   color: var(--warn); }

.tmsg__card {
  border: 1px solid var(--rule);
  border-radius: var(--r);
  background: var(--readout);
  overflow: hidden;
}
.tmsg--staff .tmsg__card    { border-left: 3px solid var(--accent); }
.tmsg--customer .tmsg__card { border-left: 3px solid var(--live); }
/* An internal note must be unmistakable — writing one into the customer-facing
   box by accident is the mistake this styling exists to prevent. */
.tmsg--note .tmsg__card {
  border-left: 3px solid var(--warn);
  background: var(--warn-soft);
  border-style: dashed;
}

.tmsg__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .5rem;
  padding: .6rem .9rem;
  border-bottom: 1px solid var(--rule-soft);
  font-size: var(--step--2);
  color: var(--ink-3);
}
.tmsg__who { font-weight: 650; color: var(--ink); font-size: var(--step--1); }
.tmsg__body { padding: .85rem .9rem; line-height: 1.65; white-space: pre-wrap; overflow-wrap: break-word; }

/* Events are one quiet line, not a card — they are context, not conversation. */
.tmsg--event { padding-left: 2.75rem; }
.tmsg--event .tmsg__card {
  border: 0; background: transparent; border-left: 0;
}
.tmsg--event .tmsg__body {
  padding: .15rem 0;
  font-size: var(--step--2);
  color: var(--ink-3);
  white-space: normal;
}

.ticketmeta { display: grid; gap: .55rem; }
.ticketmeta__row { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.ticketmeta__row dt { font-size: var(--step--2); color: var(--ink-3); }
.ticketmeta__row dd { margin: 0; font-size: var(--step--1); text-align: right; }

/* Domain search --------------------------------------------------------- */

.domainsearch { display: flex; gap: .5rem; flex-wrap: wrap; }
.domainsearch .input { flex: 1 1 240px; font-family: var(--mono); }

.domainresult {
  display: flex;
  align-items: flex-start;
  gap: .75rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  background: var(--readout);
}
.domainresult i { font-size: 20px; margin-top: .1rem; }
.domainresult--available { border-color: var(--live); background: var(--live-soft); }
.domainresult--available i { color: var(--live); }
.domainresult--taken i { color: var(--fault); }
.domainresult--unknown i, .domainresult--invalid i { color: var(--warn); }
.domainresult__name { font-family: var(--mono); font-weight: 700; font-size: var(--step-0); }

/* The price under a search result. Bigger than the surrounding text because it
   is the number the visitor is deciding on — the page used to compute it and
   never show it at all. */
.domainresult__price { margin: 0; font-size: var(--step-0); color: var(--ink); }
.domainresult__price strong { font-weight: 680; letter-spacing: -.01em; }

.domainsuggest { display: grid; gap: .5rem; margin-top: .75rem; }
.domainsuggest__item {
  display: flex; align-items: center; justify-content: space-between; gap: 1rem;
  padding: .6rem .85rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--readout);
  font-family: var(--mono);
  font-size: var(--step--1);
}
.domainsuggest__item strong { color: var(--live); font-family: var(--sans); font-size: var(--step--2); }

/* ==========================================================================
   Font Awesome sizing
   --------------------------------------------------------------------------
   The Icon helper writes an inline font-size, derived from the box size each
   call site was written for. That inline style outranks any stylesheet rule,
   which is why the per-component overrides above carry !important — it is the
   only way for a component to have the final say over an inline style, and the
   alternative was editing ~400 call sites.

   These are the remaining places where an icon appears inside a control that
   sets its own rhythm.
   ========================================================================== */

/* Panel headings, alerts and empty states: keep the glyph subordinate to the
   text it labels rather than competing with it. */
.panel__head i     { font-size: 13px !important; color: var(--ink-3); }
.alert__icon i     { font-size: 14px !important; }
.empty__icon i     { font-size: 20px !important; }
.pagehead__actions .btn i { font-size: 13px !important; }

/* Inline icons inside body copy inherit the surrounding size, which is what
   keeps a checklist looking typeset rather than assembled. */
.summary__points i,
.offercard__list i,
.field__error i    { font-size: .95em !important; width: auto; }

/* Table row actions and badges. */
.table i           { font-size: 12px !important; }
.badge i           { font-size: .9em !important; width: auto; }

/* The topbar search and notification controls. */
.search i          { font-size: 13px !important; }
.iconbtn i         { font-size: 14px !important; }

/* Colour field — swatch and hex input as one control. */
.colorfield {
  display: flex;
  align-items: stretch;
  gap: 0;
  width: 100%;
}
.colorfield .input { border-radius: 0 var(--r-sm) var(--r-sm) 0; flex: 1; min-width: 0; }

.colorfield__swatch {
  flex: none;
  width: 42px;
  padding: 3px;
  border: 1px solid var(--rule);
  border-right: 0;
  border-radius: var(--r-sm) 0 0 var(--r-sm);
  background: var(--readout);
  cursor: pointer;
  /* Browsers give type=color a chunky default box; strip it back so the
     swatch reads as part of the input rather than an OS control. */
  -webkit-appearance: none;
  appearance: none;
}
.colorfield__swatch::-webkit-color-swatch-wrapper { padding: 0; }
.colorfield__swatch::-webkit-color-swatch { border: 0; border-radius: 3px; }
.colorfield__swatch::-moz-color-swatch { border: 0; border-radius: 3px; }
.colorfield:focus-within .colorfield__swatch { border-color: var(--accent); }

/* ==========================================================================
   Busy indication
   --------------------------------------------------------------------------
   Two layers, because they answer different questions.

   The PROGRESS BAR answers "did my click register?" — it appears within a
   frame of the click, before the server has done anything. That matters most
   on the slowest actions (a WHOIS lookup, a WHM call) where the honest answer
   is "we are waiting on someone else".

   The BUTTON spinner answers "which thing is working?" and doubles as a
   double-submit guard.
   ========================================================================== */

.progressbar {
  position: fixed;
  inset: 0 0 auto 0;
  height: 3px;
  z-index: 9999;
  pointer-events: none;
  opacity: 0;
  transition: opacity .25s var(--ease);
}
.progressbar.is-active { opacity: 1; }

.progressbar__fill {
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, var(--accent), color-mix(in srgb, var(--accent) 45%, #fff));
  box-shadow: 0 0 8px color-mix(in srgb, var(--accent) 60%, transparent);
  border-radius: 0 3px 3px 0;
  /* Eased rather than linear: a bar that creeps toward the end without ever
     reaching it reads as "still working" instead of "stuck at 100%". */
  transition: width .35s cubic-bezier(.22, .8, .3, 1);
}

/* The indeterminate shimmer, used once the bar has crept as far as honesty
   allows and we still have no answer. */
.progressbar.is-waiting .progressbar__fill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .55), transparent);
  animation: progressShimmer 1.1s linear infinite;
}
.progressbar__fill { position: relative; overflow: hidden; }

@keyframes progressShimmer {
  from { transform: translateX(-100%); }
  to   { transform: translateX(300%); }
}

/* Buttons ---------------------------------------------------------------- */

/* Keep the button's width while its label is hidden, or the layout jumps as
   soon as anything is clicked. */
.btn.is-busy { min-width: var(--busy-w, auto); }

/* An anchor styled as a button (e.g. "See plans") that starts a navigation. */
.btn.is-navigating { opacity: .72; pointer-events: none; }

/* Inputs and selects are locked while their form is in flight, so a second
   change cannot race the request already carrying the first. */
form.is-submitting .input,
form.is-submitting .select,
form.is-submitting .textarea { opacity: .75; }

@media (prefers-reduced-motion: reduce) {
  .progressbar__fill { transition: none; }
  .progressbar.is-waiting .progressbar__fill::after { animation: none; }
}

/* Labelled busy state — keeps the button readable while it works. */
.btn.is-busy-labelled {
  pointer-events: none;
  opacity: .9;
}
.btn.is-busy-labelled .fa-spin { animation: fa-spin 1s linear infinite; }

/* ==========================================================================
   Pricing comparison (/plans)
   --------------------------------------------------------------------------
   Cards sit side by side because pricing is a comparison task. The previous
   version stacked them inside the auth layout's 400px column, so no two plans
   were ever on screen together.
   ========================================================================== */

.cycletoggle {
  /* flex + fit-content, not inline-flex: margin-inline:auto cannot centre an
     inline-level box, so the toggle sat hard against the left edge. */
  display: flex;
  width: fit-content;
  flex-wrap: wrap;
  gap: .25rem;
  padding: .3rem;
  margin: 0 auto 2rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background: var(--readout);
}

.cycletoggle__btn {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .5rem 1rem;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--step--1);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.cycletoggle__btn:hover { color: var(--ink); }
.cycletoggle__btn.is-active {
  background: var(--accent);
  color: var(--ink-on-accent);
  box-shadow: var(--lift-1);
}

.cycletoggle__save {
  padding: .1rem .35rem;
  border-radius: var(--r-pill);
  background: var(--live-soft);
  color: var(--live);
  font-size: var(--step--2);
  font-weight: 650;
}
.cycletoggle__btn.is-active .cycletoggle__save {
  background: rgba(255, 255, 255, .22);
  color: inherit;
}

/* Cards ------------------------------------------------------------------ */

.pricing {
  display: grid;
  gap: 1.25rem;
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
  max-width: var(--shell-max);
  margin-inline: auto;
  align-items: start;
}

.pricecard {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 1.5rem 1.35rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  background: var(--readout);
  transition: border-color var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}
.pricecard:hover { transform: translateY(-3px); box-shadow: var(--lift-2); }

/* The featured plan is lifted rather than merely recoloured — the difference
   has to survive a glance from across the page. */
.pricecard--featured {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent), var(--lift-3);
}
@media (min-width: 900px) {
  .pricecard--featured { transform: scale(1.025); }
  .pricecard--featured:hover { transform: scale(1.025) translateY(-3px); }
}

.pricecard__ribbon {
  position: absolute;
  top: -11px;
  left: 50%;
  transform: translateX(-50%);
  padding: .22rem .7rem;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--ink-on-accent);
  font-size: var(--step--2);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
}

.pricecard__head { margin-bottom: 1rem; }
.pricecard__name {
  font-size: var(--step-1);
  font-weight: 660;
  letter-spacing: -.02em;
}
.pricecard__blurb {
  margin-top: .25rem;
  font-size: var(--step--1);
  color: var(--ink-3);
  line-height: 1.5;
}

/* Only the selected cycle's block is shown; the rest stay in the DOM. */
.pricecard__price { display: none; }
.pricecard__price.is-shown { display: block; }

.pricecard__was {
  display: inline-block;
  font-size: var(--step--1);
  color: var(--ink-3);
  text-decoration: line-through;
  text-decoration-color: var(--fault);
  margin-bottom: .1rem;
}

.pricecard__amount {
  font-family: var(--mono);
  font-size: var(--step-3);
  font-weight: 700;
  letter-spacing: -.035em;
  line-height: 1.05;
  color: var(--ink);
}
.pricecard__per {
  font-family: var(--sans);
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--ink-3);
  letter-spacing: 0;
}
.pricecard__note {
  margin-top: .3rem;
  font-size: var(--step--2);
  color: var(--ink-3);
}

.pricecard__cta { margin: 1.15rem 0 1.25rem; }

.pricecard__specs {
  display: grid;
  gap: 0;
  margin: 0 0 1.15rem;
  border-top: 1px solid var(--rule);
}
.pricecard__spec {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  padding: .5rem 0;
  border-bottom: 1px solid var(--rule-soft);
  font-size: var(--step--1);
}
.pricecard__spec dt { color: var(--ink-2); }
.pricecard__spec dd { margin: 0; font-family: var(--mono); font-weight: 600; }

.pricecard__grouplabel {
  margin: 0 0 .45rem;
  font-size: var(--step--2);
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.pricecard__grouplabel:not(:first-of-type) { margin-top: 1rem; }

.pricecard__features { list-style: none; margin: 0; padding: 0; display: grid; gap: .4rem; }
.pricecard__features li {
  display: flex;
  align-items: flex-start;
  gap: .5rem;
  font-size: var(--step--1);
  color: var(--ink-2);
  line-height: 1.45;
}
.pricecard__features i { color: var(--live); margin-top: .18em; font-size: 11px !important; }

/* A missing feature is shown, not hidden. Dimmed so the eye skips it while
   scanning, but present so the trade-off between plans is legible. */
.pricecard__features li.is-missing { color: var(--ink-3); }
.pricecard__features li.is-missing i { color: var(--rule); }

.pricecard__setup {
  margin-top: 1rem;
  padding-top: .75rem;
  border-top: 1px solid var(--rule-soft);
  font-size: var(--step--2);
  color: var(--ink-3);
}

@media (prefers-reduced-motion: reduce) {
  .pricecard:hover { transform: none; }
  .pricecard--featured:hover { transform: none; }
}

/* ==========================================================================
   Article layout
   --------------------------------------------------------------------------
   A measured reading column with a sticky rail beside it, so advertising and
   related reading sit where they are seen without interrupting the text.

   The rail collapses entirely when empty — an empty column beside an article
   just makes the body look off-centre.
   ========================================================================== */

.articlewrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 2rem;
  max-width: var(--shell-max);
  margin-inline: auto;
  padding: 2.5rem 1.5rem 3rem;
  align-items: start;
}
@media (min-width: 1000px) {
  .articlewrap { grid-template-columns: minmax(0, 1fr) 320px; gap: 3rem; }
}
@media (max-width: 720px) { .articlewrap { padding: 1.5rem 1rem 2rem; } }

/* No rail: collapse to ONE column and centre at a comfortable measure.
   Setting max-width alone was a bug — the 320px rail column stayed in the
   template, so inside a 760px wrapper the text was squeezed into ~390px with
   an empty gap beside it. */
.articlewrap--solo {
  max-width: 760px;
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 1000px) {
  .articlewrap--solo { grid-template-columns: minmax(0, 1fr); }
}

.articlemain { min-width: 0; }

.articlemain__back {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  margin-bottom: 1.25rem;
  color: var(--ink-3);
}
.articlemain__back:hover { color: var(--accent); }

.articlehead { margin-bottom: 1.5rem; }

.articlehead__meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: .5rem 1rem;
  margin-top: .85rem;
  font-size: var(--step--1);
  color: var(--ink-3);
}
.articlehead__author { display: inline-flex; align-items: center; gap: .45rem; color: var(--ink-2); }

.avatar--sm { width: 26px; height: 26px; font-size: var(--step--2); }

/* The lede is the bridge from headline to body — larger than the body, lighter
   than the title, and set off with a rule rather than a box. */
.articlelede {
  margin: 1.5rem 0 0;
  padding-left: 1rem;
  border-left: 3px solid var(--accent);
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--ink-2);
}

.articlebody { margin-top: 1.75rem; }

.articlefoot {
  margin-top: 2.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
}
.sharerow { display: flex; flex-wrap: wrap; gap: .5rem; margin-top: .6rem; }

/* Rail -------------------------------------------------------------------- */

.articlerail { min-width: 0; }
@media (min-width: 1000px) {
  .articlerail__sticky { position: sticky; top: 1.5rem; display: grid; gap: 1rem; }
}
@media (max-width: 999px) {
  .articlerail__sticky { display: grid; gap: 1rem; }
}

.railcard {
  padding: 1rem 1.1rem;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  background: var(--readout);
}
.railcard__title {
  font-size: var(--step--2);
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: .75rem;
}
.railcard__text {
  font-size: var(--step--1);
  color: var(--ink-2);
  line-height: 1.55;
  margin-bottom: .9rem;
}
.railcard--cta { background: var(--accent-soft); border-color: var(--accent-ring); }
.railcard--cta .railcard__title { color: var(--accent); }

.railpost {
  display: flex;
  gap: .7rem;
  padding: .6rem 0;
  border-top: 1px solid var(--rule-soft);
  color: inherit;
  text-decoration: none;
}
.railpost:first-of-type { border-top: 0; padding-top: 0; }
.railpost img {
  width: 68px;
  height: 50px;
  flex: none;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--rule);
}
.railpost__title {
  display: block;
  font-size: var(--step--1);
  font-weight: 600;
  line-height: 1.35;
  color: var(--ink);
}
.railpost:hover .railpost__title { color: var(--accent); }
.railpost__meta {
  display: block;
  margin-top: .2rem;
  font-size: var(--step--2);
  color: var(--ink-3);
}

/* Ads inside the rail get the same card treatment as everything else, so a
   sponsored slot does not look like a rendering accident. */

/* ==========================================================================
   Blog / news feed
   --------------------------------------------------------------------------
   A lead story, a two-up grid, and a sticky rail — the shape a news site uses
   because it gives advertising somewhere legitimate to sit and keeps the page
   from looking empty when only a few posts exist.
   ========================================================================== */

.feedhead {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.25rem;
  max-width: 1560px;
  margin-inline: auto;
  padding: 2rem 1.5rem 1rem;
}
@media (max-width: 720px) { .feedhead { padding: 1.75rem 1rem 1rem; } }

.feedhead__title {
  font-size: var(--step-3);
  font-weight: 680;
  letter-spacing: -.03em;
  line-height: 1.1;
  margin-top: .35rem;
}
.feedhead__intro { color: var(--ink-2); margin-top: .35rem; max-width: 46ch; }
.feedhead__search .search { width: min(320px, 100%); }

/* Category tabs — a horizontal scroller, so a long list never wraps into a
   ragged block or pushes the feed down the page. */
.feedtabs {
  display: flex;
  gap: .375rem;
  max-width: 1560px;
  margin-inline: auto;
  padding: 0 1.5rem 1.25rem;
  overflow-x: auto;
  scrollbar-width: none;
}
.feedtabs::-webkit-scrollbar { display: none; }
@media (max-width: 720px) { .feedtabs { padding-inline: 1rem; } }

.feedtabs a {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  padding: .4rem .8rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background: var(--readout);
  color: var(--ink-2);
  font-size: var(--step--1);
  font-weight: 550;
  white-space: nowrap;
  text-decoration: none;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.feedtabs a:hover { border-color: var(--accent-ring); color: var(--accent); }
.feedtabs a.is-active { background: var(--accent); border-color: var(--accent); color: var(--ink-on-accent); }
.feedtabs a span { opacity: .65; font-size: var(--step--2); }

.feedwrap {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5rem;
  /* Wider than the rest of the site on purpose: a feed is scanned, and a
     1440px cap leaves a news grid looking half-empty on a large screen. */
  max-width: 1560px;
  margin-inline: auto;
  padding: 0 1.5rem 3rem;
  align-items: start;
}
@media (min-width: 1000px) {
  .feedwrap { grid-template-columns: minmax(0, 1fr) 320px; gap: 1.75rem; }
}
@media (max-width: 720px) { .feedwrap { padding: 0 1rem 2rem; } }

.feedmain { min-width: 0; }
.feedrail { min-width: 0; }

/* Mosaic grid ------------------------------------------------------------ *
   A dense tile grid, not a blog column. The lead spans two columns and two
   rows so the feed has a clear entry point, and everything else fills around
   it — which is what makes it read as a feed rather than a list. */

.feedgrid {
  display: grid;
  gap: .875rem;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: minmax(190px, auto);
}
@media (min-width: 700px)  { .feedgrid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1240px) { .feedgrid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* The lead tile. Copy sits over the photograph, so it reads as one object. */
.feedlead {
  grid-column: span 2;
  grid-row: span 2;
  position: relative;
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--chassis-sunk);
  min-height: 360px;
}
@media (max-width: 520px) {
  .feedlead { grid-column: span 2; grid-row: span 1; min-height: 260px; }
}

/* The tile is now two stacked layers: a media layer (image or slider) and the
   copy over it. The copy keeps pointer events so it stays clickable, but must
   not sit above the slider arrows. */
.feedlead__link { position: absolute; inset: 0; display: block; text-decoration: none; }
.feedlead__slider { position: absolute; inset: 0; aspect-ratio: auto; height: 100%; }

.feedlead img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.2s var(--ease);
}
.feedlead:hover img { transform: scale(1.05); }

/* Legibility floor. Text over an unmodified photograph is a contrast failure
   waiting for the wrong image to be uploaded. */
.feedlead__body {
  position: absolute;
  inset: auto 0 0 0;
  padding: 1.5rem 1.35rem 1.25rem;
  background: linear-gradient(180deg, transparent, rgba(6, 10, 22, .88) 62%);
  color: #fff;
  text-decoration: none;
  display: block;
  z-index: 2;
}
/* Arrows and dots sit above the copy so they remain reachable. */
.feedlead .cardslider__nav,
.feedlead .cardslider__dots,
.feedlead .cardslider__count { z-index: 3; }
/* The dots would otherwise land behind the copy gradient. */
.feedlead .cardslider__dots { bottom: auto; top: .75rem; left: auto; right: 5rem; }

.feedlead__tag {
  display: inline-block;
  margin-bottom: .5rem;
  padding: .18rem .5rem;
  border-radius: var(--r-pill);
  background: var(--accent);
  color: var(--ink-on-accent);
  font-size: var(--step--2);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
}

.feedlead__title {
  font-size: var(--step-2);
  font-weight: 680;
  letter-spacing: -.025em;
  line-height: 1.15;
  color: #fff;
}
.feedlead:hover .feedlead__title { text-decoration: underline; text-underline-offset: 3px; }

.feedlead__meta {
  margin-top: .45rem;
  font-size: var(--step--2);
  color: rgba(255, 255, 255, .72);
}

/* Tiles ------------------------------------------------------------------ */

.feedgrid .postcard {
  margin: 0;
  border-radius: var(--r);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.feedgrid .postcard__cover { aspect-ratio: 16 / 10; width: 100%; object-fit: cover; }
.feedgrid .postcard__body { padding: .75rem .85rem 1rem; gap: .4rem; }
.feedgrid .postcard__title { font-size: var(--step-0); line-height: 1.25; }

/* An in-feed advert is a tile like any other, but spans the row so it is never
   mistaken for an article at a glance. */
.feedgrid > .adslot {
  grid-column: 1 / -1;
  margin: 0;
}

/* Rail topics ------------------------------------------------------------ */

.railtopics { display: grid; gap: .1rem; }
.railtopics a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  padding: .45rem .5rem;
  border-radius: var(--r-sm);
  color: var(--ink-2);
  font-size: var(--step--1);
  text-decoration: none;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease);
}
.railtopics a:hover { background: var(--accent-soft); color: var(--accent); }
.railtopics a span {
  font-family: var(--mono);
  font-size: var(--step--2);
  color: var(--ink-3);
}

@media (prefers-reduced-motion: reduce) {
  .leadstory:hover { transform: none; }
  .leadstory:hover .leadstory__media img { transform: none; }
}

/* ==========================================================================
   Advertising slots
   --------------------------------------------------------------------------
   An advertiser's banner is a finished design. The frame therefore does three
   things and nothing else: disclose, provide a click target, and keep a row
   from going ragged. It never crops the creative and never restates its copy.

   Visually it follows the rack vocabulary used elsewhere — a hairline bay with
   a mono slot label. Mono is this site's convention for machine-assigned
   values rather than human language, which is the correct signal for content
   the publication did not write.
   ========================================================================== */

.adslot { margin-block: 2rem; }

.adslot__rail {
  display: grid;
  gap: .75rem;
  /* Never more tracks than there are units, or a lone advert stretches across
     a three-column grid. */
  grid-template-columns: repeat(min(var(--ad-count), 3), minmax(0, 1fr));
  /* Top-aligned: creatives of different heights hang from a common line
     instead of floating in centred dead space. */
  align-items: start;
}
@media (max-width: 900px) {
  .adslot__rail { grid-template-columns: repeat(min(var(--ad-count), 2), minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .adslot__rail { grid-template-columns: minmax(0, 1fr); }
}

/*
 | The shell is opt-in, not the default.
 |
 | Most slots sit inside something that already supplies the page gutter — the
 | blog feed grid, an article rail. Applying the shell everywhere inset those
 | by a further 24px, so an in-feed advert sat visibly narrower than the
 | article cards above and below it. Only the placements that sit directly on
 | the page need it.
 */
.adslot--landing_hero .adslot__rail,
.adslot--landing_mid .adslot__rail,
.adslot--landing_footer .adslot__rail,
.adslot--storefront_top .adslot__rail {
  max-width: var(--shell-max);
  margin-inline: auto;
  padding-inline: 1.5rem;
}
@media (max-width: 620px) {
  .adslot--landing_hero .adslot__rail,
  .adslot--landing_mid .adslot__rail,
  .adslot--landing_footer .adslot__rail,
  .adslot--storefront_top .adslot__rail { padding-inline: 1rem; }
}

/* A rail is one column by definition, whatever the advert count says. */
.articlerail .adslot,
.feedrail .adslot { margin-block: 0; }
.articlerail .adslot__rail,
.feedrail .adslot__rail { grid-template-columns: minmax(0, 1fr); }

.adunit { min-width: 0; }

.adunit__label {
  display: flex;
  align-items: center;
  gap: .375rem;
  margin-bottom: .35rem;
  font-family: var(--mono);
  font-size: var(--step--2);
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* A dim indicator, not a warning — the same vocabulary as the rack LEDs, at
   the lowest possible emphasis. */
.adunit__led {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--ink-3);
  opacity: .5;
  flex: none;
}

.adunit__frame {
  display: block;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  /*
   | Sunk, because refusing to crop means a creative whose ratio hits the
   | height cap is letterboxed. Against the page ground that empty space reads
   | as a rendering gap; against the sunk chassis it reads as a mount, which is
   | the vocabulary the rest of the site already uses for a unit in a bay.
   */
  background: var(--chassis-sunk);
  overflow: hidden;
}

/* The creative, whole. Width leads; height is capped rather than cropped, so
   an unexpected ratio is scaled down instead of losing half its design. */
.adunit__frame img {
  display: block;
  width: 100%;
  height: auto;
  max-height: 340px;
  object-fit: contain;
  object-position: center;
}
.articlerail .adunit__frame img,
.feedrail .adunit__frame img { max-height: 480px; }

.adunit__frame--html { padding: .5rem; background: var(--readout); }
.adunit__frame--html img { max-width: 100%; height: auto; }

/* Text adverts: no creative exists, so our typography carries the message. */
.adunit__frame--text {
  background: var(--readout);
  padding: 1.1rem 1.15rem 1.2rem;
  display: flex;
  flex-direction: column;
  gap: .45rem;
  min-height: 150px;
}
.adunit__title {
  font-size: var(--step-0);
  font-weight: 650;
  line-height: 1.3;
  letter-spacing: -.01em;
}
.adunit__body {
  font-size: var(--step--1);
  line-height: 1.55;
  opacity: .85;
}
.adunit__cta {
  display: inline-flex;
  align-items: center;
  gap: .35rem;
  margin-top: auto;
  align-self: flex-start;
  padding-top: .4rem;
  font-size: var(--step--1);
  font-weight: 620;
  color: inherit;
  text-decoration: none;
  border-bottom: 1.5px solid currentColor;
}
.adunit__cta:hover { gap: .55rem; }
.adunit__cta i { transition: transform var(--dur) var(--ease); }

/*
 | No hover lift and no shadow, deliberately. An advert that animates beside an
 | article competes with the article. The whole creative is already the click
 | target; drawing further attention to it is the advertiser's job, not the
 | frame's.
 */

@media (prefers-reduced-motion: reduce) {
  .adunit__cta:hover { gap: .35rem; }
}

/* Gallery manager (admin) ------------------------------------------------ */

.gallerylist { display: grid; gap: .5rem; margin-bottom: .625rem; }

.galleryitem {
  display: grid;
  grid-template-columns: 56px minmax(0, 1fr) auto;
  gap: .5rem;
  align-items: center;
  padding: .45rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--readout);
}
.galleryitem img {
  width: 56px;
  height: 42px;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--rule);
}
.galleryitem__fields { display: grid; gap: .3rem; min-width: 0; }
.galleryitem__tools { display: flex; flex-direction: column; gap: .1rem; }

.input--sm { padding: .3rem .5rem; font-size: var(--step--2); }

/* Card slider ------------------------------------------------------------ */

.cardslider {
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 10;
  background: var(--chassis-sunk);
}

.cardslider__track {
  display: flex;
  height: 100%;
  transition: transform .45s var(--ease);
  /* Scroll-snap is the no-JS fallback: the strip stays swipeable even if the
     arrows never get wired up. */
  scroll-snap-type: x mandatory;
}
.cardslider__slide { flex: 0 0 100%; scroll-snap-align: start; display: block; }
.cardslider__slide img { width: 100%; height: 100%; object-fit: cover; display: block; }

.cardslider__nav {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  width: 30px;
  height: 30px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: rgba(6, 10, 22, .55);
  color: #fff;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur) var(--ease), background var(--dur) var(--ease);
}
.cardslider__nav i { font-size: 12px !important; }
.cardslider__nav--prev { left: .5rem; }
.cardslider__nav--next { right: .5rem; }
.cardslider:hover .cardslider__nav,
.cardslider__nav:focus-visible { opacity: 1; }
.cardslider__nav:hover { background: rgba(6, 10, 22, .8); }

/* Always visible on touch, where there is no hover to reveal them. */
@media (hover: none) { .cardslider__nav { opacity: .85; } }

.cardslider__dots {
  position: absolute;
  left: 0; right: 0; bottom: .5rem;
  display: flex;
  justify-content: center;
  gap: .3rem;
}
.cardslider__dots span {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: rgba(255, 255, 255, .5);
  transition: background var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.cardslider__dots span.is-active { background: #fff; transform: scale(1.3); }

.cardslider__count {
  position: absolute;
  top: .5rem;
  right: .5rem;
  padding: .12rem .4rem;
  border-radius: var(--r-pill);
  background: rgba(6, 10, 22, .6);
  color: #fff;
  font-size: var(--step--2);
  font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
  .cardslider__track { transition: none; }
}

/* Article slider --------------------------------------------------------- */

.articleslider {
  margin: 1.75rem 0 0;
  border-radius: var(--r-lg);
  border: 1px solid var(--rule);
  aspect-ratio: 16 / 9;
  max-height: 460px;
}
.articleslider .cardslider__slide { display: block; }
.articleslider .cardslider__slide img { width: 100%; height: 100%; object-fit: cover; }
/* Bigger hit targets than the listing: this is the primary image, and the
   reader is deliberately looking through it rather than glancing. */
.articleslider .cardslider__nav { width: 38px; height: 38px; opacity: 1; }
.articleslider .cardslider__nav i { font-size: 14px !important; }

.articleslider__captions { position: relative; margin: .6rem 0 0; min-height: 1.4em; }
.articleslider__captions p {
  display: none;
  font-size: var(--step--1);
  color: var(--ink-3);
  line-height: 1.5;
}
.articleslider__captions p.is-shown { display: block; }

@media (max-width: 720px) {
  .articleslider { aspect-ratio: 4 / 3; max-height: 300px; }
}


/* ==========================================================================
   PROVISIONING PROGRESS
   ==========================================================================
   A card the customer watches while a control panel does something slow.

   The design problem: the honest state for most of a provisioning run is "a
   remote machine is working and has not answered yet". A bar that creeps
   forward on a timer would fill that silence with a lie, and the moment the
   panel stalls the lie becomes obvious — the bar sails past 90% while nothing
   is happening.

   So the two kinds of motion here mean different things, and neither one
   invents progress:

     WIDTH changes only when a real milestone lands. It is driven entirely by
     the server, and it eases rather than jumps so the eye can follow where it
     went.

     The SWEEP travels continuously across the filled portion. It is not
     progress — it is a pulse, and it says "this is still alive". A stalled
     task keeps sweeping at a bar that has stopped growing, which is precisely
     the truth we want to communicate.

   Everything stops under prefers-reduced-motion; the readouts stay.
   ========================================================================== */

.provision-stack {
  display: grid;
  gap: .75rem;
  margin-bottom: 1.25rem;
}

.provision {
  --tone: var(--accent);
  --p: 0%;
  position: relative;
  padding: 1rem 1.125rem 1.125rem;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  overflow: hidden;
  animation: unitIn var(--dur-slow) var(--ease-out) backwards;
}

.provision--live  { --tone: var(--live); }
.provision--fault { --tone: var(--fault); }

/* A hairline in the tone across the top edge — the rack-unit cue used
   elsewhere, so the card reads as part of the same instrument. */
.provision::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--tone);
  opacity: .9;
}

.provision__head {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin-bottom: .875rem;
}

.provision__led { flex: none; }
.provision__title { flex: 1 1 auto; min-width: 0; }

.provision__domain {
  font-size: var(--step-1);
  font-weight: 600;
  letter-spacing: -.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Tabular figures: a percentage that changes must not shift the layout
   under it every time a digit gets wider. */
.provision__pct {
  flex: none;
  font-size: var(--step-2);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--tone);
}

/* The track ------------------------------------------------------------- */

.provision__track {
  position: relative;
  height: 10px;
  border-radius: var(--r-pill);
  background: var(--chassis-sunk);
  box-shadow: var(--inset);
  overflow: hidden;
}

/* Quarter ticks, same instrument vocabulary as .meter--ticked. */
.provision__track::after {
  content: "";
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(
    to right,
    transparent 0 calc(25% - 1px),
    color-mix(in srgb, var(--ink) 18%, transparent) calc(25% - 1px) 25%
  );
  pointer-events: none;
}

.provision__fill {
  position: relative;
  height: 100%;
  width: var(--p);
  border-radius: var(--r-pill);
  background: linear-gradient(90deg,
              color-mix(in srgb, var(--tone) 78%, transparent),
              var(--tone));
  box-shadow: 0 0 10px color-mix(in srgb, var(--tone) 55%, transparent);
  /* Long and heavily eased: a milestone can be a 30% jump, and easing it over
     most of a second is what makes that read as travel rather than a cut. */
  transition: width .9s cubic-bezier(.22, .8, .3, 1);
  overflow: hidden;
}

/* The pulse. Runs on the FILLED portion, so it is visibly a sign of life
   rather than a claim about the part not yet done. */
.provision[data-status="running"] .provision__fill::after,
.provision[data-status="queued"]  .provision__fill::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
              transparent,
              color-mix(in srgb, #fff 60%, transparent),
              transparent);
  animation: provisionSweep 1.6s cubic-bezier(.45, 0, .55, 1) infinite;
}

@keyframes provisionSweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(220%); }
}

/* Queued but not started: no fill to sweep yet, so the whole track breathes
   instead. Without this the card sits completely dead while the job waits for
   a worker, which reads as broken. */
.provision[data-status="queued"] .provision__track {
  animation: provisionBreathe 2.2s ease-in-out infinite;
}

@keyframes provisionBreathe {
  0%, 100% { box-shadow: var(--inset); }
  50%      { box-shadow: var(--inset), 0 0 0 3px color-mix(in srgb, var(--tone) 12%, transparent); }
}

/* Steps ----------------------------------------------------------------- */

.provision__steps {
  display: flex;
  flex-wrap: wrap;
  gap: .375rem 1rem;
  margin: .875rem 0 0;
  padding: 0;
  list-style: none;
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-3);
}

.provision__step {
  display: inline-flex;
  align-items: center;
  gap: .375rem;
  transition: color var(--dur) var(--ease);
}

.provision__dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  opacity: .38;
  flex: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease-out);
}

.provision__step.is-done { color: var(--live); }
.provision__step.is-done .provision__dot { opacity: 1; }

.provision__step.is-current {
  color: var(--tone);
  font-weight: 600;
}
.provision__step.is-current .provision__dot {
  opacity: 1;
  transform: scale(1.35);
  animation: provisionPing 1.4s ease-out infinite;
}

.provision__step.is-failed { color: var(--fault); }
.provision__step.is-failed .provision__dot { opacity: 1; }

/* A ring leaving the active dot — the same LED language as the rack strips,
   applied to the one step that is actually happening. */
@keyframes provisionPing {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--tone) 55%, transparent); }
  70%  { box-shadow: 0 0 0 7px transparent; }
  100% { box-shadow: 0 0 0 0 transparent; }
}

/* Only the current step's label is spelled out on narrow screens — five
   step names wrapped over three lines is noise, not information. */
@media (max-width: 40rem) {
  .provision__step:not(.is-current) .provision__steplabel { display: none; }
}

/* Readouts -------------------------------------------------------------- */

.provision__now {
  margin: .625rem 0 0;
  font-size: var(--step--1);
  color: var(--ink-2);
  min-height: 1.25em;   /* reserve the line, so text arriving never shifts the card */
}

.provision--fault .provision__now { color: var(--fault); }

.provision__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .75rem;
  flex-wrap: wrap;
  margin-top: .75rem;
  padding-top: .75rem;
  border-top: 1px solid var(--rule-soft);
}

.provision__elapsed {
  font-size: var(--step--2);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}

.provision__note {
  font-size: var(--step--2);
  color: var(--ink-3);
}

.provision__actions { display: inline-flex; align-items: center; gap: .5rem; }

/* The completion moment. One pass, then it settles — a card that keeps
   celebrating is a card nobody reads the second time. */
.provision[data-status="completed"] .provision__track {
  animation: provisionLand .7s var(--ease-out) both;
}

@keyframes provisionLand {
  0%   { box-shadow: var(--inset); }
  40%  { box-shadow: var(--inset), 0 0 0 4px color-mix(in srgb, var(--live) 26%, transparent); }
  100% { box-shadow: var(--inset), 0 0 0 0 transparent; }
}

@media (prefers-reduced-motion: reduce) {
  .provision,
  .provision__track,
  .provision__fill,
  .provision__step.is-current .provision__dot { animation: none; }
  .provision__fill { transition: none; }
  .provision[data-status="running"] .provision__fill::after,
  .provision[data-status="queued"]  .provision__fill::after { animation: none; display: none; }
}

/* ==========================================================================
   UPGRADE OPTIONS
   ==========================================================================
   A plan card that has to do something the storefront's cards never do: show
   its own arithmetic. "₦1,500 to upgrade" is not checkable by the person
   paying it, and an unexplained number is the first thing support gets asked
   about. So the two lines that produce it sit above the total, in mono,
   because they are machine-derived — the same type rule the rest of the app
   uses to mark what a person wrote from what a system computed.
   ========================================================================== */

.upgrades {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
}

.upgrade {
  display: flex;
  flex-direction: column;
  gap: .875rem;
  padding: 1.125rem;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  transition: transform var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease);
}

.upgrade:hover {
  transform: translateY(-2px);
  box-shadow: var(--lift-2);
}

/* The cheapest step up carries the accent edge. One emphasised option, not
   three — a grid where everything is highlighted highlights nothing. */
.upgrade--lead {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--rule));
  box-shadow: var(--lift-1), 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent);
}

.upgrade__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: .75rem;
}

.upgrade__name {
  margin: .125rem 0 0;
  font-size: var(--step-1);
  font-weight: 600;
  letter-spacing: -.01em;
}

.upgrade__renewal {
  text-align: right;
  font-size: var(--step-0);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.upgrade__renewal span {
  display: block;
  font-family: var(--sans);
  font-size: var(--step--2);
  font-weight: 400;
  color: var(--ink-3);
  text-transform: uppercase;
  letter-spacing: .06em;
}

.upgrade__specs {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: .375rem .75rem;
  margin: 0;
  padding: .75rem 0;
  list-style: none;
  border-block: 1px solid var(--rule-soft);
  font-size: var(--step--1);
  color: var(--ink-2);
}
.upgrade__specs .mono { font-weight: 600; color: var(--ink); }

/* The working ----------------------------------------------------------- */

.upgrade__maths {
  padding: .75rem .875rem;
  background: var(--chassis-sunk);
  border-radius: var(--r-sm);
  box-shadow: var(--inset);
  font-size: var(--step--1);
}

.upgrade__line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .75rem;
  padding: .1875rem 0;
  color: var(--ink-2);
}
.upgrade__line .mono { font-variant-numeric: tabular-nums; white-space: nowrap; }

.upgrade__line--credit { color: var(--live); }

.upgrade__line--total {
  margin-top: .375rem;
  padding-top: .5rem;
  border-top: 1px solid var(--rule);
  color: var(--ink);
  font-weight: 600;
}
.upgrade__line--total .mono {
  font-size: var(--step-1);
  color: var(--accent);
}

.upgrade form { margin-top: auto; }

@media (prefers-reduced-motion: reduce) {
  .upgrade { transition: none; }
  .upgrade:hover { transform: none; }
}

/* ==========================================================================
   ACCOUNT CREDIT
   ==========================================================================
   A balance is only trustworthy if the events behind it are visible, so the
   number and its ledger are one component rather than two panels that happen
   to sit near each other.
   ========================================================================== */

.credit-hero {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.25rem;
  padding: 1.5rem;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  box-shadow: var(--lift-1);
  position: relative;
  overflow: hidden;
}

.credit-hero::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: var(--accent);
}

.credit-hero__value {
  font-family: var(--mono);
  font-size: var(--step-4);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}

.credit-hero__meta { color: var(--ink-3); font-size: var(--step--1); margin-top: .375rem; }

/* Preset amounts ---------------------------------------------------------- */

.credit-presets {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  margin-bottom: .75rem;
}

.credit-preset {
  padding: .4375rem .75rem;
  font-family: var(--mono);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--ink-2);
  background: var(--chassis-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease), transform var(--dur) var(--ease-out);
}
.credit-preset:hover { color: var(--ink); border-color: var(--ink-3); }
.credit-preset:active { transform: scale(.97); }

.credit-preset.is-picked {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}

/* Ledger ------------------------------------------------------------------ */

.ledger__amount { font-family: var(--mono); font-variant-numeric: tabular-nums; font-weight: 600; }
.ledger__amount--in  { color: var(--live); }
.ledger__amount--out { color: var(--ink-2); }

@media (prefers-reduced-motion: reduce) {
  .credit-preset { transition: none; }
  .credit-preset:active { transform: none; }
}

/* A labelled divider between two ways of doing the same thing — used between
   "pay from credit" and "pay by card". The rule is drawn either side of the
   label rather than behind it, so no background colour has to be guessed. */
.rule-or {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin: 0 0 .875rem;
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .07em;
  color: var(--ink-3);
}
.rule-or::before,
.rule-or::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--rule);
}

/* ==========================================================================
   PAYMENT METHOD CHOOSER
   ==========================================================================
   Radio cards, borrowing the billing-cycle component's shape so the two
   consecutive choices on a checkout page read as the same kind of decision.

   The native radio is hidden but present — it is what actually carries the
   value, receives focus and is announced. The visible state is drawn by
   :checked/:focus-visible on the label, so keyboard and screen-reader
   behaviour comes free instead of being reimplemented.
   ========================================================================== */

.paymethods { display: grid; gap: .5rem; }

.paymethod {
  position: relative;
  display: flex;
  align-items: center;
  gap: .75rem;
  padding: .8125rem .875rem;
  background: var(--readout);
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}

.paymethod input {
  position: absolute;
  opacity: 0;
  width: 1px;
  height: 1px;
  pointer-events: none;
}

.paymethod:hover { border-color: var(--ink-3); }

.paymethod:has(input:checked) {
  border-color: var(--accent);
  background: var(--accent-soft);
  box-shadow: 0 0 0 1px var(--accent);
}

/* Focus lands on the hidden input, so the ring has to be drawn on the label. */
.paymethod:has(input:focus-visible) {
  outline: 2px solid var(--accent-ring);
  outline-offset: 2px;
}

/* Not enough balance: readable, obviously unavailable, and still explains why. */
.paymethod.is-short {
  cursor: not-allowed;
  opacity: .6;
  background: var(--chassis-sunk);
}
.paymethod.is-short:hover { border-color: var(--rule); }

.paymethod__tick {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border: 1px solid var(--rule);
  border-radius: 50%;
  background: var(--chassis-sunk);
  color: transparent;
  font-size: 10px;
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.paymethod:has(input:checked) .paymethod__tick {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink-on-accent);
}

.paymethod__icon { flex: none; color: var(--ink-3); display: grid; place-items: center; }
.paymethod:has(input:checked) .paymethod__icon { color: var(--accent); }

.paymethod__body { display: grid; gap: .125rem; min-width: 0; }

.paymethod__name {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  flex-wrap: wrap;
  font-weight: 600;
  font-size: var(--step-0);
}

.paymethod__balance {
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--live);
  font-variant-numeric: tabular-nums;
}

.paymethod__blurb { font-size: var(--step--1); color: var(--ink-3); }

@media (prefers-reduced-motion: reduce) {
  .paymethod, .paymethod__tick { transition: none; }
}

/* Right-hand end of the checkout bar: the way out, then the reassurance.
   On a narrow screen the "Secure checkout" line is the one that gives, since
   the padlock icon carries the same message on its own. */
.checkout__barend {
  display: flex;
  align-items: center;
  gap: 1rem;
  min-width: 0;
}

.checkout__exit {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .375rem .75rem;
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--ink-2);
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  text-decoration: none;
  white-space: nowrap;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              background var(--dur) var(--ease);
}
.checkout__exit:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: var(--accent-soft);
  text-decoration: none;
}

@media (max-width: 30rem) {
  .checkout__secure span,
  .checkout__secure { font-size: 0; gap: 0; }
  .checkout__secure i { font-size: var(--step--1); }
}

@media (prefers-reduced-motion: reduce) {
  .checkout__exit { transition: none; }
}

/* ==========================================================================
   BUY MORE
   ==========================================================================
   The one route to a second purchase, in two placements: a full-width CTA at
   the top of the navigation rail, and an inline action in a page header.
   ========================================================================== */

.buymore { display: inline-block; }
.buymore--rail { display: block; margin-bottom: 1rem; }

.buymore__caret {
  margin-left: auto;
  opacity: .7;
  transition: transform var(--dur) var(--ease);
}
.buymore.is-open .buymore__caret { transform: rotate(180deg); }

/* The rail sits at the left edge, so its panel must open rightward or it
   would be clipped by the viewport. */
.buymore--rail .menu__panel {
  right: auto;
  left: 0;
  min-width: 100%;
  transform-origin: top left;
}

.buymore__panel { min-width: 244px; }

.buymore__item {
  display: flex;
  align-items: flex-start;
  gap: .625rem;
  padding: .5rem .625rem;
}
.buymore__item strong { display: block; font-size: var(--step--1); }

.buymore__icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--r-sm);
  background: var(--accent-soft);
  color: var(--accent);
}

.buymore__note {
  display: block;
  font-size: var(--step--2);
  color: var(--ink-3);
  font-weight: 400;
}

@media (prefers-reduced-motion: reduce) {
  .buymore__caret { transition: none; }
}

/* The rail's action zone, between the brand and the navigation. Padded to the
   rail's own gutter so the button lines up with the nav items below it. */
.rail__cta { padding: 0 .75rem; }

/* ==========================================================================
   TWO-FACTOR ENROLMENT
   ==========================================================================
   Vertical and numbered, unlike .steps above, which is a horizontal marketing
   sequence. These steps are genuinely ordered and each one carries a control,
   so they need the room a column gives them.
   ========================================================================== */

.enrol {
  display: grid;
  gap: 1.75rem;
  counter-reset: enrol;
  list-style: none;
  padding: 0;
  margin: 0;
}

.enrol__step {
  position: relative;
  padding-left: 2.5rem;
}

.enrol__step::before {
  counter-increment: enrol;
  content: counter(enrol);
  position: absolute;
  top: 0;
  left: 0;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font: 700 var(--step--1)/1 var(--mono);
}

/* Hairline running down the gutter, joining the steps into one sequence. */
.enrol__step::after {
  content: "";
  position: absolute;
  top: 2.125rem;
  bottom: -1.75rem;
  left: .84375rem;
  width: 1px;
  background: var(--rule);
}
.enrol__step:last-child::after { display: none; }

.enrol__title {
  font-size: var(--step-0);
  font-weight: 620;
  margin-bottom: .375rem;
  line-height: 1.75rem;
}

.enrol__text {
  font-size: var(--step--1);
  color: var(--ink-2);
}

/* -- The QR itself --------------------------------------------------------
   White, always, in both themes. A QR code is read by a camera looking for
   dark modules on a light field; inverting it for dark mode produces a
   symbol that many scanners simply will not see. The frame is what stops it
   looking like a mistake on a dark page.
   -------------------------------------------------------------------------- */

.qrframe {
  display: inline-block;
  padding: .5rem;
  margin-top: .75rem;
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  box-shadow: var(--lift-1);
  line-height: 0;
}

.qrframe svg { display: block; }

/* -- Recovery codes -------------------------------------------------------
   Monospaced and generously spaced: these get read aloud, copied onto paper
   and typed back months later by someone who has just lost their phone.
   -------------------------------------------------------------------------- */

.codegrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: .5rem;
  margin-top: 1rem;
}

.codegrid__item {
  padding: .5rem .625rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-sm);
  background: var(--readout);
  font: 600 var(--step-0)/1 var(--mono);
  letter-spacing: .06em;
  text-align: center;
  user-select: all;
}

/* ==========================================================================
   BRAND MARK — uploaded logo vs built-in glyph
   ==========================================================================
   The default tile is painted in the brand colour, which suits the built-in
   glyph: it is a currentColor shape drawn to sit on it. An uploaded logo is a
   different object. Real logos carry their own colours and are drawn for a
   white ground, so a coloured tile behind one either fights it or hides it —
   a dark logo on a dark brand colour disappears completely, and that is a
   setting an operator can reach in two clicks.
   ========================================================================== */

.rail__mark--logo {
  background: #fff;
  color: inherit;
  border: 1px solid var(--rule-soft);
  padding: 2px;
  /* White, in dark mode too. A logo is a fixed asset with its own contrast
     and cannot be re-toned to suit a theme; tinting the ground behind it is
     how you get a washed-out mark that looks like a rendering bug. */
}

.rail__mark--logo img { display: block; width: 100%; height: 100%; object-fit: contain; }

/* -- Public pages: give the mark real presence -----------------------------
   The marketing site, the sign-in screen and checkout are where the brand is
   doing actual work — for most visitors this is the first and sometimes only
   time they see it. 28px is right for a dense operator sidebar the same
   person stares at all day; it is undersized on a landing page.
   -------------------------------------------------------------------------- */

.siteheader__brand .rail__mark {
  width: 40px;
  height: 40px;
  border-radius: var(--r);
}

.siteheader__brand { font-size: var(--step-1); gap: .6875rem; }

/* Sign-in and checkout share .auth__brand. */
.auth__brand .rail__mark {
  width: 34px;
  height: 34px;
  border-radius: var(--r);
}

@media (max-width: 640px) {
  .siteheader__brand .rail__mark { width: 34px; height: 34px; }
  .siteheader__brand { font-size: var(--step-0); }
}

/* ==========================================================================
   SIGN-IN ALTERNATIVES
   ==========================================================================
   The "or email me a code" block under the 2FA challenge. Separated by a
   ruled OR rather than just spacing, because it is a genuinely different
   route rather than a secondary button for the same one — someone scanning
   this page in a hurry, locked out of their own hosting, should see the fork
   without reading.
   ========================================================================== */

.authalt { margin-top: 1.5rem; }

.authalt__rule {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin-bottom: 1rem;
  color: var(--ink-3);
  font-size: var(--step--2);
  text-transform: uppercase;
  letter-spacing: .08em;
}

/* Pseudo-element rules rather than a border on the text: this keeps the line
   centred on the cap height at any font size. */
.authalt__rule::before,
.authalt__rule::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--rule);
}

/* A disabled send button still has to read as "not now", not "broken". */
.authalt .btn[disabled] {
  opacity: .55;
  cursor: not-allowed;
  box-shadow: none;
}

/* ==========================================================================
   PROSE — muted / compact variants
   ==========================================================================
   .prose is sized for a full article page: 17px, 1.75 leading, 2.25rem above
   a heading. That is right for something someone sits down to read, and too
   loose inside a partner panel where the same markup is a few paragraphs in a
   card. These trim it without forking the whole block.
   ========================================================================== */

.prose--muted { color: var(--ink-2); }

.prose--compact { font-size: var(--step-0); line-height: 1.7; }
.prose--compact > * + * { margin-top: .875rem; }
.prose--compact h2,
.prose--compact h3,
.prose--compact h4 { margin-top: 1.375rem; font-size: var(--step-1); }
.prose--compact img,
.prose--compact video,
.prose--compact iframe { border-radius: var(--r); }

/* Partner-supplied media must not escape its column. An <img width="2000">
   pasted into the source view would otherwise stretch the whole page. */
.prose img,
.prose video,
.prose iframe { max-width: 100%; height: auto; }

/* ==========================================================================
   MOBILE — the public site
   ==========================================================================
   The whole page scrolled sideways on a phone, and it started in the header.

   `.siteheader__brand` is `white-space: nowrap`, so "CloudServe Hosting
   Manager" reserved its full ~210px whatever the viewport was. Add 48px of
   header padding, a 40px mark, a 24px gap and the actions cluster, and the
   header alone measured well over 500px on a 360px screen. Nothing was
   allowed to shrink, so the BODY grew to fit — which is why every section
   below then rendered too wide and the hero headline stopped wrapping.

   The fix is to let the one flexible thing actually flex: the brand text
   truncates, and everything around it gets tighter. A long site name must
   never be able to widen the page.
   ========================================================================== */

@media (max-width: 860px) {
  .siteheader__inner { padding: .625rem 1rem; gap: .75rem; }

  /* min-width:0 is the load-bearing part — without it a flex item refuses to
     shrink below its content, and text-overflow never gets a chance. */
  .siteheader__brand { flex: 1 1 auto; min-width: 0; }
  .siteheader__brand > span:last-child {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .siteheader__actions { flex: none; gap: .375rem; }
}

@media (max-width: 520px) {
  .siteheader__brand { font-size: var(--step--1); gap: .5rem; }
  .siteheader__brand .rail__mark { width: 30px; height: 30px; }
}

/* -- Buttons on a narrow screen -------------------------------------------
   .btn is `white-space: nowrap` by design — a wrapped button label looks
   broken. On a phone that turns a long call to action ("Get hosting — from
   ₦2,000.00/mo") into something wider than the screen, so below 480px the
   buttons in a row go full width and stack instead. That is the pattern a
   thumb wants anyway.
   -------------------------------------------------------------------------- */

@media (max-width: 480px) {
  .btnrow { gap: .625rem; }
  .btnrow > .btn { flex: 1 1 100%; }
  .btn--lg { padding: .75rem 1rem; }
}

/* -- Partner header -------------------------------------------------------- */

.partnerhead {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: start;
  gap: 1.25rem;
  margin-bottom: 1.75rem;
}

.partnerhead__logo { width: 72px; height: 72px; margin: 0; flex: none; }

/* The letter fallback is sized for a 52px tile elsewhere; in the 72px header
   tile it needs to grow with it or it floats in the middle looking wrong. */
.partner__fallback.partnerhead__logo { font-size: var(--step-2); }

/* Size comes from .page-title, which clamps for every heading in the app —
   restating it here would be a dead rule that a later reader trusts. What is
   local is the tighter leading a big name needs, and `anywhere`, which stops
   one long unbroken word from setting the width of the whole layout. */
.partnerhead__name {
  line-height: 1.14;
  overflow-wrap: anywhere;
}

.partnerhead__text { min-width: 0; }

@media (max-width: 720px) {
  .partnerhead {
    grid-template-columns: auto minmax(0, 1fr);
    gap: .875rem 1rem;
    margin-bottom: 1.25rem;
  }
  .partnerhead__logo { width: 56px; height: 56px; }
  /* Full width on its own row, under the name it belongs to. */
  .partnerhead__cta { grid-column: 1 / -1; width: 100%; }
}

/* -- Partner-written rich text --------------------------------------------
   This is the one place on the site where the markup was authored by someone
   outside it, so it gets the guards ordinary page copy does not need: pasted
   URLs that never break, and tables wider than a phone.
   -------------------------------------------------------------------------- */

.prose { overflow-wrap: break-word; }
.prose a { overflow-wrap: anywhere; }
.prose table { display: block; max-width: 100%; overflow-x: auto; }

/* -- Last-resort guard ----------------------------------------------------
   `clip`, not `hidden`: hidden on the root makes a scroll container and
   silently kills `position: sticky` on the header. This is a net for anything
   that slips through in future, not a substitute for the fixes above — an
   overflow that only this rule catches is still a bug, just an invisible one.
   -------------------------------------------------------------------------- */

html { overflow-x: clip; }

/* ==========================================================================
   MOBILE — the signed-in app
   ==========================================================================
   `.pagehead` had the same defect the partner header did, and it is on every
   page behind the login.

   .pagehead__text was `flex: 1`, which is shorthand for `flex: 1 1 0%`. A
   flex-basis of zero tells the browser the text is happy at ANY width, so the
   wrap never triggers: the buttons stayed on line one and the heading and
   sub-line got whatever was left — about 130px on a phone, which is why they
   rendered one word per line beside a comfortable pair of buttons.

   flex-wrap was already there. It simply never had a reason to fire.
   ========================================================================== */

/* A real basis. Below roughly 22rem + the buttons, the actions wrap to their
   own line; above it, `1 1` still grows the text to fill as before. Desktop
   is unchanged. */
.pagehead__text { flex: 1 1 22rem; }

/* Page titles scale with the viewport. --step-3 is 2rem, and "REACH MEDIA
   PLATFORM" at 2rem needs ~340px of unbroken width — more than a 360px phone
   has once the page gutters are taken out. The vw term reaches the full 2rem
   at about 580px and never exceeds it. */
.page-title { font-size: clamp(1.5rem, 5.5vw, var(--step-3)); }

@media (max-width: 560px) {
  .pagehead { gap: .875rem; align-items: flex-start; }

  /* Full width and sharing the row, so two actions read as a pair of equal
     targets rather than one wide button and one narrow one. */
  .pagehead__actions { width: 100%; }
  .pagehead__actions > .btn,
  .pagehead__actions > form > .btn { flex: 1 1 auto; justify-content: center; }

  /* The eyebrow is a single short line once the text column is full width —
     but a long status label should still wrap as a unit, not orphan its
     separator on a line of its own. */
  .pagehead__eyebrow { overflow-wrap: anywhere; }
}

/* -- Breadcrumb in the top bar --------------------------------------------
   Long crumb trails ("Partner / Products / Some Product Name") push the
   topbar wide on a phone. It scrolls inside itself instead.
   -------------------------------------------------------------------------- */

@media (max-width: 560px) {
  .topbar__crumb {
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
    white-space: nowrap;
  }
  .topbar__crumb::-webkit-scrollbar { display: none; }
}


/* ==========================================================================
   PARTNER PRODUCT CATALOGUE
   ==========================================================================
   Cards that behave like a shop, not like article teasers: a consistent image
   box, a name, a price, and the whole tile is the click target. Everything
   that used to expand in place now lives on the product's own page, which is
   also where a visitor can act on it.
   ========================================================================== */

.catalogue {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 1.125rem;
}

.prodcard {
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  background: var(--readout-raised);
  overflow: hidden;
  transition: transform var(--dur) var(--ease), box-shadow var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
.prodcard:hover {
  transform: translateY(-2px);
  border-color: var(--accent);
  box-shadow: var(--lift-2);
}

/* The whole tile is one link, so the target is the card rather than four
   words of title — the difference between a shop and a list of links. */
.prodcard__link { display: flex; flex-direction: column; height: 100%; color: inherit; }
.prodcard__link:hover { text-decoration: none; }
.prodcard__link:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: 2px; }

/* -- The image box --------------------------------------------------------
   A fixed aspect ratio with object-fit: cover. Partner artwork arrives in
   every shape there is, and letting each card size itself to its own image
   turns the grid into a staircase. */
.prodcard__media {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  background: #fff;
  border-bottom: 1px solid var(--rule-soft);
  overflow: hidden;
}
.prodcard__media img { width: 100%; height: 100%; object-fit: cover; display: block; }

.prodcard__placeholder {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  background: var(--chassis-sunk);
  color: var(--ink-3);
}

.prodcard__tag {
  position: absolute;
  top: .5rem; left: .5rem;
  padding: .1875rem .4375rem;
  border-radius: var(--r-sm);
  background: color-mix(in srgb, var(--readout-raised) 92%, transparent);
  -webkit-backdrop-filter: blur(4px);
  backdrop-filter: blur(4px);
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: .03em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.prodcard__body {
  display: flex;
  flex-direction: column;
  gap: .25rem;
  padding: .75rem .875rem .875rem;
  flex: 1;
}

.prodcard__name { font-weight: 620; font-size: var(--step-0); line-height: 1.3; }

/* Two lines, then ellipsis: the card must not grow to fit a long tagline or
   the grid loses its rhythm. */
.prodcard__tagline {
  font-size: var(--step--1);
  color: var(--ink-2);
  line-height: 1.45;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* margin-top:auto pins the footer to the bottom whatever the tagline does,
   so prices line up across the row. */
.prodcard__foot {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .5rem;
  margin-top: auto;
  padding-top: .625rem;
}

.prodcard__price { font-weight: 650; font-size: var(--step--1); color: var(--ink); }

.prodcard__stats { display: flex; gap: .5rem; font-size: var(--step--2); color: var(--ink-3); }
.prodcard__stats span { display: inline-flex; align-items: center; gap: .1875rem; }

@media (prefers-reduced-motion: reduce) {
  .prodcard, .prodcard:hover { transition: none; transform: none; }
}

/* ==========================================================================
   PRODUCT DETAIL
   ========================================================================== */

.proddetail {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 1.5rem;
  align-items: start;
}
@media (min-width: 860px) { .proddetail { grid-template-columns: minmax(0, 46%) minmax(0, 1fr); gap: 2.25rem; } }

.proddetail__media {
  position: relative;
  aspect-ratio: 4 / 3;
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  overflow: hidden;
}
/* contain, not cover: on the detail page the whole image matters — a
   screenshot cropped to fill a box is worse than one with margins. */
.proddetail__media img { width: 100%; height: 100%; object-fit: contain; display: block; }

.proddetail__placeholder {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  background: var(--chassis-sunk); color: var(--ink-3);
}

.proddetail__head { display: grid; gap: .625rem; align-content: start; }
.proddetail__name { font-size: clamp(1.625rem, 4.5vw, 2.25rem); line-height: 1.12; overflow-wrap: anywhere; }
.proddetail__price { font-size: var(--step-2); font-weight: 700; letter-spacing: -.02em; }

.proddetail__actions { display: flex; flex-wrap: wrap; gap: .625rem; margin-top: .375rem; }
@media (max-width: 480px) { .proddetail__actions > * { flex: 1 1 100%; } }

/* -- The like button ------------------------------------------------------ */

.prodlike { gap: .5rem; }
.prodlike.is-liked { border-color: var(--accent); color: var(--accent); background: var(--accent-soft); }

.prodlike__count {
  padding: .0625rem .375rem;
  border-radius: var(--r-pill);
  background: var(--chassis-sunk);
  font-size: var(--step--2);
  font-weight: 700;
}
.prodlike.is-liked .prodlike__count { background: var(--accent); color: var(--ink-on-accent); }

/* One-shot: the class is removed and re-added on each click, so the pop plays
   on the press that caused it rather than on every render. */
.prodlike.is-popping .prodlike__icon { animation: likePop .32s var(--ease); }

@keyframes likePop {
  0%   { transform: scale(1); }
  40%  { transform: scale(1.35); }
  100% { transform: scale(1); }
}

/* -- Comments ------------------------------------------------------------- */

.commentlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 1rem; }

.comment { display: grid; grid-template-columns: 32px minmax(0, 1fr); gap: .75rem; }

.comment__avatar {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 700;
  font-size: var(--step--1);
}

.comment__meta { font-size: var(--step--1); display: flex; align-items: center; gap: .375rem; flex-wrap: wrap; }
.comment__body { font-size: var(--step-0); line-height: 1.6; margin-top: .1875rem; overflow-wrap: break-word; }

/* -- "Also from them" ----------------------------------------------------- */

.relitem {
  display: grid;
  grid-template-columns: 40px minmax(0, 1fr);
  gap: .625rem;
  align-items: center;
  padding: .5rem;
  border-radius: var(--r);
  color: inherit;
}
.relitem:hover { background: var(--chassis-sunk); text-decoration: none; }
.relitem img {
  width: 40px; height: 40px;
  object-fit: cover;
  border-radius: var(--r-sm);
  background: #fff;
}
.relitem strong { display: block; font-size: var(--step--1); font-weight: 600; }

/* ==========================================================================
   MARKETPLACE
   ========================================================================== */

.mktghead {
  display: grid;
  gap: 1.25rem;
  align-items: end;
  margin-bottom: 1.5rem;
}
@media (min-width: 860px) { .mktghead { grid-template-columns: minmax(0, 1fr) auto; } }

.mktgsearch { display: flex; gap: .5rem; }
.mktgsearch .input { min-width: 0; }
@media (min-width: 860px) { .mktgsearch .input { width: 20rem; } }

/* The seller's name on a marketplace card. The profile-page card leaves this
   out — you already know whose page you are on there. */
.prodcard__seller {
  font-size: var(--step--2);
  color: var(--ink-3);
  font-weight: 550;
  letter-spacing: .01em;
}

/* -- Category chips -------------------------------------------------------
   Links, not buttons: each one is a real URL, so a filtered marketplace can
   be shared, bookmarked and reached by the back button. */

.chiprow {
  display: flex;
  flex-wrap: wrap;
  gap: .4375rem;
  margin-bottom: 1.5rem;
}

.chip {
  padding: .3125rem .6875rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-pill);
  background: var(--readout-raised);
  color: var(--ink-2);
  font-size: var(--step--1);
  font-weight: 550;
  transition: background var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}
.chip:hover { border-color: var(--accent); color: var(--ink); text-decoration: none; }
.chip.is-active {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink-on-accent);
}

/* ==========================================================================
   STATUS PAGE
   ==========================================================================
   Read at the worst possible moment — someone's site looks down and they are
   deciding whether it is them or us. So the verdict is the biggest thing on
   the page and the detail sits underneath it.
   ========================================================================== */

.statushero {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1.25rem;
  align-items: center;
  padding: 1.75rem;
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  background: var(--readout);
  margin-bottom: 1.75rem;
}

/* The left border carries the verdict as colour, so the answer survives being
   seen from across a room — or by someone who cannot read the icon. */
.statushero--live  { border-left: 4px solid var(--live); }
.statushero--warn  { border-left: 4px solid var(--warn); }
.statushero--fault { border-left: 4px solid var(--fault); }
.statushero--idle  { border-left: 4px solid var(--idle); }

.statushero__badge {
  display: grid;
  place-items: center;
  width: 60px; height: 60px;
  border-radius: var(--r-pill);
}
.statushero--live  .statushero__badge { background: var(--live-soft);  color: var(--live); }
.statushero--warn  .statushero__badge { background: var(--warn-soft);  color: var(--warn); }
.statushero--fault .statushero__badge { background: var(--fault-soft); color: var(--fault); }
.statushero--idle  .statushero__badge { background: var(--idle-soft);  color: var(--idle); }

.statushero__title {
  font-size: clamp(1.5rem, 4vw, 2rem);
  font-weight: 720;
  letter-spacing: -.03em;
  line-height: 1.15;
}
.statushero__sub { color: var(--ink-2); margin-top: .375rem; max-width: 60ch; line-height: 1.6; }

@media (max-width: 560px) {
  .statushero { grid-template-columns: minmax(0, 1fr); padding: 1.25rem; }
  .statushero__badge { width: 48px; height: 48px; }
}

/* -- The component list --------------------------------------------------- */

.statuslist {
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--readout-raised);
}

.statusrow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: .875rem;
  align-items: center;
  padding: .9375rem 1.125rem;
  border-bottom: 1px solid var(--rule-soft);
}
.statusrow:last-child { border-bottom: 0; }

.statusrow__dot {
  width: 10px; height: 10px;
  border-radius: var(--r-pill);
  flex: none;
}
.statusrow--live  .statusrow__dot { background: var(--live);  box-shadow: 0 0 0 3px var(--live-soft); }
.statusrow--warn  .statusrow__dot { background: var(--warn);  box-shadow: 0 0 0 3px var(--warn-soft); }
.statusrow--fault .statusrow__dot { background: var(--fault); box-shadow: 0 0 0 3px var(--fault-soft); }
.statusrow--idle  .statusrow__dot { background: var(--idle);  box-shadow: 0 0 0 3px var(--idle-soft); }

.statusrow__label  { font-weight: 600; font-size: var(--step-0); }
.statusrow__detail { font-size: var(--step--1); color: var(--ink-2); margin-top: .0625rem; }

.statusrow__state {
  font-size: var(--step--2);
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
}
.statusrow--live  .statusrow__state { color: var(--live); }
.statusrow--warn  .statusrow__state { color: var(--warn); }
.statusrow--fault .statusrow__state { color: var(--fault); }
.statusrow--idle  .statusrow__state { color: var(--ink-3); }

@media (max-width: 560px) {
  .statusrow { grid-template-columns: auto minmax(0, 1fr); }
  /* The word moves under the label rather than being squeezed beside it. */
  .statusrow__state { grid-column: 2; }
}

.statuscauses {
  margin: .75rem 0 0;
  padding-left: 1.125rem;
  display: grid;
  gap: .4375rem;
  font-size: var(--step--1);
  color: var(--ink-2);
}

/* A comment only its author can see, waiting on approval. Dimmed and dashed so
   it never reads as published — the point is to prove it saved, not to pretend
   it is live. */
.comment--pending { opacity: .72; }
.comment--pending .comment__body {
  border-left: 2px dashed var(--warn);
  padding-left: .625rem;
}

/* ==========================================================================
   ACCORDION — a partner's comment queue, grouped by product
   ==========================================================================
   Built on <details>, animated by accordion.js. The height transition lives in
   JS because <details> gives no intermediate state to transition through; the
   rest — the chevron, the open background, the panel padding — is CSS, so it
   still looks deliberate if the script never loads.
   ========================================================================== */

.acc {
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--readout-raised);
}

.acc__item { border-bottom: 1px solid var(--rule-soft); }
.acc__item:last-child { border-bottom: 0; }
.acc__item[open] { background: var(--readout); }

/* Both are needed: Safari uses the ::-webkit- pseudo, everyone else honours
   list-style on the summary itself. */
.acc__head {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: .875rem;
  align-items: center;
  padding: .875rem 1rem;
  cursor: pointer;
  list-style: none;
  transition: background var(--dur) var(--ease);
}
.acc__head::-webkit-details-marker { display: none; }
.acc__head:hover { background: var(--chassis-sunk); }
.acc__head:focus-visible { outline: 2px solid var(--accent-ring); outline-offset: -2px; }

.acc__media {
  width: 44px; height: 44px;
  border-radius: var(--r);
  overflow: hidden;
  background: #fff;
  border: 1px solid var(--rule-soft);
  flex: none;
}
.acc__media img { width: 100%; height: 100%; object-fit: cover; display: block; }

.acc__placeholder {
  width: 100%; height: 100%;
  display: grid; place-items: center;
  background: var(--chassis-sunk);
  color: var(--ink-3);
}

.acc__text { min-width: 0; }
.acc__name { display: block; font-weight: 620; font-size: var(--step-0); }
.acc__tagline {
  display: block;
  font-size: var(--step--1);
  color: var(--ink-2);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.acc__meta { display: flex; align-items: center; gap: .375rem; flex: none; }
.acc__chevron { transition: transform var(--dur) var(--ease); color: var(--ink-3); }
.acc__item[open] .acc__chevron { transform: rotate(180deg); }

/* height is driven by the script; overflow:hidden is what makes that visible
   as a slide rather than a jump. */
.acc__panel { overflow: hidden; }
.acc__inner { padding: .25rem 1rem 1rem; display: grid; gap: .75rem; }

@media (max-width: 560px) {
  .acc__head { grid-template-columns: auto minmax(0, 1fr); }
  .acc__meta { grid-column: 2; }
}

/* -- One comment in the queue -------------------------------------------- */

.qcomment {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  gap: .75rem;
  align-items: start;
  padding: .75rem;
  border: 1px solid var(--rule-soft);
  border-radius: var(--r);
  background: var(--readout-raised);
}

/* The amber edge is the whole point of the page: what still needs a decision. */
.qcomment--pending { border-left: 3px solid var(--warn); }

.qcomment__avatar {
  width: 34px; height: 34px;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-weight: 700;
  font-size: var(--step--1);
}

.qcomment__meta { display: flex; align-items: center; gap: .375rem; flex-wrap: wrap; font-size: var(--step--1); }
.qcomment__body { font-size: var(--step-0); line-height: 1.55; margin-top: .25rem; overflow-wrap: break-word; }
.qcomment__from { font-size: var(--step--2); margin-top: .3125rem; }

.qcomment__actions { display: flex; gap: .375rem; flex: none; }

@media (max-width: 640px) {
  .qcomment { grid-template-columns: 34px minmax(0, 1fr); }
  /* Buttons drop under the comment rather than being squeezed beside it. */
  .qcomment__actions { grid-column: 2; margin-top: .5rem; }
}

@media (prefers-reduced-motion: reduce) {
  .acc__chevron, .acc__head { transition: none; }
}

/* ==========================================================================
   Digital store
   ==========================================================================
   Built on the .catalogue / .prodcard components the partner marketplace
   already uses, so a product looks the same wherever it is met. Only the
   things a SHOP needs and a showcase does not are defined here — chiefly the
   price block, which is the one place this section is allowed to be loud.
   -------------------------------------------------------------------------- */

/* Breadcrumbs. No public page had them before the store, and a product page
   is the first place on the site deep enough to need one — a visitor arriving
   from search has no other way back up to the category. */
.crumbs { display: flex; align-items: center; gap: .4375rem; flex-wrap: wrap; font-size: var(--step--1); }
.crumbs a { color: var(--ink-3); }
.crumbs a:hover { color: var(--accent); }
.crumbs span[aria-hidden] { color: var(--ink-3); opacity: .55; }

/* The price, and what it used to be. The strike-through is a claim about
   money, so it is only ever rendered when compare_at_price is genuinely
   higher — see DigitalProductController::validated(). */
.price { display: flex; align-items: baseline; gap: .5rem; flex-wrap: wrap; }
.price__now { font-weight: 680; font-size: var(--step-1); color: var(--ink); letter-spacing: -.01em; }
.price__was {
  font-size: var(--step--1);
  color: var(--ink-3);
  text-decoration: line-through;
  text-decoration-thickness: 1px;
}
.price__save {
  font-size: var(--step--2);
  font-weight: 620;
  color: var(--success);
  background: color-mix(in srgb, var(--success) 12%, transparent);
  border-radius: 999px;
  padding: .125rem .4375rem;
  white-space: nowrap;
}
.price--free .price__now { color: var(--success); }

/* Sits on the card image, top-left, opposite the category tag. */
.prodcard__save {
  position: absolute;
  top: .5rem;
  left: .5rem;
  z-index: 1;
  font-size: var(--step--2);
  font-weight: 650;
  color: #fff;
  background: var(--fault);
  border-radius: 999px;
  padding: .1875rem .5rem;
  box-shadow: 0 1px 3px rgb(0 0 0 / .25);
}

/* -- Product page ---------------------------------------------------------- */

.storedetail { display: grid; gap: 1.75rem; grid-template-columns: minmax(0, 1fr) 340px; align-items: start; }
@media (max-width: 900px) { .storedetail { grid-template-columns: 1fr; } }

.storehero {
  border: 1px solid var(--rule);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--readout-raised);
  aspect-ratio: 16 / 9;
  display: grid;
  place-items: center;
}
.storehero img { width: 100%; height: 100%; object-fit: cover; display: block; }
.storehero__placeholder { color: var(--ink-3); }

/* The buy panel follows the reader down a long description. `start` keeps it
   clear of the sticky site header rather than sliding under it. */
.storebuy { position: sticky; top: 5rem; }
@media (max-width: 900px) { .storebuy { position: static; } }

.storemeta { display: grid; gap: .5rem; margin: 0; }
.storemeta__row { display: flex; justify-content: space-between; gap: 1rem; font-size: var(--step--1); }
.storemeta__key { color: var(--ink-3); }
.storemeta__val { color: var(--ink); font-weight: 550; text-align: right; }

/* -- Library --------------------------------------------------------------- */

.libitem { display: flex; gap: 1rem; align-items: flex-start; }
.libitem__thumb {
  width: 72px; height: 72px; flex: none;
  border-radius: var(--r);
  overflow: hidden; background: var(--readout-raised);
  display: grid; place-items: center; color: var(--ink-3);
}
.libitem__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
@media (max-width: 560px) {
  .libitem { flex-direction: column; }
  .libitem__thumb { width: 100%; height: 140px; }
}
