/* Bound the transition to a theme change, rather than animating live counters. */
:root{--brand-bg:#f6f7f2;--brand-surface:#fffffc;--brand-soft:#edf1e9;--brand-text:#25372e;--brand-muted:#69796e;--brand-line:#dfe5da;--brand-accent:#356b56;--brand-action:#356b56;--brand-on-action:#fff;--brand-success:#28785b;--brand-shadow:0 12px 32px #283b2e08}
:root[data-theme="dark"]{--brand-bg:#161d19;--brand-surface:#212d25;--brand-soft:#2a382e;--brand-text:#e5eee5;--brand-muted:#a1b39f;--brand-line:#384a3c;--brand-accent:#b3d6b8;--brand-action:#b3d6b8;--brand-on-action:#203626;--brand-success:#94caaa;--brand-shadow:0 12px 32px #03090624}
/* One radius for a panel, one for a control, one shadow, one 8px spacing scale.
   Both consoles derive from these; nothing below invents its own geometry. */
:root{--radius-panel:12px;--radius-control:8px;--space-1:8px;--space-2:16px;--space-3:24px;--space-4:32px;
  /* Green means it worked and amber means it only started. The two were the
     other way round, which put the calmer colour on the number that says
     nothing yet and the alarming one on success. */
  --trend-total:#c08a4e;--trend-success:#2f7a55;--trend-grid:var(--brand-line)}
/* Three control heights, and no fourth. A row of controls is only level if the
   things in it agree on how tall they are; before this a filter row held a 43px
   button, a 37px input, a 36px icon button and a 34px compact one, and every
   one of those numbers came from padding nobody had compared.
     --control-h        a full-size button, input, select
     --control-h-compact a .compact button, an icon button, a chip
     --control-h-dense   an icon cluster inside a table row
   Height is set as `min-height`, so a control whose label wraps grows instead
   of clipping — the floor is shared, the ceiling belongs to the text. */
:root{--control-h:40px;--control-h-compact:34px;--control-h-dense:32px}
:root[data-theme="dark"]{--trend-total:#e2b077;--trend-success:#7fd0a4}
/* One motion system for both consoles: one ease, three durations. Nothing below
   picks its own timing — see the reduced-motion block at the end of this file,
   which is the single place all of it is switched off. */
:root{--ease-out:cubic-bezier(.2,.8,.2,1);--ease-in-out:cubic-bezier(.4,0,.2,1);
  --motion-fast:140ms;--motion-base:220ms;--motion-page:320ms;
  /* A fourth duration, for motion that repeats rather than resolves: a sweep
     saying work is still going. The other three time a change and then stop. */
  --motion-cycle:1.5s}
/* One typeface pairing, loaded from Google Fonts by admin.html / index.html.
   A display face for headings and brand marks, a text face for everything an
   owner or customer reads at length — both ship full Cyrillic coverage. */
:root{--font-sans:'Inter',"Segoe UI",system-ui,-apple-system,Arial,sans-serif;
  --font-display:'Manrope','Inter',"Segoe UI",system-ui,sans-serif}
.ui-icon{width:20px;height:20px;flex:0 0 auto;vertical-align:middle;stroke:currentColor;fill:none;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
html[data-theme="light"]{color-scheme:light}
html[data-theme="dark"]{color-scheme:dark}
html.theme-transition,html.theme-transition body,html.theme-transition header,html.theme-transition nav,html.theme-transition main,html.theme-transition aside,html.theme-transition footer,html.theme-transition section,html.theme-transition article,html.theme-transition .panel,html.theme-transition .card,html.theme-transition .tabs,html.theme-transition button,html.theme-transition input,html.theme-transition textarea,html.theme-transition select,html.theme-transition a,html.theme-transition table,html.theme-transition td,html.theme-transition th,html.theme-transition .modal,html.theme-transition .plan-banner{
  transition:background-color .28s ease,color .28s ease,border-color .28s ease,box-shadow .28s ease;
}
@media(prefers-reduced-motion:reduce){html.theme-transition,html.theme-transition *{transition:none!important}}
/* Skeleton placeholder: a moving highlight on a flat tint, never a spinner.
   Used while first-load content has no shape yet to show instead. */
@keyframes skeleton-shimmer{from{background-position:150% 0}to{background-position:-50% 0}}
.skeleton{position:relative;overflow:hidden;color:transparent!important;background:var(--brand-soft);
  background-image:linear-gradient(100deg,transparent 30%,color-mix(in srgb,var(--brand-line) 70%,transparent) 50%,transparent 70%);
  background-size:250% 100%;background-repeat:no-repeat;animation:skeleton-shimmer 1.5s ease-in-out infinite;
  border-radius:var(--radius-control);pointer-events:none;user-select:none}
@media(prefers-reduced-motion:reduce){.skeleton{animation:none;background-image:none}}

/* ── Palettes ───────────────────────────────────────────────────────────────
   Light or dark says how bright a surface is; a palette says what colour it is
   made of. They are separate axes because they answer different questions, and
   collapsing them would double the number of themes to maintain for no gain.

   Forest is the pair at the top of this file and has no block here: it is what
   `:root` already declares, so an unset or unknown palette renders it. Each
   palette below restates the full set — a partial palette would inherit half
   its colours from forest and read as neither.

   The selectors are written `[data-palette=x]:not([data-theme=dark])` and
   `[data-palette=x][data-theme=dark]` so both weigh the same and cannot match
   at once. Ordering them by hand is then not something a later edit can get
   wrong. Everything downstream follows for free: `public.css` derives its
   `--p-*` tokens from these, so the activation site changes with the console.
   Semantic colours — success, warning, caution, danger, and the two chart
   series — deliberately stay out: they mean the same thing in every palette. */
:root[data-palette=slate]:not([data-theme=dark]){--brand-bg:#f7f8fa;--brand-surface:#fff;--brand-soft:#eef1f5;--brand-text:#1b2430;--brand-muted:#66707f;--brand-line:#dde3ea;--brand-accent:#33536f;--brand-action:#33536f;--brand-on-action:#fff;--brand-success:#28785b;--brand-shadow:0 12px 32px #1b243009}
:root[data-palette=slate][data-theme=dark]{--brand-bg:#14181d;--brand-surface:#1e242b;--brand-soft:#262e37;--brand-text:#e6ebf1;--brand-muted:#98a6b6;--brand-line:#333d48;--brand-accent:#a6c4e2;--brand-action:#a6c4e2;--brand-on-action:#15202b;--brand-success:#8ccaa8;--brand-shadow:0 12px 32px #04070a2e}
:root[data-palette=indigo]:not([data-theme=dark]){--brand-bg:#f7f6fd;--brand-surface:#fffdff;--brand-soft:#eeebfb;--brand-text:#241f3b;--brand-muted:#6b6588;--brand-line:#e0dcf2;--brand-accent:#5745cf;--brand-action:#5745cf;--brand-on-action:#fff;--brand-success:#2f7a5c;--brand-shadow:0 12px 32px #241f3b0d}
:root[data-palette=indigo][data-theme=dark]{--brand-bg:#16141f;--brand-surface:#211d2e;--brand-soft:#2a2540;--brand-text:#eae7f7;--brand-muted:#a7a0c2;--brand-line:#3a3352;--brand-accent:#b8aaff;--brand-action:#b8aaff;--brand-on-action:#211b3c;--brand-success:#8fd0ae;--brand-shadow:0 12px 32px #0705112e}
:root[data-palette=amber]:not([data-theme=dark]){--brand-bg:#faf7f0;--brand-surface:#fffdf8;--brand-soft:#f4ecdb;--brand-text:#33291a;--brand-muted:#7b6a51;--brand-line:#e8dcc4;--brand-accent:#8d5c17;--brand-action:#8d5c17;--brand-on-action:#fff;--brand-success:#2f7250;--brand-shadow:0 12px 32px #33291a0d}
:root[data-palette=amber][data-theme=dark]{--brand-bg:#1c1811;--brand-surface:#27221a;--brand-soft:#332c20;--brand-text:#f0e9dc;--brand-muted:#b4a48a;--brand-line:#453c2c;--brand-accent:#eaba79;--brand-action:#eaba79;--brand-on-action:#2b2010;--brand-success:#94caaa;--brand-shadow:0 12px 32px #0d0a052e}
/* Ocean's muted measured 4.50:1 on its own surface - exactly the AA floor, so
   any later nudge to the surface would drop it under. Darkened for headroom. */
:root[data-palette=ocean]:not([data-theme=dark]){--brand-bg:#f2f8fa;--brand-surface:#fbfeff;--brand-soft:#e3f0f4;--brand-text:#10303a;--brand-muted:#52717a;--brand-line:#cfe3e9;--brand-accent:#0f5d74;--brand-action:#0f5d74;--brand-on-action:#fff;--brand-success:#1d7a63;--brand-shadow:0 12px 32px #10303a0d}
:root[data-palette=ocean][data-theme=dark]{--brand-bg:#101a1e;--brand-surface:#1a272c;--brand-soft:#223338;--brand-text:#dff0f4;--brand-muted:#90b0b9;--brand-line:#2e454c;--brand-accent:#82d2e2;--brand-action:#82d2e2;--brand-on-action:#0c2b33;--brand-success:#7fd4b4;--brand-shadow:0 12px 32px #040c0f2e}
:root[data-palette=rose]:not([data-theme=dark]){--brand-bg:#fdf6f6;--brand-surface:#fffcfc;--brand-soft:#fbe9e9;--brand-text:#37222a;--brand-muted:#83656e;--brand-line:#f1dada;--brand-accent:#9e3253;--brand-action:#9e3253;--brand-on-action:#fff;--brand-success:#2f7250;--brand-shadow:0 12px 32px #37222a0d}
:root[data-palette=rose][data-theme=dark]{--brand-bg:#1d1418;--brand-surface:#291e23;--brand-soft:#35262d;--brand-text:#f5e6ea;--brand-muted:#be9fa9;--brand-line:#4a353d;--brand-accent:#f2a5ba;--brand-action:#f2a5ba;--brand-on-action:#32131e;--brand-success:#94caaa;--brand-shadow:0 12px 32px #0b05082e}

/* ── Scrollbars ─────────────────────────────────────────────────────────────
   The platform scrollbar is a grey slab that belongs to no palette and, on
   Windows, reserves a permanent gutter beside every panel. This one is drawn
   from the palette, sits on a transparent track so it does not put a second
   border next to a panel edge, and lights up under the pointer.

   The thumb is inset by a transparent border with `background-clip:content-box`
   rather than by width, because a narrower thumb is also a smaller thing to
   grab; the full width stays grabbable while only the paint looks slim. */
:root{--scroll-thumb:color-mix(in srgb,var(--brand-muted) 34%,transparent);--scroll-thumb-hover:color-mix(in srgb,var(--brand-accent) 58%,transparent);--scroll-size:12px}
*{scrollbar-width:thin;scrollbar-color:var(--scroll-thumb) transparent}
::-webkit-scrollbar{width:var(--scroll-size);height:var(--scroll-size)}
::-webkit-scrollbar-track,::-webkit-scrollbar-corner{background:transparent}
::-webkit-scrollbar-thumb{background-color:var(--scroll-thumb);border:3px solid transparent;background-clip:content-box;border-radius:99px;transition:background-color var(--motion-base) var(--ease-out)}
::-webkit-scrollbar-thumb:hover{background-color:var(--scroll-thumb-hover)}
::-webkit-scrollbar-thumb:active{background-color:var(--brand-accent)}
/* A scroller inside a panel gets a thinner one, so the panel keeps its edge. */
.panel ::-webkit-scrollbar,.data-pre::-webkit-scrollbar,.table-wrap::-webkit-scrollbar{width:9px;height:9px}

/* ── Motion ─────────────────────────────────────────────────────────────────
   Closing was the half nobody wrote. A dialog faded in over 220ms and then
   vanished on the frame it was closed, which reads as a glitch rather than as
   an answer. `allow-discrete` with `@starting-style` is what makes the two
   symmetric: the same properties animate in both directions, and `display` and
   `overlay` are held open for the length of the exit instead of snapping.

   A browser without discrete transitions ignores the whole block and keeps the
   instant close it has today, so nothing here is load-bearing. */
@keyframes mp-fade-up{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}
@keyframes mp-scale-in{from{opacity:0;transform:scale(.98)}to{opacity:1;transform:none}}
dialog{opacity:0;transform:translateY(12px) scale(.985);
  transition:opacity var(--motion-base) var(--ease-out),transform var(--motion-base) var(--ease-out),
    display var(--motion-base) allow-discrete,overlay var(--motion-base) allow-discrete}
dialog[open]{opacity:1;transform:none}
@starting-style{dialog[open]{opacity:0;transform:translateY(12px) scale(.985)}}
dialog::backdrop{opacity:0;transition:opacity var(--motion-fast) var(--ease-out),
  display var(--motion-fast) allow-discrete,overlay var(--motion-fast) allow-discrete}
dialog[open]::backdrop{opacity:1}
@starting-style{dialog[open]::backdrop{opacity:0}}
/* A popover is the same problem with a shorter travel: it is anchored to the
   control that opened it, so it grows from that edge rather than rising off
   the page the way a modal does. */
[popover]{opacity:0;transform:translateY(-6px) scale(.98);
  transition:opacity var(--motion-fast) var(--ease-out),transform var(--motion-fast) var(--ease-out),
    display var(--motion-fast) allow-discrete,overlay var(--motion-fast) allow-discrete}
[popover]:popover-open{opacity:1;transform:none}
@starting-style{[popover]:popover-open{opacity:0;transform:translateY(-6px) scale(.98)}}

/* The palette wipe. Cross-fading two complete palettes reads as a flash, so
   the new one is revealed as a circle growing from whatever control was
   pressed — theme.js writes those coordinates before starting the transition
   and drops the class when it finishes. Only the incoming snapshot moves; the
   outgoing one is held still underneath it. */
html.theme-reveal::view-transition-old(root){animation:none}
html.theme-reveal::view-transition-new(root){animation:mp-theme-reveal var(--motion-page) var(--ease-out)}
@keyframes mp-theme-reveal{from{clip-path:circle(0 at var(--reveal-x,50%) var(--reveal-y,50%))}
  to{clip-path:circle(150% at var(--reveal-x,50%) var(--reveal-y,50%))}}

@media(prefers-reduced-motion:reduce){
  dialog,dialog::backdrop,[popover]{transition:none}
  html.theme-reveal::view-transition-new(root){animation:none}
  ::-webkit-scrollbar-thumb{transition:none}
}
