/* apanel stylesheet.
 *
 * One file, embedded in the binary, no build step, no request that leaves the
 * server. The api serves `default-src 'self'` with no 'unsafe-inline', so
 * nothing here relies on a style attribute: quota bars get their width from a
 * bucket class (.p0 ... .p100) rather than from inline CSS. The typefaces are
 * served from /assets/fonts/ for the same reason - a control panel that asks
 * a font CDN for a stylesheet tells that CDN every hostname every operator
 * administers.
 *
 * ── THE SYSTEM ────────────────────────────────────────────────────────
 * Two themes, each with a real dark variant, over one markup:
 *
 *   Signal    light  neutral ground, dark rail, teal accent, dense.
 *             dark   "Instrument" - near-black ground, jade accent.
 *   Daylight  light  warm off-white, serif headings, blue accent, roomy.
 *             dark   "Lamplight" - warm dark, same serif, same warmth.
 *
 * EVERY colour, typeface, radius and density value in this file is a custom
 * property declared in one of the four palette blocks below. Nothing further
 * down names a colour: a buyer who wants their own accent changes one line
 * per theme, and an inconsistency has nowhere to hide. The two exceptions are
 * declared where they occur and both are documented.
 *
 * Dark is the READER's axis, the theme is the reader's CHOICE, and they are
 * independent: a palette is redefined once under prefers-color-scheme for the
 * "system" default and once under [data-theme=dark] so an explicit choice
 * wins in both directions. Four palettes therefore cost six blocks. They are
 * written out rather than derived, because a derived dark palette is how a
 * panel ends up with 3:1 text at two in the morning.
 *
 * INSIDE THE BILLING FRAME NONE OF THIS APPLIES. Every palette is scoped to
 * [data-skin], the layout omits that attribute when the page is embedded, and
 * the embedded block at the foot of this file hands colour and type back to
 * the host document (docs/client-area-style.md). See docs/panel-style.md for
 * the token reference and the component list.
 */

/* ------------------------------------------------------------------ faces

   Variable woff2, latin and latin-ext, weight axis only. font-display:swap so
   a cold cache costs a repaint and never a blank screen; every family also
   names a system fallback at the point of use. Licences: Archivo, Archivo
   Narrow, IBM Plex Sans, IBM Plex Mono and Source Serif 4 are all SIL OFL 1.1
   (assets/fonts/OFL.txt). */

@font-face {
  font-family: "Archivo apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("../fonts/archivo-latin.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("../fonts/archivo-latin-ext.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Archivo Narrow apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("../fonts/archivo-narrow-latin.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo Narrow apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("../fonts/archivo-narrow-latin-ext.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Plex Sans apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 100 700;
  src: url("../fonts/plex-sans-latin.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Plex Sans apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 100 700;
  src: url("../fonts/plex-sans-latin-ext.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Source Serif apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 200 900;
  src: url("../fonts/source-serif-latin.woff2") format("woff2-variations");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Source Serif apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 200 900;
  src: url("../fonts/source-serif-latin-ext.woff2") format("woff2-variations");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Plex Mono apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url("../fonts/plex-mono-latin-400.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Plex Mono apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 400;
  src: url("../fonts/plex-mono-latin-ext-400.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Plex Mono apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url("../fonts/plex-mono-latin-500.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Plex Mono apanel";
  font-style: normal;
  font-display: swap;
  font-weight: 500;
  src: url("../fonts/plex-mono-latin-ext-500.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ------------------------------------------------------- Signal, light

   The base palette. It sits on bare :root rather than on [data-skin=signal]
   so that a page rendered before a skin is known - the sign-in screen, an
   error page reached without a session - is still fully painted. Signal is
   the default on the operator and the reseller faces. */

:root {
  color-scheme: light dark;

  /* ground and surfaces */
  /* A STEP LIGHTER than it was (2026-10-04), and the step is what pays for
     the borderless card below: the old ground sat close enough to white that
     a card with its outline removed had no edge left at all. It is still the
     cool console grey this theme has always used. */
  --bg:        #f4f5f7;
  --bg-raised: #ffffff;
  --bg-sunken: #e4e7e9;
  --bg-hover:  #e2e6e8;
  --bg-side:   #1d2328;
  --bg-top:    #ffffff;

  /* text */
  --fg:        #171a1d;
  --fg-muted:  #545c64;
  --fg-faint:  #656d75;
  --fg-side:   #c6ced4;
  --fg-side-muted: #c6ced499;
  --fg-side-on:#ffffff;

  /* rules */
  --line:      #cbd2d6;
  --line-soft: #e4e8ea;

  /* one accent: things you can press */
  --accent:      #0f6d8c;
  --accent-weak: #ddebf1;
  --accent-fg:   #ffffff;

  /* three states, plus "working on it" */
  /* A STATE HAS TWO COLOURS, and the split is not decoration.
     --ok / --warn / --crit / --busy are the MARK: a bar fill, a rail, a
     dot, a border - shapes, where the eye only has to notice the hue.
     --*-ink is the same state as TEXT, and it is darker, because the
     approved amber cannot carry 4.5:1 as small text on any near-white
     surface and neither can the green sitting on its own tint. On a dark
     ground the two are the same value; there the mark is already legible
     text. Checked in all four theme/mode combinations. */
  --ok:        #2e7d5b;
  --ok-ink:    #26694c;
  --ok-weak:   #dcece4;
  --warn:      #b5730f;
  --warn-ink:  #8a5a0b;
  --warn-weak: #f6ebd8;
  --crit:      #c0362c;
  --crit-ink:  #ad2f26;
  --crit-weak: #f7e1df;
  --busy:      #3a5ca8;
  --busy-ink:  #3a5ca8;
  --busy-weak: #e1e7f4;

  /* the sidebar is a dark object in a light page in this theme, so the
     hairline inside it cannot come from --line, which is tuned for paper */
  --line-side: rgb(255 255 255 / 12%);
  --hover-side: rgb(255 255 255 / 8%);

  /* shape and density: Signal is the dense one, and it stays dense.
     --cell-y, --cell-x, --card-pad and --base-size are untouched: those are
     what density IS. The corner and the edge are not density, they are
     cosmetics, and they are what moved here. */
  --radius:    8px;
  --radius-sm: 5px;
  /* Was `none`, which was right while the card had an outline. With the
     outline gone the shadow is the only thing drawing the edge, so it is a
     whisper rather than nothing - half what Daylight carries, because this
     face shows twenty cards at once and Daylight shows six. */
  --shadow:    0 1px 2px rgb(24 28 33 / 5%);
  --cell-y:    .42rem;
  --cell-x:    .6rem;
  --card-pad:  .75rem .85rem;
  --main-pad:  1rem 1.15rem 2rem;
  --gap:       .7rem;
  --base-size: 13.5px;
  --title-size: 1.3rem;

  /* the bento: Signal's is tight and information-first. The corner is only
     two pixels softer than the table's, the padding barely above a card's,
     and the lift is a whisper on paper and nothing at all in the dark, where
     Instrument's whole argument is that rows on a near-black ground are one
     continuous surface. Density is what this theme is for; the grid is here
     to stop a figure and a sparkline being given the same width, not to turn
     the console into a set of boxes. */
  --bento-radius: 10px;
  --bento-pad:    .85rem .95rem;
  --bento-gap:    .7rem;
  --bento-row:    4.4rem;
  --bento-lift:   0 1px 2px rgb(24 28 33 / 5%), 0 8px 20px -16px rgb(24 28 33 / 14%);

  /* the severity rail down the left of a row that needs attention */
  --rail: 3px;

  /* type. --sans is declared exactly once per theme; --label is the
     condensed face Signal uses for column headings and small caps labels,
     and --display is the heading face. Where a theme has no separate one,
     they simply point at --sans. */
  --sans:  "Archivo apanel", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  --label: "Archivo Narrow apanel", "Archivo apanel", system-ui, sans-serif;
  --display: var(--sans);
  --mono:  "Plex Mono apanel", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --label-tracking: .07em;

  --sidebar-w: 216px;
  --topbar-h: 52px;
}

/* --------------------------------------------- Signal, dark (Instrument)

   The third direction from the specimen, used as Signal's dark form: the
   ground drops to near-black, the accent moves from teal to jade so it still
   reads as "pressable" at that luminance, and the cards give way to
   hairlines. Declared twice on purpose - see the header. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-skin="daylight"]) {
    --bg:        #0e1116;
    --bg-raised: #161a21;
    --bg-sunken: #0a0d11;
    --bg-hover:  #1d222b;
    --bg-side:   #0a0d11;
    --bg-top:    #12161c;

    --fg:        #c9d1dc;
    --fg-muted:  #98a1ad;
    --fg-faint:  #828b98;
    --fg-side:   #9ba5b2;
    --fg-side-muted: #9ba5b299;
    --fg-side-on:#ffffff;

    --line:      #2c333d;
    --line-soft: #1b2028;

    --accent:      #3fb292;
    --accent-weak: #10312a;
    --accent-fg:   #04120e;

    --ok:        #3fb292;
    --ok-ink:    #3fb292;
    --ok-weak:   #10312a;
    --warn:      #d99a34;
    --warn-ink:  #d99a34;
    --warn-weak: #33260f;
    --crit:      #e6706f;
    --crit-ink:  #e6706f;
    --crit-weak: #38191b;
    --busy:      #8aa4e8;
    --busy-ink:  #8aa4e8;
    --busy-weak: #182038;

    --line-side:  rgb(255 255 255 / 10%);
    --hover-side: rgb(255 255 255 / 6%);

    --shadow: none;
    /* Instrument drops the card, so the bento has no lift either. */
    --bento-lift: none;
  }
}
:root[data-theme="dark"]:not([data-skin="daylight"]) {
  --bg:        #0e1116;
  --bg-raised: #161a21;
  --bg-sunken: #0a0d11;
  --bg-hover:  #1d222b;
  --bg-side:   #0a0d11;
  --bg-top:    #12161c;

  --fg:        #c9d1dc;
  --fg-muted:  #98a1ad;
  --fg-faint:  #828b98;
  --fg-side:   #9ba5b2;
  --fg-side-muted: #9ba5b299;
  --fg-side-on:#ffffff;

  --line:      #2c333d;
  --line-soft: #1b2028;

  --accent:      #3fb292;
  --accent-weak: #10312a;
  --accent-fg:   #04120e;

  --ok:        #3fb292;
  --ok-ink:    #3fb292;
  --ok-weak:   #10312a;
  --warn:      #d99a34;
  --warn-ink:  #d99a34;
  --warn-weak: #33260f;
  --crit:      #e6706f;
  --crit-ink:  #e6706f;
  --crit-weak: #38191b;
  --busy:      #8aa4e8;
  --busy-ink:  #8aa4e8;
  --busy-weak: #182038;

  --line-side:  rgb(255 255 255 / 10%);
  --hover-side: rgb(255 255 255 / 6%);

  --shadow: none;
  /* Instrument drops the card, so the bento has no lift either. */
  --bento-lift: none;
}

/* ----------------------------------------------------- Daylight, light

   The customer's face, and its default. Warm paper, a serif for headings so
   the page reads as a document rather than a console, bordered cards and
   room between them. The sidebar is white here, not dark. */

[data-skin="daylight"] {
  /* THE GROUND IS COOL, not cream (2026-10-02).
     It was a warm paper white, which read as a document and was the right
     call while the serif was carrying the heading. With the serif gone the
     warmth had nothing left to belong to and began reading as a slightly
     yellowed screen. A cool near-white keeps the same lightness and lets a
     white card sit ON it rather than blend into it, which is what makes a
     borderless card legible at all. */
  --bg:        #f5f6f8;
  --bg-raised: #ffffff;
  --bg-sunken: #eceef2;
  --bg-hover:  #eef0f4;
  --bg-side:   #ffffff;
  --bg-top:    #ffffff;

  --fg:        #1d2127;
  --fg-muted:  #596070;
  --fg-faint:  #686f7d;
  --fg-side:   #596070;
  --fg-side-muted: #59607099;
  --fg-side-on:#2f5fd0;

  --line:      #d4dae3;
  --line-soft: #eef0f4;

  --accent:      #2f5fd0;
  --accent-weak: #e7edfa;
  --accent-fg:   #ffffff;

  --ok:        #2e7d5b;
  --ok-ink:    #26694c;
  --ok-weak:   #e0eee7;
  --warn:      #b5730f;
  --warn-ink:  #8a5a0b;
  --warn-weak: #f7eddc;
  --crit:      #c0362c;
  --crit-ink:  #ad2f26;
  --crit-weak: #f8e3e1;
  --busy:      #55499f;
  --busy-ink:  #55499f;
  --busy-weak: #e8e6f5;

  /* the sidebar is paper here, so its hairlines are the page's */
  --line-side:  var(--line);
  --hover-side: var(--bg-hover);

  --radius:    11px;
  --radius-sm: 7px;
  --shadow:    0 1px 2px rgb(16 24 40 / 5%);
  --cell-y:    .62rem;
  --cell-x:    .8rem;
  --card-pad:  1.05rem 1.15rem;
  --main-pad:  1.6rem 1.75rem 2.6rem;
  --gap:       1rem;
  --base-size: 14.5px;
  --title-size: 1.6rem;

  /* Daylight's bento may breathe: a properly round corner, half again the
     padding Signal allows, a wider gutter so the cards read as separate
     objects on paper, and a two-part shadow - a hairline contact edge and a
     wide, very faint ambient one. This is the document face; the grid is
     allowed to look composed. */
  --bento-radius: 14px;
  --bento-pad:    1.3rem 1.45rem;
  --bento-gap:    1.15rem;
  --bento-row:    5.6rem;
  --bento-lift:   0 1px 2px rgb(16 24 40 / 5%), 0 12px 28px -18px rgb(16 24 40 / 16%);

  --rail: 0px;

  --sans:  "Plex Sans apanel", system-ui, -apple-system, "Segoe UI", Roboto, Arial, sans-serif;
  /* THE SERIF IS GONE FROM THE CUSTOMER FACE (2026-10-02).
     It was the one deliberate risk in the approved direction and it did what
     it promised: the page read as a document. What it also did was make the
     panel look unlike every other hosting panel a customer has used, and the
     customers this face is built for are arriving from one. The heading face
     follows the body face here now. Signal keeps its own. */
  --display: var(--sans);
  --label: var(--sans);
  --mono:  "Plex Mono apanel", ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --label-tracking: .05em;

  --sidebar-w: 232px;
  --topbar-h: 58px;
}

/* ------------------------------------------- Daylight, dark (Lamplight)

   Designed, not inverted. The ground keeps Daylight's warmth - it is a warm
   near-black, not the blue-black Instrument uses - the serif heading stays,
   and the ink blue is lifted to a lamp blue so it still carries 4.5:1 on that
   ground. A customer who works at night gets the same document, at night.

   2026-10-02: the ground was a WARM near-black, matched to the cream Daylight
   used to have. Daylight's ground is cool now and the warmth had nothing left
   to pair with, so this is cooled with it. Every pair still carries 4.5:1 -
   the faint ink had to be lifted a step to keep it, and that is the only
   value here that is not a straight shift in temperature. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"])[data-skin="daylight"] {
    --bg:        #15181c;
    --bg-raised: #1c2026;
    --bg-sunken: #101317;
    --bg-hover:  #252a31;
    --bg-side:   #1c2026;
    --bg-top:    #1c2026;

    --fg:        #e3e7ee;
    --fg-muted:  #9aa3b2;
    --fg-faint:  #8b94a3;
    --fg-side:   #9aa3b2;
    --fg-side-muted: #9aa3b299;
    --fg-side-on:#9ab4f7;

    --line:      #2f353d;
    --line-soft: #22262c;

    --accent:      #9ab4f7;
    --accent-weak: #202538;
    --accent-fg:   #14161f;

    --ok:        #6bc79a;
    --ok-ink:    #6bc79a;
    --ok-weak:   #16281f;
    --warn:      #e0a752;
    --warn-ink:  #e0a752;
    --warn-weak: #2e2412;
    --crit:      #ef8079;
    --crit-ink:  #ef8079;
    --crit-weak: #331b18;
    --busy:      #b0a4f2;
    --busy-ink:  #b0a4f2;
    --busy-weak: #221e38;

    --line-side:  var(--line);
    --hover-side: var(--bg-hover);

    --shadow: none;
    /* Lamplight keeps the card, so the bento keeps a lift - but a shadow
       on a warm dark ground has to be the absence of light, not grey. */
    --bento-lift: 0 1px 2px rgb(0 0 0 / 40%);
  }
}
:root[data-theme="dark"][data-skin="daylight"] {
  --bg:        #15181c;
  --bg-raised: #1c2026;
  --bg-sunken: #101317;
  --bg-hover:  #252a31;
  --bg-side:   #1c2026;
  --bg-top:    #1c2026;

  --fg:        #e3e7ee;
  --fg-muted:  #9aa3b2;
  --fg-faint:  #8b94a3;
  --fg-side:   #9aa3b2;
  --fg-side-muted: #9aa3b299;
  --fg-side-on:#9ab4f7;

  --line:      #2f353d;
  --line-soft: #22262c;

  --accent:      #9ab4f7;
  --accent-weak: #202538;
  --accent-fg:   #14161f;

  --ok:        #6bc79a;
  --ok-ink:    #6bc79a;
  --ok-weak:   #16281f;
  --warn:      #e0a752;
  --warn-ink:  #e0a752;
  --warn-weak: #2e2412;
  --crit:      #ef8079;
  --crit-ink:  #ef8079;
  --crit-weak: #331b18;
  --busy:      #b0a4f2;
  --busy-ink:  #b0a4f2;
  --busy-weak: #221e38;

  --line-side:  var(--line);
  --hover-side: var(--bg-hover);

  --shadow: none;
  /* Lamplight keeps the card, so the bento keeps a lift - but a shadow
     on a warm dark ground has to be the absence of light, not grey. */
  --bento-lift: 0 1px 2px rgb(0 0 0 / 40%);
}

/* ------------------------------------------------------------ foundations */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--fg);
  font: 400 var(--base-size)/1.5 var(--sans);
}

h1, h2, h3 { margin: 0 0 .5rem; line-height: 1.25; font-weight: 600; }
h1 { font-size: var(--title-size); }
h2 { font-size: .95rem; letter-spacing: .01em; }
h3 { font-size: .82rem; color: var(--fg-muted); text-transform: uppercase; letter-spacing: .05em; margin-top: 1rem; }
p  { margin: 0 0 .6rem; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.mono { font-family: var(--mono); font-size: .86em; }

/* ------------------------------------------------------------- numerals

   THIS PANEL IS COLUMNS OF FIGURES: disk, bandwidth, quota, port numbers,
   dates, percentages, process counts. Proportional digits give a "1" a
   narrower advance than an "8", so 11.4 and 88.4 do not line up and two rows
   cannot be compared without reading them. Tabular figures are declared ONCE,
   here, for every place digits stack - it used to be three scattered
   declarations and everything they missed was misaligned.

   The rule is safe on mixed text: font-variant-numeric touches digits only,
   so a table cell holding a domain name is unaffected. */

.tbl th, .tbl td,
.kv dd, .kv dt,
.mono, .num, .numbers td, .numbers th,
.stat .big, .stat .unit, .metertext, .pagenum,
.secretvalue, .logbox, .errorcode,
.traffic .axis, .legend, .permoctal input,
time, code, samp, kbd, output,
input[type="number"], input[type="text"].num {
  font-variant-numeric: tabular-nums;
}

/* ---------------------------------------------------------- navigation icons

   One glyph per destination, from the closed set in internal/web/tools.go, so
   a metaphor cannot mean two things on two screens. They are decoration: the
   <svg> is aria-hidden and the link text does the naming. */

.navico {
  width: 16px; height: 16px; flex: none;
  opacity: .72;
  color: currentColor;
}
.navlink.on .navico { opacity: 1; }
.muted { color: var(--fg-muted); }
.small { font-size: .82em; }
.mt { margin-top: 1.2rem; }

.sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

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

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: var(--accent-fg);
  padding: .6rem 1rem; border-radius: 0 0 var(--radius) 0;
}
.skip:focus { left: 0; }

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

.shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  grid-template-rows: var(--topbar-h) minmax(0, 1fr);
  grid-template-areas: "top top" "side main";
  min-height: 100vh;
}

.topbar {
  grid-area: top;
  display: flex; align-items: center; gap: .8rem;
  padding: 0 .9rem;
  background: var(--bg-top);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
}

.brand { display: flex; align-items: center; gap: .5rem; color: var(--fg); }
.brand:hover { text-decoration: none; }
.brandmark { width: 22px; height: 22px; fill: none; stroke: var(--accent); stroke-width: 1.6; }
.brandmark .dot { fill: var(--accent); stroke: none; }
.brandname { font-weight: 650; letter-spacing: -.01em; }
/* The face switcher: one chip per panel this session may open. The current
   one is filled, the others read as the links they are. */
.faceswitch { display: flex; align-items: center; gap: .35rem; margin-left: .6rem; }
.facetag {
  font-size: .72rem; text-transform: uppercase; letter-spacing: .06em;
  color: var(--fg-muted); border: 1px solid var(--line); border-radius: 999px;
  padding: .1rem .5rem;
}
a.facetag:hover { border-color: var(--accent); color: var(--accent); text-decoration: none; }
.facetag.on { background: var(--accent-weak); border-color: var(--accent-weak); color: var(--fg); }

.topright { margin-left: auto; display: flex; align-items: center; gap: .6rem; }
/* The actor, as one disc. See base.html: the name used to be printed twice in
   this bar, once here unstyled and once as mono text, and the widest thing in
   the topbar was a string naming the person already reading it. */
.topuser {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  border-radius: 999px;
}
.topuser .avatar {
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-fg);
  font-family: var(--label);
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: 0;
  line-height: 1;
}
.topuser:hover .avatar { filter: brightness(1.08); }
.topuser:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.themectl { display: flex; gap: .1rem; border: 1px solid var(--line); border-radius: var(--radius); padding: 2px; }
.iconbtn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 26px; padding: 0;
  background: none; border: 0; border-radius: var(--radius-sm);
  color: var(--fg-muted); cursor: pointer;
}
.iconbtn:hover { background: var(--bg-hover); color: var(--fg); }
.iconbtn[aria-pressed="true"] { background: var(--accent-weak); color: var(--accent); }
/* ONE STROKE WEIGHT for every glyph in the panel. The tool icons carried
   stroke-width="1.5" as a presentation attribute and the chrome icons were
   set to 1.6 here, so the same drawing hand came out two different weights
   depending on which screen you were on. CSS beats the attribute, so this is
   the single place the weight is decided. */
.ico, .toolico, .navico, .navcaret, .foldcaret {
  fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
}
.ico { width: 16px; height: 16px; }
.navtoggle { display: none; }

/* -------------------------------------------------------------- sidebar */

.sidebar {
  grid-area: side;
  background: var(--bg-side);
  border-right: 1px solid var(--line);
  padding: .8rem .55rem;
  overflow-y: auto;
}

.navlist { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; }
.navlink {
  display: flex; align-items: center; gap: .55rem;
  padding: .45rem .6rem;
  border-radius: var(--radius-sm);
  color: var(--fg-side); font-size: .88rem;
}
.navlink:hover { background: var(--hover-side); color: var(--fg-side-on); text-decoration: none; }
.navlink.on { background: var(--accent); color: var(--accent-fg); font-weight: 550; }
/* A NAMED GROUP IN THE RAIL. The sidebar reached twenty-plus entries as one
   flat column, which asks the reader to know the panel's vocabulary before
   they can find anything. A heading costs one line and turns four adjacent
   entries into an answer to "where do I go for addresses". It is a real <h2>
   over a labelled <ul>, not a styled list item: a screen reader gets the
   grouping, not just the sighted reader. */
/* THE HEADING IS THE FOLD'S CONTROL. It is a <summary> holding a real <h2>,
   which the HTML content model allows: the browser supplies the disclosure
   behaviour, the keyboard and the expanded/collapsed state, and the heading
   keeps naming the list under it. Nothing here needs script, which is the
   whole reason the rail is built this way (CONTRACT §17). */
.navhead {
  display: flex; align-items: center; gap: .3rem;
  margin: .9rem .6rem .25rem;
  cursor: pointer;
  border-radius: var(--radius-sm);
  list-style: none;                 /* Firefox's default triangle */
  color: var(--fg-side-muted);
}
.navhead::-webkit-details-marker { display: none; }  /* Safari's */
.navhead h2 {
  margin: 0;
  font-size: .7rem; font-weight: 600;
  letter-spacing: .07em; text-transform: uppercase;
  color: inherit;
}
.navhead:hover { color: var(--fg-side-on); }
/* The focus ring is the browser's, moved off the text and around the whole
   control: a keyboard reader tabbing the rail has to be able to see where the
   fold is. */
.navhead:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.navcaret {
  width: 11px; height: 11px;
  flex: none;
  opacity: .75;
  transform: rotate(0deg);
  transition: transform .12s ease-out;
}
.navgroup[open] > .navhead .navcaret { transform: rotate(90deg); }

/* Every heading gets the same air above it. Measured on the rendered rail:
   the flat run at the top used to be followed by 12px and the group-to-group
   step by 14.4px, which is a difference small enough to look like a mistake
   and large enough to see. */
.navgroup { margin-bottom: .1rem; }
/* An ungrouped run after a group is a run of entries that belong to nothing,
   and it needs the same air a heading would have given it - otherwise it reads
   as the last group's tail with the heading missing. */
.navgroup + .navlist { margin-top: .9rem; }

.navnote { margin: 1rem .6rem 0; font-size: .8rem; }
.navnote a { color: var(--fg-side); }

/* ----------------------------------------------------------------- main */

.main {
  grid-area: main;
  padding: var(--main-pad);
  min-width: 0;
}
.main:focus { outline: none; }

.pagehead { display: flex; align-items: center; flex-wrap: wrap; gap: .7rem; margin-bottom: .3rem; }
.pagehead .btn { margin-left: auto; }
.pagetitle { margin: 0; }
.sub { color: var(--fg-muted); margin: 0 0 1rem; }

.foot {
  margin-top: 2.5rem; padding-top: .8rem;
  border-top: 1px solid var(--line-soft);
  display: flex; gap: .6rem;
  color: var(--fg-faint); font-size: .78rem;
}

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

.card {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--card-pad);
  margin-bottom: var(--gap);
}
.card.danger { border-color: var(--crit); }
.card.secret { border-color: var(--warn); background: var(--warn-weak); }

/* A CARD THAT FOLDS. The PHP settings and extension screens are long - nine
   fieldsets of directives, three lists of extensions - and almost every visit
   is about one of them. Same construction as the sidebar's groups and for the
   same reasons (internal/web/php_fold.go): the <summary> is the control, the
   browser supplies the disclosure behaviour, the keyboard and the announced
   state, and the heading inside it is a real <h2> so the grouping survives for
   a screen reader. Nothing here needs script.

   The padding moves onto the summary and the body so that a closed card is a
   heading with the card's own padding around it, rather than a heading with a
   band of empty card under it. */
.foldcard { padding: 0; }
.foldcard > .foldhead {
  display: flex; align-items: center; gap: .45rem;
  padding: var(--card-pad);
  cursor: pointer;
  list-style: none;                 /* Firefox's default triangle */
  border-radius: var(--radius);
}
.foldcard > .foldhead::-webkit-details-marker { display: none; }  /* Safari's */
.foldcard > .foldhead h2 { margin: 0; }
.foldcard > .foldhead:hover { background: var(--bg-sunken); }
.foldcard > .foldhead:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.foldcard[open] > .foldhead { padding-bottom: 0; }
.foldbody { padding: var(--card-pad); }
.foldcaret {
  width: 12px; height: 12px;
  flex: none;
  opacity: .7;
  transform: rotate(0deg);
  transition: transform .12s ease-out;
}
.foldcard[open] > .foldhead .foldcaret { transform: rotate(90deg); }
/* The summary line carries the answer the fold is hiding, so a reader can
   decide whether to open it without opening it. */
.foldnote { margin-left: auto; font-size: .8rem; color: var(--fg-muted); font-weight: 400; }
@media (prefers-reduced-motion: reduce) { .foldcaret { transition: none; } }

.cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: var(--gap);
  margin-bottom: var(--gap);
}
.cards .card { margin: 0; }

.cols2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: var(--gap); align-items: start; }

/* ── The bento ────────────────────────────────────────────────────────

   THE LANDING SURFACES ONLY: /admin/, /reseller/, /app/. Everywhere else a
   list is a list and a table is a table. An accounts list, a usage table or
   a queue is not material for this grid and must not be cut into cards; the
   reader of those came to compare rows, and a card is the one shape that
   stops rows lining up.

   What it is for. The old .cards row gave every card the same width, so a
   sparkline and a single figure were drawn in the same 210 pixels: one was
   cramped and the other was mostly empty, and whatever did not fit the row
   wrapped onto a line of its own with three empty columns beside it. Here a
   card asks for the cells its content needs - .b2/.b3/.b4 across, .r2 down -
   and the row closes.

   Three widths, because the arithmetic has to be exact at every one. A span
   wider than the grid does not clamp itself: CSS grid opens implicit columns
   and the page scrolls sideways, which is the one collapse this must never
   do. So the spans are declared INSIDE the breakpoints and never outside
   them, and every class is redefined at every width:

     under 640px   one column. No span class does anything; the bento is a
                   single honest stack of cards, each as tall as it needs.
     640-1199px    two columns. b2, b3 and b4 all mean "the whole row",
                   because in a two-column grid they can mean nothing else.
     1200px and up four columns, the width where the sidebar still stands
                   and the main column can hold four readable cells.

   Rows carry a floor (--bento-row) so a one-figure card is not a letterbox
   beside a chart, and a ceiling of auto so a card that has more to say grows
   rather than scrolling inside itself. An alert list that scrolls in a box
   hides the alert that has not been read yet. */

.bento {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-auto-rows: auto;
  gap: var(--bento-gap);
  margin-bottom: var(--bento-gap);
  align-items: stretch;
}

.bento > .card {
  margin: 0;
  min-width: 0;          /* a long address wraps; it never widens its column */
  border-radius: var(--bento-radius);
  padding: var(--bento-pad);
  box-shadow: var(--bento-lift);
}
/* Generous internal rhythm means the padding is the same at the bottom as at
   the top. Every block in this panel carries its own margin-bottom, so the
   last one has to give it back or the card is lopsided by .6rem. */
.bento > .card > :last-child { margin-bottom: 0; }

/* A card that is itself a destination. None of the landing cards is one
   today; the rule is here so the first one that becomes one is focusable and
   visibly so, rather than a div someone remembered to add a tabindex to. */
.bento > a.card { display: block; color: var(--fg); text-decoration: none; }
.bento > a.card:hover { border-color: var(--accent); text-decoration: none; }
.bento > .card:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* and a card holding the focused control says so, so a keyboard reader can
   see which box they are in without the ring being the only clue. */
.bento > .card:has(:focus-visible) { border-color: var(--accent); }

@media (min-width: 640px) {
  .bento { grid-template-columns: repeat(2, minmax(0, 1fr)); grid-auto-rows: minmax(var(--bento-row), auto); }
  .bento > .b2, .bento > .b3, .bento > .b4 { grid-column: 1 / -1; }
  .bento > .r2 { grid-row: span 2; }
  /* A full-width card's definition list gets two pairs to a line rather than
     one pair and half a card of paper. */
  .bento > .b4 .kv { grid-template-columns: repeat(2, minmax(120px, auto) 1fr); }
}

@media (min-width: 1200px) {
  .bento { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .bento > .b2 { grid-column: span 2; }
  .bento > .b3 { grid-column: span 3; }
  .bento > .b4 { grid-column: 1 / -1; }
}

/* A HEADLINE THAT IS NOT A FIGURE. "Overselling off" and 152.160.206.6 were
   both set at 1.9rem because the card they sit in is the card that usually
   holds a number. The address overflowed its card on the customer's first
   screen, and the phrase read as a score. A word or an address is set at
   reading size and allowed to break anywhere, so no value can push a cell
   wider than its column again. */
.stat .big { overflow-wrap: anywhere; }
.stat .big.text {
  font-size: 1.15rem; font-weight: 600; letter-spacing: 0;
  line-height: 1.35;
}

.stat .big { font-size: 1.9rem; font-weight: 600; letter-spacing: -.02em; margin: .1rem 0 .2rem; }
.stat .unit { font-size: .9rem; color: var(--fg-muted); margin-left: .1rem; }

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

.tblwrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--bg-raised); }
.card .tblwrap { border: 0; border-radius: 0; }

.tbl { width: 100%; border-collapse: collapse; font-size: .86rem; }
.tbl th, .tbl td { padding: var(--cell-y) var(--cell-x); text-align: left; vertical-align: middle; border-bottom: 1px solid var(--line-soft); }
.tbl thead th {
  position: sticky; top: 0;
  background: var(--bg-sunken); color: var(--fg-muted);
  font-weight: 600; font-size: .76rem; text-transform: uppercase; letter-spacing: .04em;
  white-space: nowrap; border-bottom: 1px solid var(--line);
}
.tbl tbody tr:last-child th, .tbl tbody tr:last-child td { border-bottom: 0; }
.tbl.striped tbody tr:hover { background: var(--bg-hover); }
.tbl.dense th, .tbl.dense td { padding: .34rem .55rem; }
.tbl .narrow { min-width: 150px; }
.tbl .sparkcell { width: 120px; }
.tbl tr.rowwarn { background: var(--warn-weak); }
/* A flagged row is tinted, and a critical one carries a bar on the leading
   edge as well: colour alone is not a signal every reader receives, and the
   stripe survives a colour-blind eye and a printout. */
.tbl tr.rowcrit { background: var(--crit-weak); }
.tbl tr.rowcrit > *:first-child { box-shadow: inset var(--rail) 0 0 var(--crit); }
.tbl tr.rowwarn > *:first-child { box-shadow: inset var(--rail) 0 0 var(--warn); }
/* The explanation under a flagged row: same tint, no stripes of its own, so
   the pair reads as one block rather than two rows. */
.tbl tr.reasonrow td { padding-top: 0; color: var(--fg-muted); border-top: 0; }
.tbl tr.reasonrow.rowcrit td, .tbl tr.reasonrow.rowwarn td { font-weight: 500; }
.usage td, .usage th { vertical-align: middle; }

/* ------------------------------------------------------------- controls */

.btn {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: .38rem .75rem;
  font: inherit; font-size: .85rem; font-weight: 500;
  color: var(--fg); background: var(--bg-raised);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  cursor: pointer; white-space: nowrap;
}
.btn:hover { background: var(--bg-hover); text-decoration: none; }
.btn.small { padding: .22rem .55rem; font-size: .8rem; }
.btn.primary { background: var(--accent); border-color: var(--accent); color: var(--accent-fg); }
.btn.primary:hover { filter: brightness(1.08); }
.btn.warn { border-color: var(--warn); color: var(--warn-ink); }
.btn.danger { border-color: var(--crit); color: var(--crit-ink); }
.btn.disabled, .btn[aria-disabled="true"] { opacity: .45; pointer-events: none; }

.btnrow { display: flex; flex-wrap: wrap; gap: .5rem; }
.inlineform { display: inline-flex; align-items: center; gap: .35rem; margin: 0; }

input[type="text"], input[type="email"], input[type="search"], input[type="number"], select {
  font: inherit; font-size: .86rem;
  padding: .35rem .5rem;
  color: var(--fg); background: var(--bg-raised);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  max-width: 100%;
}
input:disabled, select:disabled { opacity: .6; }

/* A plain multi-line field, for pasted text the panel does not edit: a PEM
   certificate, a key, a chain. It is monospace because those are fixed-width
   base64 blocks whose line breaks are load bearing, and it does not resize
   horizontally because a wider-than-the-page textarea is what makes a pasted
   certificate look truncated when it is not. */
textarea.paste {
  font-family: var(--mono);
  font-size: .78rem; line-height: 1.35;
  padding: .4rem .5rem;
  color: var(--fg); background: var(--bg-raised);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  width: 100%; max-width: 100%; resize: vertical;
}

/* The two names for the same thing. .filterbar had no rule at all, so on
   three screens the search form fell back to the browser's default layout:
   a 20-character input beside a button whose label was a whole sentence. */
.searchbar, .filterbar {
  display: flex; flex-wrap: wrap; gap: .5rem; align-items: center; margin: .8rem 0;
}
.searchbar input, .filterbar input[type="search"] { flex: 1 1 18rem; min-width: 0; }

.form { display: grid; gap: .9rem; max-width: 660px; }
.form.inline { max-width: none; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); align-items: end; }
.field { display: grid; gap: .25rem; min-width: 0; }
.field > label { font-size: .82rem; font-weight: 550; color: var(--fg-muted); }
.field.bad input, .field.bad select { border-color: var(--crit); }
.field.check { display: flex; align-items: center; gap: .45rem; }
.field.check > label { font-weight: 400; color: var(--fg); }
.checkrow { display: flex; flex-wrap: wrap; gap: 1rem; }

/* THE EXTENSION SELECTOR. `.checklist` and the `.fieldname`/`.fieldhelp` pair
   inside it had no rule at all, so the browser drew a bulleted list whose
   checkbox floated to the right of an empty first grid row with the extension
   name underneath it - a control the reader has to aim at, next to a name that
   does not look like its label. Found on the rendered page, not in the
   template, which reads correctly.

   The row is a two-column grid: the box, then the name over its one-line
   explanation, so the box lines up with the name it switches. */
.checklist { list-style: none; margin: 0 0 .9rem; padding: 0; }
.checklist > li { border-bottom: 1px solid var(--line-soft); }
.checklist > li:last-child { border-bottom: 0; }
.checklist .field {
  grid-template-columns: auto minmax(0, 1fr);
  align-items: baseline;
  gap: .1rem .6rem;
  padding: .45rem .1rem;
  cursor: pointer;
}
.checklist .field.disabled { cursor: default; opacity: .65; }
.checklist .field > input[type="checkbox"] { grid-row: 1 / span 2; align-self: center; }
.checklist .field > .fieldname { grid-column: 2; }
.checklist .field > .fieldhelp { grid-column: 2; }
.checklist .field > .badge { grid-column: 2; justify-self: start; margin-top: .2rem; }

/* The directive name over its explanation, on both PHP screens. */
.fieldname { font-size: .84rem; font-weight: 550; }
.fieldhelp { font-size: .78rem; color: var(--fg-faint); }
.hint { margin: 0; font-size: .78rem; color: var(--fg-faint); }
.actions { display: flex; gap: .6rem; align-items: center; }

fieldset { border: 1px solid var(--line-soft); border-radius: var(--radius); padding: .8rem .9rem; margin: 0; }
legend { font-size: .78rem; text-transform: uppercase; letter-spacing: .05em; color: var(--fg-muted); padding: 0 .35rem; }
fieldset.grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: .9rem; }

/* A form wider than a single column of prose: the fieldsets inside it do the
   column work, so the form itself only stacks them. */
.form.wide { max-width: none; gap: 1rem; }
.form.wide fieldset { display: grid; gap: .8rem; padding: .3rem .95rem 1rem; }
.grid2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: .9rem; }
.grid3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: .9rem;
         align-items: start; }

/* A label for a group of inputs rather than one, so it is not a <label>. */
.fieldlabel { font-size: .82rem; font-weight: 550; color: var(--fg-muted); }
.rangerow { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.rangerow input { flex: 1 1 180px; min-width: 0; }
.rangesep { color: var(--fg-faint); }

/* A card heading with its own action on the right. */
.cardhead {
  display: flex; align-items: center; justify-content: space-between;
  gap: 1rem; flex-wrap: wrap; margin-bottom: .6rem;
}
.cardhead h2 { margin: 0; }
.cardhead form { margin: 0; }

.btnrow { align-items: center; }

/* --------------------------------------------------------------- pieces */

.badge {
  display: inline-block; padding: .1rem .45rem;
  font-size: .75rem; font-weight: 600; border-radius: 999px;
  background: var(--bg-sunken); color: var(--fg-muted);
  white-space: nowrap;
}
.badge.ok   { background: var(--ok-weak);   color: var(--ok-ink); }
.badge.warn { background: var(--warn-weak); color: var(--warn-ink); }
.badge.crit { background: var(--crit-weak); color: var(--crit-ink); }
.badge.busy { background: var(--busy-weak); color: var(--busy-ink); }

.tag {
  display: inline-block; padding: .05rem .4rem;
  font-size: .74rem; font-family: var(--mono);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  color: var(--fg-muted);
}
.tags { display: inline-flex; gap: .35rem; align-items: center; }

.banner {
  padding: .55rem .8rem; margin: 0 0 1rem;
  border: 1px solid var(--line); border-left-width: 3px;
  border-radius: var(--radius-sm);
  background: var(--bg-raised);
  display: flex; gap: .6rem; align-items: center; flex-wrap: wrap;
}
.banner.ok   { border-left-color: var(--ok);   background: var(--ok-weak); }
.banner.warn { border-left-color: var(--warn); background: var(--warn-weak); }
.banner.err  { border-left-color: var(--crit); background: var(--crit-weak); }
.banner.demo { border-left-color: var(--busy); background: var(--busy-weak); font-size: .82rem; }

.note {
  margin: .7rem 0 0; padding-left: .7rem;
  border-left: 2px solid var(--line);
  color: var(--fg-muted); font-size: .82rem;
}

.empty { color: var(--fg-faint); font-style: italic; padding: 1rem 0; margin: 0; }

/* The api's own sentence on an error page: the reason, not the category. */
.errorreason {
  margin: .6rem 0 0; padding: .55rem .7rem;
  background: var(--warn-weak, var(--bg-soft)); border-radius: var(--radius);
  color: var(--fg); font-size: .9rem;
}

/* A log or command output block. It scrolls inside itself in both directions:
   a log line is arbitrarily long and must not widen the page, and the tail is
   arbitrarily tall and must not push the controls off the screen. */
.logbox {
  margin: .7rem 0 0; padding: .7rem .8rem; max-height: 26rem;
  overflow: auto; background: var(--bg-sunk, var(--bg)); border: 1px solid var(--line);
  border-radius: 6px; font-family: var(--mono); font-size: .78rem; line-height: 1.5;
  white-space: pre; tab-size: 4;
}

.kv { display: grid; grid-template-columns: minmax(120px, auto) 1fr; gap: .25rem .9rem; margin: 0; }
.kv dt { color: var(--fg-muted); font-size: .82rem; }
.kv dd { margin: 0; }
.kv.tight { gap: .1rem .8rem; }

.plainlist, .sitelist, .joblist, .alerts { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }

/* The delegated-features list: fourteen one-line answers that used to run
   down the left edge of a full-width card as browser bullets. Each is a
   badge carrying both a tint and the word "on" or "off", so the columns cost
   nothing in meaning. */
.featurelist { list-style: none; margin: 0 0 .6rem; padding: 0; display: grid; gap: .35rem 1.2rem;
  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr)); }
.featurelist li { display: flex; align-items: center; gap: .5rem; }
.sitelist li, .joblist li {
  display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding: .4rem 0; border-bottom: 1px solid var(--line-soft);
}
.sitelist li:last-child, .joblist li:last-child { border-bottom: 0; }
.joblist li { display: block; }
.joblist li > * { margin-right: .5rem; }

.alerts .alert { display: flex; gap: .6rem; padding: .5rem 0; border-bottom: 1px solid var(--line-soft); }
.alerts .alert:last-child { border-bottom: 0; }
.alerts .alert p { margin: .1rem 0; color: var(--fg-muted); font-size: .85rem; }
.alerts .dot { width: 8px; height: 8px; border-radius: 50%; margin-top: .45rem; flex: 0 0 auto; background: var(--fg-faint); }
.alerts .alert.info .dot { background: var(--busy); }
.alerts .alert.warn .dot { background: var(--warn); }
.alerts .alert.crit .dot { background: var(--crit); }
.alertlink { font-size: .82rem; }

.pager { display: flex; align-items: center; gap: .8rem; padding: .6rem .2rem 0; flex-wrap: wrap; }
.pagerbtns { margin-left: auto; display: flex; align-items: center; gap: .5rem; }
.pagenum { font-size: .82rem; color: var(--fg-muted); }

.errorcard { max-width: 480px; }
.errorcode { font-size: 2.4rem; font-weight: 700; color: var(--fg-faint); margin: 0; }

/* A REFUSAL, not a failure. The card carries the api's own sentence as its
   heading (pages/shared/error.html), so the heading is set at reading size:
   it is a statement about what the customer just asked for, not a headline
   about the page. The tinted panel comes from .errorreason, which the same
   element also carries. */
.errorcard.refusal { max-width: 34rem; }
.errorcard.refusal .pagetitle {
  font-size: .95rem; font-weight: 500; line-height: 1.5;
  margin-top: 0;
}
/* The number recedes beside a sentence the customer can act on. It is still
   on the page - support asks for it first - but it stops being the headline
   of a screen that is not about a number. */
.errorcard.refusal .errorcode {
  font-size: .78rem; font-weight: 600; letter-spacing: .04em;
  color: var(--fg-faint); margin: 0 0 .35rem;
}
/* The card's own actions row. Without it the Back control sits against the
   sentence with no air. */
.errorcard .actions { margin: .9rem 0 0; }

/* A control that ships hidden and is shown by the script must actually be
   hidden: [hidden] in the UA sheet is a bare attribute selector, and .btn's
   display: inline-flex beats it. Everything on these pages that starts hidden
   is either a .btn or inside one, so the rule is written once here rather than
   per component. */
[hidden] { display: none !important; }

.placeholder { text-align: center; padding: 2rem 1rem; }
.placeholdermark { width: 56px; height: 56px; fill: none; stroke: var(--fg-faint); stroke-width: 1.5; stroke-linecap: round; margin-bottom: .6rem; }

.secretvalue {
  font-size: 1.05rem; letter-spacing: .02em;
  padding: .5rem .7rem; margin: .4rem 0;
  background: var(--bg-raised); border: 1px dashed var(--warn);
  border-radius: var(--radius-sm);
  word-break: break-all; user-select: all;
}

/* --------------------------------------------------------------- charts */

.spark { width: 100%; height: 40px; display: block; overflow: visible; }
.sparkline { fill: none; stroke: var(--accent); stroke-width: 1.6; vector-effect: non-scaling-stroke; }
.sparkarea { fill: var(--accent); opacity: .12; stroke: none; }
.sparkdot { fill: var(--accent); }

/* The throughput chart. Two series on one scale: inbound as a filled area
   because it is the bulk figure, outbound as a line over it because on a web
   server it is the smaller one and would otherwise be hidden underneath. */
.traffic { width: 100%; height: 220px; display: block; }
.traffic .grid { stroke: var(--line-soft); stroke-width: 1; vector-effect: non-scaling-stroke; }
.traffic .axis { fill: var(--fg-faint); font-size: 9px; font-family: inherit; }
.traffic .inarea { fill: var(--accent); opacity: .14; stroke: none; }
.traffic .inline { fill: none; stroke: var(--accent); stroke-width: 1.6; vector-effect: non-scaling-stroke; }
.traffic .outline { fill: none; stroke: var(--ok); stroke-width: 1.6; vector-effect: non-scaling-stroke; }
/* The percentile is a claim about the series, not part of it: dashed, so it
   never reads as data that was measured. */
.traffic .p95 { stroke: var(--warn); stroke-width: 1.2; stroke-dasharray: 5 4; vector-effect: non-scaling-stroke; }
.traffic .p95.out { stroke: var(--crit); }

.legend { display: flex; align-items: center; gap: .4rem; margin: 0;
          font-size: .78rem; color: var(--fg-muted); flex-wrap: wrap; }
.legend .key { width: .8rem; height: .2rem; border-radius: 2px; background: var(--accent); }
.legend .key + span, .legend .key { margin-left: .5rem; }
.legend .key:first-child { margin-left: 0; }
.legend .key.out { background: var(--ok); }
.legend .key.p95 { background: var(--warn); }

/* A table of figures: digits line up in columns, so they get tabular numerals
   and right alignment. Ragged decimal columns are read one row at a time. */
.numbers td.r, .numbers th.r { text-align: right; }
.numbers td { white-space: nowrap; }

.windowpick { display: flex; gap: .3rem; margin: 0; }
.windowpick .btn.on { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }

.bars { width: 100%; height: 52px; display: block; }
.bar { fill: var(--accent); }
.bar.warn { fill: var(--warn); }
.bar.crit { fill: var(--crit); }

.meter { display: grid; gap: .2rem; min-width: 130px; }
.meterbar {
  height: 6px; border-radius: 999px;
  background: var(--bg-sunken); border: 1px solid var(--line-soft);
  overflow: hidden;
}
.meterfill { display: block; height: 100%; background: var(--ok); border-radius: 999px; }
.meterfill.warn { background: var(--warn); }
.meterfill.crit { background: var(--crit); }
.metertext { font-size: .78rem; color: var(--fg); white-space: nowrap; }

/* Quota widths come from bucket classes, not inline styles: the panel's CSP
 * has no 'unsafe-inline', and this way the bar is correct with JS disabled. */
.p0{width:0}.p5{width:5%}.p10{width:10%}.p15{width:15%}.p20{width:20%}
.p25{width:25%}.p30{width:30%}.p35{width:35%}.p40{width:40%}.p45{width:45%}
.p50{width:50%}.p55{width:55%}.p60{width:60%}.p65{width:65%}.p70{width:70%}
.p75{width:75%}.p80{width:80%}.p85{width:85%}.p90{width:90%}.p95{width:95%}
.p100{width:100%}

/* -------------------------------------------------------------- htmx bits */

[data-hx-indicator] { opacity: .55; }
.hx-busy { opacity: .55; transition: opacity .1s linear; }

/* --------------------------------------------------------------- mobile */

@media (max-width: 860px) {
  .shell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "top" "main";
  }
  .navtoggle { display: inline-flex; }
  .sidebar {
    position: fixed; inset: var(--topbar-h) auto 0 0;
    width: var(--sidebar-w); z-index: 30;
    transform: translateX(-100%);
    transition: transform .16s ease-out;
  }
  .sidebar.open { transform: translateX(0); }
  /* On a phone the switcher keeps only the chip you are on. */
  .faceswitch a.facetag { display: none; }
  .main { padding: .9rem .8rem 2rem; }
  .cols2 { grid-template-columns: minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

/* ---------------------------------------------------------------- login
   Served by internal/api (GET /login), not by the UI's layout, so these
   classes must stand on their own: no sidebar, no topbar, no JS. */
.login-body {
  display: grid;
  place-items: center;
  min-height: 100vh;
  margin: 0;
  background: var(--bg);
}
.login-card {
  width: min(22rem, calc(100vw - 2rem));
  padding: 2rem;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.login-title {
  margin: 0 0 1.25rem;
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-align: center;
  color: var(--fg);
}
.login-error {
  margin: 0 0 1rem;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
  background: var(--crit-weak);
  color: var(--crit-ink);
  font-size: 0.875rem;
}
.login-form { display: grid; gap: 0.35rem; }
.login-form label {
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--fg-muted);
}
.login-form label + input { margin-bottom: 0.75rem; }
.login-form input {
  padding: 0.55rem 0.7rem;
  font: inherit;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
.login-form input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
.login-form button {
  margin-top: 0.5rem;
  padding: 0.6rem 1rem;
  font: inherit;
  font-weight: 500;
  color: var(--accent-fg);
  background: var(--accent);
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.login-form button:hover { filter: brightness(1.08); }

/* ---------------------------------------------------------------------------
 * File manager (internal/web/files.go).
 *
 * No style attribute anywhere here either: the CSP has no 'unsafe-inline', so
 * anything that would have been an inline width is a class instead.
 * ------------------------------------------------------------------------- */

.crumbs { margin: .6rem 0 1rem; }
.crumblist {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: .25rem .1rem; margin: 0; padding: 0; list-style: none;
  font-size: .92rem;
}
.crumblist li + li::before {
  content: "/"; margin: 0 .35rem; color: var(--fg-faint);
}
.crumblist [aria-current="page"] { font-weight: 600; }

.filesquota .cardlinks,
.cardlinks { display: flex; flex-wrap: wrap; gap: .5rem; margin: .6rem 0 0; }

.filestoolbar {
  display: flex; flex-wrap: wrap; align-items: flex-end; gap: .75rem 1.25rem;
}
.filestoolbar form { display: flex; align-items: center; gap: .4rem; margin: 0; }
.filestoolbar input[type="text"],
.filestoolbar input[type="search"] { min-width: 12rem; }
.toolbarlinks { display: flex; gap: .5rem; margin: 0; }
.btn.on { border-color: var(--accent); color: var(--accent); background: var(--accent-weak); }

.uploadcard.dragover { border-color: var(--accent); background: var(--accent-weak); }
.uploadform { display: flex; flex-wrap: wrap; align-items: center; gap: .6rem; }
.progressline { margin: .5rem 0 0; font-size: .88rem; color: var(--fg-muted); }

.filestable td, .filestable th { vertical-align: top; }
.filestable .pick { width: 1.8rem; }
.filestable .num { text-align: right; white-space: nowrap; }
.filestable .fname { word-break: break-all; }
/* Not scoped to .filestable: any table cell that holds more than one control
   wants the same wrapping row, and the database list is the second one. */
.rowactions {
  display: flex; flex-wrap: wrap; gap: .35rem; align-items: flex-start;
}
.rowmenu > summary { list-style: none; cursor: pointer; display: inline-block; }
.rowmenu > summary::-webkit-details-marker { display: none; }
.rowform {
  display: flex; gap: .35rem; align-items: center;
  margin-top: .35rem; padding: .4rem;
  background: var(--bg-sunken); border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
}

.ftype {
  display: inline-block; padding: .05rem .4rem; border-radius: 999px;
  font-size: .75rem; background: var(--bg-sunken); color: var(--fg-muted);
  white-space: nowrap;
}
.ftype.dir { background: var(--accent-weak); color: var(--accent); }
.ftype.archive { background: var(--warn-weak); color: var(--warn-ink); }
.ftype.link { background: var(--busy-weak); color: var(--busy-ink); }
.ftype.special { background: var(--crit-weak); color: var(--crit-ink); }

.bulkform { display: flex; flex-wrap: wrap; gap: 1rem; }
.bulkgroup {
  flex: 1 1 15rem; margin: 0; padding: .7rem .8rem;
  border: 1px solid var(--line-soft); border-radius: var(--radius);
}
.bulkgroup legend { padding: 0 .3rem; font-size: .82rem; color: var(--fg-muted); }
.bulkgroup label { display: block; font-size: .82rem; margin-bottom: .2rem; }
.bulkgroup label.check { display: flex; align-items: center; gap: .35rem; margin: .4rem 0; }
.bulkgroup input[type="text"], .bulkgroup select { width: 100%; margin-bottom: .5rem; }
.bulkgroup.danger { border-color: var(--crit); }

/* The editor: a gutter and a textarea, side by side, scrolling together. */
.editor {
  display: flex; align-items: stretch;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg-sunken); overflow: hidden;
}
.editor .gutter {
  margin: 0; padding: .6rem .5rem; list-style: none;
  text-align: right; color: var(--fg-faint);
  font-family: var(--mono); font-size: .82rem; line-height: 1.5;
  background: var(--bg-hover); border-right: 1px solid var(--line-soft);
  overflow: hidden; user-select: none; min-width: 2.6rem;
}
.editor textarea.code {
  flex: 1; min-height: 26rem; padding: .6rem .7rem;
  border: 0; outline-offset: -2px; resize: vertical;
  font-family: var(--mono); font-size: .82rem; line-height: 1.5;
  color: var(--fg); background: var(--bg-raised);
  white-space: pre; overflow: auto; tab-size: 4;
}
.editoractions { display: flex; flex-wrap: wrap; gap: .5rem; margin: .7rem 0 .3rem; }
.kv.inline { display: flex; flex-wrap: wrap; gap: .3rem 1.4rem; margin: 0; }
.kv.inline dt { color: var(--fg-muted); font-size: .82rem; }
.kv.inline dd { margin: 0 1rem 0 0; }

/* Sidebar footer: the reader's own entries (password, sign out) sit apart from
   the working menu, with a rule above them, so "change my password" is on the
   screen instead of behind a click on your own name. */
.navlist-foot {
  margin-top: auto;
  padding-top: 0.5rem;
  border-top: 1px solid var(--line-side);
}

/* ══════════════════════════════════════════════════════════════════════
   Themes: the structure the palettes cannot carry
   ══════════════════════════════════════════════════════════════════════
   Almost everything that separates Signal from Daylight is already a token
   - density, radius, shadow, the three typefaces - and is applied by the
   component rules above. What is left here is the handful of decisions that
   are structural rather than numeric, and each one is a rule about the
   THEME, never about a screen. Nothing below adds an element, removes one
   or moves one in the DOM, which is what keeps two themes from becoming two
   front-ends to maintain.

   The theme is chosen by the reader and written server-side onto <html
   data-skin>, so the page never flashes one theme and settles into another.
   State colour is NOT a theme decision: "this account is over quota" looks
   the same wherever you are standing.
   ══════════════════════════════════════════════════════════════════════ */

/* ── Signal ──────────────────────────────────────────────────────────
   Colour is information. The ground is neutral, the accent appears only on
   things you can press, and severity gets a rail down the left of any row
   that needs attention (the rail lives in the component rules; --rail is
   what turns it on). Condensed labels keep dense tables legible: the
   column headings, the small-caps labels and the legends move to Archivo
   Narrow, which buys about two characters per heading at the same size. */

.tbl thead th,
.toolgroup h3,
h3,
legend,
.field > label,
.fieldlabel,
.metertext,
.badge,
.tag {
  font-family: var(--label);
  letter-spacing: var(--label-tracking);
}

/* ── Daylight ────────────────────────────────────────────────────────
   The one deliberate risk in the approved direction: a serif in a control
   panel. It is confined to the things that name a page or a card - never to
   a control, a figure or a table - so the page reads as a document without
   any of the machine text changing face. */

h1, h2, .pagetitle, .cardhead h2, .login-title, .errorcard .pagetitle {
  font-family: var(--display);
}

/* Daylight's sidebar is paper rather than a dark object, so the rail's own
   text colours become the page's and the active item is drawn with the
   accent wash instead of a solid accent block. */
[data-skin="daylight"] .sidebar { border-right: 1px solid var(--line); }
[data-skin="daylight"] .navlink.on {
  background: var(--accent-weak);
  color: var(--fg-side-on);
}
[data-skin="daylight"] .navlink.on .navico { color: var(--fg-side-on); }
[data-skin="daylight"] .navlist-foot { border-top-color: var(--line); }
[data-skin="daylight"] .topbar { border-bottom: 1px solid var(--line); }

/* WHAT TO DO NEXT. A row of two or three, above the figures, and absent
   entirely when there is nothing - see next_actions.go for why there is no
   dismiss control. The link sits at the bottom right of its own card so the
   row reads as three objects with three doors rather than as a paragraph. */
.nextup { margin-bottom: var(--gap); }
.nextup > h2 {
  font-family: var(--label);
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: var(--label-tracking);
  text-transform: uppercase;
  color: var(--fg-faint);
  margin: 0 0 .55rem;
}
/* THREE FIXED COLUMNS, not auto-fit. With auto-fit a single action stretched
   across the whole page, which made one small job look like the headline of
   the account. A third of the row is the right size for one thing to do, and
   the row holds at most three (next_actions.go). */
.nextup-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--bento-gap, var(--gap));
}
@media (min-width: 60rem) {
  .nextup-row { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.nextup-row .card { margin: 0; display: flex; flex-direction: column; }
/* h3 is a small uppercase label everywhere else in this panel, which is right
   for a sub-heading inside a card of data and wrong for the title of a card
   that IS the thing. The heading LEVEL stays h3 - the document order is h1
   greeting, h2 section, h3 card - and only its appearance is reset. */
.nextcard h3 {
  margin: 0 0 .2rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--fg);
  text-transform: none;
  letter-spacing: 0;
}
.nextcard .nextsubject { margin: 0 0 .45rem; font-size: .82rem; color: var(--fg-muted); }
.nextcard .muted { margin: 0 0 .9rem; }
/* margin-top:auto pins the link to the bottom edge however tall the card's
   neighbour grows, which is what keeps the three links on one line. */
.nextcard .nextgo {
  margin-top: auto;
  align-self: flex-end;
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--accent);
}
.nextcard .nextgo:hover { text-decoration: underline; }
.nextcard .nextgo .ico { width: .95em; height: .95em; }
/* Urgent is a left edge in the warning colour, not a coloured card. A filled
   card shouts at the same volume as the alarm panel further down and would be
   the loudest thing on a page whose job is to be calm. */
.nextcard.urgent { border-left: 3px solid var(--warn); }
.nextcard.urgent { border-left-color: var(--warn); }

/* A CARD HAS NO OUTLINE. Customer face 2026-10-02, the rest of the panel
   2026-10-04.

   It had a hairline AND a shadow, which is two devices doing one job: the
   hairline drew the edge and the shadow drew it again half a pixel outside.
   Across a dozen cards that reads as a wireframe of the layout rather than as
   a set of objects.

   The shadow alone can only carry it when the card is lighter than what is
   behind it, which is why each theme's GROUND moved in the same pass: a white
   card on Daylight's old cream, or on Signal's old near-white, has no edge to
   lose. The two changes are one change, in both themes.

   IT IS NO LONGER SCOPED TO A SKIN, which is the point of the second pass:
   the rule is about what a card is, not about which palette is on. Each theme
   still sets its own --bento-lift, so Signal's is a whisper over twenty cards
   and Daylight's a soft lift over six, and Instrument drops it entirely -
   a shadow on a near-black ground is invisible, and there the step between
   --bg and --bg-raised is the edge.

   THREE EXCEPTIONS, all information rather than decoration: `.danger` and
   `.secret` are drawn in a semantic colour, `.urgent` keeps its left rail,
   and a table keeps its outline because a data table's edge is structural. */
.card:not(.danger):not(.secret):not(.urgent) {
  border-color: transparent;
}
/* The urgent next-action keeps ONE edge: the left one, in the warning colour.
   Dropping the other three is what makes it read as a marked card rather than
   as a boxed one. */
.nextcard.urgent {
  border-top-color: transparent;
  border-right-color: transparent;
  border-bottom-color: transparent;
}
.card:not(.danger):not(.secret),
.bento > .card:not(.danger):not(.secret) {
  box-shadow: var(--bento-lift);
}
/* The sidebar and the topbar keep their hairlines: they are surfaces the page
   sits beside, not objects on it. */

/* Instrument - Signal in the dark - separates by SURFACE, not by edge. Rows
   on a near-black ground read as one continuous thing, which is what somebody
   scanning an account list all evening wants, and --bg-raised sitting a step
   above --bg is what makes a card distinguishable there.

   This rule used to drop the shadow while the BORDER drew the edge. The
   border is gone now, so what it drops is the only thing left - correct here,
   because a shadow on that ground is invisible anyway, and wrong anywhere
   lighter. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-skin="daylight"]) .card,
  :root:not([data-theme="light"]):not([data-skin="daylight"]) .tblwrap {
    box-shadow: none;
  }
}
:root[data-theme="dark"]:not([data-skin="daylight"]) .card,
:root[data-theme="dark"]:not([data-skin="daylight"]) .tblwrap {
  box-shadow: none;
}

/* ── Tool grid ────────────────────────────────────────────────────────
   The landing page's map of the panel. Tiles are links, the filter is an
   enhancement, and the group headings carry the only structure: the job
   you came to do, not the subsystem that implements it. */
.toolsec { margin-top: 1.5rem; }
.toolhead {
  display: flex; flex-wrap: wrap; gap: .75rem 1rem;
  align-items: center; justify-content: space-between; margin-bottom: .35rem;
}
.toolhead h2 { margin: 0; font-size: 1.05rem; }
.toolsearch input {
  min-width: 16rem; padding: .45rem .7rem;
  font: inherit; color: var(--fg);
  background: var(--bg-raised);
  border: 1px solid var(--line); border-radius: var(--radius);
}
.toolsearch input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.toolgroup { margin-top: 1.1rem; }
.toolgroup[hidden] { display: none; }
.toolgroup h3 {
  margin: 0 0 .5rem; font-size: .72rem; font-weight: 600;
  text-transform: uppercase; letter-spacing: .09em; color: var(--fg-faint);
}
.toolgrid {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: .6rem;
  grid-template-columns: repeat(auto-fill, minmax(13.5rem, 1fr));
}
.tool[hidden] { display: none; }
.tool a {
  display: grid; grid-template-columns: auto 1fr; grid-template-rows: auto auto;
  gap: 0 .65rem; align-items: center;
  height: 100%; padding: .7rem .8rem;
  background: var(--bg-raised); border: 1px solid var(--line);
  border-radius: var(--radius); color: var(--fg); text-decoration: none;
}
.tool a:hover { border-color: var(--accent); background: var(--bg-hover); }
.tool a:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.toolicon {
  grid-row: span 2; display: grid; place-items: center;
  width: 2.1rem; height: 2.1rem; border-radius: var(--radius-sm);
  background: var(--accent-weak); color: var(--accent);
}
.toolico { width: 20px; height: 20px; }
.toolname { font-weight: 600; letter-spacing: -.005em; }
.tooldesc { font-size: .78rem; color: var(--fg-muted); }

/* The multi-account bar (partials/bulk.html). The checkbox column is sized
   like the file manager's so a table that gains selection does not visibly
   shift its other columns. */
.tbl th.pick, .tbl td.pick { width: 1.8rem; }
.bulkbar {
  margin: .6rem 0 1rem; padding: .6rem .8rem;
  border: 1px solid var(--line-soft); border-radius: var(--radius);
  background: var(--bg-sunken);
}
.bulkbar .note, .bulkbar .hint { margin: 0 0 .5rem; }

/* ---------------------------------------------------------- embedded mode

   The panel rendered inside the billing system's frame (internal/web/embedded.go).
   The header and the sidebar are not hidden here - they are not rendered at
   all - so this only has to undo the grid that reserved room for them.

   The padding drops to almost nothing on purpose: the frame already sits
   inside the billing system's own card, and a second 1.2rem gutter reads as a
   box inside a box, which is the visual tell that two products are involved.

   min-height stays auto rather than 100vh. A framed document that insists on
   the full viewport height forces the iframe to scroll internally, and the
   customer then has two scrollbars - the other visual tell. The billing side
   sizes the frame. */
/* NO THEME REACHES THE FRAME.

   The two themes are a decision about apanel's own faces. Inside the billing
   system the panel is one section of somebody else's page, and a section that
   arrives with its own ground, its own accent and its own typeface is the
   loudest possible statement that two products are involved.

   Two mechanisms, because one of them being forgotten must not be a leak:

     1. The layout does not emit data-skin at all when the page is embedded
        (templates/layout/base.html), so every palette above - all four of
        them - is out of reach by selector. TestEmbeddedPageCarriesNoSkin is
        the guard.
     2. This block takes back what the base :root palette would still have
        supplied: the product's typefaces and the product's ground. The
        billing card underneath is the surface, and the reader's own browser
        font is the face.

   Text colour and the three STATE colours stay. "This certificate expired" is
   not branding, and a customer who cannot read the tool has not been served
   by our restraint. See docs/client-area-style.md. */
[data-embedded="1"] {
  --sans:  system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --display: var(--sans);
  --label:   var(--sans);
  --mono:  ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
  --label-tracking: .05em;
  --base-size: 14px;

  --bg:        transparent;
  --bg-raised: transparent;
  --bg-top:    transparent;
  --shadow:    none;

  /* The bento's shape is the ordinary card's inside the frame, and it has no
     lift. A 16px corner and a soft shadow are apanel saying which product
     drew this; the billing system has already put us inside its own card and
     a second rounded, floating object on top of that is the visual tell. */
  --bento-radius: var(--radius);
  --bento-pad:    var(--card-pad);
  --bento-gap:    var(--gap);
  --bento-lift:   none;
}

[data-embedded="1"] .shell.embedded {
  display: block;
  min-height: 0;
}
[data-embedded="1"] .main { padding: 0; }
[data-embedded="1"] body { background: transparent; }

/* THE PAGE HEADING IS PRINTED TWICE, and only one of the two is ours.

   The billing system draws the tool's name above the frame - it has to, the
   frame is one section of a page that also carries a navigation and an
   identity strip - and then this document drew "File manager" again as its own
   <h1>, four pixels below it. Two identical headings in two typefaces is the
   clearest possible statement that two applications are involved.

   It is CLIPPED, not display:none. A document with no heading at all is worse
   for somebody reading it with a screen reader than one with a heading they
   cannot see: the framed document is still a document, and its <h1> is what
   names it in the heading list. Sighted readers get the billing system's copy,
   which says the same words. */
[data-embedded="1"] .pagetitle {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* Inside the frame the cards are the page, not objects floating on a
   background: the billing system already put us inside its own card, and a
   drop shadow on top of that is the second seam. The border stays - it is what
   groups a form with its heading - and the shadow goes. */
[data-embedded="1"] .card { box-shadow: none; }

/* The first thing in the frame sits flush with the top of the frame. Anything
   else reads as a gap the billing system failed to close. */
[data-embedded="1"] .main > :first-child,
[data-embedded="1"] .main > .pagetitle + * { margin-top: 0; }

/* A brand logo replaces the built-in mark; it is sized, never trusted to
   size itself, because the file comes from an operator's config. */
.brandlogo { width: 22px; height: 22px; object-fit: contain; display: block; }

/* The file manager's permissions column. The values are a fixed-width figure
   and want the monospace face; the column HEADING is a word like every other
   heading in the row and must not have it. They were the same class, which is
   why the header sat a different size and a different weight from its
   neighbours. */
.filestable .perms { white-space: nowrap; }
.filestable td.perms { font-family: var(--mono); font-size: .8rem; }

/* A file input renders as a native control the browser paints itself: a grey
   "Choose Files" button in the platform's own font, which is the one element
   on the page that has never taken the panel's styling. Dressing its button
   costs three lines and closes the last obvious seam in the upload card. */
input[type="file"] { font: inherit; font-size: .86rem; max-width: 100%; }
input[type="file"]::file-selector-button {
  font: inherit; font-size: .85rem; font-weight: 500;
  margin-right: .6rem; padding: .3rem .7rem;
  color: var(--fg); background: var(--bg-raised);
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  cursor: pointer;
}
input[type="file"]::file-selector-button:hover { background: var(--bg-hover); }

/* The permission chooser.

   Nine checkboxes laid out the way `ls -l` already taught everybody to read a
   mode - owner, group, others down the side; read, write, execute across the
   top - with the octal underneath for the 755 somebody is pasting out of a
   tutorial. Both are real inputs; see chosenMode for which one wins when they
   disagree.

   The mono face moves from the CELL to the summary. The cell now holds a whole
   form, and typesetting a form's labels in a monospace face was the previous
   rule applied to markup it was never written for. */
.filestable td.perms { font-family: inherit; font-size: inherit; }
.filestable td.perms > .rowmenu > summary { font-family: var(--mono); font-size: .8rem; }
.permsmenu > .rowform { display: block; }
.permgrid { border: 0; margin: 0; padding: 0; min-width: 0; }
.permtable { border-collapse: collapse; font-size: .78rem; }
.permtable th, .permtable td { padding: .1rem .35rem; text-align: center; font-weight: 400; }
.permtable thead th { color: var(--fg-muted); }
.permtable th[scope="row"] { text-align: left; color: var(--fg-muted); padding-right: .6rem; }
.permoctal { display: flex; gap: .4rem; align-items: center; margin: .45rem 0 0; font-size: .8rem; }
.permoctal input { width: 5rem; font-family: var(--mono); }

/* The browser terminal (templates/pages/shared/terminal.html).

   A terminal is the one screen in this panel that must NOT inherit the panel's
   typography. Its whole contract is that column n of every row lines up, so
   the face is the monospace stack and the line height is fixed; a proportional
   fallback would turn every table a program prints into a ragged mess and the
   reader would blame the program.

   The dark ground is not a theme choice either: the escape-code palette the
   renderer implements is the standard ANSI one, and half of it - the blues,
   the bold blacks - is unreadable on white. A terminal that recolours to match
   the panel is a terminal that shows the customer different output from the
   one they would see over ssh somewhere else. */
:root {
  /* The terminal's own two colours are TOKENS rather than literals, because
     the stylesheet's own gate insists on it - and the gate is right here for a
     reason it did not anticipate: these two must NOT follow the panel theme,
     so they have to be declared somewhere a reader can see that the omission
     from the light palette is deliberate. */
  --term-bg: #1b1b1b;
  --term-fg: #d0d0d0;
  --term-focus: #4070c0;
}
#term-screen {
  font-family: var(--mono);
  font-size: .8rem;
  line-height: 1.25;
  white-space: pre;
  overflow: auto;
  max-height: 70vh;
  min-height: 24rem;
  background: var(--term-bg);
  color: var(--term-fg);
  padding: .6rem .7rem;
  border-radius: .25rem;
  tab-size: 8;
}
/* The focus ring is load-bearing here and is deliberately loud: the screen is
   a tabindex target, keystrokes only reach the session while it holds focus,
   and "I typed and nothing happened" is otherwise indistinguishable from a
   dead session. */
#term-screen:focus { outline: 2px solid var(--term-focus); outline-offset: 2px; }
