/* ==========================================================================
   Koddrio Cloud — theme tokens + components
   --------------------------------------------------------------------------
   CHANGE THE ACCENT COLOUR IN ONE PLACE: --accent-rgb below.
   Everything else (hover, soft fills, focus rings, meters) is derived from it.

   Layout is flat by default: sections are a heading plus hairline-separated
   rows. A box (.panel) is only used where it earns its keep — alerts, the
   sticky cost bar, a callout that must be noticed.
   ========================================================================== */

:root {
  /* ---- brand ---- */
  --accent-rgb: 13 118 96;          /* koddrio green   e.g. try 79 70 229 (indigo), 194 65 12 (rust) */
  --accent-on-rgb: 255 255 255;     /* text on top of the accent */

  /* ---- neutrals (light) ----
     surface = the content canvas (white)
     paper   = the page and the site sidebar — one recessed tone
     raised  = fills: panels, meter tracks, hovers                        */
  --surface-rgb: 255 255 255;
  --paper-rgb: 244 242 235;
  --raised-rgb: 243 241 236;
  --soft-rgb: 240 238 231;   /* lightest — dividers between rows            */
  --line-rgb: 224 219 208;   /* structure — section rules, column headers   */
  --edge-rgb: 203 197 185;   /* strongest — the edge of a control           */
  --ink-rgb: 26 25 23;
  --muted-rgb: 108 104 96;

  /* ---- status ---- */
  --ok-rgb: 22 130 98;
  --warn-rgb: 176 118 18;
  --err-rgb: 190 60 48;

  /* ---- the top bar is its own plane: it keeps the same colours in both
     schemes rather than following the page ---- */
  --nav-bg: #16161a;
  --nav-ink: #eceae6;
  --nav-muted: #b8b4ad;

  /* ---- derived ---- */
  --accent: rgb(var(--accent-rgb));
  --accent-soft: color-mix(in srgb, rgb(var(--accent-rgb)) 9%, rgb(var(--surface-rgb)));
  --accent-line: color-mix(in srgb, rgb(var(--accent-rgb)) 24%, rgb(var(--edge-rgb)));
  --accent-hover: color-mix(in srgb, rgb(var(--accent-rgb)) 84%, #000);

  --paper: rgb(var(--paper-rgb));
  --surface: rgb(var(--surface-rgb));
  --raised: rgb(var(--raised-rgb));
  --soft: rgb(var(--soft-rgb));
  --line: rgb(var(--line-rgb));
  --edge: rgb(var(--edge-rgb));
  --ink: rgb(var(--ink-rgb));
  --muted: rgb(var(--muted-rgb));

  --radius: 9px;

  /* ---- type ---------------------------------------------------------
     Swap a pairing by changing these three and the <link> in each page's
     head. Nothing else references a family name.                        */
  --font-ui: 'Plus Jakarta Sans', ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-display: 'DM Serif Display', ui-serif, Georgia, serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  /* ---- grid -----------------------------------------------------------
     Change the grid here and every page follows. Column widths are 1fr —
     nothing is computed. Spans stay in the markup as col-span-* classes.     */
  --frame: 1440px;      /* app's maximum width                                */
  --cols: 10;           /* columns across the frame                           */
  --gutter: 48px;       /* space between columns                              */
  --frame-pad: calc(var(--gutter) / 2);   /* half a gutter, so the sums close */
  --col: calc((var(--frame) - var(--cols) * var(--gutter)) / var(--cols));  /* 96px at full width */

  /* At full width the numbers are exact, not approximate:
       10 columns × 96 + 9 gutters × 48 + 2 pads × 24 = 1440
     Each column therefore occupies a 144px slot (1440 / 10) with the gutter
     split across its edges. Change --gutter and the column follows:
       column = (1440 − gutter − 9 × gutter) / 10                            */

  color-scheme: light;
}

.dark {
  /* The three planes need the same rhythm they have in light mode. There the
     sidebar and the canvas are 13 apart in relative luminance; here the step is
     14, and the top bar sits well below both rather than beside the sidebar.
     Borders and fills are lifted to match the brighter canvas.               */
  --surface-rgb: 33 33 32;   /* canvas   — luminance 33 */
  --paper-rgb: 19 19 18;     /* page + sidebar — 19     */
  --raised-rgb: 43 43 41;
  --soft-rgb: 45 44 42;
  --line-rgb: 60 59 55;
  --edge-rgb: 86 84 78;
  --ink-rgb: 240 238 234;
  --muted-rgb: 156 151 142;

  --ok-rgb: 82 180 138;
  --warn-rgb: 214 162 66;
  --err-rgb: 228 110 96;

  --nav-bg: #08080a;

  --accent-soft: color-mix(in srgb, rgb(var(--accent-rgb)) 17%, rgb(var(--surface-rgb)));
  --accent-hover: color-mix(in srgb, rgb(var(--accent-rgb)) 82%, #fff);

  color-scheme: dark;
}

/* --------------------------------------------------------------------------
   base — painted from plain CSS so the first frame is already correct
   -------------------------------------------------------------------------- */
html { background: var(--paper); }
body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: 15px;
  line-height: 1.5;
  letter-spacing: -0.005em;
  -webkit-font-smoothing: antialiased;
  margin: 0;
}
a { color: inherit; text-decoration: none; }
/* translucent, so it shows on panels tinted with accent-soft as well as on paper */
::selection { background: color-mix(in srgb, var(--accent) 26%, transparent); }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }

/* --------------------------------------------------------------------------
   type
   -------------------------------------------------------------------------- */
.display {
  font-family: var(--font-display);
  font-weight: 400;
  letter-spacing: -0.022em;
  line-height: 1.08;
  font-size: 38px;
}
.sec-title { font-size: 17px; font-weight: 600; letter-spacing: -0.012em; }
.mono { font-family: var(--font-mono); font-size: .92em; }
.muted { color: var(--muted); }
.meta  { color: var(--muted); }            /* same size as body — colour separates it */
.eyebrow {
  font-size: 12px; font-weight: 600; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}
.stat {
  font-size: 26px; font-weight: 600; line-height: 1.05; letter-spacing: -0.025em;
  /* display numbers don't need to align in a column, and some families give
     tabular figures a full-width comma and period, which looks broken */
  font-variant-numeric: proportional-nums;
}
.stat-sm { font-size: 20px; }
.stat u { font-size: 15px; font-weight: 400; color: var(--muted); text-decoration: none; }
.num { font-variant-numeric: tabular-nums; }
.link { color: var(--muted); }
.link:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }
.link-quiet {
  text-decoration: underline;
  text-decoration-style: dotted;
  text-decoration-thickness: 1px;
  text-decoration-color: color-mix(in srgb, currentColor 32%, transparent);
  text-underline-offset: 3px;
}
.link-quiet:hover { text-decoration-color: color-mix(in srgb, currentColor 70%, transparent); }

/* --------------------------------------------------------------------------
   top navigation
   -------------------------------------------------------------------------- */
.topnav { background: var(--nav-bg); color: var(--nav-ink); }
.topnav .bar {
  max-width: var(--frame);
  margin-inline: auto;
  padding-inline: var(--frame-pad);
  height: 58px;
  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: var(--gutter);
  align-items: center;
}
/* near-black, and a hairline so it reads as its own plane and not more page */
.dark .topnav { border-bottom: 1px solid #ffffff14; }
.topnav a { color: var(--nav-muted); }
.topnav a:hover { color: #fff; }
.topnav .navlink { font-size: 15px; font-weight: 500; padding: 8px 12px; border-radius: 7px; }
/* pull the row so the first label — not its hover pill — sits on the grid line;
   the pill is free to bleed into the gutter */
.topnav .navrow { margin-left: -12px; }
.topnav .navlink:hover { background: #ffffff12; }
.topnav .navlink[aria-current="page"] { color: #fff; background: #ffffff14; }
.topnav .iconbtn {
  width: 34px; height: 34px; border-radius: 8px; display: grid; place-items: center;
  color: var(--nav-muted);
}
.topnav .iconbtn:hover { background: #ffffff12; color: #fff; }
.wordmark { font-size: 17px; font-weight: 600; letter-spacing: -0.02em; }
.site-name { font-size: 20px; font-weight: 600; line-height: 1.15; letter-spacing: -0.02em; }


/* --------------------------------------------------------------------------
   the frame — the whole app is one centred column. The page and the sidebar
   share the recessed tone; only the content canvas is white.
   -------------------------------------------------------------------------- */
.frame {
  max-width: var(--frame);
  margin: 0 auto;
  background: var(--surface);
  min-height: calc(100vh - 58px);

  display: grid;
  grid-template-columns: repeat(var(--cols), minmax(0, 1fr));
  gap: var(--gutter);
  padding-inline: var(--frame-pad);
}

/* any nested content/rail split — the same gutter, so its tracks line up */
.split { display: grid; gap: var(--gutter); align-items: start; }

/* main already stretches to the frame's full height, so the footer only needs
   something above it that eats the leftover space. On a page that fills the
   window the spacer collapses and the footer keeps its own top margin. */
.frame > main { display: flex; flex-direction: column; }
.frame > main > .foot-push { flex: 1 1 auto; min-height: 0; }
/* flex stops the last section's margin collapsing into the footer's, so the
   footer's own margin is the whole gap */
.frame > main > .section:has(+ .foot-push) { margin-bottom: 0; }

/* --------------------------------------------------------------------------
   flat structure: sections, rules, rows
   -------------------------------------------------------------------------- */
.section { margin-bottom: 48px; }
.section-head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 12px;
}
.rows { border-top: 1px solid var(--line); }
.rows > * { border-bottom: 1px solid var(--soft); }
/* A list always ends against whitespace or the next section's own heading, so
   the closing rule divides nothing. :last-child covers the static case;
   .is-last is set by the activity filter, because the last element in the DOM
   is often not the last one still on screen. */
.rows > *:last-child,
.rows > .is-last { border-bottom: 0; }

/* Two columns of pairs — the settings block. :last-child only closes the right
   column, so the left one keeps a rule that divides nothing. The block is
   always written in pairs, which makes the final row the last two children. */
@media (min-width: 640px) {
  .rows-2col > *:nth-last-child(-n+2) { border-bottom: 0; }
}
.rule { border-top: 1px solid var(--line); }
/* a cell that leads somewhere — the underline only shows on hover */
.cell-link:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 2px; }


/* a box, used only where one is genuinely needed */
.panel {
  background: var(--raised);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
}
.panel-accent { background: var(--accent-soft); }
.panel-warn   { background: color-mix(in srgb, rgb(var(--warn-rgb)) 8%, var(--surface));
                border-color: color-mix(in srgb, rgb(var(--warn-rgb)) 30%, var(--edge)); }

/* --------------------------------------------------------------------------
   buttons
   -------------------------------------------------------------------------- */
.btn {
  display: inline-flex; align-items: center; gap: 7px;
  height: 40px; padding: 0 14px;
  border: 1px solid var(--edge); border-radius: 8px;
  background: var(--surface); color: var(--ink);
  font-size: 15px; font-weight: 500; white-space: nowrap;
  cursor: pointer; transition: background .12s, border-color .12s, color .12s;
}
.btn:hover { background: var(--raised); border-color: color-mix(in srgb, var(--edge) 55%, var(--ink)); }
.btn-primary { background: var(--accent); border-color: transparent; color: rgb(var(--accent-on-rgb)); }
.btn-primary:hover { background: var(--accent-hover); border-color: transparent; }
.btn-ghost { background: transparent; border-color: transparent; color: var(--muted); }
.btn-ghost:hover { background: var(--raised); color: var(--ink); }
.btn-danger { color: rgb(var(--err-rgb)); border-color: color-mix(in srgb, rgb(var(--err-rgb)) 30%, var(--edge)); }
/* an action that is not available yet looks spent rather than disappearing,
   so it stays where you will look for it */
.btn:disabled { opacity: .45; cursor: default; pointer-events: none; }
.btn-danger:hover { background: color-mix(in srgb, rgb(var(--err-rgb)) 8%, var(--surface)); }
/* Buttons share one fill ramp off the page's own warm greys, so two buttons of
   different weight never read as two different colours:
     .btn          the canvas, outlined
     .btn-sm       --raised
     .btn-strong   --raised a shade deeper
     .btn-primary  the accent
   A small button is filled rather than outlined — it reads as a button without
   competing with the page's primary action. */
.btn-sm {
  height: 34px; padding: 0 11px; border-radius: 7px;
  border: 0; background: var(--raised); color: var(--ink);
}
.btn-sm:hover { border: 0; background: color-mix(in srgb, var(--ink) 7%, var(--raised)); }
.btn-sm.btn-primary { background: var(--accent); color: rgb(var(--accent-on-rgb)); }
.btn-sm.btn-primary:hover { background: var(--accent-hover); }
.btn-sm.btn-ghost { background: transparent; color: var(--muted); }
.btn-sm.btn-ghost:hover { background: var(--raised); color: var(--ink); }
.btn-sm.btn-danger { background: var(--raised); color: rgb(var(--err-rgb)); }
/* a destructive action that is neither the primary nor the obvious one: no
   frame until you reach for it, and the colour still says what it does */
.btn-ghost.btn-danger { background: transparent; border-color: transparent; color: rgb(var(--err-rgb)); }
.btn-ghost.btn-danger:hover {
  color: rgb(var(--err-rgb));
  background: color-mix(in srgb, rgb(var(--err-rgb)) 10%, transparent);
}
/* the action of a pair — a shade deeper on the same ramp, not bolder */
.btn-strong { background: color-mix(in srgb, var(--ink) 8%, var(--raised)); }
.btn-strong:hover { background: color-mix(in srgb, var(--ink) 15%, var(--raised)); }
/* on the sidebar the fill would match the paper it sits on, so use the canvas */
.sidebar .btn-sm { background: var(--surface); }
.sidebar .btn-sm:hover { background: color-mix(in srgb, var(--ink) 6%, var(--surface)); }
/* --------------------------------------------------------------------------
   an icon inside a control takes its size from the control, the way .sidelink
   and .av-system already do. Nothing in the markup says how big an icon is.
   Brand marks are drawn smaller inside their em box, so they get a step up.
   -------------------------------------------------------------------------- */
.btn i, .menuitem i { font-size: 12px; }
.btn .fa-chevron-down { font-size: 10px; }
.btn .fa-brands { font-size: 15px; }
.btn-sm .fa-brands { font-size: 13px; }
.iconbtn i { font-size: 15px; }
.tag i { font-size: 11px; }

.btn-icon { width: 40px; padding: 0; justify-content: center; }
.btn-sm.btn-icon { width: 34px; }

/* --------------------------------------------------------------------------
   form controls
   -------------------------------------------------------------------------- */
.input, .select, .textarea {
  width: 100%; height: 40px; padding: 0 12px;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--edge); border-radius: 8px;
  font-size: 15px; font-family: inherit;
}
.textarea { height: auto; padding: 10px 12px; line-height: 1.5; }
.input::placeholder { color: var(--muted); }
.input:focus, .select:focus, .textarea:focus {
  outline: none; border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.select {
  appearance: none; padding-right: 32px; cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='%23888' d='M2.5 4.5 6 8l3.5-3.5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 10px center; background-size: 12px;
}
.select-sm { height: 34px; }

/* --------------------------------------------------------------------------
   the file editor — blueprints are the one thing in the product that is
   written rather than filled in, so this is the only monospaced control. The
   highlighted copy sits under a transparent textarea; both use the same metrics
   so the caret lands where the glyph is, and the line height is a whole number
   because the current-line band is positioned by multiplying it.
   -------------------------------------------------------------------------- */
.editor {
  display: flex; align-items: stretch; min-height: 0;
  background: var(--raised); border: 1px solid var(--edge); border-radius: 8px;
  overflow: hidden;
}
.editor:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent);
}
.editor-gutter {
  flex: none; overflow: hidden; user-select: none;
  padding: 14px 10px 14px 14px; text-align: right;
  color: color-mix(in srgb, var(--muted) 70%, transparent);
  background: color-mix(in srgb, var(--ink) 3%, var(--raised));
  border-right: 1px solid var(--soft);
}
.editor-gutter b { font-weight: 400; color: var(--ink); }
.editor-body { flex: 1 1 auto; overflow: auto; }
.editor-stack { position: relative; width: max-content; min-width: 100%; }
.editor-gutter, .editor-hl, .editor-input {
  font: 400 12.5px/23px var(--font-mono);
  white-space: pre;
  tab-size: 2;
}
.editor-hl {
  margin: 0; padding: 14px 16px; color: var(--ink);
  position: relative; z-index: 1; pointer-events: none;
}
.editor-input {
  position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%;
  margin: 0; padding: 14px 16px; border: 0; outline: none; resize: none;
  background: transparent; color: transparent; caret-color: var(--ink);
  white-space: pre; overflow: hidden;
}
.editor-input::selection { background: color-mix(in srgb, var(--accent) 26%, transparent); }
/* the line the caret is on, the way an editor marks it */
.editor-cur {
  position: absolute; left: 0; width: 100%; height: 23px; z-index: 0;
  background: color-mix(in srgb, var(--ink) 5%, transparent);
  pointer-events: none;
}
/* one hue for every scalar, lightened in the dark scheme where the brand green
   goes muddy against a dark ground */
:root { --code-scalar: color-mix(in srgb, rgb(var(--accent-rgb)) 88%, var(--ink)); }
.dark  { --code-scalar: color-mix(in srgb, rgb(var(--accent-rgb)) 40%, #dcefe6); }
.tok-key { color: var(--ink); font-weight: 600; }
.tok-str, .tok-num { color: var(--code-scalar); }
.tok-com { color: var(--muted); font-style: italic; }
.tok-dash { color: var(--muted); }

/* a select that reads as a label until it holds a value — for filter rows */
.select-quiet, .filter-more {
  height: 34px; padding: 0 28px 0 10px;
  border: 0; border-radius: 7px; background-color: transparent;
  color: var(--muted); font-size: 15px; font-weight: 500; font-family: inherit;
  cursor: pointer;
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M2.5 4.5 6 8l3.5-3.5z'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 9px center; background-size: 11px;
}
.select-quiet { appearance: none; field-sizing: content; }
.select-quiet:hover { background-color: var(--raised); color: var(--ink); }
.select-quiet:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
.select-quiet:not(:has(option[value=""]:checked)) { color: var(--ink); background-color: var(--raised); }

/* the More filters toggle sits in the same row as the quiet selects, so it
   wears the same shell — declared once, above — and only its chevron flips */
.filter-more:hover { background-color: var(--raised); color: var(--ink); }
.filter-more[aria-expanded="true"] {
  background-color: var(--raised); color: var(--ink);
  background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath fill='%23999' d='M2.5 7.5 6 4l3.5 3.5z'/%3E%3C/svg%3E");
}
.field-label { display: block; font-size: 15px; font-weight: 500; margin-bottom: 4px; }
.field-help { color: var(--muted); margin-top: 4px; line-height: 1.45; }
.setting-row { padding: 18px 0; }
@media (min-width: 900px) {
  /* the form spans the whole 8-column main: label 6, control 2 */
  .setting-row {
    display: grid;
    grid-template-columns: repeat(8, minmax(0, 1fr));
    gap: 16px var(--gutter);
    align-items: start;
  }
  .setting-row > *:first-child     { grid-column: span 6; }
  .setting-row > .setting-control  { grid-column: span 2; }
}

/* --------------------------------------------------------------------------
   spec form — a label column and its values, one hairline per row, no boxes.
   A control carries no border of its own: the row's rule is the only line, and
   hover and focus are what say a value can be edited. Rows hold either one
   value across the width or two side by side, so related sizes sit together.
   -------------------------------------------------------------------------- */
.frow { display: flex; align-items: center; gap: 24px; padding: 8px 0; }
.flabel { width: 118px; flex: none; color: var(--muted); }
/* a row whose control stacks: the label lines up with the control, not with
   the middle of the control plus whatever is written under it */
.frow-top { align-items: flex-start; }
.frow-top .flabel { min-height: 40px; display: flex; flex-direction: column; justify-content: center; }
.frow-top .flabel .field-help { margin-top: 2px; }

/* a section whose rows hold a single value gives the name three of the five
   columns the form spans, so every control starts on the same line down the
   page. Fluid, so it holds below full width too. */
.rows-wide .frow {
  display: grid; grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 0 var(--gutter); align-items: center;
}
.rows-wide .frow-top { align-items: start; }
.rows-wide .flabel { width: auto; grid-column: span 2; }
.rows-wide .fctl { grid-column: span 3; }

/* a follow-up row belongs to one source and shows only while it is chosen */
.rows .frow.srcrow { display: none; }
.rows:has(#n-source option[value="blueprint"]:checked) .frow.srcrow-blueprint,
.rows:has(#n-source option[value="image"]:checked)     .frow.srcrow-image,
.rows:has(#n-source option[value="copy"]:checked)      .frow.srcrow-copy { display: grid; }
.fctl { flex: 1; min-width: 0; display: flex; align-items: center; gap: 8px; }

/* an address and the text that completes it, inside one border */
.affix {
  display: flex; align-items: center; height: 40px; padding-right: 4px;
  background: var(--surface); border: 1px solid var(--edge); border-radius: 8px;
}
.affix:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent); }
.affix { flex: 1; min-width: 0; }
.affix .input { border: 0; background: none; box-shadow: none; padding-right: 2px; min-width: 0; }
.affix .input:focus { border: 0; box-shadow: none; }
.affix > .meta { white-space: nowrap; padding-right: 6px; }


/* toggle */
.switch { position: relative; display: inline-block; width: 40px; height: 23px; flex: none; }
.switch input { position: absolute; opacity: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.switch span {
  position: absolute; inset: 0; border-radius: 999px;
  background: color-mix(in srgb, var(--edge) 65%, var(--muted)); transition: background .15s;
  pointer-events: none;
}
.switch span::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 17px; height: 17px;
  border-radius: 50%; background: #fff; transition: transform .15s;
  box-shadow: 0 1px 2px rgb(0 0 0 / .25);
}
.switch input:checked + span { background: var(--accent); }
.switch input:checked + span::after { transform: translateX(17px); }
.switch input:focus-visible + span { box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 16%, transparent); }
.switch input:disabled + span { opacity: .5; }

/* --------------------------------------------------------------------------
   pills, tags, badges
   -------------------------------------------------------------------------- */
.chip {
  display: inline-flex; align-items: center; gap: 6px;
  height: 34px; padding: 0 12px; border-radius: 999px;
  border: 1px solid var(--edge); background: transparent;
  font-size: 15px; font-weight: 500; color: var(--muted); cursor: pointer;
}
.chip:hover { color: var(--ink); border-color: color-mix(in srgb, var(--edge) 55%, var(--ink)); }
.chip[aria-pressed="true"] {
  background: var(--accent-soft); border-color: var(--accent-line);
  color: color-mix(in srgb, var(--accent) 75%, var(--ink));
}
.tag {
  display: inline-flex; align-items: center; height: 23px; padding: 0 8px;
  border-radius: 5px; background: var(--raised);
  font-size: 12px; font-weight: 500; color: var(--muted); letter-spacing: 0;
  white-space: nowrap; flex: none;
}
/* a tag with a control's height — one that can be removed, so it needs a
   target rather than just a label */
.tag-lg { height: 28px; padding: 0 10px; }
/* a tag is a link to the list filtered by it; it hovers on the button ramp */
a.tag:hover { background: color-mix(in srgb, var(--ink) 7%, var(--raised)); color: var(--ink); }

.badge {
  display: inline-flex; align-items: center; gap: 5px; height: 23px; padding: 0 8px;
  border-radius: 6px; font-size: 12px; font-weight: 600; white-space: nowrap; letter-spacing: 0;
  background: var(--raised); color: var(--muted);
}
.badge-ok    { background: color-mix(in srgb, rgb(var(--ok-rgb)) 10%, transparent);   color: rgb(var(--ok-rgb)); }
/* A badge that opens a row is a column, not a word: label widths differ, and
   left to itself the text after them steps in and out down the list. The cell
   is fixed so every statement starts on the same line; the badge keeps its own
   width inside it. */
.badge-cell { flex: none; width: 60px; }
.badge-warn  { background: color-mix(in srgb, rgb(var(--warn-rgb)) 12%, transparent); color: rgb(var(--warn-rgb)); }
.badge-err   { background: color-mix(in srgb, rgb(var(--err-rgb)) 10%, transparent);  color: rgb(var(--err-rgb));  }
.badge-accent{ background: var(--accent-soft); color: color-mix(in srgb, var(--accent) 78%, var(--ink)); }

.dot { width: 8px; height: 8px; border-radius: 50%; flex: none; background: var(--muted); }
.dot-ok   { background: rgb(var(--ok-rgb)); }
.dot-warn { background: rgb(var(--warn-rgb)); }
.dot-err  { background: rgb(var(--err-rgb)); }

/* --------------------------------------------------------------------------
   avatars — initials, no network round-trip
   -------------------------------------------------------------------------- */
.av {
  --av-size: 36px;
  width: var(--av-size); height: var(--av-size);
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 9999px; flex: none;
  font-size: 13px; font-weight: 600; line-height: 1;
  color: #fff; background: #8a8a8a; user-select: none;
}
/* the account button is the one avatar that does not sit beside text — it sits
   in a 58px bar, where the standard size crowds the rule above it */
.av-bar { --av-size: 30px; font-size: 11px; }
.av-lg { --av-size: 46px; font-size: 17px; }

.av-1 { background: #3f6f8f; } .av-2 { background: #8a5a7a; } .av-3 { background: #6a7a45; }
.av-4 { background: #a06a3c; } .av-5 { background: #4a6a8a; } .av-6 { background: #7a5a45; }
.av-7 { background: #56707c; } .av-8 { background: #8f5c52; }
.av-system { background: var(--raised); color: var(--muted); border: 1px solid var(--soft); }
/* the icon is sized by the circle, the way initials are — not in markup */
.av-system > i { font-size: calc(var(--av-size) * .38); }

/* a photograph or a client's own mark takes the place of the initials. The
   colour goes with them — behind an image it only shows through the circle's
   soft edge, which reads as a fringe */
.av:has(img) { background: none; }
.av > img { display: block; width: 100%; height: 100%; border-radius: inherit; object-fit: cover; }

/* --------------------------------------------------------------------------
   stacked columns — a value axis on the left, gridlines behind, one column per
   month with its total above it. Heights are inline because they are data.
   Hovering a column drops the rest back so one month can be read on its own;
   each segment carries its own figure.
   -------------------------------------------------------------------------- */
.chart { display: grid; grid-template-columns: 58px minmax(0, 1fr); padding-top: 22px; }
.chart-y { position: relative; }
.chart-y > span {
  position: absolute; right: 14px; transform: translateY(50%);
  font-size: 13px; color: var(--muted); white-space: nowrap;
}
.chart-plot { position: relative; }
.chart-plot > .gridline { position: absolute; left: 0; right: 0; border-top: 1px solid var(--soft); }
.chart-plot > .gridline.is-base { border-top-color: var(--line); }
.dark .chart-plot > .gridline { border-top-color: var(--line); }
.dark .chart-plot > .gridline.is-base { border-top-color: var(--edge); }

.cols { position: relative; display: flex; align-items: flex-end; gap: 7px; height: 100%; }
.cols > span {
  position: relative; flex: 1; display: flex; flex-direction: column-reverse;
  border-radius: 3px 3px 0 0; overflow: visible;
  transition: opacity .12s;
}
.cols > span > i:first-child { border-radius: 3px 3px 0 0; }
.cols i { display: block; position: relative; }
.cols > span.is-now::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  border-radius: 3px 3px 0 0;
  background: repeating-linear-gradient(45deg,
    transparent 0 7px, rgb(var(--surface-rgb) / .12) 7px 14px);
}
.dark .cols > span.is-now::before {
  background: repeating-linear-gradient(45deg,
    transparent 0 7px, rgb(var(--surface-rgb) / .40) 7px 14px);
}
.cols:hover > span { opacity: .28; }
.cols:hover > span:hover { opacity: 1; }
.col-val {
  position: absolute; bottom: 100%; left: 0; right: 0; margin-bottom: 7px;
  text-align: center; font-size: 12px; color: var(--muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}

/* hovering a column names every series in it, largest first. The panel sits
   beside the column rather than over the chart, and its transparent border
   bridges the gap so the pointer can travel into it — the figures are text,
   and text should be selectable. */
.tip {
  position: absolute; z-index: 30; top: 0; left: calc(100% + 12px);
  width: 300px;                      /* .menu's min-width */
  padding: 0; text-align: left;
  background: var(--surface); border-radius: 12px;   /* .menu's radius */
  box-shadow: 0 0 0 1px var(--line), 0 14px 34px rgb(0 0 0 / .13);
  opacity: 0; pointer-events: none; transition: opacity .1s .04s;
}
/* bridges the gap so the pointer can travel from the column into the panel */
.tip::before { content: ""; position: absolute; top: 0; bottom: 0; left: -12px; width: 12px; }
.dark .tip { box-shadow: 0 0 0 1px var(--line), 0 16px 38px rgb(0 0 0 / .5); }
.cols > span:hover .tip { opacity: 1; pointer-events: auto; }
.tip-head { display: block; padding: 11px 16px; font-weight: 600;
            border-bottom: 1px solid var(--soft); }
.tip-body { display: block; padding: 12px 16px; }
/* the column total repeats what the bar is worth, under its own hairline */
.tip-foot { display: flex; align-items: baseline; gap: 10px;
            padding: 11px 16px; font-weight: 600;
            border-top: 1px solid var(--soft); }
.tip-foot .tip-name { color: var(--ink); }
.tip-row { display: flex; align-items: flex-start; gap: 10px; }
.tip-row + .tip-row { margin-top: 8px; }
.tip-row .swatch { margin-top: 6px; }      /* centres a 10px dot on a 22px line */
.tip-name { flex: 1; min-width: 0; color: var(--muted); }
.tip-val { font-variant-numeric: tabular-nums; white-space: nowrap; }
/* the last few flip to the other side rather than run off the plot */
.cols > span:nth-last-child(-n+4) .tip { left: auto; right: calc(100% + 12px); }
.cols > span:nth-last-child(-n+4) .tip::before { left: auto; right: -12px; }

/* Series colours from the EUI / Kibana categorical palette, choosing the cool
   and warm ends and leaving the pinks, purple and yellow out. Cool hues carry
   the large clients, warm ones the smaller, and the neutral tan carries the
   tail — which is a bucket rather than a client, so it should not compete. */
:root {
  --ser-1: #54B399;  --ser-2: #6092C0;  --ser-3: #DD8A83;
  --ser-4: #4A8B80;  --ser-5: #B9A888;  --ser-6: #C6CCD0;
}
.dark {
  /* Retuned for a 33-luminance plane rather than white. Two things change:
     the lightness order inverts, so the tail is the darkest band instead of
     the lightest; and the two teals are pulled further apart, because the
     value gap that separates them on white collapses on dark. */
  --ser-1: #4FB08F;  --ser-2: #6395C7;  --ser-3: #D08078;
  --ser-4: #2F7E74;  --ser-5: #A2937A;  --ser-6: #646C72;
}
.ser-1 { background: var(--ser-1); } .ser-2 { background: var(--ser-2); }
.ser-3 { background: var(--ser-3); } .ser-4 { background: var(--ser-4); }
.ser-5 { background: var(--ser-5); } .ser-6 { background: var(--ser-6); }
.swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }

/* --------------------------------------------------------------------------
   meters & sparkline
   -------------------------------------------------------------------------- */
.meter { height: 6px; border-radius: 999px; background: var(--raised); overflow: hidden; }
.meter > i { display: block; height: 100%; border-radius: 999px; background: var(--accent); }
.meter-warn > i { background: rgb(var(--warn-rgb)); }
.meter-err  > i { background: rgb(var(--err-rgb)); }
.meter-thin { height: 4px; }

.spark { display: flex; align-items: flex-end; gap: 3px; height: 40px; }
.spark > i {
  flex: 1; border-radius: 2px 2px 0 0;
  background: color-mix(in srgb, var(--accent) 32%, var(--raised));
}
.spark > i.hi { background: var(--accent); }

/* hovering a bar names the day it stands for. Only bars carrying a tip get
   the treatment, so sparklines without data stay inert. */
.spark > i[data-tip] { position: relative; }
.spark > i[data-tip]:hover { background: var(--accent); }
.spark > i[data-tip]::after {
  content: attr(data-tip);
  position: absolute; z-index: 20; bottom: calc(100% + 9px); left: 50%;
  transform: translateX(-50%);
  padding: 6px 10px; border-radius: 8px; white-space: nowrap;
  background: var(--surface); border: 1px solid var(--line); color: var(--ink);
  font-size: 13px; line-height: 1.3; letter-spacing: -0.005em;
  font-style: normal;                 /* the bars are <i>, so this is inherited */
  box-shadow: 0 8px 22px rgb(0 0 0 / .12);
  opacity: 0; pointer-events: none;
}
.dark .spark > i[data-tip]::after { box-shadow: 0 10px 26px rgb(0 0 0 / .5); }
.spark > i[data-tip]:hover::after { opacity: 1; }
/* the ends would hang off the section, so they align to their own edge */
.spark > i[data-tip]:nth-child(-n+3)::after      { left: 0; transform: none; }
.spark > i[data-tip]:nth-last-child(-n+3)::after { left: auto; right: 0; transform: none; }

/* --------------------------------------------------------------------------
   stat strip — cells span whole columns of the page grid (two each), with the
   hairlines drawn inside the gutter so no cell loses width to them.
   Markup carries the column count: `stats stats-4 xl:grid-cols-8` on a site
   page (8-column main), `stats stats-5 xl:grid-cols-10` where there is no
   sidebar. The -4 / -5 tells the dividers where each row starts.
   -------------------------------------------------------------------------- */
.stats {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 28px var(--gutter);
}
.stats > * { position: relative; }

/* a stat that links to where its number comes from */
.statlink { display: block; color: inherit; }

@media (min-width: 680px) {
  .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1280px) {
  .stats { row-gap: 0; }
  .stats > * { grid-column: span 2; }
  /* same specificity as the 2-up rule above, and later, so it wins */
}

/* --------------------------------------------------------------------------
   dropdown menus — one panel for the environment picker, the sync source and
   the row menus. A [data-menu="id"] trigger toggles [data-menu-panel] by id.
   -------------------------------------------------------------------------- */
.menu-wrap { position: relative; display: inline-flex; }
.menu {
  position: absolute; top: 100%; left: 0; z-index: 30;
  display: flex; flex-direction: column; gap: 2px;   /* so neighbouring hovers never touch */
  margin-top: 8px; min-width: 300px; padding: 7px;
  background: var(--surface); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: 0 14px 34px rgb(0 0 0 / .13);
}
.menu[hidden] { display: none; }              /* beats the UA rule this class would win against */
.dark .menu { box-shadow: 0 16px 38px rgb(0 0 0 / .5); }
.menu-right { left: auto; right: 0; }
/* a menu inside a scrolling box (a table, say) would be clipped by it and
   push its scrollbars out; those are placed against the window instead */
.menu.is-loose { position: fixed; top: auto; left: auto; right: auto; margin-top: 0; }
.menu-wide  { min-width: 340px; }
.menuitem {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 10px; border-radius: 8px; color: var(--ink);
}
button.menuitem {
  width: 100%; background: none; border: 0; font: inherit; text-align: left; cursor: pointer;
}
.menuitem:hover { background: var(--raised); }
/* where you are, in the accent rather than the neutral a hover uses */
.menuitem[aria-current="true"] { background: var(--accent-soft); }
.menu-sep { display: block; height: 1px; margin: 4px 10px; background: var(--soft); }
.menu-head { display: flex; align-items: center; gap: 11px; padding: 9px 10px 5px; }
/* the avatar is a control now, so it needs a control's focus ring and nothing else */
.avbtn { padding: 0; border: 0; background: none; cursor: pointer; line-height: 0; border-radius: 9999px; }
/* a panel opened from the dark bar is still a panel — it does not inherit the
   bar's text colours */
.topnav .menu, .topnav .menu a { color: var(--ink); }
.topnav .menu a:hover { color: var(--ink); }
.topnav .menu .meta { color: var(--muted); }
/* unread, said once, in the corner of the bell */
.topnav .iconbtn { position: relative; }
.notif-dot {
  position: absolute; top: 7px; right: 7px; width: 7px; height: 7px; border-radius: 50%;
  background: rgb(var(--err-rgb)); box-shadow: 0 0 0 2px var(--nav-bg);
}
.menu .menuitem[data-unread] { background: color-mix(in srgb, var(--accent) 5%, var(--surface)); }
/* an unread row is marked in the gutter, so nothing inside it has to change */
.rows > li.is-unread { position: relative; }
.rows > li.is-unread::before {
  content: ""; position: absolute; left: -14px; top: 14px; width: 6px; height: 6px;
  border-radius: 50%; background: var(--accent);
}

/* --------------------------------------------------------------------------
   confirmation dialog — anything that overwrites or destroys data asks first.
   A [data-confirm="id"] trigger opens #id; see app.js.
   -------------------------------------------------------------------------- */
.dialog-wrap {
  position: fixed; inset: 0; z-index: 60;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: color-mix(in srgb, var(--nav-bg) 55%, transparent);
}
.dialog-wrap[hidden] { display: none; }
.dialog {
  width: 100%; max-width: 540px; max-height: calc(100vh - 48px); overflow-y: auto;
  padding: 26px; border-radius: 14px;
  background: var(--surface); border: 1px solid var(--line);
  box-shadow: 0 24px 64px rgb(0 0 0 / .28);
}
.dark .dialog { box-shadow: 0 24px 64px rgb(0 0 0 / .6); }
.dialog h2 { margin: 0; }
/* what will actually happen, one consequence per line */
.dialog .consequences { margin: 18px 0 0; padding: 0; list-style: none; }
.dialog .consequences > li {
  display: flex; gap: 11px; padding: 9px 0; border-bottom: 1px solid var(--soft);
}
.dialog .consequences > li:last-child { border-bottom: 0; }
.dialog .consequences i { width: 15px; text-align: center; margin-top: 3px; font-size: 12px; color: var(--muted); }

/* --------------------------------------------------------------------------
   site switcher — the sidebar's site name is the control for changing site,
   the way the header's environment name is the control for changing
   environment. The offset that lines it up with the section links lives on the
   wrapper, not the button, so the panel opens on the same left edge.
   -------------------------------------------------------------------------- */
.sitewrap { width: calc(100% + 10px); margin-left: -10px; }
.sitepick {
  display: flex; align-items: center; gap: 10px; text-align: left;
  width: 100%; padding: 8px 10px;
  border: 0; border-radius: 7px; background: none; cursor: pointer;
  font-family: inherit; color: inherit;
}
.sitepick:hover,
.sitepick[aria-expanded="true"] { background: color-mix(in srgb, var(--ink) 5%, transparent); }
.sitepick .site-name { margin: 0; }
/* it floats over the canvas, never under it */
.sidebar .menu { z-index: 45; }

/* --------------------------------------------------------------------------
   tables — flat: rules only, no outer box
   -------------------------------------------------------------------------- */
table.t { width: 100%; border-collapse: collapse; }
table.t th {
  text-align: left; font-size: 12px; font-weight: 600; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
  padding: 0 20px 9px 0; border-bottom: 1px solid var(--line); white-space: nowrap;
}
table.t td { padding: 15px 20px 15px 0; border-bottom: 1px solid var(--soft); vertical-align: middle; }
/* the same closing rule as .rows: a table ends against whitespace or the
   pagination's own hairline, so its last row does not draw one too */
table.t tbody tr:last-child td,
table.t tbody tr.is-last td { border-bottom: 0; }
table.t th:last-child, table.t td:last-child { padding-right: 0; }
table.t th.text-right { text-align: right; }
/* a column that needs a qualifier takes a second line rather than a longer
   one, so the money columns stay the same width; headings sit on the same
   baseline whether they are one line or two */
table.t th { vertical-align: bottom; }
table.t th .th-sub { display: block; font-weight: 500; opacity: .72; }
/* keep the first column of a record table from collapsing */
table.t .col-wide { min-width: 260px; }
table.t .col-mid  { min-width: 170px; }
/* money columns read as a block only if they are the same width, whatever the
   heading above them happens to say */
table.t .col-num  { width: 168px; }
/* a percentage needs less room than money does */
table.t .col-chg  { width: 100px; }

/* one client's spend: the whole site row is the trigger that opens its
   category rows, so it is a button wearing the row's own layout */
.siterow {
  display: flex; align-items: center; gap: 20px; width: 100%;
  padding: 16px 0; border: 0; background: transparent;
  color: inherit; font: inherit; text-align: left; cursor: pointer;
}
.chev { transition: transform .12s; }
[aria-expanded="true"] .chev { transform: rotate(90deg); }


/* --------------------------------------------------------------------------
   site sidebar — a real column: recessed fill, full height, no divider.
   The step from --paper to --surface is what separates it from the canvas,
   so those two tones need to stay far enough apart to read on their own.
   -------------------------------------------------------------------------- */
.sidebar {
  background: var(--paper);
  /* Left: the fill runs out to the frame edge.
     Right: it bleeds half a gutter past its column line, so the fill ends on
     the gutter's midline — and because the padding matches, this sidebar's
     content and the main column's content sit the same distance from it. */
  margin-left: calc(var(--frame-pad) * -1);
  padding-left: var(--frame-pad);
  margin-right: calc(var(--gutter) / -2);
  padding-right: calc(var(--gutter) / 2);
}

.sidelink {
  display: flex; align-items: center; gap: 11px;
  padding: 7px 10px; margin-left: -10px; border-radius: 7px;
  font-size: 15px; color: var(--muted); font-weight: 500;
}
.sidelink i { width: 17px; text-align: center; font-size: 14px; opacity: .8; }
.sidelink:hover { color: var(--ink); background: color-mix(in srgb, var(--ink) 5%, transparent); }
.sidelink[aria-current="page"] {
  color: var(--ink); font-weight: 600;
  background: var(--surface);
}
.sidelink[aria-current="page"] i { opacity: 1; color: var(--accent); }
/* a numbered sidelink, for a nav that is a sequence rather than a set */
.sidelink .step {
  width: 21px; height: 21px; flex: none; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--edge); font-size: 12px; font-variant-numeric: tabular-nums;
}
.sidelink[aria-current="page"] .step {
  background: var(--accent); border-color: var(--accent); color: rgb(var(--accent-on-rgb));
}
.sidelink.is-done { color: var(--ink); }
.sidelink.is-done .step { border-color: var(--accent-line); color: var(--accent); }


/* right rail: hairline drawn in the middle of the grid gap, so the rail keeps
   its full column span */
.rail { position: relative; }

/* --------------------------------------------------------------------------
   sticky cost bar (settings) — a box, because it floats over the page
   -------------------------------------------------------------------------- */
.actionbar {
  position: sticky; bottom: 16px; z-index: 20;
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  backdrop-filter: saturate(1.6) blur(10px);
  border: 1px solid var(--edge); border-radius: var(--radius);
  box-shadow: 0 6px 22px rgb(24 22 18 / .10);
}
.dark .actionbar { box-shadow: 0 6px 22px rgb(0 0 0 / .45); }

[hidden] { display: none !important; }
