/* ============================================================================
   ChurnX interface. CyberX identity: deep navy ground, cyan signal, Primus type.
   Dark first, because this is an operations console that people sit in front of.
   ========================================================================== */

@font-face{font-family:Primus;src:url('/fonts/Primus-Light.woff2') format('woff2');font-weight:300;font-display:swap}
@font-face{font-family:Primus;src:url('/fonts/Primus-Regular.woff2') format('woff2');font-weight:400;font-display:swap}
@font-face{font-family:Primus;src:url('/fonts/Primus-SemiBold.woff2') format('woff2');font-weight:600;font-display:swap}
@font-face{font-family:Primus;src:url('/fonts/Primus-Bold.woff2') format('woff2');font-weight:700;font-display:swap}

:root{
  /* Spacing is named once and never redefined per skin, because NFR-144 says nothing
     moves when the skin changes. A caption sits closer to the heading it belongs to
     than to the content it introduces, so these two are deliberately unequal. */
  --panel-gap:16px;
  --gap-cap:10px;
  /* How many units wide a chart is drawn. Declared here so the value the stylesheet
     reads is a declared token rather than one that only ever arrives inline, and so
     a chart with no wrapper still has a sensible ratio. Every chart component that
     is wide enough for the container to matter overrides it on its own wrapper. */
  --chart-units:720;
  /* Form fields: the space between two of them, the space beside them, and the tighter
     one used inside a popover where a form has to fit in a small surface. */
  --field-gap:15px; --field-gap-tight:12px; --field-col:14px;
  --gap-after-cap:20px;

  /* The accent as a triplet, so every tint of it follows the skin instead of
     being written out twenty four times. FR-SKN-09. */
  --accent-rgb:0 200 232;
  /* The rail is its own surface and was painted by a hardcoded gradient, so no
     token could reach it and it stayed navy under every skin. */
  --rail-from:rgba(10,21,38,.96); --rail-to:rgba(5,10,18,.98);
  --scrim:rgba(3,8,14,.72);
  /* Ink that sits on a tinted hover surface, and the heading gradient. Both were
     hardcoded white, which is right on the rail and wrong on content: a white label
     over a cyan tint on a white panel measured 1.06:1, and the modal close label
     measured 1.00:1, which is invisible. NFR-75. */
  --ink-hover:#fff; --h1-from:#fff; --h1-to:var(--cyan-2); --h1-stop:20%;
  /* The rail is the one surface that stays dark in EVERY theme: --rail is #050A12 in
     the dark block and #0A1526 in both light ones. So an overlay on it must stay white
     in every theme too. Grouping these with the theme-following tints by alpha alone
     would have turned them navy on navy in the light theme, which is invisible. They
     are declared once here and never overridden. FR-SKN-09. */
  --rail-line:rgba(255,255,255,.06); --rail-fill:rgba(255,255,255,.04);
  /* --crit is declared here only, so it is one red in every theme, and white
     on it was 3.12:1 in ALL of them. This ink is invariant for the same reason. */
  --on-crit:#03151B;
  /* Invariant, like the other rail tokens. The rail is dark under both themes, so the
     unread badge is always on a dark ground and always carries dark ink. --crit itself
     follows the theme now, and using it here would put dark ink on a dark red badge in
     every light theme. */
  --rail-crit:#FF5468;
  /* The remaining tint families, as triplets. The status colours are declared once
     because a status is meaning rather than brand: an amber warning is amber in every
     visual language. The neutral tint is deliberately mid grey so it lightens a dark
     ground and darkens a light one without either skin having to think about it. */
  --neutral-rgb:127 127 127;
  --good-rgb:37 208 122; --warn-rgb:245 166 35; --serious-rgb:255 138 80;
  --crit-rgb:255 84 104; --violet-rgb:140 126 242;
  /* Shadow is darkness, but on a near white Apple ground pure black reads as dirt. */
  --shadow-rgb:0 0 0;
  --on-accent-solid:#fff;

  /* Ink on the rail. Invariant here because the CyberX rail is dark in both
     themes; the Apple skin overrides it, because its rail is a light surface. */
  --rail-ink:#fff;
  /* Two quieter inks for the rail, so nothing on it ever reaches for a content token
     again. Both are measured against the rail, not against the page. */
  --rail-ink-2:#C6D6EA; --rail-ink-3:#93AAC4;
  /* The wordmark is inlined with file_get_contents so CSS can reach it, but only its
     lettering was reachable through currentColor. Its shield, gradient and tick were
     hardcoded inside the SVG and no skin could move them. Dark values are the exact
     literals that were in the file. FR-SKN-09. */
  --mark-from:#00A8C6; --mark-to:#10283F; --mark-tick:#fff; --mark-accent:#00A8C6;
  --mark-ink:#fff;
  /* Surface tints. These were sixteen rgba(255,255,255,alpha) literals scattered
     through the file, beside fifteen rgba(127,127,127,alpha) already doing the same
     job correctly, so half this migration was already done. A white tint lightens
     correctly over a dark ground and washes a light one out, which is why the light
     theme inherited a defect it could not override. The dark values below are the
     exact literals that were there, so nothing in this theme changes. FR-SKN-09. */
  --tint-1:rgba(255,255,255,.02); --tint-2:rgba(255,255,255,.03);
  --tint-3:rgba(255,255,255,.04); --tint-4:rgba(255,255,255,.05);
  --tint-5:rgba(255,255,255,.06);
  /* CyberX ground */
  --void:#060D16; --deep:#0A1526; --panel:#0E1D31; --panel-2:#132741; --rail:#050A12;
  --line:#1C3350; --line-2:#16283F;
  --ink:#EAF2FA; --ink-2:#93AAC4; --muted:#7E96AF; --ink-3:#7E96AF;

  /* CyberX signal */
  --cyan:#00C8E8; --cyan-2:#38E0F5; --cyan-deep:#0090AC;
  /* The accent where it is TEXT rather than paint. --cyan is the brand colour and is
     tuned for a dark ground; on a light one it measured 1.75 to 1, so every link in the
     CyberX light theme was the least readable thing on the page. Darkening --cyan itself
     would repaint the brand, so the fills and the borders keep it and only the text uses
     this. In the dark themes the two are the same colour. */
  --cyan-ink:#00C8E8;

  /* Handed in per row by a template, from a score band, a stage or a platform. Declared
     here so the token always resolves and so the default lives in one place rather than
     being repeated as a fallback at every use. */
  --bandc:#63809E;
  --fc:#93AAC4;
  /* Violet measured 3.73 and rose 4.40 against the lightest dark panel, so both were
     already failing before any of this. Lightened to clear 4.5 on every dark surface. */
  --violet:#8C7EF2; --green:#25D07A; --amber:#F5A623; --coral:#FF6B5A; --rose:#F15780;

  --good:#25D07A; --warn:#F5A623; --serious:#FF8A50; --crit:#FF5468;

  /* The focus indicator is its own token, not the accent, because the accent was
     chosen to sit on a navy ground and is 1.81:1 on the light one, which is below
     the 3:1 floor a non text indicator has to clear. */
  --focus:#00C8E8;
  /* Ink that sits on the accent. It used to be --void, which is a ground token, and
     a ground swapped into an ink slot survives every lint rule and still fails. */
  --on-accent:#03151B;
  /* The health bands, as tokens rather than as literals returned from PHP. Eight of
     the ten failed 4.5:1 on a white panel, and the health score is the number this
     product exists to show. */
  /* The two deepest reds were 2.49:1 and 3.60:1 on this panel, which made "gone" and
     "critical" the two hardest bands to read in the theme that is the default. Lifted
     until they clear, and separated by hue rather than lightness so they still read
     apart from each other and from the band below them. */
  --band-0:#F0518A; --band-1:#F2564F; --band-2:#EF4A3C; --band-3:#FF7345; --band-4:#FF9A2E;
  --band-5:#F0BC3C; --band-6:#C9CB44; --band-7:#88C244; --band-8:#33BE79; --band-9:#12B981;

  --glow-cyan:0 0 0 1px rgba(0,200,232,.28), 0 0 24px -4px rgba(0,200,232,.45);
  --glow-soft:0 0 32px -10px rgba(0,200,232,.30);
  --shadow:0 1px 0 rgba(255,255,255,.04) inset, 0 18px 40px -22px rgba(0,0,0,.9);
  --r:14px; --r-sm:9px;
}
:root[data-theme=light]{
  --ink-hover:#08131F; --h1-from:#08131F; --h1-to:var(--cyan-deep); --h1-stop:30%;
  /* The rail stays dark in this theme so the mark keeps its treatment, but the login
     card is white, where a white wordmark was invisible. --mark-ink is the fix. */
  --mark-from:#00A8C6; --mark-to:#10283F; --mark-tick:#fff; --mark-accent:#0090AC;
  --mark-ink:#08131F;
  /* The same tiers, inverted, because a tint on a white panel has to darken. */
  --tint-1:rgba(10,21,38,.02); --tint-2:rgba(10,21,38,.03);
  --tint-3:rgba(10,21,38,.04); --tint-4:rgba(10,21,38,.05);
  --tint-5:rgba(10,21,38,.06);
  --void:#EEF3F9; --deep:#F4F7FB; --panel:#FFFFFF; --panel-2:#F7FAFD; --rail:#0A1526;
  /* Status colours, darkened for a light ground. The shipped values are tuned to read on
     a dark surface and measured 2.02 to 4.04 against a light one, so every light theme
     failed the threshold on status text: a red that says something is critical was the
     least readable thing on the page. Solved against #E8F0F5, the darkest light surface
     any skin declares, so all of them clear 4.5. The triplet moves with the hex, because
     two tokens naming one colour that can disagree eventually do. */
  --good:#167B48; --warn:#966007; --serious:#C04000; --crit:#D90019;
  --violet:#6451ED; --green:#167B48; --amber:#966007; --coral:#D61600; --rose:#D41245;
  --good-rgb:22 123 72; --warn-rgb:150 96 7; --serious-rgb:192 64 0;
  --crit-rgb:217 0 25; --violet-rgb:100 81 237;
  --cyan-ink:#007284;
  /* Was #DCE6F0, which measured 1.13:1 against the light ground, so a panel edge
     was very nearly invisible on the page behind it. A divider has to be
     distinguishable from BOTH surfaces it separates, and the weaker side is the
     one that decides. This clears 1.53 on the panel and 1.37 on the ground, in
     line with what the Apple light skin already reaches. NFR-61. */
  --line:#C3D3E4; --line-2:#EAF0F7;
  --ink:#08131F; --ink-2:#42596F; --muted:#556A7E; --ink-3:#556A7E;
  --focus:#007E96; --on-accent:#03151B;
  --band-0:#B3143C; --band-1:#DE2545; --band-2:#E42313; --band-3:#DB3600; --band-4:#B55E00;
  --band-5:#966E0C; --band-6:#787922; --band-7:#59802A; --band-8:#248555; --band-9:#0D855D;
  --glow-cyan:0 0 0 1px rgba(0,144,172,.22), 0 0 20px -6px rgba(0,144,172,.28);
  --glow-soft:0 0 30px -14px rgba(10,21,38,.30);
  --shadow:0 1px 2px rgba(10,21,38,.06), 0 16px 34px -24px rgba(10,21,38,.45);
}
/* The same values again, for somebody who has chosen nothing and whose machine is set
   to light. The dark tokens above are unconditional, so without this block that person
   got a dark page while the script, which does read the system preference, lit up the
   Light button and drew the canvas in light ink. A dark page with the light control
   selected was the shipped steady state, not a flash. CSS cannot share one declaration
   block between a plain selector and a media query, so it is written twice on purpose. */
@media (prefers-color-scheme: light){
  :root:not([data-theme=dark]){
  --ink-hover:#08131F; --h1-from:#08131F; --h1-to:var(--cyan-deep); --h1-stop:30%;
  /* The rail stays dark in this theme so the mark keeps its treatment, but the login
     card is white, where a white wordmark was invisible. --mark-ink is the fix. */
  --mark-from:#00A8C6; --mark-to:#10283F; --mark-tick:#fff; --mark-accent:#0090AC;
  --mark-ink:#08131F;
  /* The same tiers, inverted, because a tint on a white panel has to darken. */
  --tint-1:rgba(10,21,38,.02); --tint-2:rgba(10,21,38,.03);
  --tint-3:rgba(10,21,38,.04); --tint-4:rgba(10,21,38,.05);
  --tint-5:rgba(10,21,38,.06);
  --void:#EEF3F9; --deep:#F4F7FB; --panel:#FFFFFF; --panel-2:#F7FAFD; --rail:#0A1526;
  /* Status colours, darkened for a light ground. The shipped values are tuned to read on
     a dark surface and measured 2.02 to 4.04 against a light one, so every light theme
     failed the threshold on status text: a red that says something is critical was the
     least readable thing on the page. Solved against #E8F0F5, the darkest light surface
     any skin declares, so all of them clear 4.5. The triplet moves with the hex, because
     two tokens naming one colour that can disagree eventually do. */
  --good:#167B48; --warn:#966007; --serious:#C04000; --crit:#D90019;
  --violet:#6451ED; --green:#167B48; --amber:#966007; --coral:#D61600; --rose:#D41245;
  --good-rgb:22 123 72; --warn-rgb:150 96 7; --serious-rgb:192 64 0;
  --crit-rgb:217 0 25; --violet-rgb:100 81 237;
  --cyan-ink:#007284;
  /* Was #DCE6F0, which measured 1.13:1 against the light ground, so a panel edge
     was very nearly invisible on the page behind it. A divider has to be
     distinguishable from BOTH surfaces it separates, and the weaker side is the
     one that decides. This clears 1.53 on the panel and 1.37 on the ground, in
     line with what the Apple light skin already reaches. NFR-61. */
  --line:#C3D3E4; --line-2:#EAF0F7;
  --ink:#08131F; --ink-2:#42596F; --muted:#556A7E; --ink-3:#556A7E;
  --focus:#007E96; --on-accent:#03151B;
  --band-0:#B3143C; --band-1:#DE2545; --band-2:#E42313; --band-3:#DB3600; --band-4:#B55E00;
  --band-5:#966E0C; --band-6:#787922; --band-7:#59802A; --band-8:#248555; --band-9:#0D855D;
  --glow-cyan:0 0 0 1px rgba(0,144,172,.22), 0 0 20px -6px rgba(0,144,172,.28);
  --glow-soft:0 0 30px -14px rgba(10,21,38,.30);
  --shadow:0 1px 2px rgba(10,21,38,.06), 0 16px 34px -24px rgba(10,21,38,.45);
  }
}


/* ---------- the Apple skin ---------- */
:root[data-skin=apple]{
  --accent-rgb:0 200 232; --shadow-rgb:0 0 0;
  --rail-from:rgba(10,21,38,.93); --rail-to:rgba(5,10,18,.95); --scrim:rgba(3,8,14,.62);
  /* ground and surfaces */
  --void:#060D16; --deep:#0A1526; --panel:rgba(14,29,49,.58); --panel-2:rgba(19,39,65,.48); --rail:#050A12;
  /* Stated here as well as in the light block, so neither theme half inherits its
     palette from the base. The values match the base because these are the ones tuned
     for a dark ground; the point is that the pair is symmetric, so adding a colour to
     one theme can never silently leave the other on whatever came before. */
  --good:#25D07A; --warn:#F5A623; --serious:#FF8A50; --crit:#FF5468;
  --violet:#8C7EF2; --green:#25D07A; --amber:#F5A623; --coral:#FF6B5A; --rose:#F15780;
  --good-rgb:37 208 122; --warn-rgb:245 166 35; --serious-rgb:255 138 80;
  --crit-rgb:255 84 104; --violet-rgb:140 126 242;
  --cyan-ink:#00C8E8;
  --line:rgba(255,255,255,.14); --line-2:rgba(255,255,255,.08);
  --ink:#EAF2FA; --ink-2:#A8BED6; --muted:#8AA3BE; --ink-3:#8AA3BE;
  /* Colour is reserved for accent. The token is still named for the CyberX cyan it
     was minted for, and renaming it across 980 lines is a refactor this build does
     not need; the slot is what matters, not the word. */
  /* On black and near black the signal cyan carries itself: 10.43:1 on the ground
     and 8.36:1 on a panel, with near black ink on it at 9.25:1. */
  --cyan:#00C8E8; --cyan-2:#38E0F5; --cyan-deep:#0090AC;
  --focus:#00C8E8; --on-accent:#03151B;
  --band-0:#F0518A; --band-1:#F2564F; --band-2:#EF4A3C; --band-3:#FF7345; --band-4:#FF9A2E;
  --band-5:#F0BC3C; --band-6:#C9CB44; --band-7:#88C244; --band-8:#33BE79; --band-9:#12B981;
  /* The cyan glow is the CyberX signature and has no Apple equivalent, so the glow
     tokens resolve to a soft neutral shadow rather than to nothing. FR-SKN-11. */
  --glow-cyan:0 0 0 1px rgba(255,255,255,.10); --glow-soft:0 8px 24px -12px rgba(0,0,0,.7);
  --shadow:0 1px 2px rgba(0,0,0,.4), 0 12px 28px -20px rgba(0,0,0,.8);
  /* Softer, larger radii. Two tiers only, matching the names the rules already use. */
  --r:18px; --r-sm:12px;
  --tint-1:rgba(255,255,255,.02); --tint-2:rgba(255,255,255,.03);
  --tint-3:rgba(255,255,255,.04); --tint-4:rgba(255,255,255,.05);
  --tint-5:rgba(255,255,255,.06);
  --rail-line:rgba(255,255,255,.08); --rail-fill:rgba(255,255,255,.05);
  --rail-ink:#F5F5F7;
  --mark-from:#00C8E8; --mark-to:#0A1526; --mark-tick:#fff; --mark-accent:#38E0F5;
  --mark-ink:#F5F5F7; --ink-hover:#F5F5F7; --h1-from:#F5F5F7; --h1-to:#38E0F5; --h1-stop:20%;
}
:root[data-skin=apple][data-theme=light]{
  --accent-rgb:0 126 150; --shadow-rgb:29 29 31;
  --rail-from:rgba(10,21,38,.93); --rail-to:rgba(5,10,18,.95); --scrim:rgba(3,8,14,.42);
  /* ground and surfaces */
  --void:#EEF3F9; --deep:#F4F7FB; --panel:rgba(255,255,255,.88); --panel-2:rgba(247,250,253,.82); --rail:#0A1526;
  /* Status colours, darkened for a light ground. The shipped values are tuned to read on
     a dark surface and measured 2.02 to 4.04 against a light one, so every light theme
     failed the threshold on status text: a red that says something is critical was the
     least readable thing on the page. Solved against #E8F0F5, the darkest light surface
     any skin declares, so all of them clear 4.5. The triplet moves with the hex, because
     two tokens naming one colour that can disagree eventually do. */
  --good:#167B48; --warn:#966007; --serious:#C04000; --crit:#D90019;
  --violet:#6451ED; --green:#167B48; --amber:#966007; --coral:#D61600; --rose:#D41245;
  --good-rgb:22 123 72; --warn-rgb:150 96 7; --serious-rgb:192 64 0;
  --crit-rgb:217 0 25; --violet-rgb:100 81 237;
  --cyan-ink:#007284;
  --line:rgba(10,21,38,.16); --line-2:rgba(10,21,38,.09);
  --ink:#1D1D1F; --ink-2:#515154; --muted:#67676C; --ink-3:#67676C;
  /* Colour is reserved for accent. The token is still named for the CyberX cyan it
     was minted for, and renaming it across 980 lines is a refactor this build does
     not need; the slot is what matters, not the word. */
  /* The CyberX signal, taken down to where it can carry text on white. The bright
     cyan is 2.01:1 on a white panel, which is a colour, not a link. The button
     gradient runs deep to mid with white ink, 7.85:1 falling to 4.75:1. */
  --cyan:#007E96; --cyan-2:#00C8E8; --cyan-deep:#005A6B;
  --focus:#007E96; --on-accent:#FFFFFF;
  --band-0:#B3143C; --band-1:#DE2545; --band-2:#E42313; --band-3:#DB3600; --band-4:#B55E00;
  --band-5:#966E0C; --band-6:#787922; --band-7:#59802A; --band-8:#248555; --band-9:#0D855D;
  /* The cyan glow is the CyberX signature and has no Apple equivalent, so the glow
     tokens resolve to a soft neutral shadow rather than to nothing. FR-SKN-11. */
  --glow-cyan:0 0 0 1px rgba(29,29,31,.08); --glow-soft:0 8px 24px -14px rgba(29,29,31,.18);
  --shadow:0 1px 2px rgba(29,29,31,.05), 0 12px 28px -22px rgba(29,29,31,.30);
  /* Softer, larger radii. Two tiers only, matching the names the rules already use. */
  --r:18px; --r-sm:12px;
  --tint-1:rgba(29,29,31,.02); --tint-2:rgba(29,29,31,.03);
  --tint-3:rgba(29,29,31,.04); --tint-4:rgba(29,29,31,.05);
  --tint-5:rgba(29,29,31,.06);
  --rail-line:rgba(29,29,31,.10); --rail-fill:rgba(29,29,31,.04);
  --rail-ink:#fff;
  --mark-from:#00C8E8; --mark-to:#0A1526; --mark-tick:#fff; --mark-accent:#007E96;
  --mark-ink:#08131F; --ink-hover:#1D1D1F; --h1-from:#1D1D1F; --h1-to:#007E96; --h1-stop:30%;
}
@media (prefers-color-scheme: light){
  :root[data-skin=apple]:not([data-theme=dark]){
  --accent-rgb:0 126 150; --shadow-rgb:29 29 31;
  --rail-from:rgba(10,21,38,.93); --rail-to:rgba(5,10,18,.95); --scrim:rgba(3,8,14,.42);
  /* ground and surfaces */
  --void:#EEF3F9; --deep:#F4F7FB; --panel:rgba(255,255,255,.88); --panel-2:rgba(247,250,253,.82); --rail:#0A1526;
  /* Status colours, darkened for a light ground. The shipped values are tuned to read on
     a dark surface and measured 2.02 to 4.04 against a light one, so every light theme
     failed the threshold on status text: a red that says something is critical was the
     least readable thing on the page. Solved against #E8F0F5, the darkest light surface
     any skin declares, so all of them clear 4.5. The triplet moves with the hex, because
     two tokens naming one colour that can disagree eventually do. */
  --good:#167B48; --warn:#966007; --serious:#C04000; --crit:#D90019;
  --violet:#6451ED; --green:#167B48; --amber:#966007; --coral:#D61600; --rose:#D41245;
  --good-rgb:22 123 72; --warn-rgb:150 96 7; --serious-rgb:192 64 0;
  --crit-rgb:217 0 25; --violet-rgb:100 81 237;
  --cyan-ink:#007284;
  --line:rgba(10,21,38,.16); --line-2:rgba(10,21,38,.09);
  --ink:#1D1D1F; --ink-2:#515154; --muted:#67676C; --ink-3:#67676C;
  /* Colour is reserved for accent. The token is still named for the CyberX cyan it
     was minted for, and renaming it across 980 lines is a refactor this build does
     not need; the slot is what matters, not the word. */
  /* The CyberX signal, taken down to where it can carry text on white. The bright
     cyan is 2.01:1 on a white panel, which is a colour, not a link. The button
     gradient runs deep to mid with white ink, 7.85:1 falling to 4.75:1. */
  --cyan:#007E96; --cyan-2:#00C8E8; --cyan-deep:#005A6B;
  --focus:#007E96; --on-accent:#FFFFFF;
  --band-0:#B3143C; --band-1:#DE2545; --band-2:#E42313; --band-3:#DB3600; --band-4:#B55E00;
  --band-5:#966E0C; --band-6:#787922; --band-7:#59802A; --band-8:#248555; --band-9:#0D855D;
  /* The cyan glow is the CyberX signature and has no Apple equivalent, so the glow
     tokens resolve to a soft neutral shadow rather than to nothing. FR-SKN-11. */
  --glow-cyan:0 0 0 1px rgba(29,29,31,.08); --glow-soft:0 8px 24px -14px rgba(29,29,31,.18);
  --shadow:0 1px 2px rgba(29,29,31,.05), 0 12px 28px -22px rgba(29,29,31,.30);
  /* Softer, larger radii. Two tiers only, matching the names the rules already use. */
  --r:18px; --r-sm:12px;
  --tint-1:rgba(29,29,31,.02); --tint-2:rgba(29,29,31,.03);
  --tint-3:rgba(29,29,31,.04); --tint-4:rgba(29,29,31,.05);
  --tint-5:rgba(29,29,31,.06);
  --rail-line:rgba(29,29,31,.10); --rail-fill:rgba(29,29,31,.04);
  --rail-ink:#fff;
  --mark-from:#00C8E8; --mark-to:#0A1526; --mark-tick:#fff; --mark-accent:#007E96;
  --mark-ink:#08131F; --ink-hover:#1D1D1F; --h1-from:#1D1D1F; --h1-to:#007E96; --h1-stop:30%;
}
}


/* ---------- the Vector Grid skin ---------- */
:root[data-skin=vector]{
  /* Vector Grid, dark. Built by Replit against the CyberX palette and ported here.
     Signal first: hard edges, structural rules instead of shadows, and cyan used to
     make risk legible rather than to decorate. Dark is the operating mode. */
  --void:#060D16; --deep:#0A1526; --panel:#0A1526; --panel-2:#0E1D31; --rail:#060D16;
  /* Stated here as well as in the light block, so neither theme half inherits its
     palette from the base. The values match the base because these are the ones tuned
     for a dark ground; the point is that the pair is symmetric, so adding a colour to
     one theme can never silently leave the other on whatever came before. */
  --good:#25D07A; --warn:#F5A623; --serious:#FF8A50; --crit:#FF5468;
  --violet:#8C7EF2; --green:#25D07A; --amber:#F5A623; --coral:#FF6B5A; --rose:#F15780;
  --good-rgb:37 208 122; --warn-rgb:245 166 35; --serious-rgb:255 138 80;
  --crit-rgb:255 84 104; --violet-rgb:140 126 242;
  --cyan-ink:#00C8E8;
  /* Cyan tinted rules organise a dark surface without making every boundary look live. */
  --line:rgba(136,207,222,.18); --line-2:rgba(136,207,222,.10);
  --ink:#E5F7FB; --ink-2:#8AA7B2; --muted:#8096A4; --ink-3:#8096A4;
  --cyan:#00C8E8; --cyan-2:#38E0F5; --cyan-deep:#0090AC;
  --focus:#00C8E8; --on-accent:#03151B;
  --accent-rgb:0 200 232; --shadow-rgb:0 0 0;
  --band-0:#F0518A; --band-1:#F2564F; --band-2:#EF4A3C; --band-3:#FF7345; --band-4:#FF9A2E;
  --band-5:#F0BC3C; --band-6:#C9CB44; --band-7:#88C244; --band-8:#33BE79; --band-9:#12B981;
  /* Borders carry the structure, so the glow tokens are almost nothing. */
  --glow-cyan:0 0 0 1px rgba(136,207,222,.28); --glow-soft:none;
  /* A hard offset cut rather than a soft ambient shadow. It is the signature. */
  --shadow:5px 5px 0 rgba(0,144,172,.16);
  /* Square on purpose. Rounded geometry is restricted, not applied to everything. */
  --r:6px; --r-sm:2px;
  --tint-1:rgba(136,207,222,.03); --tint-2:rgba(136,207,222,.05);
  --tint-3:rgba(136,207,222,.07); --tint-4:rgba(136,207,222,.09);
  --tint-5:rgba(136,207,222,.12);
  --rail-line:rgba(136,207,222,.18); --rail-fill:rgba(136,207,222,.07);
  --rail-ink:#E5F7FB;
  --rail-from:#060D16; --rail-to:#060D16; --scrim:rgba(6,13,22,.82);
  --mark-from:#00C8E8; --mark-to:#0A1526; --mark-tick:#060D16; --mark-accent:#38E0F5;
  --mark-ink:#E5F7FB; --ink-hover:#E5F7FB;
  --h1-from:#E5F7FB; --h1-to:#38E0F5; --h1-stop:20%;
}
:root[data-skin=vector][data-theme=light]{
  /* Vector Grid, light. Intended for planning and review rather than for operating. */
  /* The panel is pure white because the health bands were tuned against white and
     sit at 4.46 to 4.50 on anything a shade below it. Retuning ten band colours
     to save two points of lightness would be the tail wagging the dog. */
  --void:#F4F8FB; --deep:#E8F0F5; --panel:#FFFFFF; --panel-2:#F9FCFD; --rail:#060D16;
  /* Status colours, darkened for a light ground. The shipped values are tuned to read on
     a dark surface and measured 2.02 to 4.04 against a light one, so every light theme
     failed the threshold on status text: a red that says something is critical was the
     least readable thing on the page. Solved against #E8F0F5, the darkest light surface
     any skin declares, so all of them clear 4.5. The triplet moves with the hex, because
     two tokens naming one colour that can disagree eventually do. */
  --good:#167B48; --warn:#966007; --serious:#C04000; --crit:#D90019;
  --violet:#6451ED; --green:#167B48; --amber:#966007; --coral:#D61600; --rose:#D41245;
  --good-rgb:22 123 72; --warn-rgb:150 96 7; --serious-rgb:192 64 0;
  --crit-rgb:217 0 25; --violet-rgb:100 81 237;
  --cyan-ink:#007284;
  --line:#C7D8E1; --line-2:#D8E5EC;
  --ink:#081521; --ink-2:#233B4A; --muted:#566A78; --ink-3:#566A78;
  /* The mid cyan is 3.65:1 on this paper, which is a colour rather than text, so the
     accent that carries words is the deeper one. */
  --cyan:#00697D; --cyan-2:#0090AC; --cyan-deep:#005160;
  --focus:#00697D; --on-accent:#FFFFFF;
  --accent-rgb:0 105 125; --shadow-rgb:8 21 33;
  --band-0:#B3143C; --band-1:#DE2545; --band-2:#E42313; --band-3:#DB3600; --band-4:#B55E00;
  --band-5:#966E0C; --band-6:#787922; --band-7:#59802A; --band-8:#248555; --band-9:#0D855D;
  --glow-cyan:0 0 0 1px rgba(0,105,125,.22); --glow-soft:none;
  --shadow:5px 5px 0 rgba(0,144,172,.16);
  --r:6px; --r-sm:2px;
  --tint-1:rgba(8,21,33,.02); --tint-2:rgba(8,21,33,.035);
  --tint-3:rgba(8,21,33,.05); --tint-4:rgba(8,21,33,.07);
  --tint-5:rgba(8,21,33,.09);
  --rail-line:rgba(136,207,222,.18); --rail-fill:rgba(136,207,222,.07);
  --rail-ink:#E5F7FB;
  --rail-from:#060D16; --rail-to:#060D16; --scrim:rgba(8,21,33,.42);
  --mark-from:#00C8E8; --mark-to:#0A1526; --mark-tick:#060D16; --mark-accent:#00697D;
  --mark-ink:#081521; --ink-hover:#081521;
  --h1-from:#081521; --h1-to:#00697D; --h1-stop:30%;
}
@media (prefers-color-scheme: light){
  :root[data-skin=vector]:not([data-theme=dark]){
  /* Vector Grid, light. Intended for planning and review rather than for operating. */
  /* The panel is pure white because the health bands were tuned against white and
     sit at 4.46 to 4.50 on anything a shade below it. Retuning ten band colours
     to save two points of lightness would be the tail wagging the dog. */
  --void:#F4F8FB; --deep:#E8F0F5; --panel:#FFFFFF; --panel-2:#F9FCFD; --rail:#060D16;
  /* Status colours, darkened for a light ground. The shipped values are tuned to read on
     a dark surface and measured 2.02 to 4.04 against a light one, so every light theme
     failed the threshold on status text: a red that says something is critical was the
     least readable thing on the page. Solved against #E8F0F5, the darkest light surface
     any skin declares, so all of them clear 4.5. The triplet moves with the hex, because
     two tokens naming one colour that can disagree eventually do. */
  --good:#167B48; --warn:#966007; --serious:#C04000; --crit:#D90019;
  --violet:#6451ED; --green:#167B48; --amber:#966007; --coral:#D61600; --rose:#D41245;
  --good-rgb:22 123 72; --warn-rgb:150 96 7; --serious-rgb:192 64 0;
  --crit-rgb:217 0 25; --violet-rgb:100 81 237;
  --cyan-ink:#007284;
  --line:#C7D8E1; --line-2:#D8E5EC;
  --ink:#081521; --ink-2:#233B4A; --muted:#566A78; --ink-3:#566A78;
  /* The mid cyan is 3.65:1 on this paper, which is a colour rather than text, so the
     accent that carries words is the deeper one. */
  --cyan:#00697D; --cyan-2:#0090AC; --cyan-deep:#005160;
  --focus:#00697D; --on-accent:#FFFFFF;
  --accent-rgb:0 105 125; --shadow-rgb:8 21 33;
  --band-0:#B3143C; --band-1:#DE2545; --band-2:#E42313; --band-3:#DB3600; --band-4:#B55E00;
  --band-5:#966E0C; --band-6:#787922; --band-7:#59802A; --band-8:#248555; --band-9:#0D855D;
  --glow-cyan:0 0 0 1px rgba(0,105,125,.22); --glow-soft:none;
  --shadow:5px 5px 0 rgba(0,144,172,.16);
  --r:6px; --r-sm:2px;
  --tint-1:rgba(8,21,33,.02); --tint-2:rgba(8,21,33,.035);
  --tint-3:rgba(8,21,33,.05); --tint-4:rgba(8,21,33,.07);
  --tint-5:rgba(8,21,33,.09);
  --rail-line:rgba(136,207,222,.18); --rail-fill:rgba(136,207,222,.07);
  --rail-ink:#E5F7FB;
  --rail-from:#060D16; --rail-to:#060D16; --scrim:rgba(8,21,33,.42);
  --mark-from:#00C8E8; --mark-to:#0A1526; --mark-tick:#060D16; --mark-accent:#00697D;
  --mark-ink:#081521; --ink-hover:#081521;
  --h1-from:#081521; --h1-to:#00697D; --h1-stop:30%;
}
}

*{box-sizing:border-box}
/* The hidden attribute has to beat any display a rule sets, or an element carrying it
   still paints. The drilldown overlay is position:fixed inset:0 with display:grid, so
   while hidden it sat invisibly over every page and swallowed every click on it. */
[hidden]{display:none!important}
html,body{margin:0;padding:0;height:100%}
body{
  background:var(--void); color:var(--ink);
  font-family:Primus,'Archivo',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  font-size:15px; line-height:1.5; -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
.mono,.num{font-family:'IBM Plex Mono',ui-monospace,Menlo,monospace;font-variant-numeric:tabular-nums;letter-spacing:-.01em}
h1,h2,h3,h4{margin:0;font-weight:700;letter-spacing:-.02em;text-wrap:balance}
a{color:var(--cyan-ink);text-decoration:none}
/* Hover keeps the accessible ink rather than swapping to a brighter cyan. The
   swap is comfortable on a dark ground and fails on a light one: measured 3.76
   to 1 on vector light and 2.01 to 1 on apple light, against the 4.5 to 1 that
   text this size needs. With the ink it is 5.61 in every skin and theme. The
   underline carries the hover instead, and only on a plain link, so an anchor
   that is really a button or a tab keeps its own treatment. */
a:hover{color:var(--cyan-ink)}
a:not([class]):hover{text-decoration:underline;text-underline-offset:2px}
button,input,select,textarea{font:inherit;color:inherit}
:focus-visible{outline:2px solid var(--focus);outline-offset:3px;border-radius:6px}
[dir=rtl] body{font-family:'Noto Naskh Arabic',Primus,sans-serif}

/* ---------- animated ground ---------- */
.bg{position:fixed;inset:0;z-index:-2;overflow:hidden;pointer-events:none}
.bg canvas{position:absolute;inset:0;width:100%;height:100%;opacity:.55}
.bg::after{
  content:'';position:absolute;inset:-20%;
  background:
    radial-gradient(48rem 32rem at 12% 8%, rgb(var(--accent-rgb) / .14), transparent 62%),
    radial-gradient(42rem 30rem at 88% 92%, rgb(var(--violet-rgb) / .13), transparent 62%),
    radial-gradient(36rem 26rem at 72% 12%, rgb(var(--good-rgb) / .07), transparent 60%);
  animation:drift 26s ease-in-out infinite alternate;
}
@keyframes drift{
  0%{transform:translate3d(0,0,0) scale(1)}
  100%{transform:translate3d(-2.5%,2%,0) scale(1.06)}
}
:root[data-theme=light] .bg canvas{opacity:.30}
:root[data-theme=light] .bg::after{opacity:.55}
@media (prefers-reduced-motion:reduce){
  .bg::after{animation:none}
  .bg canvas{display:none}
  /* Delay as well as duration. Zeroing only the duration leaves a delayed change
     waiting out its full delay, which is not less motion, it is the same motion with a
     pause in front of it. It mattered here: the drawer hides its contents on a delay so
     the slide is seen before they go, and with the slide reduced to nothing the contents
     stayed reachable by keyboard for 220ms after the drawer had gone. */
  *{animation-duration:.001ms!important;animation-delay:0s!important;
    transition-duration:.001ms!important;transition-delay:0s!important}
}

/* ---------- shell ---------- */
.app{display:grid;grid-template-columns:var(--railw,264px) 1fr;min-height:100vh;
  background:linear-gradient(90deg,var(--rail) 0,var(--rail) var(--railw,264px),transparent var(--railw,264px))}
[dir=rtl] .app{background:linear-gradient(270deg,var(--rail) 0,var(--rail) var(--railw,264px),transparent var(--railw,264px))}
.rail{
  position:sticky;top:0;height:100vh;display:flex;flex-direction:column;
  background:linear-gradient(180deg, var(--rail-from) 0%, var(--rail-to) 100%);
  border-right:1px solid var(--line);
  backdrop-filter:blur(14px);
  box-shadow:1px 0 0 rgb(var(--accent-rgb) / .07), 12px 0 40px -30px rgb(var(--shadow-rgb) / .9);
  z-index:5;
}
.rail .brand{padding:22px 20px 16px;border-bottom:1px solid var(--rail-line)}
.rail .brand .wordmark-wrap svg{height:36px;width:auto;color:var(--rail-ink)}
/* A link, not a heading, and it carries the key that says where it goes. The padding,
   the gap and the border are unchanged, so making it a link moved nothing. */
.rail .who{padding:14px 20px;border-bottom:1px solid var(--rail-line);display:flex;gap:11px;
  align-items:center;text-decoration:none;transition:background .14s;position:relative}
.rail a.who:hover{background:rgb(var(--accent-rgb) / .07)}
.rail a.who:hover .wkey{opacity:.95;color:var(--cyan-2)}
/* Positioned, not laid out. As a flex child with margin-left:auto it took width from
   the name beside it and the role wrapped onto a second line, which is movement this
   change had no business causing. */
.rail .who .wkey{position:absolute;right:16px;top:50%;transform:translateY(-50%);
  width:15px;height:15px;opacity:0;color:var(--rail-ink-3);
  transition:opacity .14s,color .14s}
.rail a.who:focus-visible{outline:2px solid var(--cyan);outline-offset:-2px}
.rail .who .av{
  width:34px;height:34px;border-radius:10px;flex:none;display:grid;place-items:center;
  background:linear-gradient(140deg,var(--cyan-deep),var(--violet));color:var(--on-accent-solid); /* on an accent, not on the rail */
  font-weight:700;font-size:13px;box-shadow:var(--glow-cyan)}
.rail .who b{display:block;font-size:13.5px;color:var(--rail-ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rail .who span{display:block;font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--cyan-2);margin-top:2px}

/* min-height:0 is load bearing. A flex child with flex:1 will not shrink below its
   content height without it, so a long navigation pushed the rail footer, and with
   it the sign out button, past the bottom of the 100vh column. */
.nav{padding:14px 12px;display:flex;flex-direction:column;gap:2px;overflow-y:auto;flex:1 1 auto;min-height:0}
.nav .sec{font-size:9.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--rail-ink-3);padding:14px 12px 5px;font-weight:700}
.nav a{
  display:flex;align-items:center;gap:11px;padding:9px 13px;border-radius:10px;
  color:var(--rail-ink-2);font-size:14px;font-weight:400;position:relative;
  transition:background .16s ease,color .16s ease,transform .16s ease}
.nav a svg{width:17px;height:17px;flex:none;opacity:.75}
.nav a:hover{background:rgb(var(--accent-rgb) / .07);color:var(--rail-ink);transform:translateX(2px)}
.nav a:hover svg{opacity:1}
.nav a[aria-current=page]{
  background:linear-gradient(90deg, rgb(var(--accent-rgb) / .18), rgb(var(--accent-rgb) / .04));
  color:var(--rail-ink);font-weight:600;box-shadow:inset 0 0 0 1px rgb(var(--accent-rgb) / .22)}
.nav a[aria-current=page]::before{
  content:'';position:absolute;left:0;top:50%;transform:translateY(-50%);
  width:3px;height:22px;border-radius:0 3px 3px 0;background:var(--cyan);
  box-shadow:0 0 14px 1px rgb(var(--accent-rgb) / .9)}
.nav a[aria-current=page] svg{opacity:1;color:var(--cyan)}  /* the rail is dark under both themes, so this stays the bright accent */
.nav .badge{margin-left:auto;font-size:10.5px;padding:1px 7px;border-radius:20px;
  background:rgb(var(--crit-rgb) / .16);color:var(--crit);font-family:'IBM Plex Mono',monospace}

.railfoot{padding:14px 20px;border-top:1px solid var(--line);background:var(--rail);flex:none;position:relative;z-index:2}
.railfoot svg{height:20px;color:var(--ink-2);opacity:.75;margin-bottom:9px}
.railfoot .meta{font-size:10.5px;color:var(--rail-ink-3);line-height:1.7}
.railfoot .acts{display:flex;gap:7px;margin-top:11px}
.railfoot button{
  flex:1;background:var(--rail-fill);border:1px solid var(--rail-line);color:var(--rail-ink-2);
  border-radius:9px;padding:7px 10px;font-size:11.5px;cursor:pointer;transition:.16s}
.railfoot button:hover{background:rgb(var(--accent-rgb) / .10);color:var(--rail-ink);border-color:rgb(var(--accent-rgb) / .35)}

/* ---------- main ---------- */
.main{min-width:0;padding:26px 30px 70px;position:relative}
.head{display:flex;align-items:flex-end;justify-content:space-between;gap:22px;flex-wrap:wrap;margin-bottom:22px}
/* The page header wraps, and the group of buttons inside it did not. Nineteen rows
   across eleven pages are written as an inline `display:flex` with no wrap, which is
   fine until the screen is narrower than the buttons: the calendar put five of them in
   a row 496px wide on a 430px phone, and the last two left the screen with no way to
   reach them, because a page that scrolls sideways is a page whose right edge nobody
   finds. Setting only the wrap leaves every one of those inline styles doing what it
   says, since none of them names a wrap to override. */
main [style*="display:flex"]{flex-wrap:wrap}
.head h1{font-size:34px;font-weight:700;letter-spacing:-.03em;
  background:linear-gradient(120deg,var(--h1-from) var(--h1-stop),var(--h1-to) 130%);
  -webkit-background-clip:text;background-clip:text;color:transparent}
.head p{margin:7px 0 0;color:var(--ink-2);max-width:78ch;font-size:14px}
.kicker{font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--cyan-ink);font-weight:700;
  display:flex;align-items:center;gap:8px;margin-bottom:5px}
.kicker::before{content:'';width:22px;height:2px;border-radius:2px;background:var(--cyan);
  box-shadow:0 0 10px rgb(var(--accent-rgb) / .9)}

/* ---------- tiles ---------- */
/* A row of figures keeps its count at every width. auto-fit was dropping the last
   tile onto a line of its own as soon as the row narrowed, so a row of seven read as
   six and a stray. grid-auto-flow:column puts every child on one line whatever the
   count is, and minmax(0,1fr) lets them share the width and shrink rather than push
   the page sideways. The gap is the one named distance, so the space between figures
   matches the space between panels. */
.tiles{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(0,1fr);gap:var(--panel-gap);margin-bottom:20px}
.tile{
  position:relative;background:linear-gradient(160deg,var(--panel) 0%,var(--panel-2) 100%);
  border:1px solid var(--line);border-radius:var(--r);padding:16px 17px;overflow:hidden;
  box-shadow:var(--shadow);transition:transform .18s ease,box-shadow .18s ease,border-color .18s}
.tile::before{content:'';position:absolute;inset:0 0 auto 0;height:2px;
  background:linear-gradient(90deg,transparent,var(--accent-c,var(--cyan)),transparent);opacity:.85}
.tile:hover{transform:translateY(-2px);border-color:rgb(var(--accent-rgb) / .30);
  box-shadow:var(--shadow),0 0 30px -14px var(--accent-c,var(--cyan))}
.tile .lab{display:block;font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);font-weight:600;line-height:1.4}
.tile .val{display:block;font-size:32px;font-weight:700;letter-spacing:-.035em;line-height:1.1;margin-top:6px;
  font-family:'IBM Plex Mono',monospace;
  /* --accent-c is set per tile and is often a status colour, so it is mixed toward the
     ink in force rather than used raw. On the executive page one measured 1.99 to 1. */
  color:color-mix(in oklab,var(--accent-c,var(--ink)) 60%,var(--ink))}
.tile .sub{display:block;font-size:12.5px;color:var(--ink-2);margin-top:5px;line-height:1.4;min-height:2.8em}
.tile .spark{margin-top:9px;height:26px;display:block;width:100%}

/* ---------- panels ---------- */
/* Stretch, not start. A short panel beside a tall one left a dead rectangle that read
   as a broken half section. The panel is a flex column so its scroll area or its chart
   takes the extra height instead. */
/* start, not stretch. Stretch made every panel as tall as the tallest in its row, and a
   chart's height comes from its own aspect ratio rather than from its container, so the
   drawing stayed where it was and the panel grew underneath it. On the account page that
   left 708 pixels of nothing below the health chart. A panel is now exactly as tall as
   what is in it. */
/* The distance between one block and the next, whether they sit side by side inside a
   grid or stack down the page. Fifty three templates wrote "margin-top:16px" by hand to
   mean the second of those, so the same distance had one source in the stylesheet and
   fifty three copies in the markup, and only the copies could drift. */
.grid{display:grid;gap:var(--panel-gap);align-items:stretch}
.apart{margin-top:var(--panel-gap)}
/* Except where a panel is meant to fill its row, which it says for itself. */
.grid > .panel.fill{align-self:stretch}
/* A row keeps its count at every width. auto-fit was dropping the last panel onto a
   line of its own the moment the row narrowed, which is the jump you see when you
   drag the window. minmax(0,1fr) rather than 1fr, because 1fr will not shrink below
   its content and the row would push the page sideways instead of narrowing. */
.g2{grid-template-columns:repeat(2,minmax(0,1fr))}
.g3{grid-template-columns:repeat(3,minmax(0,1fr))}
.panel{
  background:linear-gradient(160deg,var(--panel) 0%,var(--panel-2) 100%);
  border:1px solid var(--line);border-radius:var(--r);padding:18px 20px 20px;min-width:0;
  box-shadow:var(--shadow);position:relative;overflow:hidden;
  display:flex;flex-direction:column}
.panel>.scroll{flex:1 1 auto;min-height:0}
.panel .empty{margin:auto 0}
.panel::after{content:'';position:absolute;inset:0;pointer-events:none;border-radius:var(--r);
  box-shadow:inset 0 1px 0 var(--tint-4)}
/* A panel heading inside a header wrapper is still a panel heading. All thirteen on the
   command centre carry the same dot span in the markup and only nine painted it, because
   this rule reaches a direct child and four of them sit one level in behind a .ph. Those
   four stood a weight heavier with no cyan mark and read as a different class of thing.
   Written as one declaration for both forms rather than a second rule beside it. A second
   rule needs one more class to reach the wrapper, and that extra class beat the skin
   layer: under the Apple skin those four headings kept -.015em while the other nine took
   the skin's -.022em, so the fix for a missing dot quietly split the typography in two.
   Same selectors, same specificity, no divergence available. */
.panel>:is(h2,h3),.ph>:is(h2,h3){font-size:16px;font-weight:600;letter-spacing:-.015em;display:flex;align-items:center;gap:9px}
.panel>:is(h2,h3) .dot,.ph>:is(h2,h3) .dot{width:7px;height:7px;border-radius:50%;background:var(--cyan);box-shadow:0 0 10px var(--cyan)}
/* Whatever follows a panel heading stands the same distance from it. Seventy of the
   seventy one headings on the platform are followed by a caption carrying that gap, and
   the odd one out ran a table straight into the heading, with the rule the vector skin
   draws between them landing inside the table's top edge. The distance belongs to the
   heading, not to the one kind of thing that usually follows it. */
.panel>:is(h2,h3)+*,.ph>:is(h2,h3)+*{margin-top:var(--gap-cap)}
/* The caption belongs to the heading, wherever the heading sits. Read as a direct child
   only, the four captions behind a .ph took none of this: the class was on the element and
   did nothing, so they rendered at the body size in full ink with the browser's own
   paragraph margin. A caption that looks exactly like body text is not a caption, and it
   made four panels read as though the sentence under the heading was content. */
.panel>.hint,.ph>.hint{font-size:12.5px;color:var(--muted);margin:var(--gap-cap) 0 var(--gap-after-cap);line-height:1.5}
.panel.flat{padding:0}
.panel.flat>.scroll{flex:1 1 auto;min-height:0}
.panel.flat>.ph{padding:18px 20px 12px}
/* A flat panel has no padding because the two things it was built to hold, a .ph header
   and a .scroll table, bring their own. Seven panels put prose straight inside one, and
   prose has none: the heading and the paragraph sat hard against the border with the
   text touching the edge. Direct children only, so a heading inside a .ph is untouched
   and a table still reaches the full width. */
.panel.flat>:is(h2,h3){padding:18px 20px 0}
.panel.flat>:is(p,.hint,.note,.empty,.form,form,.field){padding-inline:20px}
.panel.flat>:is(h2,h3)+:is(p,.hint){padding-top:0}
.panel.flat>:last-child{padding-bottom:18px}
.panel.flat>.scroll:last-child,.panel.flat>.tblwrap:last-child{padding-bottom:0}

/* Nine tables on four pages use this and one page declared it, in a <style> of its own,
   so on the other three it was a class that did nothing. It exists to let a table wider
   than its panel be scrolled to rather than cut off, and body carries overflow-x:hidden,
   so without it the far columns are not merely off the edge, they are unreachable. */
.tblwrap{overflow-x:auto;overflow-y:hidden;max-width:100%}
.tblwrap > table{min-width:max-content}

/* ---------- charts ---------- */
.chart{width:100%;display:block;overflow:visible}
/* No glow on the data. A drop shadow on a one pixel line reads as a smear, not as
   emphasis, and it was the single thing making these charts look cheap. */
.chart{display:block;width:100%;height:auto;overflow:visible}
.chart .grid-line{stroke:var(--line);stroke-width:1;shape-rendering:crispEdges}
.chart .axis-line{stroke:var(--line);stroke-width:1.5}
/* A chart's axis is a physical arrangement, not a reading order.

   text-anchor start and end are LOGICAL: they follow the direction of the text, so in
   Arabic they swap, and the first and last labels on an axis anchored to hold themselves
   inside the drawing anchored themselves outside it instead. Measured in right to left:
   two charts pushed a label twenty one and fifteen units past their own edge.

   direction:ltr fixes the anchor to the geometry. unicode-bidi:isolate keeps the label's
   own characters laid out by the bidi algorithm, so an Arabic month or platform name
   still reads right to left inside the box it was given. */
.chart text{direction:ltr;unicode-bidi:isolate}
.chart .axis{fill:var(--muted);font-size:13px;font-family:'IBM Plex Mono',monospace;letter-spacing:.01em}
.chart .axis.val{fill:var(--ink-2);font-weight:600;font-size:13px}
/* --muted, not --line. A zero is a reading, and this drew it in the colour the
   dividers are drawn in, which measured about 1.7 to 1 on a white panel, so a real
   zero was hard to tell from a missing one. It reads 5.63 now. --line is for
   strokes. The literal it used to name is left out of this note on purpose: the
   skin lint reads comments too, and it is right to. */
.chart .axis.dim{fill:var(--muted)}
.chart .empty-note{fill:var(--muted);font-size:14px}
.chart .area{opacity:.14}
.chart .linepath{fill:none;stroke-width:2.25;stroke-linecap:round;stroke-linejoin:round}
.chart .endpoint{stroke:var(--panel);stroke-width:2.5}
.chart .bar{transition:opacity .15s}
.chart g[data-detail]{cursor:pointer}
.chart g[data-detail]:hover .bar,.chart g[data-detail]:focus-visible .bar{opacity:.72}



.bars{display:flex;flex-direction:column;gap:11px}
.barrow{display:grid;grid-template-columns:1fr auto;gap:5px 12px;align-items:center}
.barrow .nm{font-size:13px;color:var(--ink-2)}
.barrow .ct{font-size:13px;color:var(--ink);font-family:'IBM Plex Mono',monospace;font-weight:600}
.track{grid-column:1/-1;height:8px;border-radius:99px;background:var(--tint-4);overflow:hidden;
  box-shadow:inset 0 0 0 1px var(--tint-3)}
.track>i{display:block;height:100%;border-radius:99px;
  background:linear-gradient(90deg,var(--c1,var(--cyan-deep)),var(--c2,var(--cyan)));
  animation:grow .9s cubic-bezier(.2,.8,.2,1) both}
/* No !important here. CSS ignores it inside a keyframe, so this was the only
   declaration in the animation and it was being discarded: the bars and the stacked
   segments appeared at their final width instead of growing. An animation value
   already beats a normal inline width while it runs, which is what the important
   was reaching for. Found by the Codex seat; no screenshot or final geometry check
   could have seen it, because the end state was always correct. */
@keyframes grow{from{width:0}}

.stack{display:flex;height:34px;border-radius:10px;overflow:hidden;gap:2px;
  background:var(--tint-3);box-shadow:inset 0 0 0 1px var(--tint-4)}
.stack>span{height:100%;position:relative;transition:filter .15s;
  animation:grow 1s cubic-bezier(.2,.8,.2,1) both}
.stack>span:hover{filter:brightness(1.25)}
/* ---------- the space around a chart ----------

   The inside of a chart is on a named scale in ChartSpace. The OUTSIDE was not on
   anything: whatever followed a drawing carried its own margin, so one panel put fifteen
   pixels between its chart and its legend and another put thirty one, and the graphs read
   as spaced at random down the page. One number, set from that same scale, used by
   everything that can follow a drawing.

   Stated as a custom property rather than repeated, because the next thing somebody puts
   under a chart should not have to know the number. */
:root{--chart-after:16px}
.legend{display:flex;flex-wrap:wrap;gap:10px 20px;margin-top:var(--chart-after);font-size:12.5px;color:var(--ink-2)}
/* Anything at all directly after a drawing, however it is marked up. */
.panel > svg + *,
.panel > .donut-wrap + *,
.panel > .gauge + *,
.panel > .chartwrap + *{margin-top:var(--chart-after)}
/* Nothing is added ABOVE a drawing. The caption already carries the distance below
   itself, and adding a top margin to the chart put twenty in the panels where the two
   collapsed and thirty six where they did not, which is the same chart sitting at two
   different distances from its own caption on two pages. The caption owns that gap. */
.legend i{width:9px;height:9px;border-radius:3px;display:inline-block;margin-right:7px;vertical-align:middle}
.legend b{font-family:'IBM Plex Mono',monospace;color:var(--ink)}

/* ---------- tables ---------- */
table.tbl{width:100%;border-collapse:collapse;font-size:13.5px}
.tbl th{text-align:left;font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);
  font-weight:700;padding:12px 15px;border-bottom:1px solid var(--line);
  background:var(--tint-1);position:sticky;top:0;z-index:1;backdrop-filter:blur(6px)}
.tbl td{padding:12px 15px;border-bottom:1px solid var(--line-2);vertical-align:top}
.tbl tbody tr{transition:background .14s}
.tbl tbody tr:hover{background:rgb(var(--accent-rgb) / .05)}
.tbl tbody tr:last-child td{border-bottom:0}
.scroll{max-height:560px;overflow:auto}
.scroll::-webkit-scrollbar{width:9px;height:9px}
.scroll::-webkit-scrollbar-thumb{background:var(--line);border-radius:9px}
.scroll::-webkit-scrollbar-thumb:hover{background:var(--cyan-deep)}
[dir=rtl] .tbl th,[dir=rtl] .tbl td{text-align:right}

/* ---------- chips ---------- */
.chip{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:7px;
  font-size:11px;font-weight:600;white-space:nowrap;font-family:'IBM Plex Mono',monospace;
  border:1px solid transparent}
/* A chip's text was the same colour as its own tint, which is the lowest contrast
   arrangement available: the ink and the paint are one hue at two strengths. Measured
   3.35 to 4.39 to 1 across the set. The tint stays, because that is what makes a chip
   read as a chip, and the ink is pulled toward whichever ink is in force, so it lightens
   on a dark ground and darkens on a light one from one declaration. */
.chip.good{background:rgb(var(--good-rgb) / .14);
  color:color-mix(in oklab,var(--good) 66%,var(--ink));border-color:rgb(var(--good-rgb) / .30)}
.chip.warn{background:rgb(var(--warn-rgb) / .14);
  color:color-mix(in oklab,var(--warn) 66%,var(--ink));border-color:rgb(var(--warn-rgb) / .30)}
.chip.serious{background:rgb(var(--serious-rgb) / .14);
  color:color-mix(in oklab,var(--serious) 62%,var(--ink));border-color:rgb(var(--serious-rgb) / .30)}
.chip.crit{background:rgb(var(--crit-rgb) / .15);
  color:color-mix(in oklab,var(--crit) 58%,var(--ink));border-color:rgb(var(--crit-rgb) / .32)}
.chip.accent{background:rgb(var(--accent-rgb) / .13);
  color:color-mix(in oklab,var(--cyan-ink) 70%,var(--ink));border-color:rgb(var(--accent-rgb) / .30)}
/* A chip whose colour comes from a score band. The tint is the band colour at 18% and
   the ink is the same band colour mixed toward the ink in force, so the two are never one
   hue at two strengths. */
.chip.band-chip{
  background:color-mix(in oklab,var(--bandc) 18%,transparent);
  border-color:color-mix(in oklab,var(--bandc) 40%,transparent);
  color:color-mix(in oklab,var(--bandc) 54%,var(--ink))}
.chip.neutral{background:var(--tint-4);
  color:color-mix(in oklab,var(--ink-2) 74%,var(--ink));border-color:var(--line)}

.note{border-radius:12px;padding:13px 16px;font-size:13px;margin-bottom:18px;
  border:1px solid rgb(var(--warn-rgb) / .30);background:rgb(var(--warn-rgb) / .08);color:var(--ink)}
.note.crit{border-color:rgb(var(--crit-rgb) / .32);background:rgb(var(--crit-rgb) / .09)}
.note.info{border-color:rgb(var(--accent-rgb) / .30);background:rgb(var(--accent-rgb) / .07)}
.stale{font-size:10.5px;color:var(--warn);font-weight:600}
.cov{font-size:11px;color:var(--muted)}

.btn{display:inline-flex;align-items:center;gap:8px;border:0;border-radius:10px;padding:10px 18px;
  font-size:13.5px;font-weight:600;cursor:pointer;text-decoration:none;
  background:linear-gradient(135deg,var(--cyan-deep),var(--cyan));color:var(--on-accent);
  box-shadow:0 0 24px -8px var(--cyan);transition:.16s}
.btn:hover{filter:brightness(1.10);transform:translateY(-1px);box-shadow:0 0 30px -6px var(--cyan);color:var(--on-accent)}
.btn.ghost{background:var(--tint-3);color:var(--ink);border:1px solid var(--line);box-shadow:none}
.btn.ghost:hover{border-color:rgb(var(--accent-rgb) / .4);background:rgb(var(--accent-rgb) / .07);color:var(--ink-hover)}
/* A grid of form fields, which is not a grid of panels. Twelve templates carried the
   same "gap:0 14px" inline to say so, and the row spacing then came from the field's own
   margin, which four rules set to four different values. One name, one scale. */
.grid.fields{gap:0 var(--field-col)}
.field{display:flex;flex-direction:column;gap:6px;margin-bottom:var(--field-gap)}
.field label{font-size:11px;font-weight:600;color:var(--ink-2);letter-spacing:.06em;text-transform:uppercase}
.field input,.field select,.field textarea{padding:11px 14px;border:1px solid var(--line);border-radius:10px;
  background:var(--tint-2);width:100%;transition:.16s}
.field input:focus,.field select:focus{border-color:var(--cyan-ink);box-shadow:0 0 0 3px rgb(var(--accent-rgb) / .12);outline:none}

.login{min-height:100vh;display:grid;place-items:center;padding:26px;position:relative}
.login .card{background:linear-gradient(160deg,var(--panel),var(--panel-2));border:1px solid var(--line);
  border-radius:20px;padding:38px;width:min(430px,100%);box-shadow:var(--shadow),var(--glow-soft)}
.login .card svg{height:40px;margin-bottom:10px;color:var(--mark-ink);}
.empty{padding:34px;text-align:center;color:var(--muted);font-size:13px}

/* ---------- below the width where the rail fits beside the content ---------- */
/* It used to lay out above the page: one column, rail static, and the navigation
   turned into a wrapping row. Three things went wrong at once. Every page began
   with the whole menu, so the content started roughly 500px down. The groups have
   children, and wrapping a list whose items expand put the open group's links in
   the middle of the row with the rest of the menu scattered around them. And the
   stripe below is painted on .app rather than on the rail, so it went on painting
   264px of rail colour down the left of a page that no longer had a rail there.

   So it becomes a drawer over the page instead, which is what a menu this size is
   everywhere else. The content keeps the top of the screen, the groups keep the
   vertical list they were written for, and the stripe is cancelled. */
@media(max-width:980px){
  .app{grid-template-columns:1fr;background:none}
  [dir=rtl] .app{background:none}

  /* Two selectors on purpose. The score sheet loads after this one and sets the rail
     to sticky inside its own phone breakpoint, which beat a single class here and put
     the rail back into the flow, pushing every page down again on a phone. This is the
     same defect the drawer was written to fix, reappearing below 760 only. */
  .app>.rail{position:fixed;inset-block:0;inset-inline-start:0;z-index:60;
    width:min(288px,86vw);height:100%;overflow-y:auto;overscroll-behavior:contain;
    background:var(--rail);
    transform:translateX(-100%);visibility:hidden;
    /* Moving something off screen does not take it out of the tab order. Closed, this
       drawer held 38 focusable controls that a keyboard user walked through before
       reaching the page, which is worse than the thing the skip link was added to fix and
       is invisible to anybody using a pointer. visibility does take them out, and the
       delay holds it visible for the length of the slide so the close is still seen. */
    transition:transform .22s cubic-bezier(.2,.8,.2,1),visibility 0s .22s}
  [dir=rtl] .app>.rail{transform:translateX(100%)}
  :root[data-drawer="open"] .app>.rail{transform:none;visibility:visible;
    transition:transform .22s cubic-bezier(.2,.8,.2,1),visibility 0s}

  /* The rail is its own width here, so the icons-only state does not apply. Left on,
     a drawer opened from a collapsed rail showed a column of unlabelled icons. */
  :root[data-rail="collapsed"] .app>.rail{--railw:264px}
  :root[data-rail="collapsed"] .brand svg.wordmark,
  :root[data-rail="collapsed"] .nav a span,
  :root[data-rail="collapsed"] .nav .sec,
  :root[data-rail="collapsed"] .who div,
  :root[data-rail="collapsed"] .railfoot .meta{display:revert;opacity:revert;width:revert}

  .nav{flex-direction:column;flex-wrap:nowrap}
  .nav a{width:auto}

  /* The control that collapses the rail to icons has no meaning once it is a drawer,
     and it already sits where a close control belongs, so it becomes one. Its handler
     closes the drawer at this width rather than collapsing a rail that is not there. */
  :root[data-drawer="open"] .railopen{display:none}

  .railopen{display:inline-flex;align-items:center;justify-content:center;
    position:fixed;inset-block-start:10px;inset-inline-start:10px;z-index:61;
    width:40px;height:40px;border-radius:11px;border:1px solid var(--line);
    background:var(--panel);color:var(--ink);cursor:pointer}
  .railopen:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
  .railopen svg{width:20px;height:20px}

  /* The token, not a literal. Each theme already defines how heavily it veils what is
     behind it, and a literal here would have been one weight across all six looks. */
  .railscrim{position:fixed;inset:0;z-index:59;background:var(--scrim);
    opacity:0;transition:opacity .22s;pointer-events:none}
  :root[data-drawer="open"] .railscrim{opacity:1;pointer-events:auto}

  /* The opener is fixed over the top left corner, so the bar underneath starts clear
     of it rather than behind it. */
  .main{padding:18px}
  /* Two selectors, because the base rule sets padding with the shorthand and is
     declared further down the file. A single class here has the same weight and loses
     on order, so the bar kept starting under the opener. */
  .main .topbar{padding-inline-start:58px}
  .head h1{font-size:26px}
}

/* The drawer is a fixed panel, so nothing behind it should scroll while it is open. */
:root[data-drawer="open"] body{overflow:hidden}

/* ---------- chart chrome added with the component set ---------- */
/* Column labels are drawn inside the SVG now, on the same scale as the bars. A
   separate HTML strip underneath could never line up with them. */
.legend .legrow{display:flex;align-items:center;width:100%;cursor:pointer;padding:4px 7px;border-radius:8px;transition:.14s}
.legend .legrow:hover{background:rgb(var(--accent-rgb) / .08);color:var(--ink)}

.gauge{display:flex;flex-direction:column;align-items:center;gap:2px}
.gauge .glab{font-size:11px;color:var(--muted);letter-spacing:.1em;text-transform:uppercase;font-weight:600;text-align:center}
.gauges{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:14px}

.funnel{display:flex;flex-direction:column;gap:9px}
.fstage{display:grid;grid-template-columns:minmax(120px,190px) 1fr 46px;align-items:center;gap:14px;
  cursor:pointer;padding:7px 9px;border-radius:9px;transition:background .14s}
.fstage:hover{background:rgb(var(--neutral-rgb) / .07)}
.fstage .fname{font-size:13px;color:var(--ink-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.fstage .ftrack{height:18px;border-radius:6px;background:rgb(var(--neutral-rgb) / .10);overflow:hidden;
  box-shadow:inset 0 0 0 1px rgb(var(--neutral-rgb) / .10)}
.fstage .ftrack>i{display:block;height:100%;border-radius:6px;animation:grow .8s cubic-bezier(.2,.8,.2,1) both}
.fstage .fval{font-size:13.5px;font-weight:700;text-align:right;font-family:'IBM Plex Mono',monospace;
  /* The stage colour arrives as --fc and is mixed toward the ink in force, so it reads on
     a light ground as well as a dark one. Written straight into `color` by the template
     it measured 1.99 to 1 in the light themes. */
  color:color-mix(in oklab,var(--fc) 52%,var(--ink))}
@media(max-width:620px){.fstage{grid-template-columns:1fr 46px}.fstage .ftrack{grid-column:1/-1}}



/* ---------- drilldown modal ---------- */
.modal{position:fixed;inset:0;z-index:60;display:grid;place-items:center;padding:26px;
  background:var(--scrim);backdrop-filter:blur(7px);animation:fade .18s ease both}
@keyframes fade{from{opacity:0}}
.modal-card{width:min(940px,100%);max-height:86vh;display:flex;flex-direction:column;
  background:linear-gradient(160deg,var(--panel),var(--panel-2));border:1px solid var(--line);
  border-radius:18px;box-shadow:0 40px 90px -40px rgb(var(--shadow-rgb) / 1), var(--glow-soft);animation:rise .22s cubic-bezier(.2,.8,.2,1) both}
@keyframes rise{from{transform:translateY(14px) scale(.985);opacity:0}}
.modal-head{display:flex;align-items:center;gap:14px;padding:18px 22px;border-bottom:1px solid var(--line)}
.modal-head h3{font-size:18px;flex:1}
.modal-head .x{background:var(--tint-4);border:1px solid var(--line);color:var(--ink-2);
  width:32px;height:32px;border-radius:9px;font-size:19px;line-height:1;cursor:pointer}
.modal-head .x:hover{border-color:var(--cyan-ink);color:var(--ink-hover)}
.modal-body{padding:18px 22px 22px;overflow:auto}
.modal-body .scroll{max-height:none}
.mnote{font-size:13px;color:var(--ink-2);margin:0 0 14px}
.mnote.dim{color:var(--muted);font-size:12px;margin:14px 0 0}
.mstats{display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:11px;margin-bottom:16px}
.mstats>div{background:var(--tint-2);border:1px solid var(--line);border-radius:11px;padding:11px 13px}
.mstats .lab{display:block;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);font-weight:700}
.mstats .val{display:block;font-size:22px;font-weight:700;margin-top:3px;color:var(--cyan-ink)}
[data-detail]{cursor:pointer}
[data-detail]:focus-visible{outline:2px solid var(--focus);outline-offset:2px}

/* ---------- the activation ladder ---------- */
.ladder{display:flex;gap:8px;flex-wrap:wrap;margin-top:6px}
.rung{flex:1;min-width:120px;position:relative;display:flex;flex-direction:column;gap:5px;
  padding:13px 14px;border-radius:12px;border:1px solid var(--line);cursor:pointer;
  background:var(--tint-1);transition:transform .16s,border-color .16s,box-shadow .16s}
.rung:hover{transform:translateY(-2px);border-color:rgb(var(--accent-rgb) / .35)}
.rung .n{font-family:'IBM Plex Mono',monospace;font-size:10px;color:var(--muted);font-weight:700}
.rung .t{font-size:13px;font-weight:600;line-height:1.3}
.rung .c{position:absolute;top:10px;right:11px;font-size:10px;font-family:'IBM Plex Mono',monospace;
  background:color-mix(in oklab,var(--cyan) 16%,var(--panel));color:var(--cyan-ink);
  border:1px solid color-mix(in oklab,var(--cyan) 30%,transparent);border-radius:20px;padding:1px 7px}
.rung.done{border-color:color-mix(in oklab,var(--good) 34%,transparent);
  background:color-mix(in oklab,var(--good) 8%,transparent)}
/* Mixed toward the ink in force rather than left as the raw green. At ten pixels on
   the light CyberX ground the raw colour measured 4.30 to 1 against the rung's own
   eight per cent tint, and eleven point five is the smallest text the platform uses, so
   the threshold is the full 4.5. The mix darkens it on a light ground and lightens it
   on a dark one from one declaration, which is the same treatment the chips take. */
.rung.done .n{color:color-mix(in oklab,var(--good) 62%,var(--ink))}

/* The current rung used to set white text on a pale tint. Readable on the dark ground
   and close to invisible on the light one. Both the tint and the text now come from
   tokens, so the pair resolves in whichever theme the reader is in. */
.rung.now{
  border-color:var(--cyan-ink);
  background:color-mix(in oklab,var(--cyan) 12%,var(--panel));
  box-shadow:inset 0 0 0 1px color-mix(in oklab,var(--cyan) 35%,transparent)}
.rung.now .t{color:var(--ink);font-weight:700}
.rung.now .n{color:var(--cyan-ink);font-weight:700}
.rung.now::after{
  content:'';position:absolute;left:0;top:0;bottom:0;width:3px;
  border-radius:12px 0 0 12px;background:var(--cyan)}

/* ---------- the client history ---------- */
.tl{list-style:none;margin:0;padding:0 0 0 6px;position:relative}
.tl::before{content:'';position:absolute;left:19px;top:8px;bottom:8px;width:2px;
  background:linear-gradient(180deg,var(--cyan),transparent);opacity:.28;border-radius:2px}
.tlrow{display:flex;gap:16px;padding:11px 0;position:relative}
.tldot{flex:none;width:28px;height:28px;border-radius:9px;display:grid;place-items:center;
  background:var(--panel);border:1px solid var(--line);color:var(--k,var(--cyan));z-index:1;
  box-shadow:0 0 14px -5px var(--k,var(--cyan))}
.tldot svg{width:14px;height:14px}
.tlbody{min-width:0;flex:1}
.tlhead{display:flex;align-items:center;gap:9px;flex-wrap:wrap;font-size:14px}
.tltext{margin:5px 0 4px;font-size:13px;color:var(--ink-2);white-space:pre-wrap}
details>summary{list-style:none;cursor:pointer}
details>summary::-webkit-details-marker{display:none}
textarea{padding:11px 14px;border:1px solid var(--line);border-radius:10px;
  background:var(--tint-2);width:100%;font-family:inherit;resize:vertical}
textarea:focus{border-color:var(--cyan-ink);box-shadow:0 0 0 3px rgb(var(--accent-rgb) / .12);outline:none}

/* ---------- the phase strip ---------- */
.phasedots{display:flex;align-items:center;gap:12px;flex-wrap:wrap}
.phasedots .track{display:flex;align-items:center;gap:0}
.phasedots .dot{
  width:13px;height:13px;border-radius:50%;flex:none;box-sizing:border-box;
  border:2px solid var(--line-2);background:var(--panel);transition:.2s}
.phasedots .dot.done{background:var(--cyan-deep);border-color:var(--cyan-deep)}
.phasedots .dot.ahead{border-color:var(--line);background:transparent}
.phasedots .dot.now{
  background:var(--cyan);border-color:var(--cyan-ink);
  box-shadow:0 0 0 4px color-mix(in oklab,var(--cyan) 22%,transparent)}
.phasedots .link{width:10px;height:3px;background:var(--line);flex:none;border-radius:2px}
.phasedots .link.done{background:var(--cyan-deep)}
.phasedots .phaselabel{font-size:13px;color:var(--ink-2);font-weight:600}
.phasedots.sm .dot{width:8px;height:8px;border-width:1.5px}
.phasedots.sm .dot.now{box-shadow:0 0 0 3px color-mix(in oklab,var(--cyan) 22%,transparent)}
.phasedots.sm .link{width:7px;height:2px}
.phasedots.sm .phaselabel{font-size:11.5px;font-weight:500}
.phasedots.lg .dot{width:15px;height:15px;border-width:3px}
.phasedots.lg .link{width:9px;height:3px}
.phasedots.lg .phaselabel{font-size:14.5px}

/* ---------- collapsible rail ---------- */
:root{--railw:264px}
:root[data-rail="collapsed"]{--railw:74px}
.railtop{display:flex;align-items:center;gap:10px;justify-content:space-between}
.railbtn{
  flex:none;width:30px;height:30px;display:grid;place-items:center;cursor:pointer;
  background:rgb(var(--neutral-rgb) / .10);border:1px solid var(--line);border-radius:9px;color:var(--ink-2);transition:.15s}
.railbtn:hover{border-color:var(--cyan-ink);color:var(--ink)}
.railbtn svg{width:15px;height:15px}
:root[data-rail="collapsed"] .railbtn svg{transform:scaleX(-1)}
:root[data-rail="collapsed"] .brand svg.wordmark,
:root[data-rail="collapsed"] .nav a span,
:root[data-rail="collapsed"] .nav .sec,
:root[data-rail="collapsed"] .who div,
:root[data-rail="collapsed"] .railfoot .meta,
:root[data-rail="collapsed"] .railfoot svg.cyberx{display:none}
:root[data-rail="collapsed"] .rail .brand{padding:18px 0 14px;display:flex;justify-content:center}
:root[data-rail="collapsed"] .railtop{flex-direction:column;gap:12px}
:root[data-rail="collapsed"] .who{justify-content:center;padding:12px 0}
:root[data-rail="collapsed"] .nav{padding:12px 8px;align-items:center}
:root[data-rail="collapsed"] .nav a{justify-content:center;padding:10px 0;width:100%}
:root[data-rail="collapsed"] .railfoot{padding:12px 8px}
:root[data-rail="collapsed"] .railfoot .acts{flex-direction:column;gap:6px}
:root[data-rail="collapsed"] .railfoot .acts .label{display:none}

/* ---------- the theme switch ---------- */
.themeswitch{display:flex;background:rgb(var(--neutral-rgb) / .10);border:1px solid var(--line);border-radius:10px;padding:2px;gap:2px}
.themeswitch button{
  flex:1;display:flex;align-items:center;justify-content:center;gap:6px;cursor:pointer;
  background:transparent;border:0;color:var(--ink-2);border-radius:8px;padding:6px 9px;
  font-size:11.5px;font-weight:600;transition:.15s}
.themeswitch button svg{width:14px;height:14px;flex:none}
.themeswitch button:hover{color:var(--ink)}
.themeswitch button[aria-pressed="true"]{background:var(--panel-2);color:var(--cyan-ink);box-shadow:0 1px 3px rgb(var(--shadow-rgb) / .25)}

/* the sign out button shows a glyph when the rail is collapsed */
.railfoot .iconly{display:none;font-size:15px}
:root[data-rail="collapsed"] .railfoot .iconly{display:inline}
.brand .railtop{width:100%}
.brand .wordmark-wrap{display:flex;min-width:0}
.brand .wordmark-wrap svg{height:32px;width:auto}
:root[data-rail="collapsed"] .brand .wordmark-wrap svg{height:26px}

.profile-phase{margin-top:14px}
.phasehead{display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;align-items:flex-start;margin-bottom:14px}
.phasemove{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.phasemove label{font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);font-weight:700}
.phasemove select,.phasemove input{padding:8px 11px;border:1px solid var(--line);border-radius:9px;
  background:rgb(var(--neutral-rgb) / .06);font-size:13px}
.phasemove input{min-width:180px}
.phasemove .btn{padding:8px 16px;font-size:13px}

/* ---------- donut and gauge: fixed size, no stretch, no glow ---------- */
.donut{display:block;flex:none}
.donut .seg{transition:opacity .15s}
.donut g[data-detail]{cursor:pointer}
.donut g[data-detail]:hover .seg,.donut g[data-detail]:focus-visible .seg{opacity:.7}
.donut-mid{text-anchor:middle;fill:var(--ink);font-family:'IBM Plex Mono',monospace}
.donut-mid.cap{font-size:9px;fill:var(--muted);letter-spacing:.18em;font-weight:600}
.donut-wrap{display:flex;align-items:center;gap:24px;flex-wrap:wrap}
.donut-key{display:flex;flex-direction:column;gap:2px;flex:1;min-width:150px}
.donut-key .legrow{
  display:flex;align-items:center;gap:9px;padding:5px 8px;border-radius:8px;
  font-size:13px;color:var(--ink-2);cursor:pointer;transition:background .14s}
.donut-key .legrow:hover{background:rgb(var(--neutral-rgb) / .08);color:var(--ink)}
.donut-key .legrow i{width:9px;height:9px;border-radius:3px;flex:none}
.donut-key .legrow .k{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.donut-key .legrow b{font-family:'IBM Plex Mono',monospace;color:var(--ink);font-variant-numeric:tabular-nums}
.gauge{display:flex;flex-direction:column;align-items:center;gap:4px}

/* The rail has to fit a full navigation at a 900px viewport before it scrolls. */
.rail .brand{padding:16px 18px 12px}
.rail .who{padding:11px 18px}
.nav{padding:10px 10px 12px}
.nav a{padding:8px 12px;font-size:13.5px}
.nav .sec{padding:12px 12px 4px}
.railfoot{padding:12px 18px}
.railfoot .meta{font-size:10px;line-height:1.6}
.railfoot .cyberx-wrap svg{height:16px;margin-bottom:7px}
.railfoot .acts{margin-top:8px}
.themeswitch{margin-top:9px}

/* The funnel track needs a visible fill even at a low share of the peak. */
.fstage .ftrack>i{min-width:26px}
.subpanel{background:rgb(var(--neutral-rgb) / .045);border:1px solid var(--line-2);border-radius:11px;padding:15px 16px 17px}
.subpanel h4{font-size:13px;font-weight:700;letter-spacing:.02em;margin-bottom:12px;color:var(--ink)}
.panel.flat>.ph :is(h2,h3){font-size:16px}

/* ---------- the matrix ---------- */
.heatwrap{overflow-x:auto;margin:0 -4px;padding:0 4px}
table.heat{border-collapse:separate;border-spacing:3px;width:100%;font-size:12px}
table.heat th{font-weight:600;color:var(--ink-2);padding:6px 10px;text-align:left;vertical-align:middle}
table.heat thead th{
  font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);font-weight:700;
  text-align:center;vertical-align:bottom;padding:0 4px 8px;width:64px;min-width:64px}
table.heat thead th span{display:inline-block;line-height:1.25}
table.heat th.corner{width:auto;min-width:220px}
table.heat tbody th{
  font-size:12.5px;font-weight:500;color:var(--ink-2);background:rgb(var(--neutral-rgb) / .04);
  border-radius:7px;white-space:normal;line-height:1.35}
table.heat td{
  height:34px;border-radius:7px;text-align:center;vertical-align:middle;
  font-family:'IBM Plex Mono',monospace;color:var(--ink);transition:transform .12s, box-shadow .12s}
table.heat td[data-detail]{cursor:pointer}
table.heat td[data-detail]:hover{transform:scale(1.06);box-shadow:0 0 0 1px var(--cyan)}
table.heat td.na{background:repeating-linear-gradient(45deg,rgb(var(--neutral-rgb) / .07) 0 4px,transparent 4px 8px)}

/* mark mode: a tick or a muted dash, never a number */
table.heat.marks td{background:rgb(var(--neutral-rgb) / .05);box-shadow:inset 0 0 0 1px var(--line-2)}
table.heat.marks td.on{background:color-mix(in oklab,var(--cyan) 18%,transparent);
  box-shadow:inset 0 0 0 1px color-mix(in oklab,var(--cyan) 45%,transparent);color:var(--cyan-ink)}
table.heat.marks td svg{width:15px;height:15px;display:block;margin:0 auto}
table.heat.marks td .no{display:block;width:9px;height:2px;border-radius:2px;background:var(--line);margin:0 auto}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
/* ---------- pagination ---------- */
.pager{display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-top:14px;font-size:13px}
.pager .pg{
  display:inline-flex;align-items:center;justify-content:center;min-width:34px;padding:6px 11px;
  border:1px solid var(--line);border-radius:9px;color:var(--ink-2);background:var(--panel);
  text-decoration:none;transition:.14s}
.pager a.pg:hover{border-color:var(--cyan-ink);color:var(--ink)}
.pager .pg.current{background:var(--cyan);border-color:var(--cyan-ink);color:var(--on-accent);font-weight:700}
.pager .pg.disabled{opacity:.4}
.pager .pg.gap{border:0;background:transparent;min-width:0;padding:6px 4px}
.pager .pgcount{margin-left:auto;order:9;color:var(--muted);font-size:12.5px;font-family:'IBM Plex Mono',monospace}

/* A filter bar is a row, not a column. Making every panel a flex column collapsed the
   inline forms that use the panel shell, leaving the fields stacked against one edge. */
.panel.row{flex-direction:row;align-items:flex-end;flex-wrap:wrap}

/* A list page shows its page of rows. Capping the scroll height at 560px hid two thirds
   of a twenty five row page behind a scrollbar nobody could see. */
.panel.flat.full>.scroll{max-height:none}

/* An unreached phase still has to be visible, or the strip reads as one dot and a rule. */
.phasedots .dot.ahead{background:transparent;border-color:var(--ink-3)}
.phasedots .track{gap:3px}
.phasedots.sm .track{gap:1px}
/* ---------- the success plan ---------- */
.planstats{display:grid;grid-template-columns:repeat(auto-fit,minmax(90px,1fr));gap:10px;margin-bottom:16px}
.planstats>div{background:rgb(var(--neutral-rgb) / .05);border:1px solid var(--line-2);border-radius:10px;padding:10px 12px}
.planstats .lab{display:block;font-size:9.5px;letter-spacing:.14em;text-transform:uppercase;color:var(--muted);font-weight:700}
.planstats .val{display:block;font-size:20px;font-weight:700;margin-top:3px}
.milestones{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.ms{display:grid;grid-template-columns:auto 1fr auto;gap:12px;align-items:start;padding:11px 10px;border-radius:10px}
.ms:hover{background:rgb(var(--neutral-rgb) / .05)}
.ms .msdot{width:10px;height:10px;border-radius:50%;margin-top:6px;background:var(--line);flex:none}
.ms.met .msdot{background:var(--good)}
.ms.late .msdot{background:var(--amber)}
.ms.overdue .msdot{background:var(--crit)}
.ms.ahead .msdot{background:var(--cyan-deep)}
.ms b{font-size:14px}
.msact{display:flex;align-items:center}

/* A fifteen by fifteen matrix needs narrow columns to fit on one screen. */
table.heat.dense thead th{width:46px;min-width:46px;font-size:9px;letter-spacing:.04em}
table.heat.dense th.corner{min-width:170px}
table.heat.dense tbody th{font-size:11.5px;padding:5px 8px}
table.heat.dense td{height:30px;font-size:11px}

/* ---------- the top bar ---------- */
/* The header stays. The controls that act on the whole session, and the title of the
   page you are on with whatever it offers you, are the two things worth still having in
   front of you three screens down a long client list. Both stick to the top of the
   scrolling area rather than to the window, so the rail is unaffected.

   The topbar sits above the page header, so the header's own sticky offset is the
   topbar's height. Stated once here rather than guessed at per page. */
.main{--topbar-h:42px}
.topbar{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin:-6px 0 10px;
  flex-wrap:nowrap;
  position:sticky;top:0;z-index:40;
  /* Its own ground, or the page scrolls through it. The blur is what the rest of the
     product uses where one layer passes over another. */
  background:var(--void);padding:8px 0 6px;
  backdrop-filter:saturate(1.6) blur(8px);-webkit-backdrop-filter:saturate(1.6) blur(8px)}
.head{position:sticky;top:var(--topbar-h);z-index:30;
  background:var(--void);padding-block:10px;
  /* A hairline so the header has an edge when content passes under it, and none when it
     is at rest at the top. A transparent border keeps the geometry identical either way,
     which is what NFR-144 asks of anything that can change. */
  border-bottom:1px solid transparent}
.head.is-stuck{border-bottom-color:var(--line)}

/* The search gives up width before anything else does, down to a point where its own
   placeholder still reads. Everything to its right is a control whose label IS the
   control, so shrinking those loses the meaning rather than the room.

   Then a fourth control arrived and the row ran out of width: the search shrank past its
   own placeholder and pushed itself over the rail beside it. min-width is what stops a
   flex item shrinking below its content, and 150 was too small to hold anything. Below
   the width where they genuinely do not fit, the labels on the two account controls drop
   to their icons, which is a control losing its caption rather than the page breaking. */
.topbar .findbox{flex:1 1 260px;min-width:min(260px, 100%)}
.topbar .themeswitch{flex:none}
@media (max-width:1320px){
  .topbar a.signout span,.topbar .signout button span{display:none}
  .topbar a.signout,.topbar .signout button{padding:0 9px}
}

/* Below about a thousand the row still does not fit, even with the two account controls
   down to their icons. The bar is right aligned and does not wrap, so what does not fit
   leaves by the LEFT edge, where nothing can scroll to reach it: at 768 the search box
   sat at minus 55 with its first fifty five pixels simply gone, on every page.

   Nothing scrolls sideways when that happens, so a sideways-scroll check reports the
   page clean. It was found by asking which elements lie outside the viewport instead.

   The search gives the width back, because a narrower search box is still a search box
   and its icon says so. The three appearance switchers do not shrink: each is a set of
   words that ARE the control, and shortening them removes the meaning rather than the
   room. */
@media (max-width:1200px){
  /* 120 rather than a rounder number, from the arithmetic: the three appearance
     switchers and sign out come to 526 including their gaps and none of them shrinks, so
     the search can have whatever the bar has left. The narrowest the bar gets before the
     rail collapses is 665, which leaves 139. The floor only binds when the room is
     genuinely that tight; at 1200 the search is still 339 wide because it grows into
     whatever is spare. */
  .topbar .findbox{flex:1 1 120px;min-width:min(120px, 100%)}
}

/* Sign out, beside the controls it belongs with.

   flex:none and a nowrap, because the topbar is a flex row and without them the button
   is the only thing in it that can be squeezed: it wrapped to "Sign / out" on two lines
   and took the width it lost out of the search box beside it. */
.signout{margin:0;flex:none}
a.signout{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 11px;
  white-space:nowrap;border:1px solid var(--line);border-radius:9px;
  background:rgb(var(--neutral-rgb) / .10);color:var(--ink-2);font-size:12.5px;text-decoration:none}
a.signout:hover{color:var(--ink)}
a.signout svg{width:15px;height:15px;flex:none}
@media (max-width:1180px){a.signout span{display:none}a.signout{padding:0 8px}}
.signout button{display:inline-flex;align-items:center;gap:7px;height:30px;padding:0 11px;
  white-space:nowrap;flex:none;
  border:1px solid var(--line);border-radius:9px;background:rgb(var(--neutral-rgb) / .10);
  color:var(--ink-2);font-size:12.5px;cursor:pointer}
.signout button:hover{color:var(--ink);border-color:var(--line-2,var(--line))}
.signout svg{width:15px;height:15px;flex:none}
@media (max-width:900px){.signout span{display:none}.signout button{padding:0 8px}}
[dir=rtl] .topbar{justify-content:flex-start}
.themeswitch{display:inline-flex;background:rgb(var(--neutral-rgb) / .10);border:1px solid var(--line);
  border-radius:9px;padding:2px;gap:2px;margin:0}
.themeswitch button{
  display:grid;place-items:center;width:28px;height:26px;padding:0;cursor:pointer;
  background:transparent;border:0;color:var(--ink-3);border-radius:7px;transition:.14s;flex:none}
.themeswitch button svg{width:15px;height:15px}
.themeswitch button:hover{color:var(--ink)}
.themeswitch button[aria-pressed="true"]{background:var(--panel);color:var(--cyan-ink);
  box-shadow:0 1px 2px rgb(var(--shadow-rgb) / .22)}

/* ---------- the collapse control ---------- */
.railbtn{width:28px;height:28px;border-radius:8px}
.railbtn svg{width:16px;height:16px}
.railbtn .i-open{display:none}
:root[data-rail="collapsed"] .railbtn svg{transform:none}
:root[data-rail="collapsed"] .railbtn .i-close{display:none}
:root[data-rail="collapsed"] .railbtn .i-open{display:block}

/* Collapsed, the rail is 74px wide and the wordmark does not fit in it. Only the
   toggle remains in the brand row, centred, so the header reads as a control rather
   than as a clipped logo. */
:root[data-rail="collapsed"] .brand .wordmark-wrap{display:none}
:root[data-rail="collapsed"] .brand{padding:14px 0 12px}
:root[data-rail="collapsed"] .railtop{flex-direction:row;justify-content:center;gap:0}
:root[data-rail="collapsed"] .rail .who{border-bottom:1px solid var(--line)}
:root[data-rail="collapsed"] .railfoot .cyberx-wrap{display:none}
:root[data-rail="collapsed"] .nav a[aria-current="page"]::before{display:none}
:root[data-rail="collapsed"] .nav a svg{width:19px;height:19px}

/* A hint is one line. Anything longer is an argument the reader did not ask for. */
.panel>.hint{max-width:74ch}
/* The pipeline bar is the data. A washed gradient over a full width track read as an
   empty row whatever the number was, which is why the track carries a flat, quiet fill
   and no shadow rather than a width limit. The limit that used to sit here capped the
   track at 560px, so on a wide screen the chart stopped less than half way across its own
   panel and left the rest of the row empty. The track now takes the column the row gives
   it, and the column already accounts for the spacing either side. */
.fstage .ftrack{background:rgb(var(--neutral-rgb) / .09);box-shadow:none}
.fstage .ftrack>i{border-radius:6px;opacity:.9}
.fstage:hover .ftrack>i{opacity:1}
.fstage.empty .fval{color:var(--muted)}
.fstage.empty{opacity:.62}

/* A jump target has to clear the top bar, and has to look like it was arrived at. */
/* This is the floor for a jump the browser makes on its own, which is what happens
   when somebody opens a link that already carries a fragment: the script's handler
   never runs for that one. 22px was under even the header's sticky offset, so the
   target landed behind it. The header's height is its own content and measures
   between 151 and 343 across these pages and widths, so no single value is right
   everywhere; this clears the common case, and the script computes the exact
   clearance for every jump made from inside the page and on arrival. */
[id]{scroll-margin-top:210px}
@media (max-width:760px){[id]{scroll-margin-top:270px}}
@keyframes arrived{
  0%{box-shadow:var(--shadow), 0 0 0 0 color-mix(in oklab,var(--cyan) 55%,transparent)}
  100%{box-shadow:var(--shadow), 0 0 0 14px transparent}
}
.panel.arrived{border-color:var(--cyan-ink);animation:arrived 1.4s ease-out}
/* ---------- the CyberX mark ---------- */
.cyberx-wrap{display:block;margin-bottom:9px}
.cyberx-wrap img{display:none;height:17px;width:auto;opacity:.9}
@media (prefers-color-scheme: dark){
}

/* The glyph is drawn on a twenty unit grid, so it needs the box to match. */
.railbtn svg{width:18px;height:18px}
/* ---------- the platform dropdown ----------
   The native select stays in the document and stays what submits. It is hidden only
   once the script has painted the replacement, so a browser that runs no script is
   left with a control that works rather than a button that does nothing. */
/* A dropdown never collapses to the width of its shortest option.
   On the portfolio screen the search box beside it was flex:1 with a 220px floor and
   the status field had neither, so status shrank to the width of the word Any: a 76px
   control whose own list is far wider than the thing that opens it. A floor here fixes
   every such row at once, and the rows wrap, so nothing is pushed off the side. */
/* Off the screen and still read aloud.

   This lived in churnx-score.css, which only the console layout loads, and this sheet's
   own note further down says why that is the wrong place for anything shared: a rule put
   there reaches 36 pages and silently misses the Desk and every signed out page. It was
   used by the search label on one layout, so nobody noticed, until a heading was added to
   the sign in screen and rendered as visible thirty pixel text because the class that was
   supposed to hide it did not exist there.

   A placeholder is not a label: it disappears the moment somebody types, and a screen
   reader announcing an unlabelled search field says only "search". NFR-73. */
.sr-only{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;border:0}

/* A way past the rail, for anybody moving by keyboard.

   Twenty six navigation links come before the content on every page, and somebody using
   a keyboard or a screen reader walked all of them, on every page, before reaching what
   they came for. Off screen until it has focus, so it costs a pointer user nothing and
   is the first thing a keyboard user meets. Not display:none and not visibility:hidden,
   because neither can be focused. */
.skiplink{position:fixed;inset-inline-start:12px;top:-60px;z-index:200;
  padding:10px 16px;border-radius:0 0 10px 10px;border:1px solid var(--line);
  background:var(--panel);color:var(--ink);font-size:13px;font-weight:600;
  text-decoration:none;box-shadow:var(--shadow);transition:top .14s ease}
.skiplink:focus{top:0}
.skiplink:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
/* The landing place takes focus without drawing a ring around the whole page. */
[id="content"]:focus{outline:none}

/* A floor, not a demand. A painted select collapses to nothing in a flexible row, so
   the field holding one asks for 170. Written as a flat 170 it also overflowed a grid
   column narrower than that: the dashboard builder puts two of these in a 140px pair
   of columns, and each label was drawn 16px over its neighbour. min() keeps the floor
   wherever there is room and yields where there is not. */
.field:has(.xsel){min-width:min(170px,100%)}
.xsel{position:relative;min-width:0}
.xsel.ready > select{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none;clip-path:inset(50%)}
.xsel-btn{display:flex;width:100%;align-items:center;justify-content:space-between;gap:10px;
  padding:11px 13px;border:1px solid var(--line);border-radius:10px;text-align:left;
  background:var(--panel-2);color:var(--ink);font-size:13.5px;cursor:pointer;
  transition:border-color .16s ease,box-shadow .16s ease}
.xsel-btn:hover{border-color:color-mix(in oklab,var(--cyan) 40%,var(--line))}
.xsel-btn[aria-expanded=true],.xsel-btn:focus-visible{border-color:var(--cyan-ink);
  box-shadow:0 0 0 3px rgb(var(--accent-rgb) / .12);outline:none}
.xsel-btn svg{flex:0 0 auto;color:var(--muted);transition:transform .16s ease}
.xsel-btn[aria-expanded=true] svg{transform:rotate(180deg);color:var(--cyan-ink)}
.xsel-val{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.xsel-val.ph{color:var(--muted)}
/* Fixed, not absolute. A panel is overflow:hidden so it can clip its own rounded
   corners, and an absolutely positioned list inside one gets clipped with it however
   high its z-index. Fixed leaves that context, and the script places it against the
   button's viewport rectangle. */
.xsel-pop{position:fixed;z-index:60;
  background:var(--panel);border:1px solid var(--line);border-radius:12px;
  box-shadow:var(--shadow),var(--glow-soft);overflow:hidden;
  display:flex;flex-direction:column;
  animation:xselIn .12s ease-out}
.xsel-pop.up{animation:xselUp .12s ease-out}
@keyframes xselUp{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
@keyframes xselIn{from{opacity:0;transform:translateY(-4px)}to{opacity:1;transform:none}}
.xsel-find{padding:8px;border-bottom:1px solid var(--line-2);flex:0 0 auto}
.xsel-find input{width:100%;padding:8px 11px;border:1px solid var(--line);border-radius:8px;
  background:var(--panel-2);color:var(--ink);font-size:13px}
.xsel-find input:focus{border-color:var(--cyan-ink);outline:none}
.xsel-list{list-style:none;margin:0;padding:5px;overflow-y:auto;flex:1 1 auto;min-height:0}
.xsel-list li{display:flex;align-items:center;gap:9px;padding:8px 10px;border-radius:8px;
  font-size:13.5px;cursor:pointer;color:var(--ink);line-height:1.35}
.xsel-list li small{display:block;color:var(--muted);font-size:11px;margin-top:1px}
.xsel-list li[aria-selected=true]{background:color-mix(in oklab,var(--cyan) 15%,transparent);
  color:var(--ink);font-weight:600}
.xsel-list li.here{background:color-mix(in oklab,var(--cyan) 22%,transparent)}
.xsel-list li .tick{flex:0 0 14px;color:var(--cyan-ink);opacity:0}
.xsel-list li[aria-selected=true] .tick{opacity:1}
.xsel-empty{margin:0;padding:14px;color:var(--muted);font-size:12.5px;text-align:center}
@media (prefers-reduced-motion:reduce){.xsel-pop{animation:none}}

/* ---------- platform logos ----------
   The artwork comes in two cuts, white and ink. Both are in the document and CSS picks
   one, so the correct cut is on screen in the first paint. */
.plogo{display:inline-flex;align-items:center;line-height:0}
.plogo img{height:var(--plh,22px);width:auto;max-width:150px;object-fit:contain;display:block}
.plogo img.on-light{display:none}
:root[data-theme=light] .plogo img.on-dark{display:none}
:root[data-theme=light] .plogo img.on-light{display:block}
.plogo .plogo-name{display:none;font-size:13.5px;font-weight:650;color:var(--ink);letter-spacing:.01em}
.plogo.nologo img{display:none!important}
.plogo.nologo .plogo-name{display:inline}

/* ---------- the platform board ---------- */
.pboard{display:grid;grid-template-columns:repeat(auto-fill,minmax(258px,1fr));gap:12px}
.pcard{display:flex;flex-direction:column;gap:11px;padding:15px 16px;border-radius:var(--r);
  border:1px solid var(--line);background:linear-gradient(160deg,var(--panel) 0%,var(--panel-2) 100%);
  box-shadow:var(--shadow);position:relative;overflow:hidden}
.pcard::before{content:'';position:absolute;inset:0 0 auto 0;height:2px;background:var(--pc,var(--line));opacity:.9}
.pcard.idle{border-style:dashed;background:none;box-shadow:none}
.pcard .ptop{display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:24px}
.pcard .pblurb{font-size:11.5px;color:var(--muted);line-height:1.45;margin:0}
.pcard .pstate{display:flex;align-items:center;gap:7px;font-size:12.5px;font-weight:650;
  /* The colour is already on the dot in the ::before below, so the words do not need to
     carry it as well. A colour from the record can be any lightness, and mixing an
     already pale one toward the ink still measured 2.46 to 1. The dot says which
     platform; the sentence just has to be readable. */
  color:var(--ink-2)}
.pcard .pstate::before{content:'';width:8px;height:8px;border-radius:50%;background:var(--pc,var(--muted));
  box-shadow:0 0 0 3px color-mix(in oklab,var(--pc,var(--muted)) 22%,transparent)}
.pcard .pdetail{font-size:11.5px;color:var(--ink-2);line-height:1.45;margin:0}
.pcard .pcode{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--muted);
  letter-spacing:.04em;word-break:break-all}
.pcard .pacts{display:flex;gap:7px;flex-wrap:wrap;margin-top:auto;padding-top:4px}
.pcard .pacts .btn{padding:7px 13px;font-size:12px}
.pcard.good{--pc:var(--good)} .pcard.warn{--pc:var(--warn)}
.pcard.bad{--pc:var(--crit)} .pcard.idle{--pc:var(--line)}

/* ---------- the score band legend ---------- */
.bands{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:8px}
.band{display:flex;gap:10px;align-items:flex-start;padding:9px 11px;border-radius:10px;
  border:1px solid var(--line-2);background:var(--panel-2)}
.band.here{border-color:var(--bc);background:color-mix(in oklab,var(--bc) 12%,var(--panel-2));
  box-shadow:0 0 0 1px var(--bc) inset}
.band .swatch{flex:0 0 4px;align-self:stretch;border-radius:3px;background:var(--bc);min-height:34px}
.band b{display:block;font-size:12.5px;color:var(--ink);line-height:1.3}
.band .rng{font-family:'IBM Plex Mono',monospace;font-size:10.5px;color:var(--muted);letter-spacing:.04em}
.band p{margin:3px 0 0;font-size:11.5px;color:var(--ink-2);line-height:1.45}
.band .act{margin-top:4px;font-size:11px;color:var(--muted);font-style:italic}

/* ---------- the Add menu ---------- */
.addmenu{position:relative}
.addpop{position:absolute;right:0;top:calc(100% + 7px);z-index:70;min-width:216px;padding:5px;
  background:var(--panel);border:1px solid var(--line);border-radius:12px;
  box-shadow:var(--shadow),var(--glow-soft);animation:xselIn .12s ease-out}
.addpop a{display:block;padding:9px 12px;border-radius:8px;font-size:13.5px;color:var(--ink);
  text-decoration:none;line-height:1.3}
.addpop a:hover,.addpop a:focus-visible{background:color-mix(in oklab,var(--cyan) 15%,transparent);
  color:var(--ink);outline:none}
@media (prefers-reduced-motion:reduce){.addpop{animation:none}}

/* ---------- inline raise forms ---------- */
.raiser{margin-bottom:14px;border:1px solid var(--line);border-radius:12px;background:var(--panel-2)}
.raiser > summary{cursor:pointer;padding:11px 14px;font-size:13px;font-weight:650;color:var(--cyan-ink);
  list-style:none;display:flex;align-items:center;gap:8px}
.raiser > summary::-webkit-details-marker{display:none}
.raiser > summary::before{content:'+';font-size:16px;line-height:1;font-weight:700}
.raiser[open] > summary::before{content:'\2212'}
.raiser > form{padding:4px 14px 15px}
.raiser .field{margin-bottom:var(--field-gap-tight)}

/* ---------- the licence form ---------- */
.licform{margin-top:16px;padding:15px 16px;border:1px solid var(--cyan);border-radius:12px;
  background:color-mix(in oklab,var(--cyan) 6%,var(--panel-2));
  box-shadow:0 0 0 3px rgb(var(--accent-rgb) / .09)}
/* The heading moved from the fourth level to the third, because the panel above it is the
   second and a page should not step over a level on the way down. Named for both so the
   look does not depend on which, exactly as the panel headings above are. */
.licform :is(h3,h4){margin:0 0 12px;font-size:14px;color:var(--ink);font-weight:650}
.licform .field{margin-bottom:var(--field-gap-tight)}

/* ---------- the phase board ---------- */
.kanban{display:grid;grid-auto-flow:column;grid-auto-columns:minmax(228px,1fr);gap:12px;
  overflow-x:auto;padding-bottom:8px;align-items:start}
.kcol{background:var(--panel-2);border:1px solid var(--line);border-radius:var(--r);padding:12px;
  display:flex;flex-direction:column;gap:9px;min-height:120px}
.kcol > header{display:flex;flex-direction:column;gap:2px;padding-bottom:9px;border-bottom:1px solid var(--line-2)}
.kcol > header b{font-size:12.5px;color:var(--ink);line-height:1.3}
.kcol > header span{font-size:10.5px;color:var(--muted);letter-spacing:.04em;text-transform:uppercase}
/* A card keeps its own height. The column it sits in is a flex column that scrolls, and
   a flex item shrinks below its content by default, so once there were more cards than
   room every card was squashed and its text spilled out of the bottom into the card
   below. On a phone that meant client names cut off mid word and two cards' text on one
   line. The column scrolls; that is what the overflow on it is for. */
.kcard{flex:0 0 auto;display:block;padding:11px 12px;border-radius:10px;border:1px solid var(--line);
  border-left:3px solid var(--kc,var(--muted));background:var(--panel);text-decoration:none;
  box-shadow:var(--shadow);transition:transform .16s ease,border-color .16s ease}
.kcard:hover{transform:translateY(-2px);border-color:color-mix(in oklab,var(--cyan) 45%,var(--line))}
.kcard b{display:block;font-size:12.5px;color:var(--ink);line-height:1.35;margin-bottom:6px}
.kcard .kmeta{display:flex;align-items:center;justify-content:space-between;gap:8px}
.kcard .kscore{font-family:'IBM Plex Mono',monospace;font-size:16px;font-weight:600;color:var(--kc,var(--muted))}
.kcard .kage{font-size:10.5px;color:var(--muted);text-align:right;line-height:1.3}
.kcard .kage.stale{color:var(--warn);font-weight:650}
.kcard .kage.cold{color:var(--crit);font-weight:650}
.kcol .kempty{font-size:11.5px;color:var(--muted);padding:10px 2px;text-align:center}

/* ---------- the calendar grid ---------- */
.calgrid{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:6px}
.calhead{font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--muted);
  font-weight:700;padding:4px 6px 6px;text-align:center}
.calday{min-height:104px;display:flex;flex-direction:column;gap:3px;padding:7px;border-radius:10px;
  border:1px solid var(--line-2);background:var(--panel-2);overflow:hidden}
.calday.outside{opacity:.42}
.calday.today{border-color:var(--cyan-ink);box-shadow:0 0 0 1px var(--cyan) inset}
.calnum{font-family:'IBM Plex Mono',monospace;font-size:11px;color:var(--muted);font-weight:600;
  align-self:flex-end;line-height:1}
.calday.today .calnum{color:var(--cyan-ink)}
.calpip{display:flex;gap:5px;align-items:baseline;padding:3px 6px;border-radius:6px;font-size:10.5px;
  text-decoration:none;line-height:1.3;color:var(--ink);
  background:color-mix(in oklab,var(--pc) 16%,transparent);
  border-left:2px solid var(--pc);overflow:hidden}
.calpip:hover{background:color-mix(in oklab,var(--pc) 28%,transparent)}
.calpip b{font-family:'IBM Plex Mono',monospace;font-size:9.5px;color:var(--pc);flex:0 0 auto}
.calpip span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.calmore{font-size:9.5px;color:var(--muted);padding-left:6px}
@media (max-width:900px){
  .calgrid{grid-template-columns:repeat(7,minmax(0,1fr));gap:3px}
  .calday{min-height:76px;padding:5px}
  /* The title stays hidden here on purpose. Seven columns on a 390 screen give a
     day about 50 pixels, and a label in that space shows one character and an
     ellipsis, which is worse to look at than a clean pip and no more use. The link
     carries the full label in its title attribute, so it still has an accessible
     name and a screen reader reads it; a timed event also keeps its time, because
     only the span is hidden. What is left is an all day event appearing as a
     coloured sliver, which is a question about wanting an agenda list on a phone
     rather than something CSS should answer here. */
  .calpip span{display:none}
}

/* ---------- nudges ---------- */
.nudges{display:flex;flex-direction:column;gap:9px}
.nudge{display:flex;gap:14px;align-items:flex-start;justify-content:space-between;flex-wrap:wrap;
  padding:13px 15px;border-radius:12px;border:1px solid var(--line);background:var(--panel-2);
  border-left:3px solid var(--nc,var(--cyan))}
.nudge.hot{--nc:var(--crit);background:color-mix(in oklab,var(--crit) 6%,var(--panel-2))}
.nudge.away{opacity:.55;--nc:var(--muted)}
.nudge .nbody{flex:1 1 340px;min-width:0}
.nudge .nbody b{display:block;font-size:13.5px;color:var(--ink);line-height:1.35}
.nudge .nbody p{margin:4px 0 0;font-size:12px;color:var(--ink-2);line-height:1.5}
.nudge .nmeta{display:block;margin-top:6px;font-size:10.5px;color:var(--muted);letter-spacing:.02em}
.nudge .nacts{display:flex;gap:7px;align-items:flex-start;flex-wrap:wrap}
.nudge .nacts .btn{padding:7px 14px;font-size:12px}
.snoozer{position:relative}
.snoozer > summary{list-style:none;cursor:pointer}
.snoozer > summary::-webkit-details-marker{display:none}
/* In normal flow, not absolutely positioned. It used to hang below its summary
   inside a .panel, and a panel clips its own corners with overflow:hidden, so
   opening Put away cut 127 pixels off the bottom of the form including the button
   that submits it. Raising its z-index cannot help: an ancestor that clips is not
   a stacking question. Flowing makes the panel grow instead, which moves what is
   underneath and is the smaller cost by a distance. */
/* The details sits in a wrapping flex row, so it is only as wide as the word on its
   summary. The form inside then inherited that: eighty three pixels, with a reason field
   of fifty seven and a placeholder reading "Waiting on them, on holiday, already in hand"
   that could never show a word of itself. Visible and unusable is not fixed.
   The open state gets the width instead, and the form fills it. */
.snoozer[open]{min-width:250px}
.snoozer > form{margin-top:6px;width:100%;padding:12px;
  background:var(--panel);border:1px solid var(--line);border-radius:12px;box-shadow:var(--shadow),var(--glow-soft)}
.snoozer .field{margin-bottom:var(--field-gap-tight)}

/* the unread count in the rail */
.nav a .badge{margin-left:auto;min-width:19px;height:19px;padding:0 6px;border-radius:10px;
  background:var(--rail-crit);color:var(--on-crit);font-size:10.5px;font-weight:700;display:inline-flex;
  align-items:center;justify-content:center;line-height:1;box-shadow:0 0 12px -3px var(--rail-crit)}
:root[data-rail="collapsed"] .nav a .badge{position:absolute;top:4px;right:10px;margin:0}
:root[data-rail="collapsed"] .nav a{position:relative}

/* The layout half of a labelled checkbox. The control itself is styled globally
   further down, so a checkbox added without this class still looks right. */
.check{display:flex;align-items:center;gap:9px;font-size:12.5px;color:var(--ink-2);cursor:pointer}

.kfilters{display:flex;gap:18px;flex-wrap:wrap;margin-bottom:14px}

/* A column scrolls inside itself. Without this one column of sixty clients makes the
   whole briefing seven screens tall and every other column reads as empty. */
.kcol{max-height:620px}
.kcol .kbody{display:flex;flex-direction:column;gap:9px;overflow-y:auto;min-height:0;flex:1 1 auto;
  padding-right:3px}
.kcol .kbody::-webkit-scrollbar{width:7px}
.kcol .kbody::-webkit-scrollbar-thumb{background:var(--line);border-radius:4px}

/* A card is a fixed width in a scrolling row, so everything inside it has to be allowed
   to wrap. Without min-height:0 and min-width:0 the owner name is clipped mid-word and
   reads as a rendering fault rather than as a long name. */
.kcard .kmeta{align-items:flex-start;min-width:0}
.kcard .kage{min-width:0;white-space:normal;word-break:break-word;flex:1 1 auto}
.kcard .kscore{flex:0 0 auto}
.kanban{scrollbar-width:thin}
.kanban::-webkit-scrollbar{height:8px}
.kanban::-webkit-scrollbar-thumb{background:var(--line);border-radius:4px}

/* ---------- checkboxes and radios, drawn by the platform ----------
   Applied to the element itself rather than to a wrapper class, so a checkbox added
   anywhere later is already right. The browser's own control is switched off with
   appearance:none, which also switches off its focus ring, so one is drawn back. */
input[type=checkbox],
input[type=radio]{
  appearance:none;-webkit-appearance:none;
  /* padding:0 because a checkbox dropped inside a .field wrapper matches the text
     field rule above it and inherits 11px 14px. With border-box that made the
     control 30 by 24 instead of 17 by 17, and put its tick in the wrong place.
     Measured on clients-new before this line existed. */
  width:17px;height:17px;flex:0 0 17px;margin:0;padding:0;
  border:1px solid var(--line);background:var(--panel-2);
  cursor:pointer;position:relative;vertical-align:-3px;
  transition:border-color .14s ease,background .14s ease,box-shadow .14s ease}
input[type=checkbox]{border-radius:5px}
input[type=radio]{border-radius:50%}
input[type=checkbox]:hover:not(:disabled),
input[type=radio]:hover:not(:disabled){border-color:color-mix(in oklab,var(--cyan) 48%,var(--line))}
input[type=checkbox]:checked,
input[type=radio]:checked{background:var(--cyan);border-color:var(--cyan-ink)}
input[type=checkbox]:checked::after{
  content:'';position:absolute;left:5px;top:1.5px;width:4px;height:9px;
  border:solid var(--on-accent);border-width:0 2.2px 2.2px 0;transform:rotate(43deg)}
input[type=radio]:checked::after{
  content:'';position:absolute;inset:4px;border-radius:50%;background:var(--on-accent)}
input[type=checkbox]:focus-visible,
input[type=radio]:focus-visible{outline:2px solid var(--focus);outline-offset:2px}
input[type=checkbox]:disabled,
input[type=radio]:disabled{opacity:.42;cursor:not-allowed}

/* A label wrapping one of them lines up on the text baseline rather than the box. */
label:has(> input[type=checkbox]),
label:has(> input[type=radio]){display:inline-flex;align-items:center;gap:9px;cursor:pointer}

/* ---------- scrollbars ----------
   The rail's own scrollbar is the one that shows most, because the navigation is the
   tallest fixed column on the page. */
.nav{scrollbar-width:thin;scrollbar-color:var(--line) transparent}
.nav::-webkit-scrollbar{width:8px}
.nav::-webkit-scrollbar-track{background:transparent}
.nav::-webkit-scrollbar-thumb{
  background:color-mix(in oklab,var(--cyan) 26%,transparent);
  border-radius:6px;border:2px solid transparent;background-clip:content-box}
.nav::-webkit-scrollbar-thumb:hover{background:color-mix(in oklab,var(--cyan) 55%,transparent);
  background-clip:content-box}
.nav::-webkit-scrollbar-button{display:none}

/* Everything else that scrolls inside a panel gets the same treatment. */
.scroll,.xsel-list,.kcol .kbody,.kanban,.modal-card,main{
  scrollbar-width:thin;scrollbar-color:var(--line) transparent}
.scroll::-webkit-scrollbar,.xsel-list::-webkit-scrollbar,
.kcol .kbody::-webkit-scrollbar,.modal-card::-webkit-scrollbar{width:8px;height:8px}
.scroll::-webkit-scrollbar-track,.xsel-list::-webkit-scrollbar-track,
.kcol .kbody::-webkit-scrollbar-track,.modal-card::-webkit-scrollbar-track{background:transparent}
.scroll::-webkit-scrollbar-thumb,.xsel-list::-webkit-scrollbar-thumb,
.kcol .kbody::-webkit-scrollbar-thumb,.modal-card::-webkit-scrollbar-thumb{
  background:var(--line);border-radius:6px;border:2px solid transparent;background-clip:content-box}
.scroll::-webkit-scrollbar-thumb:hover,.xsel-list::-webkit-scrollbar-thumb:hover,
.kcol .kbody::-webkit-scrollbar-thumb:hover{
  background:color-mix(in oklab,var(--cyan) 50%,var(--line));background-clip:content-box}

/* ---------- the collapse control, aligned ----------
   An inline SVG sits on the text baseline, which reserves room under it for a
   descender that does not exist. Inside a 28px grid cell that pushed the glyph six
   pixels below the button's own centre, which is what read as misaligned. display:block
   takes it off the baseline; the grid then centres the box itself. */
.rail .railbtn{padding:0;line-height:0;box-sizing:border-box}
.rail .railbtn svg{width:16px;height:16px;flex:none}

/* The wordmark is a fixed width drawing. Without a limit it pushed the button past the
   rail's right padding, and the button was clipped by the rail edge at narrow widths. */
.brand .wordmark-wrap{flex:0 1 auto;min-width:0;overflow:hidden;align-items:center}
.railtop{min-width:0}

/* Centring the glyph explicitly rather than relying on the grid shorthand, which was
   being partly overridden further up the file. Flex with all three axes stated leaves
   nothing for a later rule to half undo. */
.rail .railbtn{
  display:flex;align-items:center;justify-content:center;
  align-content:center;line-height:0;padding:0}
.rail .railbtn > svg{margin:0;align-self:center;flex:0 0 auto}

/* Which glyph shows, stated at the same specificity as the sizing rule above. Setting
   display on the generic svg selector beat the .i-open rule and painted both arrows
   side by side in a button only wide enough for one. */
.rail .railbtn .i-close{display:block}
.rail .railbtn .i-open{display:none}
:root[data-rail="collapsed"] .rail .railbtn .i-close{display:none}
:root[data-rail="collapsed"] .rail .railbtn .i-open{display:block}

.kfilters{display:flex;gap:16px;flex-wrap:wrap;align-items:flex-end;margin-bottom:14px}
.kfilters .check{padding-bottom:11px}
.kcount{margin-left:auto;padding-bottom:11px;font-family:'IBM Plex Mono',monospace;
  font-size:11.5px;color:var(--muted);letter-spacing:.04em}

/* ---------- the home page's list rows ---------- */
.attrow{display:flex;align-items:center;gap:14px;padding:12px 14px;border-radius:11px;
  border:1px solid var(--line);border-left:3px solid var(--ac,var(--cyan));
  background:var(--panel-2);margin-bottom:8px}
.attrow:last-child{margin-bottom:0}
.attrow .attmain{flex:1 1 auto;min-width:0}
.attrow .attmain b{display:block;font-size:13.5px;color:var(--ink);line-height:1.35}
.attrow .attmain a{text-decoration:none}
.attrow .attmain p{margin:3px 0 0;font-size:12px;color:var(--ink-2);line-height:1.45}
.attrow .attmore{color:var(--muted);font-size:11px;margin-left:4px}
.attrow .attscore{flex:0 0 auto;font-family:'IBM Plex Mono',monospace;font-size:19px;
  /* --ac is the band colour from the record, mixed toward the ink in force rather than
     used raw. It measured 4.40 to 1 in the light themes. */
  font-weight:600;color:color-mix(in oklab,var(--ac,var(--muted)) 62%,var(--ink));
  min-width:34px;text-align:right}
.attrow .btn{padding:7px 13px;font-size:12px;white-space:nowrap}
.attrow.late{background:color-mix(in oklab,var(--crit) 6%,var(--panel-2))}

/* ---------- the visual language control ---------- */
/* At the end on purpose. .themeswitch button pins every button to a 28px square, which
   is right for an icon and impossible for a word, so a label rendered as "CyberX"
   overflowed and collided with "Apple" beside it. These rules have the same specificity
   as the ones they correct, so they only win from here. FR-SKN-03. */
.skinswitch{margin-inline-end:8px}
.skinswitch button{width:auto;min-width:28px;padding:0 10px}
.skinswitch .skinname{font-size:11px;font-weight:700;letter-spacing:.02em;
  text-transform:uppercase;white-space:nowrap}
.skinswitch button[data-skin-set=""]{padding:0;width:28px}

/* The CyberX mark in the rail foot swapped on the THEME, but the rail is dark in both
   CyberX themes, so the light theme showed the dark artwork on a dark ground and the
   mark simply vanished. It has to follow the surface it sits on, which is the rail,
   and the rail only changes with the SKIN. FR-SKN-09, FR-LIC-18. */
/* One rule set, chosen by the surface rather than by the theme. The rail is dark in
   both CyberX themes and in Apple dark, and light only in Apple light, so that is the
   single condition. The theme driven rules this replaces were not merely losing the
   cascade, they were asking the wrong question, which is why the dark artwork appeared
   on the dark rail in light mode. FR-LIC-18. */
.cyberx-wrap img.cx-light{display:none}
.cyberx-wrap img.cx-dark{display:block}
:root[data-skin=apple][data-theme=light] .cyberx-wrap img.cx-light,
:root[data-skin=apple]:not([data-theme=dark]) .cyberx-wrap img.cx-light{display:block}
:root[data-skin=apple][data-theme=light] .cyberx-wrap img.cx-dark,
:root[data-skin=apple]:not([data-theme=dark]) .cyberx-wrap img.cx-dark{display:none}
@media (prefers-color-scheme: dark){
  :root[data-skin=apple]:not([data-theme=light]) .cyberx-wrap img.cx-light{display:none}
  :root[data-skin=apple]:not([data-theme=light]) .cyberx-wrap img.cx-dark{display:block}
}
/* Frost belongs only where one layer genuinely sits over another. Under CyberX the rail
   floats over the animated ground, so blurring it means something. Under Apple that
   canvas is not drawn at all, so the blur is filtering an empty backdrop: it costs a
   compositing layer, produces artefacts over an opaque surface, and expresses nothing.
   FR-SKN-16, FR-SKN-14. */
/* ---------- liquid glass, made real rather than decorative ---------- */
/* An earlier version of this removed the frost, on the grounds that nothing passed
   behind the rail and a blur over an opaque strip is decoration pretending to be depth.
   That reasoning was right about the mechanism and wrong about the fix. The answer is
   not to drop the glass, it is to KEEP THE LAYER: CyberX's animated ground stays on,
   every surface above it becomes translucent, and now the blur has something to do.
   That is also what makes this a mix rather than a swap: the ground and the signal are
   CyberX, the surface discipline and the type are Apple. */
:root[data-skin=apple] .bg{display:block}

/* The recipe, everywhere a surface floats over the ground. Saturation lifts the colour
   the blur washes out, which is the half people notice without being able to name. */
:root[data-skin=apple] .rail,
:root[data-skin=apple] .panel,
:root[data-skin=apple] .tiles,
:root[data-skin=apple] .modal,
:root[data-skin=apple] .modal-card,
:root[data-skin=apple] .tbl th{
  backdrop-filter:saturate(180%) blur(20px);
  -webkit-backdrop-filter:saturate(180%) blur(20px)}
/* The top bar is NOT on this list. It sits in normal flow with nothing passing behind
   it, so a blur there would be the over-glassing that reads as dirty. The gate caught
   it, which is the whole reason the gate measures floating rather than counting blurs. */

/* The rail is glass over the ground rather than a painted strip, so the strip the
   layout paints behind it has to go, or the blur filters that instead. */
:root[data-skin=apple] .app{background:none}
:root[data-skin=apple] .rail{
  background:linear-gradient(180deg, var(--rail-from) 0%, var(--rail-to) 100%);
  border-right:1px solid var(--line)}

/* Unified surfaces, Apple's first rule, now expressed in glass: one translucent panel
   carrying the strip, hairlines between the tiles, nothing floating separately. */
:root[data-skin=apple] .tiles{
  background:var(--panel);border-radius:var(--r);
  box-shadow:0 0 0 1px var(--line), var(--shadow);overflow:hidden}
:root[data-skin=apple] .tile{
  background:transparent;border:1px solid transparent;border-radius:0;
  box-shadow:1px 0 0 var(--line), 0 1px 0 var(--line)}
:root[data-skin=apple] .tile:hover{transform:none;border-color:transparent;
  box-shadow:1px 0 0 var(--line), 0 1px 0 var(--line)}
:root[data-skin=apple] .tile::before{display:none}
:root[data-skin=apple] .panel{
  background:var(--panel);box-shadow:0 0 0 1px var(--line), var(--shadow)}
:root[data-skin=apple] .panel::before{display:none}
:root[data-skin=apple] .subpanel{background:var(--panel-2);border-color:var(--line-2)}
:root[data-skin=apple] .modal-card{
  background:var(--panel);box-shadow:var(--shadow),0 0 0 1px var(--line)}


/* Panels lose the gradient and the inset highlight. One flat surface, one hairline. */
:root[data-skin=apple] .panel{background:var(--panel);box-shadow:var(--shadow)}
:root[data-skin=apple] .panel::before{display:none}
:root[data-skin=apple] .subpanel{background:var(--panel-2);border-color:var(--line-2)}
/* The two CyberX marks are 272 and 275 pixels wide, so swapping one for the other
   moved the rail foot by three pixels. Both are pinned to the same box, which makes
   the swap pure paint. NFR-144. */
/* The parent mark, small. It sits at the foot of the rail as a note about whose platform
   this is, not as a second logo competing with the one at the top: at a hundred and twenty
   across it was wider than the ChurnX wordmark above it and read as the main mark. Sized
   by height so both files land the same however they are proportioned. */
.cyberx-wrap img.cx-light,.cyberx-wrap img.cx-dark{width:auto;height:24px;max-width:100%}

/* A disabled option has to LOOK unavailable as well as be unavailable, or refusing the
   click reads as the control being broken. */
/* .xsel-list, not .cxsel. The class this named is created nowhere: the script
   builds .xsel and .xsel-list, so these two rules matched nothing and a choice
   that cannot be taken looked exactly like one that can, pointer cursor included.
   The script does refuse the click, so the behaviour was right and only the
   appearance lied. */
.xsel-list li[aria-disabled="true"]{opacity:.45;cursor:not-allowed}
.xsel-list li[aria-disabled="true"]:hover{background:transparent}
/* ---------- the Apple type voice ---------- */
/* The system stack, which is the single largest thing separating this from a recolour.
   It is also the one change that MOVES text, because different glyphs are different
   widths. NFR-144 is therefore narrowed here on purpose: switching language may reflow
   text, and must still not move a box. The geometry test enforces that narrower rule. */
:root[data-skin=apple] body{
  font-family:-apple-system,BlinkMacSystemFont,'SF Pro Text','Segoe UI',system-ui,
    'Helvetica Neue',sans-serif;
  -webkit-font-smoothing:antialiased;
  -moz-osx-font-smoothing:grayscale}
:root[data-skin=apple] h1{letter-spacing:-.035em}
:root[data-skin=apple] h2,:root[data-skin=apple] h3,:root[data-skin=apple] h4{letter-spacing:-.022em}
/* A figure that changes should not change width while it does. */
:root[data-skin=apple] .num,
:root[data-skin=apple] .tile b,
:root[data-skin=apple] .tbl td{font-variant-numeric:tabular-nums}
/* Long prose wants room to breathe; dense table rows do not. */
/* Body prose only. A panel caption is chrome, not reading matter, and giving it Apple's
   1.85 made it 23.13px tall against 18.75px under the other two skins, which moved every
   element below it by 13.11px. NFR-144 says nothing moves when the skin changes. */
:root[data-skin=apple] .head p,
:root[data-skin=apple] .panel>p:not(.hint){line-height:1.85}
/* Arabic keeps its own face under every skin, because the system stack does not carry
   this text and substituting it silently is worse than not applying the voice. */
:root[data-skin=apple][dir=rtl] body{font-family:'Noto Naskh Arabic',Primus,sans-serif}
/* ---------- the Vector Grid structure ---------- */
/* What a token block cannot carry. Structure comes from rules rather than from floating
   shadows, tiles are grouped into a tight strip with a signal line across the top, and
   the selected navigation item is marked with a cyan left rule rather than a glow. */

/* The signal line. Two pixels of cyan across the top of every metric, which is the one
   piece of decoration this language allows itself, because it is also the thing that
   makes a strip of numbers scan as one instrument. */
:root[data-skin=vector] .tile::before{
  display:block;height:2px;background:var(--cyan);opacity:1;
  background-image:none}

/* Borders, not shadows. An ordinary panel earns its edge from a rule. */
:root[data-skin=vector] .panel,
:root[data-skin=vector] .tile{
  background:var(--panel);box-shadow:none;border:1px solid var(--line)}
:root[data-skin=vector] .panel::before{display:none}
/* A heading band separated from the body by a single rule, drawn WITHOUT taking any
   space. The first version of this added an 11 pixel padding and a 1 pixel border,
   which pushed every chart and table in the panel down by twelve pixels and grew the
   auto sized panels. That is exactly the movement NFR-144 forbids, and my own geometry
   gate missed it because the gate only scanned rules guarded by the Apple attribute.
   The rule is now a positioned element, so it paints and occupies nothing. */
:root[data-skin=vector] .panel>:is(h2,h3),
:root[data-skin=vector] .ph>:is(h2,h3){position:relative}
/* Centred in the gap it sits in, rather than measured from the heading alone. At -9px
   it stood nine pixels under the heading and one pixel over the caption, so the rule
   read as belonging to the sentence below it instead of separating the two. Half the
   caption gap puts the same distance on both sides, and it still occupies nothing. */
:root[data-skin=vector] .panel>:is(h2,h3)::after,
:root[data-skin=vector] .ph>:is(h2,h3)::after{
  content:'';position:absolute;left:0;right:0;height:1px;background:var(--line);
  bottom:calc(var(--gap-cap) / -2)}
:root[data-skin=vector] .tile:hover{transform:none;border-color:var(--cyan-ink)}

/* The cut shadow is for lift over the page, so it belongs on the modal and nowhere
   else. Ordinary surfaces rely on borders and surface contrast. */
:root[data-skin=vector] .modal-card{
  box-shadow:var(--shadow);border:1px solid var(--line)}
:root[data-skin=vector] .modal{backdrop-filter:none;-webkit-backdrop-filter:none}
/* No frost anywhere in this language: it organises with edges, not with depth. */
:root[data-skin=vector] .rail,
:root[data-skin=vector] .tbl th{backdrop-filter:none;-webkit-backdrop-filter:none}
:root[data-skin=vector] .bg{display:none}

/* The selected navigation item: a cyan left rule and a tinted fill, not a glow. */
:root[data-skin=vector] .nav a[aria-current],
:root[data-skin=vector] .nav a.on{
  box-shadow:inset 3px 0 0 var(--cyan);background:var(--rail-fill)}

/* Type. The display face carries the product voice, the body face carries dense
   interface copy, and operational values are set in the mono that is ALREADY loaded.
   Space Mono was specified and IBM Plex Mono is substituted deliberately: it is the
   same role, it is a serious engineering face, and it costs no extra download. */
:root[data-skin=vector] body{
  font-family:'DM Sans','Trebuchet MS',system-ui,sans-serif;
  font-variant-numeric:tabular-nums}
:root[data-skin=vector] h1,
:root[data-skin=vector] h2,
:root[data-skin=vector] h3,
:root[data-skin=vector] h4{
  font-family:'Bricolage Grotesque','Trebuchet MS',system-ui,sans-serif;
  letter-spacing:-.04em}
:root[data-skin=vector] h1{letter-spacing:-.055em}
/* Labels, dates, scores, percentages and metadata are operational values, so they are
   set in the data face rather than in the body face. */
:root[data-skin=vector] .num,
:root[data-skin=vector] .chip,
:root[data-skin=vector] .tbl td,
:root[data-skin=vector] .tile b,
:root[data-skin=vector] .head .eyebrow{
  font-family:'IBM Plex Mono',ui-monospace,Menlo,monospace;
  letter-spacing:.02em;font-variant-numeric:tabular-nums}
/* A table cell is the one of these that holds arbitrary content. The others carry a
   figure, a date or a label the template wrote, and the tracking is right for those.
   A cell carries whatever the client is called, and three clients in this book are
   named in Arabic, which is a connected script: at 13.5px the .02em came to 0.27px
   between letters that are supposed to join. Found by the right to left server, which
   reads the value that won the cascade rather than the declaration. The face stays;
   only the tracking goes, and mono Latin loses almost nothing by it. */
:root[data-skin=vector] .tbl td{letter-spacing:normal}
:root[data-skin=vector][dir=rtl] body{font-family:'Noto Naskh Arabic',sans-serif}
/* ---------- the chart hover readout ---------- */
/* Token driven, so it belongs to whichever language is in force rather than being a
   fourth style that agrees with none of them. */
.charttip{
  position:fixed;z-index:80;pointer-events:none;
  display:flex;flex-direction:column;gap:2px;
  background:var(--panel-2);color:var(--ink);
  border:1px solid var(--line);border-radius:var(--r-sm);
  padding:7px 10px;max-width:260px;
  box-shadow:var(--shadow);
  font-size:12px;line-height:1.35}
.charttip .k{color:var(--ink-2);font-size:10.5px;letter-spacing:.06em;text-transform:uppercase}
.charttip .v{font-weight:700;font-size:13.5px;font-variant-numeric:tabular-nums}

/* Under a glass language the readout is glass too, or it is the one opaque rectangle
   on the screen and it looks pasted on. */
:root[data-skin=apple] .charttip{
  backdrop-filter:saturate(180%) blur(20px);
  -webkit-backdrop-filter:saturate(180%) blur(20px)}

/* The marks themselves. A point is small on purpose and its hit area is not, because a
   three pixel dot is not something anybody can point at. */
.chart .pt .dot{transition:r .12s ease}
/* :has, not a sibling combinator. The dot is written before the hit inside the
   group, and the old selector asked for a dot AFTER a focused hit, which does not
   exist, so it never matched anything: the point grew under the pointer and not
   under the keyboard. The outline beside this always worked, so this was a missing
   emphasis rather than an invisible focus. Found by the Codex seat reading the
   selector against the markup, which no rendering check would have questioned
   because hover looks correct. */
.chart .pt:hover .dot,
.chart .pt:has(> .hit:focus-visible) > .dot{r:5}
.chart .pt .hit{cursor:pointer}
.chart .pt .hit:focus-visible{outline:2px solid var(--focus);outline-offset:1px}
/* ---------- main tabs and subtabs ---------- */
/* The rail used to be three headings over twenty two links, all at one level, so
   finding anything meant reading everything. The level that groups is now the level you
   click, and the two screens people open every morning stay at the top level rather
   than being tidied into a group nobody wants to expand at nine in the morning. */
.navgroup{display:flex;flex-direction:column}
.navgroup .tab{
  display:flex;align-items:center;gap:11px;width:100%;
  padding:9px 12px;margin:1px 0;border:0;background:transparent;cursor:pointer;
  color:var(--rail-ink-2);font:inherit;font-size:14px;text-align:start;
  border-radius:var(--r-sm);transition:background .14s,color .14s}
.navgroup .tab:hover{background:var(--rail-fill);color:var(--rail-ink)}
.navgroup .tab svg:not(.caret){width:17px;height:17px;flex:none;opacity:.9}
.navgroup .tab span{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.navgroup .tab .caret{width:15px;height:15px;flex:none;opacity:.6;transition:transform .16s ease}
.navgroup.open .tab .caret{transform:rotate(180deg)}
.navgroup.open .tab{color:var(--rail-ink)}
.navgroup .tab:focus-visible{outline:2px solid var(--focus);outline-offset:-2px}

/* Closed by default, and closed means gone rather than merely invisible: a hidden link
   that still takes a tab stop is worse than no rail at all. */
.navgroup .sub{display:none;flex-direction:column;margin:1px 0 4px}
.navgroup.open .sub{display:flex}
/* One rule down the left, so a subtab reads as belonging to the tab above it without
   needing an indent deep enough to lose the label. */
/* The guide rule lives in a gutter of its own, to the LEFT of the pill.
   It used to sit three pixels inside the link, which put it underneath the selected
   item's rounded ring: a straight 2px line crossing a 10px corner radius, which reads
   as the line and the selection being misaligned because they are. Giving the rule its
   own column means the pill starts where the rule ends and the two never touch. */
.navgroup .sub{position:relative}
/* The rule runs the exact height of the items it groups, from the top edge of the first
   to the bottom edge of the last. Inset by three pixels it stopped INSIDE the first and
   last rounded rectangles, so the line appeared to start partway down the first item and
   stop partway up the last: the misalignment was between the line's ends and the
   rectangles' ends, not between their left positions. */
.navgroup .sub::before{
  content:'';position:absolute;inset-inline-start:6px;top:0;bottom:0;width:2px;
  background:var(--rail-line);border-radius:1px}
.navgroup .sub a{
  margin-inline-start:16px;padding-inline-start:13px;font-size:13.5px;position:relative}
/* The current item colours its own segment of that same rule, at exactly the rule's
   position AND exactly its own height, so the coloured segment and the rounded rectangle
   beside it begin and end on the same two lines. Three pixels shorter at each end made a
   30px marker sit against a 36px rectangle, which is what read as misaligned.

   Then it was written as top:0;bottom:0, which reads as the full height and is not. The
   item is a flex container, and an absolutely positioned pseudo element inside one does
   not resolve those two offsets against the box you expect: the segment came out 22px
   against a 36px item and sat seven pixels above its centre. Measured in the browser
   rather than reasoned about. Stating the height and centring on the item is independent
   of how that containing block resolves, so it lands on the item's own top and bottom. */
.navgroup .sub a[aria-current="page"]::before{
  content:'';position:absolute;inset-inline-start:-10px;
  top:50%;height:100%;transform:translateY(-50%);width:2px;
  background:var(--cyan);border-radius:1px}
:root[data-rail="collapsed"] .navgroup .sub::before,
:root[data-rail="collapsed"] .navgroup .sub a[aria-current="page"]::before{display:none}
:root[data-rail="collapsed"] .navgroup .sub a{margin-inline-start:0}

/* Collapsed to icons, a disclosure triangle and an indent have nothing to disclose or
   indent, so the group flattens back to its children. */
:root[data-rail="collapsed"] .navgroup .tab{display:none}
:root[data-rail="collapsed"] .navgroup .sub{display:flex}
:root[data-rail="collapsed"] .navgroup .sub a{padding-inline-start:0;box-shadow:none}

/* ---------- long text: a button that opens a real writing surface ----------
   Sizes come from the same named scale as everything else, and none of it is redefined
   per skin, so NFR-144 holds: the control occupies the same box under all three. */
.longtext{width:100%}
.lt-open{display:flex;align-items:center;gap:12px;width:100%;text-align:start;
  padding:11px 14px;border:1px solid var(--line);border-radius:10px;
  background:var(--panel-2);color:var(--ink);font:inherit;font-size:13.5px;
  cursor:pointer;transition:border-color .14s ease,background .14s ease}
.lt-open:hover{border-color:rgb(var(--accent-rgb) / .45);background:rgb(var(--accent-rgb) / .05)}
.lt-open:focus-visible{border-color:var(--cyan-ink);box-shadow:0 0 0 3px rgb(var(--accent-rgb) / .12);outline:none}
/* --ink-2, not --muted. This is the value the field holds, or the prompt for it, and at
   13.5px on a light panel --muted measured 3.67 to 1. */
.lt-open.empty .lt-preview{color:var(--ink-2)}
.lt-preview{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  line-height:1.5}
.lt-cue{flex:0 0 auto;display:inline-flex;align-items:center;gap:6px;color:var(--ink-2);
  font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase}
.lt-open:hover .lt-cue{color:var(--cyan-ink)}
/* A required field that was left empty says so where the person is looking, which is
   the button, because the field the form submits is hidden and cannot be pointed at. */
.longtext.missing .lt-open{border-color:var(--crit);box-shadow:0 0 0 3px rgb(var(--crit-rgb) / .16)}

.lt-card{width:min(820px,100%)}
.lt-area{width:100%;min-height:min(52vh,420px);resize:vertical;line-height:1.65;
  font-size:14.5px}
/* The two readouts under the box sit on one line, the note on the left and the count on
   the right, so neither pushes the writing surface around as it changes. */
.lt-foot{display:flex;align-items:baseline;justify-content:space-between;gap:16px;
  margin:var(--gap-cap) 0 0;font-size:12px;color:var(--muted)}
.lt-count{font-family:'IBM Plex Mono',monospace;font-variant-numeric:tabular-nums;
  flex:0 0 auto}
.lt-count.full{color:var(--warn)}
.lt-hint{flex:1;min-width:0}
.modal-foot{display:flex;justify-content:flex-end;gap:10px;padding:16px 22px;
  border-top:1px solid var(--line)}

/* ---------- the dashboard set ----------
   Spacing comes from the same named scale as everything else. Nothing here is redefined
   per skin, so the five screens occupy identical boxes under all three. */

/* A tile with no basis behind it. Dimmed rather than absent, because the reader needs to
   see that the figure exists and has not been measured, not that it was left out. */
.tile.unknown{opacity:.86}
.tile .val.muted{font-size:19px;line-height:1.35;color:var(--muted);font-weight:600}
/* Said out loud on the tile itself. A confident number over a fraction of the book is
   the failure that looks most like success, so it cannot live only in a tooltip. */
.tile .warnline{display:block;margin-top:6px;font-size:11px;line-height:1.45;
  color:var(--warn);font-weight:600}

/* Readings taken from the record. One row, one claim, one place to go and check it. */
.obs{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px}
.obs-row{display:flex;gap:10px;align-items:flex-start;font-size:13px;line-height:1.6;
  color:var(--ink-2)}
.obs-row a{margin-inline-start:6px;color:var(--cyan-ink);white-space:nowrap}
.obs-dot{flex:0 0 8px;width:8px;height:8px;border-radius:50%;margin-top:6px;
  background:rgb(var(--neutral-rgb))}
.obs-row.t-good .obs-dot{background:var(--good)}
.obs-row.t-warn .obs-dot{background:var(--warn)}
.obs-row.t-bad .obs-dot{background:var(--crit)}

.tasklist{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.tasklist li{display:flex;align-items:baseline;gap:12px;padding:10px 12px;border-radius:10px;
  font-size:13.5px;border:1px solid transparent}
.tasklist li:hover{background:rgb(var(--neutral-rgb) / .07)}
.tasklist li.late{box-shadow:inset 2px 0 0 var(--crit)}
.tk-name{flex:1;min-width:0;color:var(--ink)}
.tk-client{flex:0 0 auto;color:var(--cyan-ink);font-size:12.5px}
.tk-due{flex:0 0 auto;font-size:12px;color:var(--muted);font-family:'IBM Plex Mono',monospace}
.tasklist li.late .tk-due{color:var(--crit)}

/* How close a date is, said as a word and a colour rather than as a colour alone.

   The base rule is the load bearing one. Without it a pill whose tone matches no rule
   below renders as bare text, and three of the four states the integration board can
   report did exactly that: only warn had a rule, so reachable, failing and off arrived
   on the page as unstyled words. Nothing caught it because a pill is only drawn when
   there is a row to draw it for, and the demonstration database has no integrations, so
   the element was never on any page that was measured.

   So the tones are spelled both ways. The stylesheet said ok, crit and warn; the health
   method says good, bad, muted and warn. Neither vocabulary is wrong and renaming either
   one moves a defect rather than fixing it. */
.pill{display:inline-block;padding:2px 9px;border-radius:999px;font-size:12px;
      font-weight:600;line-height:1.55;white-space:nowrap;
      background:rgb(var(--neutral-rgb) / .10);color:var(--ink-2)}
.pill.ok,.pill.good{background:rgb(var(--good-rgb) / .14);color:color-mix(in oklab,var(--good) 66%,var(--ink))}
.pill.warn,.pill.unverified{background:rgb(var(--warn-rgb) / .16);color:color-mix(in oklab,var(--warn) 66%,var(--ink))}
.pill.crit,.pill.bad{background:rgb(var(--crit-rgb) / .16);color:color-mix(in oklab,var(--crit) 58%,var(--ink))}

/* The section strip inside Customer 360. Real links, so a section can be sent to
   somebody, and the same gutter and marker grammar as the rail's subtabs so the two
   read as the same idea at two scales. */
.subnav{display:flex;gap:4px;flex-wrap:wrap;margin:0 0 20px;padding:4px;
  border:1px solid var(--line);border-radius:12px;background:var(--panel-2)}
.subnav-tab{padding:9px 16px;border-radius:9px;font-size:13.5px;font-weight:600;
  color:var(--ink-2);border:1px solid transparent;transition:.14s}
.subnav-tab:hover{color:var(--ink);background:rgb(var(--accent-rgb) / .07)}
.subnav-tab.here{color:var(--ink);background:rgb(var(--accent-rgb) / .14);
  border-color:rgb(var(--accent-rgb) / .30)}

/* A record laid out as label and value. The label column is fixed so the values line up
   down the page rather than stepping in and out with the length of each label. */
table.tbl.kv th[scope="row"]{width:34%;min-width:130px;font-size:11px;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:var(--ink-2);
  vertical-align:top;padding-top:12px}
table.tbl.kv td{color:var(--ink);padding-top:12px}

/* What happened, most recent first. */
.tline{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.tline li{display:grid;grid-template-columns:96px 1fr auto;gap:14px;align-items:baseline;
  padding:11px 12px;border-radius:10px;font-size:13.5px}
.tline li:hover{background:rgb(var(--neutral-rgb) / .07)}
/* --ink-2, not --muted. At 12px in the light themes --muted measured 3.47 to 1 against
   the panel, and a date is content rather than decoration. */
.tl-when{font-family:'IBM Plex Mono',monospace;font-size:12px;color:var(--ink-2)}
.tl-what{color:var(--ink);min-width:0}
.tl-who{color:var(--ink-2);font-size:12px}
@media(max-width:620px){.tline li{grid-template-columns:1fr}}

/* A failed save says what was wrong, in the same red the rest of the interface uses. */
.note.bad{border-color:rgb(var(--crit-rgb) / .32);background:rgb(var(--crit-rgb) / .09)}
.note.bad p{margin:0}
.note.bad p + p{margin-top:6px}

/* ---------- cards: reporting administration and the integration centre ---------- */
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px;
  margin-bottom:22px}
.cards .card{display:flex;flex-direction:column;gap:10px;padding:18px 20px;
  border:1px solid var(--line);border-radius:14px;background:var(--panel);
  box-shadow:var(--shadow)}
.cards .card header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}
/* A card heading takes its level from where the card sits: below the page title on the
   reporting screen, below a panel heading on the integrations one. The look is the same
   either way, so the rule names both rather than the markup pretending to one depth. */
.cards .card :is(h2,h3){font-size:15px;margin:0;line-height:1.35}
.cards .card p{margin:0;font-size:12.5px;line-height:1.6;color:var(--ink-2);flex:1}
.card-n{flex:0 0 auto;font-family:'IBM Plex Mono',monospace;font-size:22px;font-weight:700;
  line-height:1;color:var(--cyan-ink);font-variant-numeric:tabular-nums}
/* Zero is a real answer here, so it is shown rather than hidden, but it is not the
   colour the interface uses for a figure worth acting on. */
.card-n.zero{color:var(--muted)}
.card-meta{font-size:11px;letter-spacing:.06em;text-transform:uppercase;color:var(--muted);
  font-weight:600}
.cards .card.int header{align-items:center}

/* A family with nothing in it is still offered, dimmed, so the reader can see that the
   family exists and holds nothing rather than wondering where it went. */
.subnav-tab.empty{opacity:.55}
.subnav-tab b{margin-inline-start:7px;font-family:'IBM Plex Mono',monospace;font-size:11.5px;
  color:var(--muted);font-variant-numeric:tabular-nums}
.subnav-tab.here b{color:var(--cyan-ink)}

/* ---------- the builder ---------- */
.palette{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:2px}
.pal{display:flex;flex-direction:column;gap:3px;padding:11px 12px;border-radius:10px;
  border:1px solid transparent}
.pal:hover{background:rgb(var(--neutral-rgb) / .07);border-color:var(--line)}
.pal-name{font-size:13.5px;font-weight:600;color:var(--ink)}
.pal-for{font-size:12px;line-height:1.5;color:var(--ink-2)}
.pal-n{font-size:11px;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);
  font-weight:600}

/* One widget per row, each the full width of whatever holds it.
   This was a column flow, which put every widget on one line and divided the
   space between them. Four widgets in a 726 pixel panel came out at 159 pixels
   each, and the chart inside one of them drew a 720 unit wide picture at 121
   pixels, a scale of 0.17. A widget that narrow cannot carry a chart, an axis
   and a caption, so it carried none of them legibly.
   Stacking costs vertical space and buys a readable widget, which is the whole
   point of drawing one. */
.canvas{display:grid;grid-template-columns:minmax(0,1fr);gap:var(--panel-gap)}
.widget{display:flex;flex-direction:column;gap:12px;padding:16px 18px;
  border:1px solid var(--line);border-radius:14px;background:var(--panel-2)}
.widget header{display:flex;align-items:center;justify-content:space-between;gap:10px}
.widget h4{margin:0;font-size:14px;line-height:1.35}
.widget header form{margin:0;line-height:0}
.widget .x{background:transparent;border:1px solid var(--line);color:var(--muted);
  width:26px;height:26px;border-radius:8px;cursor:pointer;font-size:15px;line-height:1}
.widget .x:hover{border-color:var(--crit);color:var(--crit)}
.widget-figure{font-family:'IBM Plex Mono',monospace;font-size:34px;font-weight:700;
  line-height:1.1;color:var(--ink);font-variant-numeric:tabular-nums}
.widget-basis{margin:0;font-size:12.5px;color:var(--ink-2);line-height:1.55}
/* Every widget states the reading behind it and how much of the book that reading
   covers, so a figure lifted out of a dashboard still carries its own provenance. */
.widget-foot{margin-top:auto;padding-top:12px;border-top:1px solid var(--line-2);
  font-size:11px;line-height:1.5;color:var(--muted)}
/* The tile now carries two lines under its figure: what the number is, and how much of
   the book it speaks for. The second is quieter than the first, because the basis is the
   claim and the coverage is the caveat on it. */
.tile .sub.cover{display:block;margin-top:5px;font-size:11px;line-height:1.5;
  color:var(--muted);opacity:.92}
/* A caption in the page head follows a paragraph rather than a heading, so it needs the
   same breathing room the panel captions get. The panel rule is scoped to .panel and
   never reached here, which left it 7px under the line above it. */
.head .hint{margin:var(--gap-cap) 0 0;font-size:12.5px;line-height:1.5;color:var(--muted)}
/* The arrangement sits in the same control strip as the language and the brightness, and
   is a link because choosing it moves you into a different shell. It borrows the skin
   switch's shape so the three axes read as one group of related choices. */
.layoutswitch a{
  display:inline-flex;align-items:center;justify-content:center;
  padding:0 11px;height:28px;border-radius:8px;
  /* --ink-2, not a rail token. These controls sit inside main and not in the rail, and
     --rail-ink-2 is deliberately invariant light because the rail is dark under both
     themes. On a light page it measured 1.32 to 1. */
  color:var(--ink-2);font-size:11px;font-weight:600;
  letter-spacing:.04em;text-transform:uppercase;
  border:1px solid transparent;transition:.14s}
.layoutswitch a:hover{color:var(--ink-hover);background:rgb(var(--accent-rgb) / .10)}
.layoutswitch a[aria-current="true"]{
  color:var(--ink);background:rgb(var(--accent-rgb) / .18);
  border-color:rgb(var(--accent-rgb) / .38)}
/* The plain textarea, which is what a long text field is until the script takes over.
   Styled rather than left bare, because on a slow load or with scripting off it is the
   control somebody actually uses. */
.lt-native{width:100%;min-height:120px;resize:vertical;line-height:1.6}
.lt-nativehint{margin:var(--gap-cap) 0 0;font-size:12px;line-height:1.5;color:var(--ink-2)}

/* ---------- what the browser draws, drawn by the platform instead ----------

   Three things were left to the browser and looked like it: the scrollbars, the colour a
   selection takes, and the grey box a tap leaves on a phone. Each is a surface a person
   touches constantly, and each was the one part of the screen that belonged to Chrome
   rather than to this product. The Desk showed it most, because it is five scrolling
   panes side by side.

   Both mechanisms are declared. scrollbar-width and scrollbar-color are the standard
   ones and are what Firefox and current Chrome honour; the webkit pseudo elements are
   what older Chrome and Safari honour. Neither alone covers the browsers this is opened
   in. Everything is expressed in the same tokens as the rest of the product, so it
   follows the skin and the theme rather than being painted once and going wrong in the
   other one. */
*{scrollbar-width:thin;scrollbar-color:var(--line) transparent}
*::-webkit-scrollbar{width:10px;height:10px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{
  background:var(--line);border-radius:8px;
  /* A border in the track's colour, so the thumb reads as inset rather than as a bar
     stuck to the edge. */
  border:2px solid transparent;background-clip:padding-box}
*::-webkit-scrollbar-thumb:hover{background:var(--cyan-deep,var(--cyan));background-clip:padding-box}
*::-webkit-scrollbar-corner{background:transparent}

/* A selection in the product's own accent, at an opacity that leaves the text readable
   rather than reversing it. The default blue is the one colour on the page that belongs
   to no skin. */
::selection{background:rgb(var(--accent-rgb) / .28);color:var(--ink)}
::-moz-selection{background:rgb(var(--accent-rgb) / .28);color:var(--ink)}

/* The grey flash a touch leaves is the browser telling somebody it noticed. Every
   control here says so itself, in its own colours. */
*{-webkit-tap-highlight-color:transparent}

/* No override here, and the reason is worth keeping. This link was briefly changed
   to a deeper ink because the sign in card measured as a grey gradient in a light
   theme and the accent read 3.51 to 1 against it. The card is not grey: its stops
   are translucent white, and the two readings were the same colour handled two
   ways, one raw and one premultiplied against black. Over the real ground the
   accent reads about 5.4 and clears. The override was removed rather than kept for
   being harmless, because a rule nobody can justify is a rule somebody later has to
   argue with. */

/* ---- Rules every arrangement needs ---------------------------------------
   These live here, in the one stylesheet all three layouts load, rather than in
   churnx-score.css. That sheet is loaded by the app layout only, so a rule
   placed there reaches 36 pages and silently misses the Desk and every guest
   page including sign in. That is not hypothetical: it happened today, and the
   Desk kept tracking Arabic for an hour after the fix was reported done.
   EveryArrangementCarriesTheSharedRulesTest fails if any of these goes missing,
   which is what makes it safe to keep them in the file the skin work rewrites.

   Arabic is a connected script. Letter spacing inserts a gap between letters
   that are supposed to join, so a word comes apart into loose glyphs. Measured
   with real text: a tile label at 1.6px stretched a four word phrase from 78px
   to 104px. The negative tracking on headings is the same fault pointing the
   other way. Marked important on purpose, because no Arabic anywhere wants
   tracking, and raising specificity instead would mean chasing each rule that
   outranks it one at a time. */
:root[dir=rtl] body,:root[dir=rtl] body *{letter-spacing:normal!important}
/* Digits, dates and identifiers are Latin whichever way the page runs, and they
   are set in the data face, so they keep the tracking that face was drawn with.
   Declared after the rule above so it wins between two equally absolute ones. */
:root[dir=rtl] .num,:root[dir=rtl] .mono,:root[dir=rtl] code{letter-spacing:.01em!important}

/* A client name, a platform name, a date and a figure are Latin however the
   page runs. Dropped into a right to left line without isolation they are
   reordered against the text around them, which is how a trailing full stop
   ends up at the front of a sentence. The chart labels already do this, and the
   same reason applies to every cell that carries a value. */
:root[dir=rtl] :is(.num,.mono,.chip,.tbl td,.tbl th,.cov,.nmeta,time,code){
  unicode-bidi:isolate}

/* A native dropdown list is drawn by the browser, and which colours it uses is a
   platform decision rather than a choice this stylesheet gets to make. macOS draws
   it in system chrome and ignores what is set here; Windows and Linux use the
   author's colours. The options inherited the page ink and had no ground of their
   own, so on those platforms light ink was being drawn onto the list's own light
   panel. Stating both removes the question rather than leaving it to the platform.
   Found by the accessibility server, which measured 115 options below the ratio.
   Most of that reading is an artifact, because an option in a closed select has a
   zero sized box and is never painted, but the risk underneath it was real. */
select option{background:var(--panel);color:var(--ink)}
select option:disabled{color:var(--muted)}

/* ---- A drawing has a size it was drawn at ---------------------------------
   A chart is authored in its own units and the element fills its container, so
   the ratio between the two is a magnification nobody chose. Both ends of that
   are defects and both were measured today: at 1920 a reader dashboard drew a
   1000 unit chart at 1.55, so the axis labels and markers were half again their
   intended size and it read as zoomed; at 320 the same drawing came out at 0.19,
   which puts a 13px label on screen at about two pixels.
   The wrapper carries the drawing width as a number, because that is the one
   thing the server does know and CSS cannot otherwise find out. */
.chartwrap{overflow-x:auto;overflow-y:hidden;max-width:100%}
/* A grid or flex item defaults to min-width:auto, which is its min-content, so a
   drawing held wider than its track pushes the whole panel out rather than
   scrolling inside it. Measured at 620: a 580 pixel chart forced a widget to 618
   in a 531 track, and four of them left the screen. The scroll container only
   works if the thing holding it is allowed to be narrower than its contents. */
.panel,.widget,.chartwrap{min-width:0}
/* Never much larger than it was drawn. The panel keeps whatever room is left
   rather than the picture growing into it, which is the trade this makes on a
   very wide screen: some space beside a correct drawing, instead of a correct
   space around a zoomed one. */
.chartwrap > .chart{max-width:calc(var(--chart-units,720) * 1.15px);margin-inline:auto}
/* And never so small the type goes with it, at ANY width. This was a phone rule
   at first, which left a chart in a narrow panel on a 1024 screen drawing at 0.25,
   and a label authored at 13px arrives on the glass at three. The wrapper scrolls
   instead, which is the same answer the tables already give. A drawing you have to
   push sideways is worse than one that fits, and far better than one you cannot
   read. */
.chartwrap > .chart{min-width:calc(var(--chart-units,720) * 0.58px)}
@media (max-width:760px){
  .chartwrap > .chart{margin-inline:0}
}

/* The trail above a client profile. FR-ACP-33.
   A separator drawn by CSS rather than typed into the markup, so the character
   never reaches a screen reader as a word between two link names. */
.crumbs{margin:0 0 10px}
.crumbs ol{display:flex;flex-wrap:wrap;gap:0;margin:0;padding:0;list-style:none;
  font-size:12.5px;line-height:1.5}
.crumbs li{display:flex;align-items:center;min-width:0}
.crumbs li + li::before{content:"/";margin:0 7px;opacity:.45;flex:none}
.crumbs a{color:inherit;opacity:.72;text-decoration:none;border-bottom:1px solid transparent}
.crumbs a:hover,.crumbs a:focus-visible{opacity:1;border-bottom-color:currentColor}
/* The step the reader is on is not a link and must not look like one. */
.crumbs [aria-current="page"]{opacity:1;font-weight:600}
.crumbs p{margin:5px 0 0;font-size:12px}

/* Account details. FR-ACP-27.
   Fixed columns rather than one per field, so the same label sits in the same
   place on every client. A row whose value is not recorded still occupies its
   cell: the gap is the actionable part and hiding it flattens nine fields into
   however many happen to be filled. */
.detailgrid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
  gap:14px 22px;margin:14px 0 0}
.detailgrid > div{min-width:0}
.detailgrid dt{font-size:11.5px;letter-spacing:.04em;text-transform:uppercase;
  opacity:.6;margin:0 0 3px}
/* Long values wrap rather than pushing the grid sideways. A website or a
   LinkedIn path is routinely wider than a third of the panel. */
.detailgrid dd{margin:0;font-size:13.5px;overflow-wrap:anywhere}
@media (max-width:900px){.detailgrid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.detailgrid{grid-template-columns:1fr}}

/* The stakeholder map. FR-ACP-16, FR-ACP-17.
   Two columns because they are two questions, side by side so the disagreement
   between them is visible at a glance: the person at the top of the chart is
   frequently not the person at the top of the influence list, and that gap is
   the reason both are drawn. */
.stakeviews{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:24px;margin:12px 0 0}
.stakeviews > div{min-width:0}
.stakeviews h3{font-size:13px;margin:0 0 2px}
.influencelist,.orgchart{list-style:none;margin:10px 0 0;padding:0;display:flex;
  flex-direction:column;gap:6px}
.influencelist li,.orgchart li{display:flex;flex-wrap:wrap;gap:8px;align-items:baseline;
  min-width:0;font-size:13px}
.influencelist .who,.orgchart .who{font-weight:600}
/* Depth as an indent rather than nested lists, so a chart eight deep cannot push
   the panel sideways. Capped, because the indent stops carrying meaning long
   before the hierarchy does. */
.orgchart li{padding-inline-start:calc(min(var(--depth,0),6) * 18px)}
.orgchart li[style*="--depth:0"]{padding-inline-start:0}
.stakelegend{list-style:none;display:flex;flex-wrap:wrap;gap:8px 18px;
  margin:16px 0 0;padding:12px 0 0;border-top:1px solid var(--line);
  font-size:12.5px}
.stakelegend li{display:flex;gap:7px;align-items:baseline;min-width:0}
@media (max-width:820px){.stakeviews{grid-template-columns:1fr;gap:20px}}

/* Forced colours, and why this block has to exist.
 *
 * Eighteen focus selectors across these three sheets trade the outline for a box-shadow
 * ring, a border colour, or a background tint. In forced-colours mode the browser paints
 * no box-shadow and no background image, and it replaces every border colour from the
 * user's own palette, so each of those three mechanisms reduces to a colour change or to
 * nothing at all.
 *
 * NFR-74 is a Must at P0 and says focus is "always visible, and never only a colour
 * change". This is the mode in which the rest of the stylesheet cannot keep that promise,
 * so the promise is kept here instead.
 *
 * It lives in churnx.css deliberately. All three layouts load this sheet, so one block
 * reaches the desk and score rules too, which is the same reasoning the right-to-left
 * letter-spacing override already relies on further up this file.
 *
 * CanvasText rather than var(--focus): in this mode a token would be overridden by the
 * system palette anyway, and a system colour is the only value guaranteed to contrast
 * with whatever ground the user has chosen. It is also not a colour literal, so the skin
 * lint is satisfied for the right reason rather than by accident.
 *
 * The score sheet's five are named one by one on purpose. They use :focus rather than
 * :focus-visible, so the broad rule below does not reach them, and the first draft of
 * this block missed all five. The guard is what found that.
 *
 * @requirement NFR-73, NFR-74
 */
@media (forced-colors: active){
  /* Everything whose focus style comes from the keyboard pseudo-class. */
  :focus-visible{outline:2px solid CanvasText !important;outline-offset:2px}

  /* Controls styled on :focus, which the rule above does not match. */
  .field input:focus,.field select:focus,textarea:focus,.xsel-find input:focus,
  .bench-form input:focus,
  .inp:focus,.numin:focus,.whyin:focus,.itgform input:focus,.itgform select:focus{
    outline:2px solid CanvasText !important;outline-offset:2px}

  /* The skip-link landing target is focused by script, not tabbed to, so a ring on it
   * would announce a position nobody moved to. This is the one of the eighteen that is
   * correct as it stands. */
  [id="content"]:focus{outline:none !important}
}

/* Targets a thumb can actually hit, at a phone width.
 *
 * Found by running e2e/06-a-phone.spec.ts for the first time. That spec was written on
 * 10 September, lived on the build server where there is no Playwright runner and no
 * config, and was absent from the Mac where the runner lives, so it had never executed.
 * On its first run it failed on all seven pages, and it was right on every one.
 *
 * The floor is PhoneContract::TARGET_FLOOR, 44, and the width floor is BOX_FLOOR, 24.
 * The breakpoint is BECOMES_A_PHONE, 760, so nothing here reaches a desk: these controls
 * are 30 and 34 pixels tall by design in a dense toolbar somebody is using with a mouse,
 * and that design is not wrong at a desk. It is wrong under a thumb.
 *
 * Raised here rather than at the base rule for exactly that reason. A 44 pixel sign-out
 * button in a desktop top bar would push the whole bar taller on every screen to fix a
 * problem that only exists on a phone.
 *
 * @requirement FR-MOB-02, FR-MOB-03, NFR-72
 */
@media (max-width:760px){
  /* The drawer button. This one was mine, added with the drawer itself, and it was the
     worst of them: 40 by 40 is four pixels under the floor on the single control that
     opens the navigation on a phone. */
  .railopen{width:44px;height:44px}

  /* Sign out. 35 by 30 on six pages, because below 900 the caption is hidden and only
     the icon is left, which shrinks the box rather than keeping the target. */
  .signout button,a.signout{min-height:44px;min-width:44px;justify-content:center}

  /* Pagination. 34 by 34, and the one control somebody presses repeatedly, which is the
     worst case for a target that is slightly too small. */
  .pager .pg{min-height:44px;min-width:44px}
  /* The gap marker is an ellipsis, not a control, and giving it a target invites a press
     that does nothing. */
  .pager .pg.gap{min-height:0;min-width:0}
}

/* The way back off an error page.
 *
 * On the error pages the only control is a link inside a paragraph of small print, so its
 * height is that paragraph's line height: 56 by 11 pixels, measured on the 500 page. It
 * is the single control on a screen somebody has arrived at because something already
 * went wrong, which is the worst place to put a target a thumb can miss.
 *
 * Found by the phone spec, and found by accident: the spec measures /accounts/7, that page
 * returns 500 on this server because of the pending migrations, so it was measuring the
 * error page rather than the client record.
 *
 * All of the error pages share the pattern, so this is one rule rather than a change to
 * the 500 page. Inline-flex because min-height does nothing to an inline element.
 *
 * @requirement FR-MOB-03, NFR-72
 */
@media (max-width:760px){
  .cov.apart a{display:inline-flex;align-items:center;min-height:44px;padding-inline:2px}
}

/* Below 760 the drawer was opening empty, and this is the rule that stops it.
 *
 * Two phone designs were live at once and neither knew about the other. The phone bar
 * work folded the rail's navigation away at this width and brought it back with a class
 * on the rail: `.rail .nav{display:none}` and `.rail.open .nav{display:block}` in
 * churnx-score.css, with a bottom tab bar for the common destinations and a More entry
 * to reach the rest. The drawer work then made the rail an off-canvas panel revealed by
 * data-drawer on the root, which outranks that sheet's positioning on purpose.
 *
 * What survived was the half that hides. The drawer slid in carrying the brand and who
 * you are and nothing else, so below 760 there was no way to reach the navigation at all:
 * the rail's nav was display:none, the More entry toggled a class that no longer revealed
 * anything, and what was left was the handful of entries on the tab bar. That is the
 * regression the drawer introduced, and the phone spec was the one thing that would have
 * caught it.
 *
 * Both designs are kept, because both are right about their own half. The tab bar stays
 * as the everyday surface. The drawer becomes what the More entry opens, which is what
 * that entry always promised. One state, data-drawer, drives it.
 *
 * The selectors are deliberately more specific than the ones they answer, because
 * churnx-score.css loads after this file and equal specificity would lose on order.
 *
 * @requirement FR-MOB-02, NFR-73
 */
@media (max-width:760px){
  :root[data-drawer="open"] .app>.rail .nav{display:block}

  /* The mark and the meta come back with it. Hidden at this width for the same reason
     the nav was, and by the same sheet. */
  :root[data-drawer="open"] .app>.rail .railfoot{display:block}

  /* The drawer already scrolls and contains its own overscroll, so the nav does not need
     a height of its own here. The sheet this replaces had to cap the nav because the rail
     was sticky and the page scrolled behind it; a fixed panel that scrolls itself has no
     such problem, and capping it twice put a second scrollbar inside the first.
     Stated rather than left out, because its absence looks like an omission. */
}

/* The way back, at a thumb's size.
 *
 * The breadcrumb links are 40 by 20 pixels, measured on the client record at 320. They are
 * a bare <a> inside nav.crumbs, so they inherit the line height of the small print they sit
 * in and nothing else. On a phone the crumb is often the only way back to the list, which
 * makes it a poor place for a target somebody misses.
 *
 * Found by the phone spec, and it reproduced identically on the live site and the build
 * server, which is what marks it as the interface rather than the data.
 *
 * The floor is PhoneContract::TARGET_FLOOR and the breakpoint is BECOMES_A_PHONE, the same
 * two numbers the other targets here use. Inline-flex because min-height does nothing to an
 * inline element.
 *
 * @requirement FR-MOB-03, NFR-72
 */
@media (max-width:760px){
  .crumbs a{display:inline-flex;align-items:center;min-height:44px}
}

/* ---- Nothing left for the browser to draw ------------------------------------
   Every control above has its look, but only where somebody remembered a wrapper:
   .field, .inp, .itgform. An input written anywhere else, a seat count in a table
   cell or a reason beside a Set button, came out as the user agent's white box
   with two pixels of padding, on a dark page. So the look is the default here,
   at zero specificity, and every named rule above still wins where it says
   something different. Width is left alone for the same reason as before. */
:root{color-scheme:dark}
:root[data-theme=light]{color-scheme:light}
@media (prefers-color-scheme: light){
  :root:not([data-theme=dark]){color-scheme:light}
}
/* Without that the calendar, the spinners, the autofill panel and every scrollbar
   the platform does not paint were drawn for a light page whatever the theme. */

:where(input:not([type]),input[type=text],input[type=search],input[type=email],input[type=url],
  input[type=tel],input[type=password],input[type=number],input[type=date],input[type=time],
  input[type=datetime-local],input[type=month],input[type=week]){
  padding:11px 14px;border:1px solid var(--line);border-radius:10px;
  background:var(--tint-2);color:inherit;max-width:100%;text-overflow:ellipsis;
  transition:border-color .16s ease,box-shadow .16s ease}
input:where(:not([type]),[type=text],[type=search],[type=email],[type=url],[type=tel],
  [type=password],[type=number],[type=date],[type=time],[type=datetime-local]):focus{
  border-color:var(--cyan-ink);box-shadow:0 0 0 3px rgb(var(--accent-rgb) / .12);outline:none}
:where(input,textarea)::placeholder{color:var(--muted);opacity:1}
:where(input,textarea):disabled{opacity:.55;cursor:not-allowed}

/* A count is typed. The arrows the browser adds to a number box step it by one and
   appear only under the pointer, in the operating system's own grey. */
input[type=number]{-moz-appearance:textfield;appearance:textfield}
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}
input[type=search]::-webkit-search-cancel-button,
input[type=search]::-webkit-search-decoration{-webkit-appearance:none;appearance:none}
input::-webkit-calendar-picker-indicator{cursor:pointer;opacity:.75}

/* Autofill paints its own pale blue, which cannot be overridden with a background,
   only covered from inside. */
input:-webkit-autofill,input:-webkit-autofill:hover{
  -webkit-text-fill-color:var(--ink);caret-color:var(--ink);
  -webkit-box-shadow:0 0 0 1000px var(--panel-2) inset;transition:background-color 99999s}
input:-webkit-autofill:focus{-webkit-text-fill-color:var(--ink);
  -webkit-box-shadow:0 0 0 1000px var(--panel-2) inset,0 0 0 3px rgb(var(--accent-rgb) / .12)}

/* ---- What the browser used to say in a bubble --------------------------------
   The script answers the invalid event itself and writes this under the field, in
   the platform's words, so a refusal stays on screen while it is being fixed. */
.cx-invalid{margin:6px 0 0;flex-basis:100%;font-size:12.5px;line-height:1.4;color:var(--crit)}
/* In a row of controls the note goes under the whole row, not between a field and its button. */
form > .cx-invalid{order:99}
:where(input,textarea)[aria-invalid=true],
.xsel:has(select[aria-invalid=true]) .xsel-btn,
.xdate:has(input[aria-invalid=true]) .xdate-btn{border-color:var(--crit)}
.xfile:has(input[aria-invalid=true]) .xfile-btn{border-color:var(--crit)}

/* ---- Dates ---------------------------------------------------------------------
   An empty date box printed mm/dd/yyyy, the order of one country, in a workspace
   that writes 13 Sep 2026. The real input stays in the form and carries the value;
   this button shows it and the calendar sets it. */
.xdate{position:relative;display:block;min-width:0}
.xdate > input{position:absolute;width:1px;height:1px;padding:0;border:0;opacity:0;
  pointer-events:none;clip-path:inset(50%)}
.xdate-btn{min-width:min(150px,100%)}
.xdate-btn svg{transform:none!important}
.xdate-pop{padding:10px;width:288px;max-width:calc(100vw - 16px)}
.xdate-head{display:flex;align-items:center;gap:6px;margin-bottom:6px}
.xdate-head strong{flex:1;text-align:center;font-size:13.5px;font-weight:600}
.xdate-nav{width:32px;height:32px;flex:none;display:grid;place-items:center;border-radius:8px;
  border:1px solid var(--line);background:var(--panel-2);color:var(--ink-2);cursor:pointer}
.xdate-nav:hover{border-color:var(--cyan-ink);color:var(--ink)}
.xdate-grid{width:100%;border-collapse:collapse;table-layout:fixed}
.xdate-grid th{padding:4px 0;font-size:11px;font-weight:600;color:var(--muted);text-align:center}
.xdate-grid td{padding:1px;text-align:center}
.xdate-day{width:100%;height:34px;border:0;border-radius:8px;background:transparent;color:var(--ink);
  font:inherit;font-size:13px;font-variant-numeric:tabular-nums;cursor:pointer}
.xdate-day:hover:not(:disabled){background:color-mix(in oklab,var(--cyan) 14%,transparent)}
.xdate-day.out{color:var(--muted)}
.xdate-day.today{box-shadow:inset 0 0 0 1px var(--cyan-ink)}
.xdate-day[aria-pressed=true]{background:var(--cyan);color:var(--on-accent);font-weight:600}
.xdate-day:disabled{opacity:.35;cursor:not-allowed}
.xdate-day:focus-visible{outline:2px solid var(--focus);outline-offset:-2px}
.xdate-foot{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;
  margin-top:8px;padding-top:8px;border-top:1px solid var(--line-2)}
.xdate-at{display:flex;align-items:center;gap:8px;font-size:12px;color:var(--ink-2)}
.xdate-time{width:84px;padding:7px 10px}
.xdate-acts{display:flex;gap:6px;margin-inline-start:auto}
.xdate-acts .btn{padding:6px 12px;font-size:12.5px}
.xdate-foot .cx-invalid{margin:0}

/* ---- Files ---------------------------------------------------------------------
   Next to its button the browser writes "No file chosen". The input still covers the
   whole control, so a click, a keyboard press and a dropped file all reach it. */
.xfile{position:relative;display:inline-flex;align-items:center;gap:10px;max-width:100%;min-width:0}
.xfile > input[type=file]{position:absolute;inset:0;width:100%;height:100%;margin:0;padding:0;
  opacity:0;cursor:pointer;z-index:1}
.xfile-btn{pointer-events:none;flex:none}
.xfile-name{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:13px;color:var(--ink-2)}
.xfile-name.ph{color:var(--muted)}
.xfile:hover .xfile-btn,.xfile.over .xfile-btn{border-color:var(--cyan-ink);color:var(--ink)}
.xfile:focus-within .xfile-btn{outline:2px solid var(--focus);outline-offset:2px}

/* ---- Suggestions under a typed field -------------------------------------------
   A datalist opens the browser's own grey menu. The list is the dropdown's list. */
.xlist-pop .xsel-list li{display:block}
.xlist-pop .xsel-list li.here{background:color-mix(in oklab,var(--cyan) 22%,transparent)}

/* ---- Questions asked in the page -----------------------------------------------
   window.confirm and window.prompt draw a box that names the site rather than the
   platform, ignores the theme, and blocks every other tab of the same site. */
.ask-card{width:min(460px,100%)}
.ask-card .modal-body p.ask-msg{margin:0 0 12px;color:var(--ink-2);font-size:14px;line-height:1.5}
.ask-card .modal-body label{display:block;margin:0 0 6px;font-size:11px;font-weight:600;
  letter-spacing:.06em;text-transform:uppercase;color:var(--ink-2)}
.ask-card .modal-body input{width:100%}

/* ---- A reason beside its number or its button ------------------------------------
   These sat at whatever width the browser gives an input with no size, which cut the
   sentence that says what to write after four words. A seat count's cell is set in the
   numeric face, which is right for the count and wrong for the sentence beside it. */
.num form input:not([type=number]){font-family:Primus,'Archivo',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;letter-spacing:0}
td form > input[name=from]{flex:1 1 15rem;min-width:min(15rem,100%)}
form > input[name=because]{flex:1 1 18rem;min-width:min(16rem,100%)}
td > form:has(> input[name=value]){display:flex;flex-wrap:wrap;align-items:center;gap:6px}
td > form > input[name=value]{flex:1 1 11rem;min-width:min(11rem,100%)}
form:has(select[name=to_user_id]){width:min(320px,calc(100vw - 32px))}
.snoozer[open]{min-width:min(340px,calc(100vw - 32px))}

/* The note under a seat count's reason is a sentence too. */
.num .cx-invalid{font-family:Primus,'Archivo',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;letter-spacing:0}
