/* ===========================================================================
 * CloudTeam Design System — vendored token layer for the CloudLabs portal.
 *
 * Source of truth: the "CloudTeam Design System" claude.ai/design project.
 * Vendored via /design-sync. Keep in sync there — do not hand-edit token values
 * here except the clearly-marked portal-local override block at the bottom.
 *
 * Applied on <html>:  data-theme="ocean"  (product accent = ocean)
 *                     data-mode="dark|high-contrast" (display-mode axis)
 *
 * Fonts: Inter is self-hosted as a VARIABLE font (wght 100–900), subsetted to
 * latin + latin-ext (latin-ext covers Polish diacritics), from the DS fonts/
 * assets. The DS ships the variable file once and re-uses it per weight name;
 * we vendor it once per subset and declare a weight RANGE. system-ui remains
 * the fallback while the woff2 load (font-display: swap). Everything else
 * (color, type scale, spacing, shape) is the real DS token set.
 * =========================================================================== */

/* ---------------------------------------------------------------------------
 * Self-hosted Inter (variable, wght 100–900). One physical file per subset —
 * the browser renders 400 / 600 / 700 from the single variable face.
 * --------------------------------------------------------------------------- */
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(../fonts/inter-var-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url(../fonts/inter-var-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---------------------------------------------------------------------------
 * Accessibility fonts (tokens/fonts.css). The panel's Font axis selects these by
 * family name; the token layer only re-points --ct-font-body, so the @font-face
 * has to exist here. THE WOFF2 FILES ARE NOT YET VENDORED — copy them from the
 * DS project's fonts/ directory into wwwroot/fonts/ (10 files: lexend-{400,500,
 * 600,700}-latin{,-ext}, atkinson-{400,700}-latin{,-ext}, OpenDyslexic-{Regular,
 * Bold}). Until then a font request 404s and the stack falls back to Inter —
 * degraded, not broken.
 * --------------------------------------------------------------------------- */
@font-face {
  font-family: 'Lexend'; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url(../fonts/lexend-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Lexend'; font-style: normal; font-weight: 400 700; font-display: swap;
  src: url(../fonts/lexend-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Atkinson Hyperlegible'; font-style: normal; font-weight: 400; font-display: swap;
  src: url(../fonts/atkinson-400-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Atkinson Hyperlegible'; font-style: normal; font-weight: 400; font-display: swap;
  src: url(../fonts/atkinson-400-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Atkinson Hyperlegible'; font-style: normal; font-weight: 700; font-display: swap;
  src: url(../fonts/atkinson-700-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Atkinson Hyperlegible'; font-style: normal; font-weight: 700; font-display: swap;
  src: url(../fonts/atkinson-700-latin.woff2) format('woff2');
}
@font-face {
  font-family: 'OpenDyslexic'; font-style: normal; font-weight: 400; font-display: swap;
  src: url(../fonts/OpenDyslexic-Regular.woff2) format('woff2');
}
@font-face {
  font-family: 'OpenDyslexic'; font-style: normal; font-weight: 700; font-display: swap;
  src: url(../fonts/OpenDyslexic-Bold.woff2) format('woff2');
}

/* ---------------------------------------------------------------------------
 * spacing.css
 * --------------------------------------------------------------------------- */
:root {
  --ct-space-0:  0;
  --ct-space-1:  0.25rem;  /* 4px  */
  --ct-space-2:  0.5rem;   /* 8px  */
  --ct-space-3:  0.75rem;  /* 12px */
  --ct-space-4:  1rem;     /* 16px */
  --ct-space-5:  1.25rem;  /* 20px */
  --ct-space-6:  1.5rem;   /* 24px */
  --ct-space-8:  2rem;     /* 32px — page gutters */
  --ct-space-10: 2.5rem;   /* 40px */
  --ct-space-12: 3rem;     /* 48px */
  --ct-space-16: 4rem;     /* 64px */

  --ct-sidebar-width:           248px;
  --ct-sidebar-width-collapsed: 68px;
  --ct-topbar-height:           60px;
  /* Reading measure for page content. On a wide monitor the content column is
   * centred inside the main area rather than stretched edge to edge — beyond
   * roughly this width, table rows and form rows become tiring to track across. */
  --ct-content-max:             1400px;

  --ct-target-min: 44px;

  --ct-bp-sm:  40rem;
  --ct-bp-md:  48rem;
  --ct-bp-lg:  64rem;
  --ct-bp-xl:  80rem;
  --ct-bp-2xl: 96rem;
}

/* ---------------------------------------------------------------------------
 * radii.css
 * --------------------------------------------------------------------------- */
:root {
  --ct-radius-xs:   4px;
  --ct-radius-sm:   6px;
  --ct-radius-md:   10px;   /* buttons, inputs, selects */
  --ct-radius-lg:   14px;   /* cards */
  --ct-radius-xl:   18px;   /* modals */
  --ct-radius-pill: 999px;
}

/* ---------------------------------------------------------------------------
 * shadows.css
 * --------------------------------------------------------------------------- */
:root {
  --ct-shadow-xs: 0 1px 2px rgba(16, 24, 40, 0.05);
  --ct-shadow-sm: 0 1px 3px rgba(16, 24, 40, 0.10), 0 1px 2px rgba(16, 24, 40, 0.06);
  --ct-shadow-md: 0 4px 8px -2px rgba(16, 24, 40, 0.10), 0 2px 4px -2px rgba(16, 24, 40, 0.06);
  --ct-shadow-lg: 0 12px 16px -4px rgba(16, 24, 40, 0.08), 0 4px 6px -2px rgba(16, 24, 40, 0.03);
  --ct-shadow-xl: 0 20px 24px -4px rgba(16, 24, 40, 0.08), 0 8px 8px -4px rgba(16, 24, 40, 0.03);
  --ct-shadow-btn: 0 1px 2px rgba(16, 24, 40, 0.08);
}
[data-mode="dark"] {
  --ct-shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.30);
  --ct-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.40), 0 1px 2px rgba(0, 0, 0, 0.30);
  --ct-shadow-md: 0 4px 8px -2px rgba(0, 0, 0, 0.45), 0 2px 4px -2px rgba(0, 0, 0, 0.30);
  --ct-shadow-lg: 0 12px 16px -4px rgba(0, 0, 0, 0.50), 0 4px 6px -2px rgba(0, 0, 0, 0.30);
  --ct-shadow-xl: 0 20px 24px -4px rgba(0, 0, 0, 0.55), 0 8px 8px -4px rgba(0, 0, 0, 0.30);
  --ct-shadow-btn: 0 1px 2px rgba(0, 0, 0, 0.40);
}

/* ---------------------------------------------------------------------------
 * typography.css  (Inter self-hosted above; system-ui is the swap fallback)
 * --------------------------------------------------------------------------- */
:root {
  --ct-font-sans:
    var(--ct-font-body, "Inter"), system-ui, -apple-system, "Segoe UI", Roboto,
    Helvetica, Arial, sans-serif;
  --ct-font-mono:
    "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  --ct-text-xs:   0.75rem;
  --ct-text-sm:   0.875rem;
  --ct-text-base: 0.9375rem;
  --ct-text-md:   1rem;
  --ct-text-lg:   1.125rem;
  --ct-text-xl:   1.25rem;
  --ct-text-2xl:  1.5rem;
  --ct-text-3xl:  1.75rem;
  --ct-text-4xl:  2rem;

  --ct-weight-regular:  400;
  --ct-weight-medium:   500;
  --ct-weight-semibold: 600;
  --ct-weight-bold:     700;

  --ct-leading-tight:   1.2;
  --ct-leading-snug:    1.35;
  --ct-leading-normal:  1.5;
  --ct-leading-relaxed: 1.7;
  --ct-leading-loose:   2.0;

  --ct-text-scale: 1;
}
[data-text-size="large"] { --ct-text-scale: 1.125; }
[data-text-size="xl"]    { --ct-text-scale: 1.25; }
[data-text-size="xxl"]   { --ct-text-scale: 1.5; }
[data-font="lexend"]        { --ct-font-body: "Lexend"; }
[data-font="atkinson"]      { --ct-font-body: "Atkinson Hyperlegible"; }
[data-font="open-dyslexic"] { --ct-font-body: "OpenDyslexic"; }
[data-line-spacing="relaxed"] :is(p, li, blockquote, dd, dt, td, figcaption) { line-height: var(--ct-leading-relaxed); }
[data-line-spacing="loose"]   :is(p, li, blockquote, dd, dt, td, figcaption) { line-height: var(--ct-leading-loose); }

/* ---------------------------------------------------------------------------
 * colors.css  (shared neutrals + semantic hues + default indigo accent)
 * --------------------------------------------------------------------------- */
:root {
  --ct-neutral-0:   #FFFFFF;
  --ct-neutral-25:  #FCFCFC;
  --ct-neutral-50:  #FAFAFA;
  --ct-neutral-100: #F4F4F5;
  --ct-neutral-200: #E4E4E7;
  --ct-neutral-300: #D1D1D6;
  --ct-neutral-400: #A0A0AB;
  --ct-neutral-500: #70707B;
  --ct-neutral-600: #51525C;
  --ct-neutral-700: #3F3F46;
  --ct-neutral-800: #26272B;
  --ct-neutral-900: #18181B;
  --ct-neutral-950: #131316;

  --ct-success-500: #17B26A;  --ct-success-bg: #ECFDF3;  --ct-success-text: #067647;
  --ct-info-500:    #0BA5EC;  --ct-info-bg:    #F0F9FF;  --ct-info-text:    #026AA2;
  --ct-warning-500: #F79009;  --ct-warning-bg: #FFFAEB;  --ct-warning-text: #B54708;
  --ct-danger-500:  #F04438;  --ct-danger-bg:  #FEF3F2;  --ct-danger-text:  #B42318;
  --ct-purple-500:  #7A5AF8;  --ct-purple-bg:  #F4F3FF;  --ct-purple-text:  #5925DC;

  --ct-primary-50:  #EEF2FF;
  --ct-primary-100: #E0E7FF;
  --ct-primary-200: #C7D2FE;
  --ct-primary-500: #6366F1;
  --ct-primary-600: #4F46E5;
  --ct-primary-700: #4338CA;
  --ct-primary-800: #3730A3;
  --ct-primary: var(--ct-primary-500);
  --ct-primary-hover: var(--ct-primary-600);
  --ct-primary-press: var(--ct-primary-700);
  --ct-primary-soft:   var(--ct-primary-50);
  --ct-primary-soft-2: var(--ct-primary-100);
  --ct-primary-action:        #4F46E5;
  --ct-primary-action-hover:  #4338CA;
  --ct-primary-action-press:  #3730A3;
  --ct-primary-text:          #4F46E5;
  --ct-on-primary:            #FFFFFF;

  --ct-secondary-50:  #F0F4F8;
  --ct-secondary-100: #E2E8F0;
  --ct-secondary-200: #CBD5E1;
  --ct-secondary-500: #64748B;
  --ct-secondary-600: #475569;
  --ct-secondary-700: #334155;
  --ct-secondary: var(--ct-secondary-500);
  --ct-secondary-hover: var(--ct-secondary-600);
  --ct-secondary-press: var(--ct-secondary-700);
  --ct-secondary-soft:   var(--ct-secondary-50);
  --ct-secondary-soft-2: var(--ct-secondary-100);
  --ct-secondary-action:        #475569;
  --ct-secondary-action-hover:  #334155;
  --ct-secondary-action-press:  #1E293B;
  --ct-secondary-text:          #334155;
  --ct-on-secondary:            #FFFFFF;

  --ct-bg-app:         var(--ct-neutral-50);
  --ct-surface:        var(--ct-neutral-0);
  --ct-surface-sunken: var(--ct-neutral-50);
  --ct-surface-hover:  var(--ct-neutral-100);

  --ct-text-strong: var(--ct-neutral-900);
  --ct-text-body:   var(--ct-neutral-700);
  --ct-text-muted:  var(--ct-neutral-600);
  --ct-text-subtle: var(--ct-neutral-500);
  --ct-text-link:   var(--ct-primary-text);

  --ct-border:        var(--ct-neutral-200);
  --ct-border-strong: var(--ct-neutral-300);
  --ct-border-focus:  var(--ct-primary);

  --ct-ring:        0 0 0 2px var(--ct-surface), 0 0 0 4px var(--ct-primary-action);
  --ct-ring-danger: 0 0 0 2px var(--ct-surface), 0 0 0 4px var(--ct-danger-500);
}

/* ---------------------------------------------------------------------------
 * themes.css  (per-product accent — ocean is applied via <html data-theme="ocean">)
 * --------------------------------------------------------------------------- */
[data-theme="indigo"] {
  --ct-primary-50:  #EEF2FF;  --ct-primary-100: #E0E7FF;  --ct-primary-200: #C7D2FE;
  --ct-primary-500: #6366F1;  --ct-primary-600: #4F46E5;  --ct-primary-700: #4338CA;  --ct-primary-800: #3730A3;
  --ct-primary: var(--ct-primary-500);
  --ct-primary-hover: var(--ct-primary-600);
  --ct-primary-press: var(--ct-primary-700);
  --ct-primary-soft: var(--ct-primary-50);  --ct-primary-soft-2: var(--ct-primary-100);
  --ct-primary-action: #4F46E5;  --ct-primary-action-hover: #4338CA;  --ct-primary-action-press: #3730A3;
  --ct-primary-text: #4F46E5;
}
[data-theme="coral"] {
  --ct-primary-50:  #FDF2EF;  --ct-primary-100: #FAE6E0;  --ct-primary-200: #F4C9BD;
  --ct-primary-500: #E0613D;  --ct-primary-600: #B55034;  --ct-primary-700: #8F412D;  --ct-primary-800: #6E3122;
  --ct-primary: var(--ct-primary-500);
  --ct-primary-hover: var(--ct-primary-600);
  --ct-primary-press: var(--ct-primary-700);
  --ct-primary-soft: var(--ct-primary-50);  --ct-primary-soft-2: var(--ct-primary-100);
  --ct-primary-action: #B55034;  --ct-primary-action-hover: #96442E;  --ct-primary-action-press: #7B3928;
  --ct-primary-text: #B55034;
}
[data-theme="teal"] {
  --ct-primary-50:  #ECF7F6;  --ct-primary-100: #D9EFEE;  --ct-primary-200: #AFDDDA;
  --ct-primary-500: #149B93;  --ct-primary-600: #127F79;  --ct-primary-700: #116562;  --ct-primary-800: #0F4F4D;
  --ct-primary: var(--ct-primary-500);
  --ct-primary-hover: var(--ct-primary-600);
  --ct-primary-press: var(--ct-primary-700);
  --ct-primary-soft: var(--ct-primary-50);  --ct-primary-soft-2: var(--ct-primary-100);
  --ct-primary-action: #127F79;  --ct-primary-action-hover: #116A66;  --ct-primary-action-press: #105856;
  --ct-primary-text: #127F79;
}
[data-theme="ocean"] {
  --ct-primary-50:  #EEF5FA;  --ct-primary-100: #DDEBF5;  --ct-primary-200: #B7D3E9;
  --ct-primary-500: #2A7FBF;  --ct-primary-600: #24689C;  --ct-primary-700: #1E547D;  --ct-primary-800: #184363;
  --ct-primary: var(--ct-primary-500);
  --ct-primary-hover: var(--ct-primary-600);
  --ct-primary-press: var(--ct-primary-700);
  --ct-primary-soft: var(--ct-primary-50);  --ct-primary-soft-2: var(--ct-primary-100);
  --ct-primary-action: #24689C;  --ct-primary-action-hover: #205883;  --ct-primary-action-press: #1C496D;
  --ct-primary-text: #24689C;
}

/* ---------------------------------------------------------------------------
 * modes.css  (display-mode axis: dark / high-contrast — composes with themes)
 * --------------------------------------------------------------------------- */
[data-mode="dark"] {
  color-scheme: dark;

  --ct-neutral-0:   #0B1120;
  --ct-neutral-25:  #0E1526;
  --ct-neutral-50:  #101828;
  --ct-neutral-100: #1C2740;
  --ct-neutral-200: #2A3654;
  --ct-neutral-300: #3A4970;
  --ct-neutral-400: #64729A;
  --ct-neutral-500: #8B97B8;
  --ct-neutral-600: #A9B4CE;
  --ct-neutral-700: #C6CEE0;
  --ct-neutral-800: #DEE3EF;
  --ct-neutral-900: #EFF2F8;
  --ct-neutral-950: #F8FAFD;

  --ct-bg-app:         #0B1120;
  --ct-surface:        #101828;
  --ct-surface-sunken: #0B1120;
  --ct-surface-hover:  #1C2740;

  --ct-text-strong: var(--ct-neutral-900);
  --ct-text-body:   var(--ct-neutral-700);
  --ct-text-muted:  var(--ct-neutral-600);
  --ct-text-subtle: var(--ct-neutral-500);

  --ct-border:        var(--ct-neutral-200);
  --ct-border-strong: var(--ct-neutral-300);

  /* default (indigo) dark accent — the ocean-in-dark override below re-points this */
  --ct-primary-500: #818CF8;
  --ct-primary: #818CF8;
  --ct-primary-text: #A5B4FC;
  --ct-primary-soft:   rgba(99, 102, 241, 0.16);
  --ct-primary-soft-2: rgba(99, 102, 241, 0.24);
  --ct-primary-action: #4F46E5;
  --ct-primary-action-hover: #6366F1;
  --ct-primary-action-press: #4338CA;

  --ct-success-bg: rgba(23, 178, 106, 0.16);  --ct-success-text: #75E0A7;
  --ct-info-bg:    rgba(11, 165, 236, 0.16);  --ct-info-text:    #7CD4FD;
  --ct-warning-bg: rgba(247, 144, 9, 0.16);   --ct-warning-text: #FEC84B;
  --ct-danger-bg:  rgba(240, 68, 56, 0.16);   --ct-danger-text:  #FDA29B;
  --ct-purple-bg:  rgba(122, 90, 248, 0.16);  --ct-purple-text:  #C3B5FD;

  --ct-ring: 0 0 0 2px var(--ct-surface), 0 0 0 4px var(--ct-primary-500);
}
[data-mode="high-contrast"] {
  color-scheme: dark;

  --ct-neutral-0:   #000000;
  --ct-neutral-25:  #0A0A0A;
  --ct-neutral-50:  #000000;
  --ct-neutral-100: #1A1A1A;
  --ct-neutral-200: #FFFFFF;
  --ct-neutral-300: #FFFFFF;
  --ct-neutral-400: #E6E6E6;
  --ct-neutral-500: #F2F2F2;
  --ct-neutral-600: #FFFFFF;
  --ct-neutral-700: #FFFFFF;
  --ct-neutral-800: #FFFFFF;
  --ct-neutral-900: #FFFFFF;
  --ct-neutral-950: #FFFFFF;

  --ct-bg-app:         #000000;
  --ct-surface:        #000000;
  --ct-surface-sunken: #0A0A0A;
  --ct-surface-hover:  #1A1A1A;

  --ct-text-strong: #FFFFFF;
  --ct-text-body:   #FFFFFF;
  --ct-text-muted:  #FFFFFF;
  --ct-text-subtle: #E6E6E6;

  --ct-border:        #FFFFFF;
  --ct-border-strong: #FFFFFF;

  --ct-primary: #A5B4FC;
  --ct-primary-500: #A5B4FC;
  --ct-primary-text: #C7D2FE;
  --ct-primary-soft:   #1A1A1A;
  --ct-primary-soft-2: #262626;
  --ct-primary-action: #4F46E5;
  --ct-primary-action-hover: #6366F1;
  --ct-primary-action-press: #4338CA;

  --ct-success-text: #7DEBB0;  --ct-info-text: #8FD9FF;  --ct-warning-text: #FFD24D;
  --ct-danger-text:  #FFB4AC;  --ct-purple-text: #CBBEFF;
  --ct-success-bg: #0A0A0A; --ct-info-bg: #0A0A0A; --ct-warning-bg: #0A0A0A;
  --ct-danger-bg: #0A0A0A;  --ct-purple-bg: #0A0A0A;

  --ct-ring: 0 0 0 2px #000000, 0 0 0 5px #FFFFFF;
}

/* ---------------------------------------------------------------------------
 * Portal-local: ocean accent in dark mode.
 * modes.css hardcodes the indigo dark accent for every theme, so for the ocean
 * product we re-point the dark accent to ocean hues, lifted for legibility on
 * dark surfaces (same technique the DS uses for indigo). Higher specificity
 * ([data-mode][data-theme] = 0,2,0) wins over modes.css ([data-mode] = 0,1,0).
 * --------------------------------------------------------------------------- */
[data-mode="dark"][data-theme="ocean"] {
  --ct-primary-500:          #5AA9E6;
  --ct-primary:              #5AA9E6;
  --ct-primary-text:         #8FC7EE;   /* light blue — AA on dark surface */
  --ct-primary-soft:         rgba(42, 127, 191, 0.16);
  --ct-primary-soft-2:       rgba(42, 127, 191, 0.24);
  --ct-primary-action:       #24689C;   /* keeps white text AA */
  --ct-primary-action-hover: #2A7FBF;
  --ct-primary-action-press: #1E547D;
  --ct-ring: 0 0 0 2px var(--ct-surface), 0 0 0 4px var(--ct-primary-500);
}

/* ---------------------------------------------------------------------------
 * base.css  (minimal resets + a11y primitives — reference tokens above)
 * --------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

/* The `hidden` attribute must actually hide.
 *
 * The UA rule is only `[hidden] { display: none }`, which ANY component class
 * setting `display` silently defeats — and most of ours do, being flex or grid.
 * The result is an element the author marked hidden that still occupies space:
 * a conditionally-rendered "clear filters" button, for instance, shunting the
 * last filter off the end of a toolbar.
 *
 * This is the case `!important` exists for: `hidden` is the author saying "not
 * now", and no component's layout should outrank it. Use `.ct-sr-only` when you
 * want something present but only for screen readers — that is a different
 * intent, and it is not affected by this rule. */
[hidden] { display: none !important; }

html {
  font-size: calc(100% * var(--ct-text-scale, 1));
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ct-surface);
  color: var(--ct-text-body);
  font-family: var(--ct-font-sans);
  font-size: var(--ct-text-base);
  line-height: var(--ct-leading-normal);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* One shared, always-visible keyboard focus indicator (WCAG 2.4.7).
 * Components override with box-shadow rings; this is the catch-all fallback. */
:focus-visible {
  outline: 2px solid var(--ct-primary-action);
  outline-offset: 2px;
}
/* Don't show the ring for mouse users on elements that manage their own. */
:focus:not(:focus-visible) { outline: none; }

/* Links */
a { color: var(--ct-text-link); text-underline-offset: 0.15em; }

.ct-sr-only {
  position: absolute !important;
  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;
}
.ct-sr-only--focusable:focus,
.ct-sr-only--focusable:focus-visible {
  position: static !important;
  width: auto; height: auto;
  margin: 0; overflow: visible; clip: auto; clip-path: none; white-space: normal;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}
[data-reduced-motion="reduce"] *,
[data-reduced-motion="reduce"] *::before,
[data-reduced-motion="reduce"] *::after {
  animation-duration: 0.01ms !important;
  animation-iteration-count: 1 !important;
  transition-duration: 0.01ms !important;
  scroll-behavior: auto !important;
}


/* ---------------------------------------------------------------------------
 * tokens/motion.css  (needed by the layout components below)
 * --------------------------------------------------------------------------- */
:root {
  --ct-dur-fast:   120ms;
  --ct-dur-normal: 180ms;
  --ct-dur-slow:   260ms;

  --ct-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --ct-ease-in-out: cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes ct-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes ct-rise-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }
@keyframes ct-scale-in { from { opacity: 0; transform: scale(0.98); } to { opacity: 1; transform: scale(1); } }

/* ===========================================================================
 * DS FRAME COMPONENTS — re-synced from the design system (2026-07-29):
 *   components/layout/{app-shell,sidebar,topbar,footer,account}.css
 *   components/core/avatar.css   (composed by the account block)
 *
 * All VERBATIM. Two things changed upstream since the first vendoring and are
 * now inherited rather than patched here:
 *   - app-shell grid tracks use minmax(0, …) on BOTH axes (my local fix landed
 *     upstream, with the rows fixed too)
 *   - sidebar gained .ct-sidebar__footer, the bottom region for the a11y
 *     trigger + account block
 * Portal-level overrides live in the DEVIATIONS block after the page components
 * — never edit a rule above it.
 *
 * 2026-08-01 audit — every rule of app-shell, sidebar, topbar, footer, account,
 * button, input, table and card was compared declaration-by-declaration against
 * the DS. Seven of the nine were byte-identical; app-shell was not, and the two
 * defects it had are the pattern to watch for:
 *
 *   - HALF A COMPONENT. Only the rules the portal happened to need were copied.
 *     .ct-app-shell--no-topbar (the modifier the operator layout actually sets),
 *     --collapsed, .ct-skip-link, the whole below-lg media block and the
 *     .ct-drawer family were all absent. The shell therefore kept a 60px
 *     grid row reserved for a topbar it does not render, and the rail never
 *     narrowed on a phone even though sidebar.css was busy hiding its labels.
 *   - A CLOBBERED SELECTOR. Re-vendoring footer.css overwrote the app-shell's
 *     one-liner `.ct-app-shell__content > .ct-footer { margin-top: auto; }`,
 *     leaving that selector on the footer's own base rule (so the footer stopped
 *     being pushed down, AND the auth screens' footer — which is not inside an
 *     app shell — lost its base styling entirely) plus one stray `}`.
 *
 * A class-level check does not catch either: every class involved was mentioned
 * *somewhere*. Compare RULES, and check the brace balance after every paste.
 * =========================================================================== */

/* ---------------------------------------------------------------------------
 * app-shell.css
 * --------------------------------------------------------------------------- */
.ct-app-shell {
  display: grid;
  /* minmax(0, …) not plain 1fr: a bare `1fr` is `minmax(auto, 1fr)`, whose auto
   * minimum is content-based, so a wide child (a nowrap-header table) could
   * floor the track at its min-content width. */
  grid-template-columns: var(--ct-sidebar-width) minmax(0, 1fr);
  grid-template-rows: var(--ct-topbar-height) minmax(0, 1fr);
  grid-template-areas:
    "topbar  topbar"
    "sidebar main";
  height: 100vh;
  height: 100dvh;
  background: var(--ct-bg-app);
  color: var(--ct-text-body);
  overflow: hidden;                 /* only .ct-app-shell__main scrolls */
}

.ct-app-shell > .ct-topbar  { grid-area: topbar; }
.ct-app-shell > .ct-sidebar { grid-area: sidebar; min-height: 0; }

.ct-app-shell__main {
  grid-area: main;
  min-width: 0;                     /* let content shrink, don't blow out grid */
  min-height: 0;
  overflow-y: auto;
  padding: var(--ct-space-8);
  /* Flex column so __content can claim the full height and push the footer
   * down. min-height:100% would add the padding on top of a full viewport and
   * force a scrollbar on every page. */
  display: flex;
  flex-direction: column;
}
.ct-app-shell__main:focus { outline: none; }   /* programmatic skip-link target */

/* Centred content measure: on a wide monitor the area left of the sidebar is
 * NOT filled edge to edge — the column caps at --ct-content-max and centres. */
.ct-app-shell__content {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: var(--ct-content-max);
  margin-inline: auto;
}

/* The page itself. Grows so a short page still pushes the footer to the bottom
 * of the viewport; a long page lets the footer scroll off like any block. */
.ct-app-shell__body { flex: 1 1 auto; min-width: 0; }

/* Footer sits at the end of the measure, never pinned to the viewport. */
.ct-app-shell__content > .ct-footer { margin-top: auto; }

/* Icon-rail variant: narrows the sidebar column to match .ct-sidebar--collapsed. */
.ct-app-shell--collapsed { grid-template-columns: var(--ct-sidebar-width-collapsed) minmax(0, 1fr); }

/* ===================== Sidebar-only shell (no topbar) =====================
 * The default frame: the sidebar runs the FULL height and owns the brand, the
 * collapse toggle and the account block; there is no horizontal header at all.
 * Page identity comes from the <h1> in the content, not from chrome, so nothing
 * is duplicated between a topbar and the page.
 *
 * Add a .ct-topbar only for a persistent global control — app-wide search, an
 * org switcher — that cannot live in the rail or the page. */
.ct-app-shell--no-topbar {
  grid-template-rows: minmax(0, 1fr);
  grid-template-areas: "sidebar main";
}

/* Skip link (WCAG 2.4.1). Uses the shared .ct-sr-only--focusable behaviour but
 * anchors itself visibly at the top-left when focused. */
.ct-skip-link:focus,
.ct-skip-link:focus-visible {
  position: fixed; top: var(--ct-space-3); left: var(--ct-space-3); z-index: 60;
  padding: var(--ct-space-2) var(--ct-space-4);
  background: var(--ct-surface); color: var(--ct-text-link);
  border: 1px solid var(--ct-border-strong); border-radius: var(--ct-radius-md);
  box-shadow: var(--ct-shadow-md); font-weight: var(--ct-weight-semibold);
}

/* ===================== Responsive: sidebar → drawer ===================== */
/* max-width: 63.999rem === below --ct-bp-lg (64rem). */
@media (max-width: 63.999rem) {
  .ct-app-shell,
  .ct-app-shell--collapsed {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "topbar" "main";
  }
  /* The in-grid sidebar is hidden; the drawer copy takes over. */
  .ct-app-shell > .ct-sidebar { display: none; }
  .ct-app-shell__main { padding: var(--ct-space-5); }

  /* Without a topbar there is nowhere for a drawer trigger to live, so the
   * rail STAYS in the grid as icons instead of hiding navigation behind a
   * button with no home. */
  .ct-app-shell--no-topbar {
    grid-template-columns: var(--ct-sidebar-width-collapsed) minmax(0, 1fr);
    grid-template-areas: "sidebar main";
  }
  .ct-app-shell--no-topbar > .ct-sidebar {
    display: flex;
    width: var(--ct-sidebar-width-collapsed);
  }
}

/* The drawer host: a native <dialog> docked to the inline-start edge, full
 * height, sliding in from the side. Only relevant below `lg`. */
.ct-drawer {
  margin: 0;
  height: 100vh; height: 100dvh;
  max-height: 100vh; max-height: 100dvh;
  width: min(var(--ct-sidebar-width), 86vw);
  padding: 0;
  border: 0;
  border-inline-end: 1px solid var(--ct-border);
  background: var(--ct-surface);
  color: var(--ct-text-body);
  box-shadow: var(--ct-shadow-xl);
  /* dock to the inline-start edge (respects dir="rtl") */
  inset-block: 0;
  inset-inline: 0 auto;
}
.ct-drawer::backdrop { background: rgba(16, 24, 40, 0.55); }
.ct-drawer[open] { animation: ct-drawer-in var(--ct-dur-normal) var(--ct-ease-out); }
.ct-drawer > .ct-sidebar { height: 100%; border-inline-end: 0; }

/* On `lg` and up the drawer is never shown even if left in the DOM open. */
@media (min-width: 64rem) {
  .ct-drawer, .ct-topbar__menu { display: none; }
}

@keyframes ct-drawer-in {
  from { opacity: 0; transform: translateX(-8px); }
  to   { opacity: 1; transform: translateX(0); }
}
[dir="rtl"] .ct-drawer[open] { animation-name: ct-drawer-in-rtl; }
@keyframes ct-drawer-in-rtl {
  from { opacity: 0; transform: translateX(8px); }
  to   { opacity: 1; transform: translateX(0); }
}

/* ---------------------------------------------------------------------------
 * footer.css
 * --------------------------------------------------------------------------- */
.ct-footer {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--ct-space-3) var(--ct-space-6);
  padding: var(--ct-space-4) 0;
  border-top: 1px solid var(--ct-border);
  /* No background of its own. The footer is the end of the page, not a panel —
   * a surface fill makes it read as a separate block floating on the canvas.
   * The border-top is the only separation it needs. */
  background: none;
  color: var(--ct-text-muted);
  font-size: var(--ct-text-xs);
  min-inline-size: 0;
}

.ct-footer__main { display: flex; flex-direction: column; gap: var(--ct-space-1); min-inline-size: 0; }

/* The single row. Everything informational lives here, in order. */
.ct-footer__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ct-space-1) var(--ct-space-2);
  margin: 0;
}

/* Separator dots, drawn rather than typed.
 *
 * `content: ""` with a background, NOT content: "•" — generated text is read
 * aloud by most screen readers, and four "bullet"s between four links is pure
 * noise. `display: inline-block` also stops the parent link's underline from
 * running through the dot on hover. */
.ct-footer__row > * + *::before {
  content: "";
  display: inline-block;
  inline-size: 3px;
  block-size: 3px;
  margin-inline-end: var(--ct-space-2);
  vertical-align: middle;
  border-radius: 50%;
  background: currentColor;
  opacity: 0.45;
}

/* Product name © year vendor. First, because it says whose product this is. */
.ct-footer__brand { color: var(--ct-text-muted); }
.ct-footer__brand a { font-weight: var(--ct-weight-medium); }

.ct-footer__row a {
  color: var(--ct-text-muted);
  text-decoration: none;
  border-radius: var(--ct-radius-sm);
}
.ct-footer__row a:hover { color: var(--ct-text-link); text-decoration: underline; }
.ct-footer__row a:focus-visible { outline: none; box-shadow: var(--ct-ring); }

/* LAST in the row, not a block of its own. Support asks for it by name, so it
 * has to be selectable text and exact — never an abbreviation, never a tooltip. */
.ct-footer__version {
  color: var(--ct-text-subtle);
  font-variant-numeric: tabular-nums;
}

/* Optional second line — a strapline, a legal notice. Never a link target. */
.ct-footer__tagline {
  margin: 0;
  color: var(--ct-text-subtle);
  font-style: italic;
}

/* A control, not information — so it sits apart, at the far edge. */
.ct-footer__lang { margin-inline-start: auto; }

/* Centred variant, for the auth screens: there is no content column to align
 * to, so the footer centres under the form instead of hugging the start edge. */
.ct-footer--center { justify-content: center; text-align: center; }
.ct-footer--center .ct-footer__row { justify-content: center; }
.ct-footer--center .ct-footer__main { align-items: center; }
.ct-footer--center .ct-footer__lang { margin-inline-start: 0; }

/* Stack on narrow screens; drop the auto-margin so items read top-to-bottom. */
@media (max-width: 47.999rem) {
  .ct-footer { flex-direction: column; align-items: flex-start; }
  .ct-footer__lang { margin-inline-start: 0; }
  .ct-footer--center { align-items: center; }
}

@media print { .ct-footer { display: none; } }


/* ---------------------------------------------------------------------------
 * layout/topbar.css  (sticky application header — brand, search, actions)
 * The operator shell has no topbar; this is the signed-out/public header.
 * --------------------------------------------------------------------------- */
.ct-topbar {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; gap: var(--ct-space-3);
  height: var(--ct-topbar-height);
  padding-inline: var(--ct-space-4);
  background: var(--ct-surface);
  border-bottom: 1px solid var(--ct-border);
}

.ct-topbar__menu { flex: none; }        /* hidden ≥ lg by app-shell.css */

.ct-topbar__brand {
  display: inline-flex; align-items: center; gap: var(--ct-space-2);
  flex: none; text-decoration: none;
  font-size: var(--ct-text-md); font-weight: var(--ct-weight-semibold);
  color: var(--ct-text-strong);
}
.ct-topbar__brand-mark {
  width: 28px; height: 28px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--ct-radius-sm);
  background: var(--ct-primary); color: var(--ct-on-primary);
}
.ct-topbar__brand-mark svg { width: 18px; height: 18px; }

/* Centred search — grows to fill, capped so it stays a "search box", not a bar. */
.ct-topbar__search {
  flex: 1 1 auto; display: flex; justify-content: center;
  min-width: 0;
}
.ct-topbar__search-field {
  position: relative; width: 100%; max-width: 420px;
}
.ct-topbar__search-field > svg {
  position: absolute; inset-inline-start: var(--ct-space-3); top: 50%;
  transform: translateY(-50%);
  width: 18px; height: 18px; color: var(--ct-text-subtle); pointer-events: none;
}
.ct-topbar__search-input {
  width: 100%; height: 38px;
  padding-block: 0;
  padding-inline: calc(var(--ct-space-3) + 18px + var(--ct-space-2)) var(--ct-space-3);
  font: inherit; font-size: var(--ct-text-sm);
  color: var(--ct-text-body);
  background: var(--ct-surface-sunken);
  border: 1px solid var(--ct-border);
  border-radius: var(--ct-radius-md);
}
.ct-topbar__search-input::placeholder { color: var(--ct-text-subtle); }
.ct-topbar__search-input:focus-visible {
  outline: none; box-shadow: var(--ct-ring);
  border-color: var(--ct-border-focus); background: var(--ct-surface);
}

.ct-topbar__actions {
  flex: none; display: inline-flex; align-items: center; gap: var(--ct-space-2);
}

/* A notification action button with an unread count bubble. */
.ct-topbar__badge-btn { position: relative; }
.ct-topbar__badge-btn .ct-topbar__badge {
  position: absolute; top: 4px; inset-inline-end: 4px;
  min-width: 16px; height: 16px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--ct-radius-pill);
  background: var(--ct-danger-500); color: #fff;
  border: 2px solid var(--ct-surface);
  font-size: 10px; font-weight: var(--ct-weight-bold); line-height: 1;
}

/* An avatar chip for the account menu trigger. */
.ct-topbar__avatar {
  width: 32px; height: 32px; flex: none; border-radius: var(--ct-radius-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--ct-primary-soft); color: var(--ct-primary-text);
  font-size: var(--ct-text-xs); font-weight: var(--ct-weight-semibold);
}

/* Below `md`, drop the search into an icon action to save room. */
@media (max-width: 47.999rem) {
  .ct-topbar__search { display: none; }
}

/* ---------------------------------------------------------------------------
 * layout/sidebar.css  (primary vertical navigation)
 * The active destination is marked with aria-current="page" and gets a soft
 * fill, accent label text AND an inline-start bar — never colour alone (1.4.1).
 * --------------------------------------------------------------------------- */
.ct-sidebar {
  display: flex; flex-direction: column;
  width: var(--ct-sidebar-width);
  background: var(--ct-surface);
  border-inline-end: 1px solid var(--ct-border);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Brand block: the organisation the user is signed in to, at the head of the
 * rail, plus the collapse toggle. In a sidebar-only shell (no topbar) this is
 * where product/org identity lives — there is no header to put it in. */
.ct-sidebar__brand {
  display: flex; align-items: center; gap: var(--ct-space-3);
  flex: none;
  padding: var(--ct-space-4) var(--ct-space-3);
  color: var(--ct-text-strong);
}
.ct-sidebar__brand-mark {
  flex: none; width: 28px; height: 28px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--ct-radius-sm);
  overflow: hidden;
}
.ct-sidebar__brand-mark img,
.ct-sidebar__brand-mark svg { width: 100%; height: 100%; object-fit: contain; }

/* Two lines: the org name, then what it is. Both clamp — an org name is user
 * data and can be long, and the rail is a fixed width. */
.ct-sidebar__brand-text { min-width: 0; display: flex; flex-direction: column; }
.ct-sidebar__brand-name,
.ct-sidebar__brand-org {
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2;
  line-clamp: 2; overflow: hidden; overflow-wrap: anywhere;
}
.ct-sidebar__brand-name {
  font-size: var(--ct-text-sm); font-weight: var(--ct-weight-semibold);
  line-height: var(--ct-leading-snug);
}
.ct-sidebar__brand-org {
  font-size: var(--ct-text-xs); color: var(--ct-text-muted);
  line-height: var(--ct-leading-snug);
}

.ct-sidebar__toggle {
  flex: none; margin-inline-start: auto;
  width: var(--ct-target-min); height: var(--ct-target-min);
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--ct-radius-md);
  background: none; color: var(--ct-text-muted); cursor: pointer;
  transition: background var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-sidebar__toggle:hover { background: var(--ct-surface-hover); color: var(--ct-text-body); }
.ct-sidebar__toggle:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-sidebar__toggle > svg { width: 20px; height: 20px; }

/* Nav region. A FLAT list is the default — see the prompt. */
.ct-sidebar__section { padding: var(--ct-space-2) var(--ct-space-3); }
.ct-sidebar__section + .ct-sidebar__section { border-top: 1px solid var(--ct-border); }

/* Bottom region: user-level controls that are not navigation — the
 * accessibility trigger and the account block, in that order. */
.ct-sidebar__footer {
  margin-top: auto;
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--ct-space-1);
  padding: var(--ct-space-3);
  border-top: 1px solid var(--ct-border);
}
.ct-sidebar--collapsed .ct-sidebar__footer {
  align-items: center;
  padding-inline: var(--ct-space-2);
}

.ct-sidebar__heading {
  margin: var(--ct-space-2) var(--ct-space-3) var(--ct-space-1);
  font-size: var(--ct-text-xs); font-weight: var(--ct-weight-semibold);
  text-transform: uppercase; letter-spacing: 0.05em;
  color: var(--ct-text-subtle);
}

.ct-sidebar__nav { list-style: none; margin: 0; padding: var(--ct-space-2) var(--ct-space-3); }
.ct-sidebar__section > .ct-sidebar__nav { padding: 0; }
.ct-sidebar__nav li + li { margin-top: 2px; }

/* Works as an <a> (a destination) AND as a <button> (opens a pane). Without the
 * button reset a <button> here renders with UA chrome. */
.ct-sidebar__item {
  appearance: none; -webkit-appearance: none;
  inline-size: 100%;
  border: 0;
  background: none;
  font-family: inherit;
  text-align: start;
  cursor: pointer;
  display: flex; align-items: center; gap: var(--ct-space-3);
  min-height: var(--ct-target-min);       /* 44px target (WCAG 2.5.8) */
  padding: var(--ct-space-2) var(--ct-space-3);
  border-radius: var(--ct-radius-md);
  color: var(--ct-text-body); text-decoration: none;
  font-size: var(--ct-text-sm); font-weight: var(--ct-weight-medium);
  position: relative;
  transition: background var(--ct-dur-fast) var(--ct-ease-out),
              color var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-sidebar__item:hover { background: var(--ct-surface-hover); }
.ct-sidebar__item:focus-visible { outline: none; box-shadow: var(--ct-ring); }

.ct-sidebar__icon { width: 20px; height: 20px; flex: none; color: var(--ct-text-muted); }
.ct-sidebar__label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ct-sidebar__count {
  flex: none; min-width: 20px; height: 20px; padding: 0 6px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--ct-radius-pill);
  background: var(--ct-neutral-100); color: var(--ct-text-muted);
  font-size: var(--ct-text-xs); font-weight: var(--ct-weight-semibold);
}

/* Active destination — colour + accent bar + weight, never colour alone. */
.ct-sidebar__item[aria-current="page"] {
  background: var(--ct-primary-soft);
  color: var(--ct-primary-text);
  font-weight: var(--ct-weight-semibold);
}
.ct-sidebar__item[aria-current="page"] .ct-sidebar__icon { color: var(--ct-primary-text); }
.ct-sidebar__item[aria-current="page"] .ct-sidebar__count {
  background: var(--ct-primary-soft-2); color: var(--ct-primary-text);
}
.ct-sidebar__item[aria-current="page"]::before {
  content: ""; position: absolute; inset-inline-start: 0; inset-block: 8px;
  width: 3px; border-radius: var(--ct-radius-pill); background: var(--ct-primary);
}

/* ===================== Collapsed icon-rail ===================== */
.ct-sidebar--collapsed { width: var(--ct-sidebar-width-collapsed); }
.ct-sidebar--collapsed .ct-sidebar__heading,
.ct-sidebar--collapsed .ct-sidebar__label,
.ct-sidebar--collapsed .ct-sidebar__count { display: none; }
.ct-sidebar--collapsed .ct-sidebar__item { justify-content: center; padding-inline: 0; gap: 0; }
.ct-sidebar--collapsed .ct-sidebar__nav { padding-inline: var(--ct-space-2); }

.ct-sidebar--collapsed .ct-sidebar__brand-mark,
.ct-sidebar--collapsed .ct-sidebar__brand-text { display: none; }
.ct-sidebar--collapsed .ct-sidebar__brand { justify-content: center; padding-inline: var(--ct-space-2); }
.ct-sidebar--collapsed .ct-sidebar__toggle { margin-inline-start: 0; }

.ct-sidebar--collapsed .ct-a11y-trigger {
  width: var(--ct-target-min); justify-content: center; padding: 0; gap: 0;
}
.ct-sidebar--collapsed .ct-a11y-trigger > span { display: none; }
.ct-sidebar--collapsed .ct-account { flex-direction: column; gap: var(--ct-space-2); }
.ct-sidebar--collapsed .ct-account__identity { display: none; }
.ct-sidebar--collapsed .ct-account__link { flex: none; justify-content: center; padding: 0; }

/* Below `lg` in a sidebar-only shell the rail is forced to icons, since there
 * is no topbar to hold a drawer trigger (see app-shell.css). */
@media (max-width: 63.999rem) {
  .ct-app-shell--no-topbar > .ct-sidebar :is(.ct-sidebar__heading,
                                             .ct-sidebar__label,
                                             .ct-sidebar__count,
                                             .ct-sidebar__brand-mark,
                                             .ct-sidebar__brand-text,
                                             .ct-account__identity) { display: none; }
  .ct-app-shell--no-topbar > .ct-sidebar .ct-a11y-trigger > span { display: none; }
  .ct-app-shell--no-topbar > .ct-sidebar :is(.ct-sidebar__item, .ct-account__link) {
    justify-content: center; padding-inline: 0; gap: 0; flex: none;
  }
  .ct-app-shell--no-topbar > .ct-sidebar .ct-a11y-trigger {
    width: var(--ct-target-min); justify-content: center; padding: 0; gap: 0;
  }
  .ct-app-shell--no-topbar > .ct-sidebar .ct-sidebar__brand { justify-content: center; padding-inline: var(--ct-space-2); }
  .ct-app-shell--no-topbar > .ct-sidebar .ct-sidebar__toggle { margin-inline-start: 0; }
  .ct-app-shell--no-topbar > .ct-sidebar .ct-sidebar__nav { padding-inline: var(--ct-space-2); }
  .ct-app-shell--no-topbar > .ct-sidebar .ct-account { flex-direction: column; gap: var(--ct-space-2); }
}
/* A tiny dot signals an unread count is hidden behind the collapsed icon. */
.ct-sidebar--collapsed .ct-sidebar__item:has(.ct-sidebar__count) .ct-sidebar__icon::after {
  content: ""; position: absolute; top: 6px; inset-inline-end: 14px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--ct-primary); border: 2px solid var(--ct-surface);
}

/* ---------------------------------------------------------------------------
 * core/avatar.css  (the account block composes .ct-avatar--md)
 * --------------------------------------------------------------------------- */
.ct-avatar {
  --_size: 40px;
  --_font: var(--ct-text-sm);

  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  flex: none;
  width: var(--_size); height: var(--_size);
  border-radius: var(--ct-radius-pill);
  overflow: hidden;
  background: var(--ct-primary-soft);
  color: var(--ct-primary-text);
  font-family: inherit;
  font-weight: var(--ct-weight-semibold);
  font-size: var(--_font);
  line-height: 1;
  user-select: none;
  /* subtle hairline so light avatars still separate from a white surface */
  box-shadow: inset 0 0 0 1px rgba(16, 24, 40, 0.06);
}

.ct-avatar__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.ct-avatar__initials { text-transform: uppercase; letter-spacing: 0.02em; }

.ct-avatar--neutral { background: var(--ct-neutral-100); color: var(--ct-text-muted); }

.ct-avatar--sm { --_size: 28px; --_font: var(--ct-text-xs); }
.ct-avatar--md { --_size: 40px; --_font: var(--ct-text-sm); }
.ct-avatar--lg { --_size: 56px; --_font: var(--ct-text-lg); }

/* ---------------------------------------------------------------------------
 * account.css — the signed-in user at the FOOT OF THE SIDEBAR. Deliberately
 * ONE account surface: this replaces a topbar avatar row and any duplicated
 * "My profile" nav item.
 * --------------------------------------------------------------------------- */
.ct-account {
  display: flex;
  align-items: center;
  gap: var(--ct-space-1);
}

/* The profile link. flex:1 + min-width:0 so the identity can shrink and wrap
 * instead of pushing the sign-out button out of the rail. */
.ct-account__link {
  display: flex;
  align-items: center;
  gap: var(--ct-space-3);
  flex: 1 1 auto;
  min-width: 0;
  min-height: var(--ct-target-min);
  padding: var(--ct-space-1);
  border-radius: var(--ct-radius-md);
  color: inherit;
  text-decoration: none;
  transition: background var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-account__link:hover { background: var(--ct-surface-hover); }
.ct-account__link:focus-visible { outline: none; box-shadow: var(--ct-ring); }

/* Unavailable account (deactivated, still loading). pointer-events alone only
 * stops the mouse — the markup must ALSO carry tabindex="-1" to keep keyboard
 * users out of a dead link. */
.ct-account__link[aria-disabled="true"] {
  pointer-events: none;
  opacity: 0.6;
}

.ct-account__identity {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

/* Both identity lines clamp to two lines rather than truncating to one.
 *
 * A single-line ellipsis loses characters under the WCAG 1.4.12 text-spacing
 * metrics (a name that fits exactly at ordinary spacing no longer does), and
 * far worse at 200% text (1.4.4): the rail is a fixed --ct-sidebar-width, so
 * text grows while the box does not. `title` is NOT a remedy — it is
 * mouse-only, so it reaches neither keyboard nor touch users.
 *
 * overflow-wrap: anywhere because an e-mail address is one unbreakable token;
 * without it the clamp cannot find a break point and the text spills. */
.ct-account__name,
.ct-account__meta {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.ct-account__name {
  font-size: var(--ct-text-sm);
  font-weight: var(--ct-weight-medium);
  line-height: var(--ct-leading-snug);
  color: var(--ct-text-strong);
}
.ct-account__meta {
  font-size: var(--ct-text-xs);
  line-height: var(--ct-leading-snug);
  color: var(--ct-text-muted);
}

/* Sign-out is wrapped in a <form method="post"> (see the prompt: a link would
 * be firable by prefetch or a crawler). Keep that wrapper out of the layout so
 * the row measures the button, not the form box. */
.ct-account > form {
  flex: none;
  display: flex;
  margin: 0;
}

/* Sign-out. Icon-only, so the accessible name lives on aria-label. */
.ct-account__action {
  flex: none;
  width: var(--ct-target-min);
  height: var(--ct-target-min);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: var(--ct-radius-md);
  background: none;
  color: var(--ct-text-body);
  cursor: pointer;
  transition: background var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-account__action:hover { background: var(--ct-surface-hover); }
.ct-account__action:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-account__action > svg { width: 20px; height: 20px; display: block; }

/* Collapsed rail: avatar over button, identity hidden. Both targets stay 44px. */
.ct-account--collapsed {
  flex-direction: column;
  gap: var(--ct-space-2);
}
.ct-account--collapsed .ct-account__link {
  flex: none;
  justify-content: center;
  padding: 0;
}
.ct-account--collapsed .ct-account__identity { display: none; }

@media (forced-colors: active) {
  .ct-account__link:focus-visible,
  .ct-account__action:focus-visible { outline: 2px solid CanvasText; }
}

/* ===========================================================================
 * DS PAGE COMPONENTS (wave 0) — vendored verbatim from the design system:
 *   core/button.css · data-display/{table,card,badge}.css
 *   forms/{field,input}.css · composites/{empty,headline}.css
 *
 * The portal's old .cl-* component layer is GONE — every page now uses these.
 * What remains in app.css is the app-* layer (composition only) and js-* hooks.
 * NOTE the cascade: app.css loads AFTER this file, so an app-* class that set the
 * same property as a .ct-* rule would win. Never restyle a .ct-* element there.
 * =========================================================================== */

/* ---------------------------------------------------------------------------
 * core/button.css
 * --------------------------------------------------------------------------- */
.ct-btn {
  --_bg: var(--ct-primary-action);
  --_fg: var(--ct-on-primary);
  --_bd: transparent;
  --_sh: var(--ct-shadow-btn);

  appearance: none; -webkit-appearance: none;
  display: inline-flex; align-items: center; justify-content: center; gap: var(--ct-space-2);
  font-family: inherit; font-weight: var(--ct-weight-semibold); line-height: 1;
  border: 1px solid var(--_bd); border-radius: var(--ct-radius-md);
  background: var(--_bg); color: var(--_fg); box-shadow: var(--_sh);
  cursor: pointer; white-space: nowrap; text-decoration: none; user-select: none;
  transition:
    background var(--ct-dur-fast) var(--ct-ease-out),
    border-color var(--ct-dur-fast) var(--ct-ease-out),
    box-shadow var(--ct-dur-fast) var(--ct-ease-out),
    transform var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-btn > svg { width: 1.15em; height: 1.15em; flex: none; }
.ct-btn:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-btn:active:not([disabled]):not([aria-disabled="true"]) { transform: translateY(0.5px); }
.ct-btn[disabled], .ct-btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; box-shadow: none; }

.ct-btn--xs { height: 30px; padding: 0 10px; font-size: var(--ct-text-xs); border-radius: var(--ct-radius-sm); }
.ct-btn--sm { height: 36px; padding: 0 12px; font-size: var(--ct-text-sm); }
.ct-btn--md { height: 44px; padding: 0 16px; font-size: var(--ct-text-sm); }
.ct-btn--lg { height: 48px; padding: 0 20px; font-size: var(--ct-text-md); }
.ct-btn--icon { height: 44px; width: 44px; padding: 0; }
.ct-btn--icon.ct-btn--sm { height: 36px; width: 36px; }
.ct-btn--icon.ct-btn--xs { height: 30px; width: 30px; }

.ct-btn--primary:hover:not([disabled]):not([aria-disabled="true"]) { --_bg: var(--ct-primary-action-hover); }
.ct-btn--primary:active:not([disabled]):not([aria-disabled="true"]) { --_bg: var(--ct-primary-action-press); }

.ct-btn--secondary { --_bg: var(--ct-surface); --_fg: var(--ct-text-body); --_bd: var(--ct-border-strong); --_sh: var(--ct-shadow-xs); }
.ct-btn--secondary:hover:not([disabled]):not([aria-disabled="true"]) { --_bg: var(--ct-surface-hover); }

.ct-btn--outline { --_bg: transparent; --_fg: var(--ct-primary-text); --_bd: var(--ct-primary); --_sh: none; }
.ct-btn--outline:hover:not([disabled]):not([aria-disabled="true"]) { --_bg: var(--ct-primary-soft); }

.ct-btn--ghost { --_bg: transparent; --_fg: var(--ct-text-body); --_bd: transparent; --_sh: none; }
.ct-btn--ghost:hover:not([disabled]):not([aria-disabled="true"]) { --_bg: var(--ct-surface-hover); }

.ct-btn--danger { --_bg: var(--ct-danger-bg); --_fg: var(--ct-danger-text); --_bd: transparent; --_sh: none; }
.ct-btn--danger:hover:not([disabled]):not([aria-disabled="true"]) { --_bd: var(--ct-danger-500); }

.ct-btn--danger-solid { --_bg: var(--ct-danger-500); --_fg: #fff; --_bd: transparent; --_sh: var(--ct-shadow-btn); }
.ct-btn--danger-solid:hover:not([disabled]):not([aria-disabled="true"]) { --_bg: var(--ct-danger-text); }

.ct-btn--link { --_bg: transparent; --_fg: var(--ct-text-link); --_bd: transparent; --_sh: none; height: auto; padding: 0; text-underline-offset: 0.2em; }
.ct-btn--link:hover:not([disabled]):not([aria-disabled="true"]) { text-decoration: underline; }

.ct-btn--block { width: 100%; }

.ct-btn[aria-busy="true"] { cursor: progress; }
.ct-btn[aria-busy="true"] .ct-btn__spinner {
  width: 1.1em; height: 1.1em; flex: none;
  border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%;
  animation: ct-spin 0.6s linear infinite;
}
@keyframes ct-spin { to { transform: rotate(360deg); } }

/* ---------------------------------------------------------------------------
 * data-display/table.css
 * --------------------------------------------------------------------------- */
/* The wrap is the table's FRAME: surface, hairline border, card radius and a
 * small shadow. That frame is what lets a list view stand alone on the canvas
 * with no .ct-card around it — before, the card supplied it, and a bare table
 * read as a square-cornered slab with no edge.
 *
 * overflow-x also does the clipping, so the radius applies to the table inside. */
.ct-table-wrap {
  width: 100%;
  overflow-x: auto;
  background: var(--ct-surface);
  border: 1px solid var(--ct-border);
  border-radius: var(--ct-radius-lg);
  box-shadow: var(--ct-shadow-sm);
}

.ct-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--ct-text-sm);
  background: var(--ct-surface);
}

.ct-table th {
  text-align: left; padding: 10px 16px;
  font-size: var(--ct-text-xs); font-weight: var(--ct-weight-semibold);
  text-transform: uppercase; letter-spacing: 0.04em;
  color: var(--ct-text-muted);
  border-bottom: 1px solid var(--ct-border);
  white-space: nowrap;
}
.ct-table td {
  padding: 12px 16px;
  color: var(--ct-text-body);
  border-bottom: 1px solid var(--ct-border);
}
/* The last row's rule would sit a hair inside the wrap's own border and read as
 * a doubled line just above the rounded edge. */
.ct-table tbody tr:last-child > td { border-bottom: 0; }

/* Zebra striping is the DEFAULT, not an opt-in. Across a wide table it is what
 * keeps your eye on one row from the first column to the last — the reason
 * Erudis stripes every list it ships. Use --plain for a short table where the
 * banding is noise. */
.ct-table tbody tr:nth-child(odd) { background: var(--ct-surface-sunken); }
.ct-table--plain tbody tr:nth-child(odd) { background: none; }

/* Hover wins over the stripe. */
.ct-table tbody tr:hover { background: var(--ct-surface-hover); }

/* Numeric columns align right for scannability. */
.ct-table .ct-num { text-align: right; font-variant-numeric: tabular-nums; }

.ct-table .ct-num { text-align: right; font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
 * data-display/card.css
 * --------------------------------------------------------------------------- */
.ct-card {
  background: var(--ct-surface);
  border: 1px solid var(--ct-border);
  border-radius: var(--ct-radius-lg);
  box-shadow: var(--ct-shadow-sm);
  overflow: hidden;
}
.ct-card__header {
  padding: var(--ct-space-5) var(--ct-space-6);
  border-bottom: 1px solid var(--ct-border);
}
.ct-card__title {
  margin: 0; font-size: var(--ct-text-lg); font-weight: var(--ct-weight-semibold);
  color: var(--ct-text-strong);
}
.ct-card__desc { margin: 4px 0 0; font-size: var(--ct-text-sm); color: var(--ct-text-muted); }
.ct-card__body { padding: var(--ct-space-6); font-size: var(--ct-text-base); }
.ct-card__footer {
  display: flex; justify-content: flex-end; gap: var(--ct-space-3);
  padding: var(--ct-space-4) var(--ct-space-6);
  border-top: 1px solid var(--ct-border);
  background: var(--ct-surface-sunken);
}

/* ---------------------------------------------------------------------------
 * data-display/badge.css
 * --------------------------------------------------------------------------- */
.ct-badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 2px 10px; border-radius: var(--ct-radius-pill);
  font-size: var(--ct-text-xs); font-weight: var(--ct-weight-semibold); line-height: 1.5;
  background: var(--ct-neutral-100); color: var(--ct-text-body);
  white-space: nowrap;
}
.ct-badge__dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }

.ct-badge--primary { background: var(--ct-primary-soft); color: var(--ct-primary-text); }
.ct-badge--success { background: var(--ct-success-bg);  color: var(--ct-success-text); }
.ct-badge--info    { background: var(--ct-info-bg);     color: var(--ct-info-text); }
.ct-badge--warning { background: var(--ct-warning-bg);  color: var(--ct-warning-text); }
.ct-badge--danger  { background: var(--ct-danger-bg);   color: var(--ct-danger-text); }
.ct-badge--purple  { background: var(--ct-purple-bg);   color: var(--ct-purple-text); }

.ct-badge--outline { background: transparent; border: 1px solid currentColor; }

/* ---------------------------------------------------------------------------
 * forms/field.css
 * --------------------------------------------------------------------------- */
.ct-field { display: flex; flex-direction: column; gap: var(--ct-space-2); }

.ct-label {
  font-size: var(--ct-text-sm);
  font-weight: var(--ct-weight-medium);
  color: var(--ct-text-body);
}
.ct-label__req { color: var(--ct-danger-text); }   /* visual only; use `required` for semantics */

.ct-hint  { margin: 0; font-size: var(--ct-text-sm); color: var(--ct-text-muted); }

.ct-error {
  margin: 0; display: flex; align-items: center; gap: 6px;
  font-size: var(--ct-text-sm); color: var(--ct-danger-text);
}
.ct-error > svg { width: 1em; height: 1em; flex: none; }

/* ---------------------------------------------------------------------------
 * forms/input.css
 * --------------------------------------------------------------------------- */
.ct-input {
  display: block; width: 100%;
  height: 44px; padding: 0 12px;
  font-family: inherit; font-size: var(--ct-text-base); color: var(--ct-text-body);
  background: var(--ct-surface);
  border: 1px solid var(--ct-border-strong);
  border-radius: var(--ct-radius-md);
  transition: border-color var(--ct-dur-fast) var(--ct-ease-out),
              box-shadow var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-input::placeholder { color: var(--ct-text-subtle); }
.ct-input:hover:not(:disabled) { border-color: var(--ct-neutral-400); }
.ct-input:focus-visible, .ct-input:focus {
  outline: none;
  border-color: var(--ct-border-focus);
  box-shadow: var(--ct-ring);
}
.ct-input:disabled {
  background: var(--ct-surface-sunken);
  color: var(--ct-text-subtle);
  cursor: not-allowed;
}
.ct-input[aria-invalid="true"] { border-color: var(--ct-danger-500); }
.ct-input[aria-invalid="true"]:focus { box-shadow: var(--ct-ring-danger); }

.ct-input--sm { height: 36px; font-size: var(--ct-text-sm); }

.ct-input-wrap { position: relative; display: block; }
.ct-input-wrap__icon {
  position: absolute; left: 12px; top: 50%; transform: translateY(-50%);
  width: 18px; height: 18px; color: var(--ct-text-subtle); pointer-events: none;
}
.ct-input-wrap .ct-input { padding-left: 40px; }

/* ---------------------------------------------------------------------------
 * composites/empty.css
 * --------------------------------------------------------------------------- */
.ct-empty {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: var(--ct-space-3);
  padding: var(--ct-space-12) var(--ct-space-6);
}

.ct-empty__icon {
  width: 48px; height: 48px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--ct-radius-lg);
  background: var(--ct-surface-sunken);
  color: var(--ct-text-subtle);
  border: 1px solid var(--ct-border);
}
.ct-empty__icon svg { width: 24px; height: 24px; }

.ct-empty__title {
  margin: 0; font-size: var(--ct-text-md); font-weight: var(--ct-weight-semibold);
  color: var(--ct-text-strong);
}
.ct-empty__desc {
  margin: 0; max-width: 44ch;
  font-size: var(--ct-text-sm); color: var(--ct-text-muted);
  line-height: var(--ct-leading-normal);
}
.ct-empty__actions {
  margin-top: var(--ct-space-2);
  display: flex; gap: var(--ct-space-2); flex-wrap: wrap; justify-content: center;
}

/* Tones for the icon tile. The block is also the shape an *outcome* takes —
 * "check your email", "link expired", "password changed" — so it needs to say
 * which kind of outcome without relying on the icon glyph alone. The title text
 * always carries the meaning too (1.4.1). */
.ct-empty--success .ct-empty__icon {
  background: var(--ct-success-bg); color: var(--ct-success-text);
  border-color: color-mix(in srgb, var(--ct-success-text) 20%, transparent);
}
.ct-empty--warning .ct-empty__icon {
  background: var(--ct-warning-bg); color: var(--ct-warning-text);
  border-color: color-mix(in srgb, var(--ct-warning-text) 20%, transparent);
}

.ct-empty--sm { padding: var(--ct-space-8) var(--ct-space-4); }
.ct-empty--sm .ct-empty__icon { width: 40px; height: 40px; }
.ct-empty--sm .ct-empty__icon svg { width: 20px; height: 20px; }

/* ---------------------------------------------------------------------------
 * composites/headline.css
 * --------------------------------------------------------------------------- */
.ct-headline {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--ct-space-4); flex-wrap: wrap;
  /* The headline owns the space beneath itself. Without it the action button
   * sits directly on whatever follows — against a toolbar's field labels it
   * looked like the button belonged to the first filter. */
  margin-block-end: var(--ct-space-6);
}
.ct-headline__text { min-width: 0; }
.ct-headline__title {
  margin: 0;
  font-size: var(--ct-text-3xl); font-weight: var(--ct-weight-bold);
  line-height: var(--ct-leading-tight); color: var(--ct-text-strong);
}
.ct-headline__desc {
  margin: var(--ct-space-2) 0 0;
  font-size: var(--ct-text-md); color: var(--ct-text-muted);
  line-height: var(--ct-leading-snug);
}
.ct-headline__actions {
  display: flex; align-items: center; gap: var(--ct-space-2); flex-wrap: wrap;
  flex: none;
}

@media (max-width: 40rem) {
  .ct-headline { flex-direction: column; align-items: stretch; }
  .ct-headline__actions { width: 100%; }
}



/* ---------------------------------------------------------------------------
 * lang-select.css  (the footer's __lang slot)
 * --------------------------------------------------------------------------- */
.ct-lang-select {
  position: relative;
  display: inline-flex; align-items: center;
  color: var(--ct-text-body);
}

.ct-lang-select__icon {
  position: absolute; inset-inline-start: var(--ct-space-2); top: 50%;
  transform: translateY(-50%);
  width: 16px; height: 16px; color: var(--ct-text-muted); pointer-events: none;
}

.ct-lang-select__control {
  appearance: none; -webkit-appearance: none;
  font: inherit; font-size: var(--ct-text-sm); font-weight: var(--ct-weight-medium);
  color: var(--ct-text-body);
  height: 36px;
  padding-block: 0;
  /* room for the globe (start) and the chevron (end) */
  padding-inline: calc(var(--ct-space-2) + 16px + var(--ct-space-2)) calc(var(--ct-space-2) + 14px + var(--ct-space-2));
  background: var(--ct-surface);
  border: 1px solid var(--ct-border-strong);
  border-radius: var(--ct-radius-md);
  cursor: pointer;
}
.ct-lang-select__control:hover { background: var(--ct-surface-hover); }
.ct-lang-select__control:focus-visible { outline: none; box-shadow: var(--ct-ring); border-color: var(--ct-border-focus); }

/* Custom chevron, drawn with a CSS mask so it inherits currentColor and mirrors
 * under RTL via the logical inset. */
.ct-lang-select::after {
  content: ""; position: absolute; inset-inline-end: var(--ct-space-2); top: 50%;
  transform: translateY(-50%);
  width: 14px; height: 14px; pointer-events: none;
  background: var(--ct-text-muted);
  -webkit-mask: no-repeat center / 14px url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
          mask: no-repeat center / 14px url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='M6 9l6 6 6-6'/></svg>");
}

/* Bare variant: no border/background, for dense footers. */
.ct-lang-select--bare .ct-lang-select__control {
  border-color: transparent; background: transparent;
}
.ct-lang-select--bare .ct-lang-select__control:hover { background: var(--ct-surface-hover); }


/* ===========================================================================
 * DS COMPONENTS (wave 1+) — verbatim:
 *   forms/{select,textarea,checkbox,switch,radio}.css · feedback/alert.css
 *   navigation/tabs.css · overlay/popover.css · layout/a11y-panel.css
 *   composites/{stat-card,properties,form-scaffold}.css
 * =========================================================================== */

/* forms/select.css */
.ct-select {
  display: block; width: 100%;
  height: 44px; padding: 0 40px 0 12px;
  font-family: inherit; font-size: var(--ct-text-base); color: var(--ct-text-body);
  background-color: var(--ct-surface);
  border: 1px solid var(--ct-border-strong);
  border-radius: var(--ct-radius-md);
  appearance: none; -webkit-appearance: none;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2371717A' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  transition: border-color var(--ct-dur-fast) var(--ct-ease-out),
              box-shadow var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-select:hover:not(:disabled) { border-color: var(--ct-neutral-400); }
.ct-select:focus-visible, .ct-select:focus {
  outline: none; border-color: var(--ct-border-focus); box-shadow: var(--ct-ring);
}
.ct-select:disabled {
  background-color: var(--ct-surface-sunken); color: var(--ct-text-subtle); cursor: not-allowed;
}
.ct-select[aria-invalid="true"] { border-color: var(--ct-danger-500); }
.ct-select--sm { height: 36px; font-size: var(--ct-text-sm); }

/* forms/textarea.css */
.ct-textarea {
  display: block; width: 100%;
  min-height: 88px; padding: 10px 12px;
  font-family: inherit; font-size: var(--ct-text-base); line-height: var(--ct-leading-normal);
  color: var(--ct-text-body);
  background: var(--ct-surface);
  border: 1px solid var(--ct-border-strong);
  border-radius: var(--ct-radius-md);
  resize: vertical;
  transition: border-color var(--ct-dur-fast) var(--ct-ease-out),
              box-shadow var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-textarea::placeholder { color: var(--ct-text-subtle); }
.ct-textarea:hover:not(:disabled) { border-color: var(--ct-neutral-400); }
.ct-textarea:focus-visible, .ct-textarea:focus {
  outline: none; border-color: var(--ct-border-focus); box-shadow: var(--ct-ring);
}
.ct-textarea:disabled { background: var(--ct-surface-sunken); color: var(--ct-text-subtle); cursor: not-allowed; }
.ct-textarea[aria-invalid="true"] { border-color: var(--ct-danger-500); }
.ct-textarea[aria-invalid="true"]:focus { box-shadow: var(--ct-ring-danger); }

/* forms/checkbox.css */
.ct-checkbox {
  appearance: none; -webkit-appearance: none;
  width: 20px; height: 20px; flex: none; margin: 0;
  display: inline-grid; place-content: center;
  border: 1.5px solid var(--ct-border-strong);
  border-radius: var(--ct-radius-xs);
  background: var(--ct-surface);
  cursor: pointer;
  transition: background var(--ct-dur-fast) var(--ct-ease-out),
              border-color var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-checkbox::before {
  content: ""; width: 12px; height: 12px;
  background-color: var(--ct-on-primary);
  transform: scale(0);
  transition: transform var(--ct-dur-fast) var(--ct-ease-out);
  -webkit-mask: center / contain no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
          mask: center / contain no-repeat url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}
.ct-checkbox:hover:not(:disabled) { border-color: var(--ct-primary); }
.ct-checkbox:checked, .ct-checkbox:indeterminate {
  background: var(--ct-primary-action); border-color: var(--ct-primary-action);
}
.ct-checkbox:checked::before { transform: scale(1); }
.ct-checkbox:indeterminate::before {
  transform: scale(1);
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round'%3E%3Cpath d='M5 12h14'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='3' stroke-linecap='round'%3E%3Cpath d='M5 12h14'/%3E%3C/svg%3E");
}
.ct-checkbox:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-checkbox:disabled { opacity: 0.5; cursor: not-allowed; }

.ct-check {
  display: inline-flex; align-items: center; gap: var(--ct-space-2);
  font-size: var(--ct-text-sm); color: var(--ct-text-body); cursor: pointer;
  min-height: var(--ct-target-min);
}
.ct-check:has(.ct-checkbox:disabled) { cursor: not-allowed; color: var(--ct-text-subtle); }

/* forms/switch.css */
.ct-switch {
  appearance: none; -webkit-appearance: none;
  position: relative; flex: none; margin: 0;
  width: 40px; height: 24px; border-radius: var(--ct-radius-pill);
  background: var(--ct-border-strong);
  cursor: pointer;
  transition: background var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-switch::before {
  content: ""; position: absolute; top: 2px; left: 2px;
  width: 20px; height: 20px; border-radius: 50%;
  background: #fff; box-shadow: var(--ct-shadow-xs);
  transition: transform var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-switch:checked { background: var(--ct-primary-action); }
.ct-switch:checked::before { transform: translateX(16px); }
.ct-switch:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-switch:disabled { opacity: 0.5; cursor: not-allowed; }

/* forms/radio.css */
.ct-radio {
  appearance: none; -webkit-appearance: none;
  width: 20px; height: 20px; flex: none; margin: 0;
  display: inline-grid; place-content: center;
  border: 1.5px solid var(--ct-border-strong);
  border-radius: 50%;
  background: var(--ct-surface);
  cursor: pointer;
  transition: border-color var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-radio::before {
  content: ""; width: 10px; height: 10px; border-radius: 50%;
  background: var(--ct-on-primary);
  transform: scale(0);
  transition: transform var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-radio:hover:not(:disabled) { border-color: var(--ct-primary); }
.ct-radio:checked { background: var(--ct-primary-action); border-color: var(--ct-primary-action); }
.ct-radio:checked::before { transform: scale(1); }
.ct-radio:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-radio:disabled { opacity: 0.5; cursor: not-allowed; }

.ct-radio-group { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--ct-space-2); }
.ct-radio-group > .ct-label { padding: 0; margin-bottom: var(--ct-space-1); }

/* feedback/alert.css */
.ct-alert {
  display: flex; gap: var(--ct-space-3);
  padding: var(--ct-space-4);
  border-radius: var(--ct-radius-md);
  border: 1px solid transparent;
  background: var(--ct-neutral-100); color: var(--ct-text-body);
  font-size: var(--ct-text-sm);
}
.ct-alert__icon { width: 20px; height: 20px; flex: none; margin-top: 1px; }
.ct-alert__title { margin: 0; font-weight: var(--ct-weight-semibold); color: var(--ct-text-strong); }
.ct-alert__body  { margin: 2px 0 0; color: var(--ct-text-muted); }

.ct-alert--info    { background: var(--ct-info-bg);    color: var(--ct-info-text);    border-color: color-mix(in srgb, var(--ct-info-text) 20%, transparent); }
.ct-alert--success { background: var(--ct-success-bg); color: var(--ct-success-text); border-color: color-mix(in srgb, var(--ct-success-text) 20%, transparent); }
.ct-alert--warning { background: var(--ct-warning-bg); color: var(--ct-warning-text); border-color: color-mix(in srgb, var(--ct-warning-text) 20%, transparent); }
.ct-alert--danger  { background: var(--ct-danger-bg);  color: var(--ct-danger-text);  border-color: color-mix(in srgb, var(--ct-danger-text) 20%, transparent); }
.ct-alert--info .ct-alert__body,
.ct-alert--success .ct-alert__body,
.ct-alert--warning .ct-alert__body,
.ct-alert--danger .ct-alert__body { color: inherit; opacity: 0.9; }
.ct-alert--info .ct-alert__title,
.ct-alert--success .ct-alert__title,
.ct-alert--warning .ct-alert__title,
.ct-alert--danger .ct-alert__title { color: inherit; }

/* navigation/tabs.css */
.ct-tabs {
  display: flex;
  align-items: stretch;
  gap: var(--ct-space-1);
  /* The rail is an INSET SHADOW, not a border-bottom, and the tabs carry no
   * negative margin. Why: a border shrinks the content box to 43px while the
   * 44px tabs (the 2.5.8 target floor) still fill it, and `overflow-x: auto`
   * forces `overflow-y` from `visible` to `auto` — so that one pixel painted a
   * vertical scrollbar down the tab strip on every platform with classic
   * scrollbars. The shadow paints beneath the tabs, so the active tab's 2px
   * underline still covers the rail exactly as the border version did.
   *
   * Do not "fix" a future 1px overflow here with `overflow-y: hidden`: the
   * focus ring extends 4px outside the tab and would be clipped to slivers.
   * Keep the content box big enough instead. */
  box-shadow: inset 0 -1px 0 var(--ct-border);
  /* let a long strip scroll horizontally rather than wrap/overflow the page */
  overflow-x: auto;
  scrollbar-width: thin;
}
.ct-tabs__tab {
  appearance: none; -webkit-appearance: none;
  display: inline-flex; align-items: center; gap: var(--ct-space-2);
  flex: none;
  min-height: var(--ct-target-min);
  padding: var(--ct-space-2) var(--ct-space-3);
  border: none;
  border-bottom: 2px solid transparent;
  background: transparent;
  font-family: inherit; font-size: var(--ct-text-sm); font-weight: var(--ct-weight-medium);
  line-height: 1; color: var(--ct-text-muted);
  text-decoration: none; white-space: nowrap; cursor: pointer;
  transition: color var(--ct-dur-fast) var(--ct-ease-out),
              border-color var(--ct-dur-fast) var(--ct-ease-out),
              background var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-tabs__tab > svg { width: 1.05em; height: 1.05em; flex: none; }
.ct-tabs__tab:hover { color: var(--ct-text-strong); }
.ct-tabs__tab:focus-visible {
  outline: none; box-shadow: var(--ct-ring);
  border-radius: var(--ct-radius-sm);
}
.ct-tabs__tab[aria-selected="true"],
.ct-tabs__tab[aria-current="page"] {
  color: var(--ct-primary-text);
  border-bottom-color: var(--ct-primary);
  font-weight: var(--ct-weight-semibold);
}
.ct-tabs__tab[disabled],
.ct-tabs__tab[aria-disabled="true"] {
  opacity: 0.5; cursor: not-allowed; color: var(--ct-text-muted);
}
.ct-tabs__count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 20px; padding: 0 6px; height: 20px;
  border-radius: var(--ct-radius-pill);
  background: var(--ct-neutral-100); color: var(--ct-text-muted);
  font-size: var(--ct-text-xs); font-weight: var(--ct-weight-semibold); line-height: 1;
}
.ct-tabs__tab[aria-selected="true"] .ct-tabs__count,
.ct-tabs__tab[aria-current="page"] .ct-tabs__count {
  background: var(--ct-primary-soft); color: var(--ct-primary-text);
}
.ct-tabs--pills {
  border-bottom: none;
  box-shadow: none;                            /* pills draw their own border */
  gap: var(--ct-space-1);
  padding: var(--ct-space-1);
  background: var(--ct-surface-sunken);
  border: 1px solid var(--ct-border);
  border-radius: var(--ct-radius-md);
  align-self: flex-start;
}
.ct-tabs--pills .ct-tabs__tab {
  min-height: 36px;
  border-bottom: none;
  border-radius: var(--ct-radius-sm);
  padding: var(--ct-space-2) var(--ct-space-3);
}
.ct-tabs--pills .ct-tabs__tab:hover { background: var(--ct-surface-hover); color: var(--ct-text-strong); }
.ct-tabs--pills .ct-tabs__tab[aria-selected="true"],
.ct-tabs--pills .ct-tabs__tab[aria-current="page"] {
  background: var(--ct-surface);
  color: var(--ct-primary-text);
  box-shadow: var(--ct-shadow-xs);
}
.ct-tabs--pills .ct-tabs__count { background: var(--ct-neutral-200); }
.ct-tabs__panel { padding: var(--ct-space-4) var(--ct-space-1); }
.ct-tabs__panel:focus-visible { outline: none; box-shadow: var(--ct-ring); border-radius: var(--ct-radius-sm); }

/* overlay/popover.css */
.ct-popover {
  margin: 0;
  padding: var(--ct-space-3);
  inset: auto;
  width: max-content;
  max-width: min(320px, calc(100vw - 2 * var(--ct-space-4)));
  border: 1px solid var(--ct-border);
  border-radius: var(--ct-radius-lg);
  background: var(--ct-surface);
  color: var(--ct-text-body);
  box-shadow: var(--ct-shadow-lg);
  font-size: var(--ct-text-sm);
  line-height: var(--ct-leading-normal);
}
.ct-popover:popover-open {
  position: fixed;
  top: var(--ct-space-8);
  left: 50%;
  transform: translateX(-50%);
}
@supports (anchor-name: --a) {
  .ct-popover {
    position-anchor: var(--ct-popover-anchor, --ct-anchor);
  }
  .ct-popover:popover-open {
    position: absolute;
    top: anchor(bottom);
    left: anchor(left);
    transform: none;
    margin-top: var(--ct-space-2);
    position-try-fallbacks: flip-block, flip-inline;
  }
  .ct-popover--end:popover-open { left: auto; right: anchor(right); }
  .ct-popover--top:popover-open { top: auto; bottom: anchor(top); margin: 0 0 var(--ct-space-2); }
}
.ct-popover:popover-open {
  animation: ct-rise-in var(--ct-dur-normal) var(--ct-ease-out);
}
.ct-popover__title {
  margin: 0 0 var(--ct-space-1);
  font-size: var(--ct-text-sm); font-weight: var(--ct-weight-semibold);
  color: var(--ct-text-strong);
}
.ct-popover__body { margin: 0; color: var(--ct-text-muted); }

/* layout/a11y-panel.css */
.ct-a11y-trigger {
  display: flex;
  align-items: center;
  gap: var(--ct-space-3);
  width: 100%;
  min-height: var(--ct-target-min);
  padding: var(--ct-space-2) var(--ct-space-3);
  border: 0;
  border-radius: var(--ct-radius-md);
  background: none;
  color: var(--ct-text-body);
  font-family: inherit;
  font-size: var(--ct-text-sm);
  font-weight: var(--ct-weight-medium);
  text-align: start;
  cursor: pointer;
  transition: background var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-a11y-trigger:hover { background: var(--ct-surface-hover); }
.ct-a11y-trigger:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-a11y-trigger > svg { width: 20px; height: 20px; flex: none; }

.ct-a11y-trigger--icon {
  width: var(--ct-target-min);
  justify-content: center;
  padding: 0;
  gap: 0;
}
.ct-a11y-trigger--collapsed {
  width: var(--ct-target-min);
  justify-content: center;
  padding: 0;
  gap: 0;
}
.ct-a11y-trigger--collapsed > span { display: none; }

/* THREE REGIONS, and only the middle one scrolls. Five option groups overflow a capped panel —
 * more so at the largest text size, which this panel is the very control for — and with overflow
 * on the ROOT the title scrolls away with them. A flex column whose body scrolls keeps the header
 * and the footer in view without stickiness, z-index or scroll-padding. */
.ct-a11y-panel {
  /* DEVIATION (display + overflow only) — see DS-DEVIATIONS 7. Upstream sets both unscoped, which
   * un-hides the popover permanently; everything else here is verbatim. */
  width: min(22rem, calc(100vw - 2 * var(--ct-space-4)));
  max-width: none;
  /* svh, not vh: on mobile 100vh exceeds the visible viewport, so a panel capped at 80vh can
   * still run under the URL bar. */
  max-height: min(80svh, 40rem);
  padding: 0;              /* each region owns its own padding */
}
.ct-a11y-panel:popover-open {
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.ct-a11y-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ct-space-3);
  flex: none;
  padding: var(--ct-space-4) var(--ct-space-4) var(--ct-space-3);
  border-bottom: 1px solid var(--ct-border);
}

.ct-a11y-panel__body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--ct-space-4);
  /* Keep a control tabbed to near the bottom clear of the region's edge. */
  scroll-padding-block: var(--ct-space-4);
  overscroll-behavior: contain;
}

.ct-a11y-panel__title {
  margin: 0;
  font-size: var(--ct-text-base);
  font-weight: var(--ct-weight-semibold);
  color: var(--ct-text-body);
}
.ct-a11y-panel__form {
  display: flex;
  flex-direction: column;
  gap: var(--ct-space-4);
}

/* Only needed when the deployment cannot apply on change (server-rendered, no JS). A flex sibling
 * of the scrolling body — never inside it — so it is always in view without overlaying anything. */
.ct-a11y-panel__actions {
  display: flex;
  gap: var(--ct-space-2);
  flex: none;
  padding: var(--ct-space-3) var(--ct-space-4) var(--ct-space-4);
  border-top: 1px solid var(--ct-border);
  background: var(--ct-surface);
}

/* The reset, when settings apply on change and there is nothing to submit. Low emphasis, in the
 * header, visible the moment the panel opens. */
.ct-a11y-panel__reset {
  flex: none;
  min-height: var(--ct-target-min);
  padding: 0 var(--ct-space-2);
  border: 0;
  border-radius: var(--ct-radius-md);
  background: none;
  color: var(--ct-text-muted);
  font: inherit;
  font-size: var(--ct-text-sm);
  cursor: pointer;
  white-space: nowrap;
}
.ct-a11y-panel__reset:hover { background: var(--ct-surface-hover); color: var(--ct-text-body); }
.ct-a11y-panel__reset:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-a11y-group {
  border: 0;
  margin: 0;
  padding: 0;
  min-width: 0;
}
.ct-a11y-group__legend {
  padding: 0 0 var(--ct-space-2);
  font-size: var(--ct-text-sm);
  font-weight: var(--ct-weight-medium);
  color: var(--ct-text-body);
}
.ct-a11y-options {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ct-space-2);
}
.ct-a11y-options--row {
  display: flex;
  flex-wrap: wrap;
}
.ct-a11y-options--row > .ct-a11y-option {
  flex: 1 1 auto;
  min-width: 4rem;
  justify-content: center;
}
.ct-a11y-option {
  display: flex;
  align-items: center;
  gap: var(--ct-space-2);
  min-height: var(--ct-target-min);
  padding: var(--ct-space-2) var(--ct-space-3);
  border: 1px solid var(--ct-border-strong);
  border-radius: var(--ct-radius-md);
  background: var(--ct-surface);
  color: var(--ct-text-body);
  font-size: var(--ct-text-sm);
  font-weight: var(--ct-weight-medium);
  line-height: var(--ct-leading-normal);
  cursor: pointer;
  transition: background var(--ct-dur-fast) var(--ct-ease-out),
              border-color var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-a11y-option:hover { background: var(--ct-surface-hover); }
.ct-a11y-option:focus-within { outline: none; box-shadow: var(--ct-ring); }
.ct-a11y-option:has(.ct-radio:checked) {
  border-color: var(--ct-primary-action);
  background: var(--ct-primary-soft);
  color: var(--ct-primary-text);
}
.ct-a11y-option__label {
  min-width: 0;
  overflow-wrap: anywhere;
}
.ct-a11y-option__preview {
  flex: none;
  font-size: var(--ct-text-lg);
  line-height: 1;
}
.ct-a11y-option__preview > svg { width: 18px; height: 18px; display: block; }
.ct-a11y-option__preview--lexend   { font-family: "Lexend", sans-serif; }
.ct-a11y-option__preview--atkinson { font-family: "Atkinson Hyperlegible", sans-serif; }
.ct-a11y-option__preview--dyslexic { font-family: "OpenDyslexic", sans-serif; }
.ct-a11y-option__preview--sm  { font-size: var(--ct-text-sm); }
.ct-a11y-option__preview--md  { font-size: var(--ct-text-base); }
.ct-a11y-option__preview--lg  { font-size: var(--ct-text-lg); }
.ct-a11y-option__preview--xl  { font-size: var(--ct-text-xl); }

@supports not selector(:has(*)) {
  .ct-a11y-option { border-color: var(--ct-border); }
}
@media (forced-colors: active) {
  .ct-a11y-option:has(.ct-radio:checked) { border-width: 2px; }
  .ct-a11y-trigger:focus-visible,
  .ct-a11y-option:focus-within { outline: 2px solid CanvasText; }
}

/* composites/stat-card.css */
.ct-stat-card {
  display: flex; flex-direction: column; gap: var(--ct-space-3);
  padding: var(--ct-space-5) var(--ct-space-6);
}
.ct-stat-card__head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: var(--ct-space-4);
}
.ct-stat-card__label {
  margin: 0;
  font-size: var(--ct-text-sm); font-weight: var(--ct-weight-medium);
  color: var(--ct-text-muted);
}
.ct-stat-card__icon {
  width: 44px; height: 44px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--ct-radius-md);
  background: var(--ct-primary-soft); color: var(--ct-primary-text);
}
.ct-stat-card__icon svg { width: 22px; height: 22px; }
.ct-stat-card__icon--primary { background: var(--ct-primary-soft); color: var(--ct-primary-text); }
.ct-stat-card__icon--success { background: var(--ct-success-bg);  color: var(--ct-success-text); }
.ct-stat-card__icon--info    { background: var(--ct-info-bg);     color: var(--ct-info-text); }
.ct-stat-card__icon--warning { background: var(--ct-warning-bg);  color: var(--ct-warning-text); }
.ct-stat-card__icon--danger  { background: var(--ct-danger-bg);   color: var(--ct-danger-text); }
.ct-stat-card__icon--purple  { background: var(--ct-purple-bg);   color: var(--ct-purple-text); }
.ct-stat-card__metric {
  margin: 0;
  font-size: var(--ct-text-4xl); font-weight: var(--ct-weight-bold);
  line-height: var(--ct-leading-tight); color: var(--ct-text-strong);
  font-variant-numeric: tabular-nums;
}
.ct-stat-card__foot {
  margin: 0;
  display: flex; align-items: center; gap: var(--ct-space-2); flex-wrap: wrap;
  font-size: var(--ct-text-sm); color: var(--ct-text-muted);
}
.ct-stat-card__delta {
  display: inline-flex; align-items: center; gap: var(--ct-space-1);
  font-weight: var(--ct-weight-semibold); font-variant-numeric: tabular-nums;
}
.ct-stat-card__delta svg { width: 14px; height: 14px; flex: none; }
.ct-stat-card__delta--up   { color: var(--ct-success-text); }
.ct-stat-card__delta--down { color: var(--ct-danger-text); }
.ct-stat-card__delta--flat { color: var(--ct-text-muted); }

/* composites/properties.css */
.ct-properties { margin: 0; }
.ct-properties__row {
  display: grid; grid-template-columns: 1fr; gap: var(--ct-space-1);
  padding: var(--ct-space-3) 0;
  border-bottom: 1px solid var(--ct-border);
}
.ct-properties__row:first-child { padding-top: 0; }
.ct-properties__row:last-child { padding-bottom: 0; border-bottom: 0; }
.ct-properties dt {
  margin: 0; font-size: var(--ct-text-sm); color: var(--ct-text-muted);
}
.ct-properties dd {
  margin: 0; font-size: var(--ct-text-sm); font-weight: var(--ct-weight-medium);
  color: var(--ct-text-strong); min-width: 0; word-wrap: break-word;
}
@media (min-width: 40rem) {
  .ct-properties__row {
    grid-template-columns: minmax(120px, 200px) 1fr;
    gap: var(--ct-space-4); align-items: baseline;
  }
}
.ct-properties--plain .ct-properties__row { border-bottom: 0; padding: var(--ct-space-2) 0; }

/* composites/form-scaffold.css — DROPPED 2026-08-05.
 * The file is gone from the DS: it is absent from _ds_integrity.json's 161 paths, which the index
 * says means REMOVED upstream, not merely unchanged. Its job — a sticky action bar with a dirty
 * indicator — is now done by .ct-pagebar / .ct-pagebar__status in layout/page-header.css, at the
 * TOP of the record page rather than the foot of the form. Nothing in the portal referenced
 * .ct-form-actions, so this block was dead weight that would have read as a live DS component the
 * next time somebody went looking for a place to put form buttons. */


/* ===========================================================================
 * DS PAGE PATTERNS — composites/{listview,formview}.css, verbatim.
 * These are the canonical shapes for index and create/edit pages:
 *   list  = toolbar + table + pagination, bare on the canvas, NEVER carded
 *   form  = sticky bar + hero + CARDED field groups (fields have no surface
 *           of their own, so the card is what delimits the editable region)
 * =========================================================================== */

/* --------------------------------- listview -------------------------------- */
.ct-listview { display: flex; flex-direction: column; gap: var(--ct-space-4); min-width: 0; }

.ct-listview__toolbar {
  display: flex; flex-wrap: wrap;
  align-items: flex-end;            /* labels sit above; align the controls */
  gap: var(--ct-space-4); min-width: 0;
}
/* Search takes the largest share of the row, but it GROWS rather than being
 * capped — a capped search leaves a dead gap at the end of the row whenever
 * there are only one or two filters. (The --filters modifier that used to cap
 * it is gone from the DS for exactly that reason.) */
.ct-listview__search { flex: 1 1 20rem; min-width: 0; }
.ct-listview__search .ct-input-wrap { width: 100%; }

/* The slot sits at its natural width and does NOT grow — the search's flex-grow
 * absorbs the free space and pushes it to the trailing edge. That keeps the
 * proportions Erudis ships: a wide search, a modest filter beside it.
 *
 * Do not make this grow to close a gap. A gap on the right means something else
 * is capped — the search's flex-basis, or a max-width on it. Growing the filters
 * instead just inflates a select to half the toolbar. */
.ct-listview__filters {
  display: flex; flex-wrap: wrap; align-items: flex-end;
  gap: var(--ct-space-3);
  flex: 0 1 auto;
  min-width: 0;
}
.ct-listview__filters .ct-field { min-width: 10rem; }

.ct-listview__clear {
  display: inline-flex; align-items: center; gap: var(--ct-space-2);
  min-height: var(--ct-target-min); padding: 0 var(--ct-space-3);
  border: 0; border-radius: var(--ct-radius-md);
  background: none; color: var(--ct-text-muted);
  font: inherit; font-size: var(--ct-text-sm); cursor: pointer; white-space: nowrap;
}
.ct-listview__clear:hover { background: var(--ct-surface-hover); color: var(--ct-text-body); }
.ct-listview__clear:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-listview__clear > svg { width: 16px; height: 16px; flex: none; }

.ct-listview__note { margin: 0; font-size: var(--ct-text-sm); color: var(--ct-text-muted); }

.ct-listview__applied {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--ct-space-2); min-width: 0;
}

.ct-listview__bulk {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--ct-space-4);
  padding: var(--ct-space-3) var(--ct-space-4);
  border: 1px solid var(--ct-border); border-radius: var(--ct-radius-md);
  background: var(--ct-surface); min-width: 0;
}
.ct-listview__bulk[hidden] { display: none; }
.ct-listview__bulk-count {
  font-size: var(--ct-text-sm); font-weight: var(--ct-weight-medium); color: var(--ct-text-strong);
}
.ct-listview__bulk-clear {
  margin-inline-start: auto; min-height: var(--ct-target-min); padding: 0 var(--ct-space-2);
  border: 0; background: none; color: var(--ct-text-muted);
  font: inherit; font-size: var(--ct-text-sm); text-decoration: underline; cursor: pointer;
  border-radius: var(--ct-radius-sm);
}
.ct-listview__bulk-clear:hover { color: var(--ct-text-body); }
.ct-listview__bulk-clear:focus-visible { outline: none; box-shadow: var(--ct-ring); }

.ct-listview__footer {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end;
  gap: var(--ct-space-3); min-width: 0;
}

@media (max-width: 47.999rem) {
  .ct-listview__filters { width: 100%; }
  .ct-listview__footer { justify-content: center; }
}

/* ---------------------------------------------------------------------------
 * advanced/file-upload.css  (dropzone + file list — a styled shell; needs JS)
 * --------------------------------------------------------------------------- */

.ct-dropzone {
  position: relative;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: var(--ct-space-2);
  padding: var(--ct-space-8) var(--ct-space-6);
  text-align: center;
  border: 2px dashed var(--ct-border-strong);
  border-radius: var(--ct-radius-lg);
  background: var(--ct-surface-sunken);
  color: var(--ct-text-muted);
  cursor: pointer;
  transition: border-color var(--ct-dur-fast) var(--ct-ease-out),
              background var(--ct-dur-fast) var(--ct-ease-out);
}

/* The native input covers the whole zone so a click anywhere opens the picker,
 * yet it stays keyboard-focusable. Visually hidden but not display:none. */
.ct-dropzone__input {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  opacity: 0; cursor: pointer;
}

/* Idle hover (pointer over the zone) and keyboard focus on the input. */
.ct-dropzone:hover,
.ct-dropzone:focus-within {
  border-color: var(--ct-primary);
  background: var(--ct-primary-soft);
}
.ct-dropzone:focus-within { box-shadow: var(--ct-ring); outline: none; }

/* Dragging state — set by JS on dragenter/dragover (add .is-dragging). */
.ct-dropzone.is-dragging {
  border-color: var(--ct-primary);
  border-style: solid;
  background: var(--ct-primary-soft-2);
  color: var(--ct-primary-text);
}

/* Invalid drop (wrong type / too large) — JS adds .is-invalid. Not color-only:
 * pair with the __hint text switched to an error message. */
.ct-dropzone.is-invalid {
  border-color: var(--ct-danger-500);
  background: var(--ct-danger-bg);
  color: var(--ct-danger-text);
}

.ct-dropzone__icon {
  display: grid; place-items: center;
  width: 44px; height: 44px;
  border-radius: var(--ct-radius-pill);
  background: var(--ct-surface);
  border: 1px solid var(--ct-border);
  color: var(--ct-primary-text);
}
.ct-dropzone__icon > svg { width: 22px; height: 22px; }

.ct-dropzone__title {
  font-size: var(--ct-text-base); font-weight: var(--ct-weight-semibold);
  color: var(--ct-text-strong);
}
.ct-dropzone__title em {
  font-style: normal; color: var(--ct-primary-text);
  text-decoration: underline; text-underline-offset: 0.15em;
}
.ct-dropzone__hint { font-size: var(--ct-text-xs); color: var(--ct-text-muted); }

/* ============================ File list ============================ */
.ct-filelist {
  list-style: none; margin: var(--ct-space-4) 0 0; padding: 0;
  display: flex; flex-direction: column; gap: var(--ct-space-2);
}

.ct-fileitem {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center; gap: var(--ct-space-3);
  padding: var(--ct-space-3);
  border: 1px solid var(--ct-border);
  border-radius: var(--ct-radius-md);
  background: var(--ct-surface);
}

.ct-fileitem__icon {
  display: grid; place-items: center;
  width: 36px; height: 36px; flex: none;
  border-radius: var(--ct-radius-sm);
  background: var(--ct-primary-soft);
  color: var(--ct-primary-text);
}
.ct-fileitem__icon > svg { width: 18px; height: 18px; }

.ct-fileitem__main { min-width: 0; }
.ct-fileitem__name {
  font-size: var(--ct-text-sm); font-weight: var(--ct-weight-medium);
  color: var(--ct-text-strong);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ct-fileitem__meta {
  display: flex; align-items: center; gap: var(--ct-space-2);
  margin-top: 2px;
  font-size: var(--ct-text-xs); color: var(--ct-text-muted);
}

/* Progress track — a native <progress> can be used; this styles a div bar so
 * the value can be driven by width. Uploading vs done is NOT color-only: the
 * meta line shows a percentage / "done" label. */
.ct-fileitem__bar {
  margin-top: var(--ct-space-2);
  height: 6px; border-radius: var(--ct-radius-pill);
  background: var(--ct-surface-hover); overflow: hidden;
}
.ct-fileitem__bar > span {
  display: block; height: 100%;
  border-radius: inherit;
  background: var(--ct-primary-action);
  transition: width var(--ct-dur-normal) var(--ct-ease-out);
}
.ct-fileitem.is-done .ct-fileitem__bar > span { background: var(--ct-success-500); }
.ct-fileitem.is-error .ct-fileitem__icon { background: var(--ct-danger-bg); color: var(--ct-danger-text); }
.ct-fileitem.is-error .ct-fileitem__bar > span { background: var(--ct-danger-500); }

.ct-fileitem__status { display: inline-flex; align-items: center; gap: 4px; }
.ct-fileitem__status--done  { color: var(--ct-success-text); }
.ct-fileitem__status--error { color: var(--ct-danger-text); }
.ct-fileitem__status > svg { width: 14px; height: 14px; }

.ct-fileitem__remove { flex: none; }


/* ------------------------------ page-header -------------------------------- */
/* CloudTeam Design System — Page header (sticky bar + hero)
 * ---------------------------------------------------------------------------
 * The chrome at the top of any record page — create, edit, or details. Shared
 * by FormView and DetailView so the two cannot drift apart.
 *
 * WHAT GOES IN THE BAR: navigation, and actions that act on the WHOLE record —
 * publish, delete, and (on a single-purpose form page) save. Never a control
 * that acts on one tab: the bar carries the page title, so it reads as
 * belonging to the page, and a per-tab control there changes meaning as you
 * switch tabs. See detailview.prompt.md.
 *
 * Requires: button.css.
 * --------------------------------------------------------------------------- */

/* ===================== Sticky bar =====================
 * Bleeds to the edges of the padded content column with negative margins, so
 * its border-bottom spans the full measure rather than being inset. */
.ct-pagebar {
  position: sticky;
  /* Negative, to cancel the scroll container's padding. Sticky `top` is measured
   * from the scrollport's PADDING edge, so top:0 would pin the bar 32px down and
   * leave a strip above it where content scrolls past in the open. */
  top: calc(-1 * var(--ct-space-8));
  z-index: 20;
  display: flex; align-items: center; gap: var(--ct-space-3);
  min-height: 64px;
  margin: calc(-1 * var(--ct-space-8)) calc(-1 * var(--ct-space-8)) var(--ct-space-6);
  padding: var(--ct-space-3) var(--ct-space-8);
  /* The CANVAS colour, not --ct-surface. It must be opaque — content scrolls
   * underneath a sticky bar, and a transparent one would let fields slide up
   * through the title and buttons. Painting it with the canvas makes it read as
   * part of the page rather than a floating slab, with the border as the only
   * separation. That is the footer treatment, just not see-through. */
  background: var(--ct-bg-app);
  border-bottom: 1px solid var(--ct-border);
}

/* Back to the parent list. A real link, not history.back(): the parent is a
 * known destination, and back() lands wherever the user came from. */
.ct-pagebar__back {
  flex: none;
  width: var(--ct-target-min); height: var(--ct-target-min);
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--ct-border-strong);
  border-radius: var(--ct-radius-md);
  background: var(--ct-surface);
  color: var(--ct-text-muted);
  transition: background var(--ct-dur-fast) var(--ct-ease-out),
              color var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-pagebar__back:hover { background: var(--ct-surface-hover); color: var(--ct-text-body); }
.ct-pagebar__back:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-pagebar__back > svg { width: 18px; height: 18px; }

/* Eyebrow (parent list) over the record title. Both clamp to one line — this is
 * chrome, and a wrapping title would change the bar's height as you navigate. */
.ct-pagebar__crumb { min-width: 0; display: flex; flex-direction: column; line-height: var(--ct-leading-snug); }
.ct-pagebar__eyebrow,
.ct-pagebar__title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ct-pagebar__eyebrow {
  font-size: var(--ct-text-xs); font-weight: var(--ct-weight-semibold);
  color: var(--ct-text-subtle);
}
.ct-pagebar__title {
  font-size: var(--ct-text-sm); font-weight: var(--ct-weight-semibold);
  color: var(--ct-text-strong);
}

/* Unsaved-changes indicator. role="status" so it is announced when it appears.
 * Only meaningful where the bar owns a save — a details page has none. */
.ct-pagebar__status {
  margin: 0 0 0 auto;
  display: none; align-items: center; gap: var(--ct-space-2);
  font-size: var(--ct-text-sm); font-weight: var(--ct-weight-medium);
  color: var(--ct-warning-text);
  white-space: nowrap;
}
.ct-pagebar__status::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%; flex: none;
  background: var(--ct-warning-500);
}
.ct-pagebar--dirty .ct-pagebar__status { display: inline-flex; }

.ct-pagebar__actions {
  flex: none; display: flex; align-items: center; gap: var(--ct-space-2);
  margin-inline-start: auto;
}
/* When dirty, the status owns the auto margin and the buttons follow it. */
.ct-pagebar--dirty .ct-pagebar__actions { margin-inline-start: var(--ct-space-4); }

@media print {
  /* Chrome: navigation and buttons, same as the sidebar. */
  .ct-pagebar { display: none; }
}

/* ===================== Hero ===================== */
.ct-pagehead {
  display: flex; align-items: center; gap: var(--ct-space-4);
  margin-block-end: var(--ct-space-6);
  min-width: 0;
}
/* Round mark: an icon for a new record, the subject's avatar when editing one. */
.ct-pagehead__mark {
  flex: none; width: 48px; height: 48px;
  display: inline-flex; align-items: center; justify-content: center;
  border-radius: 50%;
  background: var(--ct-primary-soft);
  color: var(--ct-primary-text);
}
.ct-pagehead__mark > svg { width: 22px; height: 22px; }
.ct-pagehead__text { min-width: 0; }
.ct-pagehead__title {
  margin: 0;
  font-size: var(--ct-text-2xl); font-weight: var(--ct-weight-semibold);
  line-height: var(--ct-leading-tight); color: var(--ct-text-strong);
  overflow-wrap: anywhere;
}
.ct-pagehead__desc {
  margin: var(--ct-space-1) 0 0;
  font-size: var(--ct-text-sm); color: var(--ct-text-muted);
}
/* Status badges sit on their own line under the title. */
.ct-pagehead__meta {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--ct-space-2);
  margin-block-start: var(--ct-space-2);
}

/* --------------------------------- formview -------------------------------- */
/* The sticky bar and hero are SHARED chrome — page-header.css above — so the
 * create/edit page and the details page cannot drift apart. Only the
 * form-specific parts live here.
 *
 * Requires: page-header.css, card.css, button.css, field.css. */

.ct-formview { display: flex; flex-direction: column; min-width: 0; }

/* ===================== Main + aside ===================== */
/* minmax(0, 1fr) so a wide field cannot floor the main column, and a fixed
 * 320px aside — the width Erudis uses. */
.ct-formview__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  align-items: start;
  gap: var(--ct-space-6);
  min-width: 0;
}
/* The aside is OPTIONAL. Without it the main column takes the whole measure. */
.ct-formview__grid:not(:has(.ct-formview__aside)) { grid-template-columns: minmax(0, 1fr); }

.ct-formview__main,
.ct-formview__aside {
  display: flex; flex-direction: column; gap: var(--ct-space-6);
  min-width: 0;
}

@media (max-width: 63.999rem) {
  /* The aside carries secondary controls, so it drops below the form rather
   * than squeezing beside it. */
  .ct-formview__grid { grid-template-columns: minmax(0, 1fr); }
}

/* ===================== Fields inside a card =====================
 * Field groups are separated by a hairline, not only by space — it keeps a long
 * form scannable and matches the products. */
.ct-form-fields { display: flex; flex-direction: column; }
.ct-form-fields > * + * {
  margin-block-start: var(--ct-space-5);
  padding-block-start: var(--ct-space-5);
  border-block-start: 1px solid var(--ct-border);
}

/* Three widths, chosen by CONTENT not by taste:
 *   full   names, titles, e-mails, descriptions — anything free-text and long
 *   --half codes, numbers, dates, short enumerations
 *   __row  two genuinely paired fields (first/last name, from/to) */
.ct-field--half { max-width: 22rem; }

.ct-form-fields__row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--ct-space-4);
}
@media (max-width: 47.999rem) {
  .ct-form-fields__row { grid-template-columns: minmax(0, 1fr); }
  .ct-field--half { max-width: none; }
}

/* Progressive disclosure for an optional field ("+ Dodaj opis"). A real
 * <button>, and the revealed field must receive focus. */
.ct-form-fields__reveal {
  align-self: flex-start;
  display: inline-flex; align-items: center; gap: var(--ct-space-2);
  min-height: var(--ct-target-min);
  padding: 0 var(--ct-space-2);
  border: 0; background: none; cursor: pointer;
  color: var(--ct-text-link); font: inherit; font-size: var(--ct-text-sm);
  border-radius: var(--ct-radius-sm);
}
.ct-form-fields__reveal:hover { text-decoration: underline; }
.ct-form-fields__reveal:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-form-fields__reveal > svg { width: 16px; height: 16px; flex: none; }

/* ===================== Danger zone =====================
 * A card, but bordered and titled in danger. Always in the aside (or last),
 * never inline among ordinary fields — a destructive action should not sit in
 * the path of someone filling in a form. */
.ct-formview__danger { border-color: var(--ct-danger-500); }
.ct-formview__danger .ct-card__title { color: var(--ct-danger-text); }

/* ===================== Error summary =====================
 * Rendered at the top of __main on a failed submit, with a link per failing
 * field. It is the only reliable way to report an error whose field is
 * off-screen — or on a hidden tab. */
.ct-formview__errors {
  padding: var(--ct-space-4);
  border: 1px solid var(--ct-danger-500);
  border-radius: var(--ct-radius-lg);
  background: var(--ct-danger-bg);
  color: var(--ct-text-body);
}
.ct-formview__errors > h2 {
  margin: 0 0 var(--ct-space-2);
  font-size: var(--ct-text-sm); font-weight: var(--ct-weight-semibold);
  color: var(--ct-danger-text);
}
.ct-formview__errors ul { margin: 0; padding-inline-start: var(--ct-space-5); font-size: var(--ct-text-sm); }
.ct-formview__errors a { color: var(--ct-danger-text); }

/* -------------------------------- detailview ------------------------------- */
/* The record workspace: shared page chrome, then tabs, then whatever that tab
 * is.
 *
 * TWO KINDS OF TAB, and the difference decides where a save lives:
 *
 *   COLLECTION  a set of things you manipulate. Every action is its own
 *               transaction: add a module, delete a group. NO save button,
 *               no dirty state.
 *
 *   ATTRIBUTES  fields that only make sense committed together. Needs a save,
 *               and that save belongs to THE PANEL (.ct-detailview__actions),
 *               never to .ct-pagebar.
 *
 * Why the panel and not the bar: the bar carries the page title, so it reads as
 * belonging to the whole record. A save there acts on one tab and vanishes when
 * you switch — leaving no way to tell whether changes are still pending. Keep
 * the bar for navigation and record-level actions (publish, delete) only.
 *
 * Requires: page-header.css, tabs.css. Then whatever the tab holds. */

.ct-detailview { display: flex; flex-direction: column; min-width: 0; }

/* Tabs sit directly under the hero and above the panel. */
.ct-detailview > .ct-tabs { margin-block-end: var(--ct-space-6); }

.ct-detailview__panel { min-width: 0; }

/* ===================== Main + aside =====================
 * Same split as FormView: content, plus a 320px column for facts and secondary
 * controls. Optional — a collection tab often wants the full width. */
.ct-detailview__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  align-items: start;
  gap: var(--ct-space-6);
  min-width: 0;
}
.ct-detailview__grid:not(:has(.ct-detailview__aside)) { grid-template-columns: minmax(0, 1fr); }

.ct-detailview__main,
.ct-detailview__aside {
  display: flex; flex-direction: column; gap: var(--ct-space-6);
  min-width: 0;
}

@media (max-width: 63.999rem) {
  .ct-detailview__grid { grid-template-columns: minmax(0, 1fr); }
}

/* ===================== Section head inside a panel =====================
 * A heading with its own action, above a collection. Distinct from
 * .ct-pagehead: that names the record, this names one list within a tab. */
.ct-detailview__section-head {
  display: flex; align-items: center; justify-content: space-between;
  flex-wrap: wrap; gap: var(--ct-space-3);
  margin-block-end: var(--ct-space-4);
  min-width: 0;
}
.ct-detailview__section-title {
  margin: 0;
  font-size: var(--ct-text-lg); font-weight: var(--ct-weight-semibold);
  color: var(--ct-text-strong);
}
.ct-detailview__section-desc {
  margin: var(--ct-space-1) 0 0;
  font-size: var(--ct-text-sm); color: var(--ct-text-muted);
}

/* ===================== Collection rows =====================
 * A reorderable list of things — modules, groups. Each row is its own
 * transaction, so there is no form and no save. */
.ct-detailview__rows { display: flex; flex-direction: column; gap: var(--ct-space-2); }

.ct-detailview__row {
  display: flex; align-items: center; gap: var(--ct-space-3);
  min-height: var(--ct-target-min);
  padding: var(--ct-space-3) var(--ct-space-4);
  border: 1px solid var(--ct-border);
  border-radius: var(--ct-radius-md);
  background: var(--ct-surface);
  min-width: 0;
}
.ct-detailview__row-label { flex: 1 1 auto; min-width: 0; overflow-wrap: anywhere; }
/* Row-level controls sit at the end and never shrink. */
.ct-detailview__row-actions {
  flex: none; display: flex; align-items: center; gap: var(--ct-space-2);
}

/* Drag handle. Reordering MUST also be reachable without a pointer — see the
 * prompt: pair it with move up/down in the row's menu (WCAG 2.1.1). */
.ct-detailview__drag {
  flex: none;
  width: var(--ct-target-min); height: var(--ct-target-min);
  display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: none; cursor: grab;
  color: var(--ct-text-subtle); border-radius: var(--ct-radius-sm);
}
.ct-detailview__drag:hover { color: var(--ct-text-body); }
.ct-detailview__drag:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-detailview__drag > svg { width: 16px; height: 16px; }

/* ===================== Panel-scoped save =====================
 * For an ATTRIBUTES tab. Sits at the foot of the panel's cards, so the save is
 * visibly attached to the fields it commits. */
.ct-detailview__actions {
  display: flex; align-items: center; gap: var(--ct-space-3);
  flex-wrap: wrap;
  padding-block-start: var(--ct-space-2);
}
.ct-detailview__actions > .ct-btn:first-child { margin-inline-start: auto; }

/* ===================== Read-only rendering =====================
 * Not a mode — a PERMISSION render. When the user cannot edit, the panel emits
 * a .ct-properties list instead of a form. That is safer than a form with every
 * field disabled, because there is no form to submit at all. */
.ct-detailview__readonly .ct-properties { margin: 0; }

@media (max-width: 47.999rem) {
  .ct-detailview__row { flex-wrap: wrap; }
  .ct-detailview__row-actions { margin-inline-start: auto; }
}


/* ---------------------------------------------------------------------------
 * navigation/pagination.css
 * --------------------------------------------------------------------------- */
.ct-pagination {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--ct-space-1);
  font-size: var(--ct-text-sm);
}
.ct-pagination__page,
.ct-pagination__btn {
  appearance: none; -webkit-appearance: none;
  display: inline-flex; align-items: center; justify-content: center;
  gap: var(--ct-space-1);
  min-width: var(--ct-target-min); min-height: var(--ct-target-min);   /* 44px targets */
  padding: 0 var(--ct-space-2);
  border: 1px solid transparent;
  border-radius: var(--ct-radius-md);
  background: transparent; color: var(--ct-text-body);
  font-family: inherit; font-size: var(--ct-text-sm); font-weight: var(--ct-weight-medium);
  line-height: 1; text-decoration: none; cursor: pointer;
  transition: background var(--ct-dur-fast) var(--ct-ease-out),
              border-color var(--ct-dur-fast) var(--ct-ease-out),
              color var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-pagination__btn > svg { width: 1.1em; height: 1.1em; flex: none; }
.ct-pagination__page:hover,
.ct-pagination__btn:hover { background: var(--ct-surface-hover); color: var(--ct-text-strong); }
.ct-pagination__page:focus-visible,
.ct-pagination__btn:focus-visible { outline: none; box-shadow: var(--ct-ring); }
/* Current page — filled accent chip behind white text (AA). */
.ct-pagination__page[aria-current="page"] {
  background: var(--ct-primary-action); color: var(--ct-on-primary);
  border-color: transparent; font-weight: var(--ct-weight-semibold);
}
.ct-pagination__page[aria-current="page"]:hover {
  background: var(--ct-primary-action-hover); color: var(--ct-on-primary);
}
.ct-pagination__btn[disabled],
.ct-pagination__btn[aria-disabled="true"] {
  opacity: 0.45; cursor: not-allowed; color: var(--ct-text-muted);
  background: transparent;
}
.ct-pagination__gap {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--ct-space-6); min-height: var(--ct-target-min);
  color: var(--ct-text-subtle);
}
.ct-pagination__summary { color: var(--ct-text-muted); }
.ct-pagination__size {
  display: inline-flex; align-items: center; gap: var(--ct-space-2);
  color: var(--ct-text-muted);
}
.ct-pagination__size .ct-select { width: auto; min-width: 84px; }
.ct-pagination__spacer { margin-left: auto; }

/* ---------------------------------------------------------------------------
 * composites/data-table.css  (sortable headers on top of .ct-table)
 * --------------------------------------------------------------------------- */
/* The <th> keeps its scope="col"; a full-bleed control inside carries the click
 * target and aria-sort lives on the <th> (announced by screen readers). */
.ct-data-table th.ct-data-table__th--sortable { padding: 0; }

.ct-data-table__sort {
  appearance: none; -webkit-appearance: none;
  display: inline-flex; align-items: center; gap: var(--ct-space-2);
  width: 100%; padding: 10px 16px; margin: 0;
  font: inherit; color: inherit; text-align: inherit;
  text-transform: inherit; letter-spacing: inherit; font-weight: inherit;
  background: none; border: 0; cursor: pointer;
}
.ct-data-table__sort:hover { color: var(--ct-text-body); }
.ct-data-table__sort:focus-visible { outline: none; box-shadow: var(--ct-ring); border-radius: var(--ct-radius-sm); }
.ct-data-table .ct-num .ct-data-table__sort { justify-content: flex-end; }

.ct-data-table th[aria-sort="ascending"],
.ct-data-table th[aria-sort="descending"] { color: var(--ct-text-strong); }

/* Caret: faint when idle, solid + flipped by sort direction. */
.ct-data-table__caret {
  width: 14px; height: 14px; flex: none; opacity: 0.35;
  transition: opacity var(--ct-dur-fast) var(--ct-ease-out),
              transform var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-data-table__sort:hover .ct-data-table__caret { opacity: 0.7; }
.ct-data-table th[aria-sort="ascending"] .ct-data-table__caret,
.ct-data-table th[aria-sort="descending"] .ct-data-table__caret { opacity: 1; }
.ct-data-table th[aria-sort="descending"] .ct-data-table__caret { transform: rotate(180deg); }

.ct-data-table__select { width: 44px; padding-right: 0 !important; text-align: center; }
.ct-data-table tbody tr[aria-selected="true"] { background: var(--ct-primary-soft); }
.ct-data-table tbody tr[aria-selected="true"] > td:first-child {
  box-shadow: inset 2px 0 0 0 var(--ct-primary);
}
.ct-data-table tbody tr[aria-selected="true"]:hover { background: var(--ct-primary-soft-2); }

.ct-data-table tbody[aria-busy="true"] tr:hover { background: transparent; }
.ct-data-table .ct-skeleton { max-width: 100%; }

.ct-data-table__empty-cell { padding: 0 !important; }
.ct-data-table__empty-cell:hover { background: transparent; }

.ct-data-table__footer {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--ct-space-4); flex-wrap: wrap;
  padding: var(--ct-space-3) var(--ct-space-4);
  border-top: 1px solid var(--ct-border);
  background: var(--ct-surface);
  font-size: var(--ct-text-sm); color: var(--ct-text-muted);
}
.ct-data-table__count { font-variant-numeric: tabular-nums; }
.ct-data-table__pager { display: inline-flex; align-items: center; gap: var(--ct-space-2); }
.ct-data-table__pager b { color: var(--ct-text-strong); font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------------------
 * composites/usage-meter.css  (quota bar with an ok/warn/over zone model)
 * The zone is ALWAYS text as well as colour (WCAG 1.4.1), and the track carries
 * role="progressbar" + aria values.
 * --------------------------------------------------------------------------- */
.ct-usage-meter {
  --_zone: var(--ct-success-500);       /* fill — non-text, >=3:1 */
  --_zone-text: var(--ct-success-text); /* zone label — AA text on white */
  display: flex; flex-direction: column; gap: var(--ct-space-2);
}
.ct-usage-meter__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--ct-space-3);
}
.ct-usage-meter__label {
  font-size: var(--ct-text-sm); font-weight: var(--ct-weight-medium);
  color: var(--ct-text-body);
}
.ct-usage-meter__value {
  font-size: var(--ct-text-sm); color: var(--ct-text-muted);
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
.ct-usage-meter__value b { color: var(--ct-text-strong); font-weight: var(--ct-weight-semibold); }
.ct-usage-meter__track {
  position: relative;
  height: 8px; border-radius: var(--ct-radius-pill);
  background: var(--ct-neutral-100); overflow: hidden;
}
.ct-usage-meter__fill {
  height: 100%; width: 0; border-radius: inherit;
  background: var(--_zone);
  transition: width var(--ct-dur-slow) var(--ct-ease-out);
}
.ct-usage-meter__marker {
  position: absolute; top: -2px; bottom: -2px; left: var(--_at, 100%);
  width: 2px; background: var(--ct-neutral-600); border-radius: 2px;
}
.ct-usage-meter__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--ct-space-3); font-size: var(--ct-text-xs);
}
.ct-usage-meter__zone {
  display: inline-flex; align-items: center; gap: var(--ct-space-1);
  font-weight: var(--ct-weight-semibold); color: var(--_zone-text);
}
.ct-usage-meter__pct { color: var(--ct-text-muted); font-variant-numeric: tabular-nums; }
.ct-usage-meter--warn { --_zone: var(--ct-warning-500); --_zone-text: var(--ct-warning-text); }
.ct-usage-meter--over { --_zone: var(--ct-danger-500);  --_zone-text: var(--ct-danger-text); }
.ct-usage-meter--unmetered .ct-usage-meter__track {
  background-image: repeating-linear-gradient(
    -45deg,
    var(--ct-neutral-100) 0, var(--ct-neutral-100) 6px,
    var(--ct-neutral-200) 6px, var(--ct-neutral-200) 12px);
}
.ct-usage-meter--unmetered .ct-usage-meter__fill { display: none; }
.ct-usage-meter--unmetered { --_zone-text: var(--ct-text-muted); }
.ct-usage-meter--override { --_zone-text: var(--ct-info-text); }


/* ---------------------------------------------------------------------------
 * feedback/dialog.css  (native <dialog> — focus trap, Esc, backdrop for free)
 * --------------------------------------------------------------------------- */
.ct-dialog {
  padding: 0;
  width: min(520px, calc(100vw - 2 * var(--ct-space-4)));
  max-height: min(85vh, 720px);
  border: 1px solid var(--ct-border);
  border-radius: var(--ct-radius-xl);
  background: var(--ct-surface);
  color: var(--ct-text-body);
  box-shadow: var(--ct-shadow-xl);
  overflow: hidden;
  margin: auto;                       /* center in the viewport */
}

.ct-dialog::backdrop { background: rgba(16, 24, 40, 0.55); }

.ct-dialog[open] { animation: ct-scale-in var(--ct-dur-normal) var(--ct-ease-out); }

.ct-dialog__header {
  display: flex; align-items: flex-start; gap: var(--ct-space-4);
  padding: var(--ct-space-5) var(--ct-space-6);
  border-bottom: 1px solid var(--ct-border);
}
.ct-dialog__title {
  margin: 0; flex: 1;
  font-size: var(--ct-text-xl); font-weight: var(--ct-weight-semibold);
  color: var(--ct-text-strong); line-height: var(--ct-leading-snug);
}
.ct-dialog__desc {
  margin: var(--ct-space-1) 0 0;
  font-size: var(--ct-text-sm); color: var(--ct-text-muted);
}
.ct-dialog__close { margin: -6px -6px 0 0; }

.ct-dialog__body {
  padding: var(--ct-space-6);
  overflow-y: auto;
  font-size: var(--ct-text-base); line-height: var(--ct-leading-normal);
}

.ct-dialog__footer {
  display: flex; justify-content: flex-end; gap: var(--ct-space-3);
  padding: var(--ct-space-4) var(--ct-space-6);
  border-top: 1px solid var(--ct-border);
  background: var(--ct-surface-sunken);
}

/* Responsive: on narrow screens dock to the bottom as a sheet with full width
 * and a large top radius — easier thumb reach on phones. */
@media (max-width: 40rem) {
  .ct-dialog {
    width: 100vw;
    max-width: 100vw;
    margin: auto 0 0;                 /* stick to bottom */
    max-height: 90vh;
    border-radius: var(--ct-radius-xl) var(--ct-radius-xl) 0 0;
  }
  .ct-dialog__footer { position: sticky; bottom: 0; }
}


/* ---------------------------------------------------------------------------
 * advanced/imagefield.css  (single-value image: preview, replace, remove)
 * NOT the multi-file uploader — see the comment in file-upload.css above.
 * --------------------------------------------------------------------------- */
.ct-imagefield {
  display: flex;
  flex-direction: column;
  gap: var(--ct-space-3);
  min-width: 0;
}

/* A <label> so the whole preview is one large click target that opens the
 * picker — the same trick .ct-dropzone uses, so the no-JS path still works. */
.ct-imagefield__frame {
  position: relative;
  display: grid;
  place-items: center;
  padding: var(--ct-space-3);
  /* A backdrop so a transparent PNG has something to sit on and the bounds of
   * the image are visible. Override per use when the asset assumes a specific
   * background — e.g. a logo drawn for white:
   *   .org-logo { --ct-imagefield-bg: #FFFFFF; } */
  background: var(--ct-imagefield-bg, var(--ct-surface-sunken));
  border: 1px solid var(--ct-border);
  border-radius: var(--ct-radius-md);
  cursor: pointer;
  overflow: hidden;
}
.ct-imagefield__frame:focus-within { box-shadow: var(--ct-ring); }

.ct-imagefield__input {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  opacity: 0; cursor: pointer;
}

/* THE CONTRACT: `contain`, never `cover`. A logo must appear exactly as it was
 * uploaded — `cover` fills the frame by cropping, which silently eats the edges
 * of a wordmark. Only --circle opts into cropping, and it says so in its name. */
.ct-imagefield__preview {
  display: block;
  max-inline-size: 100%;
  max-block-size: var(--ct-imagefield-h, 8rem);
  object-fit: contain;
}

/* The scrim is a fixed black/white pair rather than tokens on purpose: it sits
 * on user-supplied artwork, so no theme colour can be guaranteed to contrast. */
.ct-imagefield__overlay {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  padding: var(--ct-space-1) var(--ct-space-2);
  font-size: var(--ct-text-xs);
  font-weight: var(--ct-weight-medium);
  text-align: center;
  color: #FFFFFF;
  background: rgba(0, 0, 0, 0.62);
  opacity: 0;
  transition: opacity var(--ct-dur-fast) var(--ct-ease-out);
}
.ct-imagefield__frame:hover .ct-imagefield__overlay,
.ct-imagefield__frame:focus-within .ct-imagefield__overlay { opacity: 1; }

/* The ONLY variant that crops — right for a face, wrong for a logo. */
.ct-imagefield--circle .ct-imagefield__frame {
  inline-size: var(--ct-imagefield-size, 6rem);
  block-size: var(--ct-imagefield-size, 6rem);
  justify-self: start;
  padding: 0;
  border-radius: 50%;
}
.ct-imagefield--circle .ct-imagefield__preview {
  inline-size: 100%;
  block-size: 100%;
  max-block-size: none;
  object-fit: cover;
}

.ct-imagefield__hint {
  margin: 0;
  font-size: var(--ct-text-xs);
  color: var(--ct-text-muted);
}

.ct-imagefield__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--ct-space-2);
}

/* Live region. Collapses when empty so it never leaves a gap, but stays in the
 * DOM so the announcement lands. */
.ct-imagefield__status {
  margin: 0;
  font-size: var(--ct-text-sm);
  color: var(--ct-text-muted);
}
.ct-imagefield__status:empty { display: none; }
.ct-imagefield__status--error { color: var(--ct-danger-text); }
.ct-imagefield__status--done  { color: var(--ct-success-text); }


/* CloudTeam Design System — AuthLayout (login / signup / password reset)
 * ---------------------------------------------------------------------------
 * The shell for signed-out pages that are FORMS — login, signup, password reset.
 * NOT the shell for signed-out pages in general: terms, privacy, accessibility,
 * public errors are content and belong in .ct-public-shell (public-shell.css).
 *
 * A branded panel beside a single, narrow form column.
 * Below `lg` the panel is dropped entirely and the form takes the screen, so
 * NOTHING essential may live in the panel — it is a pitch, not a step.
 *
 *   <div class="ct-auth">
 *     <aside class="ct-auth__panel">…brand, headline, features…</aside>
 *     <main class="ct-auth__main">
 *       <div class="ct-auth__col">
 *         <a class="ct-auth__brand ct-auth__brand--compact">…</a>  ← below lg only
 *         <h1 class="ct-auth__title">…</h1>
 *         <form class="ct-auth__form">…</form>
 *         <div class="ct-auth__divider"><span>lub</span></div>
 *         <div class="ct-auth__providers">…</div>
 *       </div>
 *     </main>
 *   </div>
 *
 * BRANDING. The panel is themed by CUSTOMER colour, not by our four accents, so
 * it reads from its own custom properties and falls back to the DS accent when
 * nothing is configured — an unbranded single-tenant install needs no setup:
 *
 *   --ct-auth-panel-from / --ct-auth-panel-via / --ct-auth-panel-to
 *   --ct-auth-on-panel     text on the panel — MUST be ≥4.5:1 on it
 *   --ct-auth-cta-from / --ct-auth-cta-to / --ct-auth-on-cta
 *
 * The foreground values are computed by the app, not here: CSS cannot measure
 * contrast against an arbitrary customer hex. See auth-layout.prompt.md.
 *
 * Requires: button.css, field.css, input.css. Optionally imagefield.css.
 * --------------------------------------------------------------------------- */

.ct-auth {
  /* The one input a customer actually supplies. Unset → the product accent. */
  --_ct-auth-brand: var(--ct-auth-brand, var(--ct-primary-600));

  display: grid;
  grid-template-columns: minmax(0, 1fr);
  /* svh, not vh: on mobile Safari/Chrome `100vh` is taller than the visible
   * viewport, which pushes the submit button under the URL bar. */
  min-block-size: 100svh;
  background: var(--ct-surface);
}

/* --- The branded panel -----------------------------------------------------
 * Hidden below `lg`. Decorative + persuasive content only. */
.ct-auth__panel { display: none; }

@media (min-width: 64rem) {
  .ct-auth { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

  .ct-auth__panel {
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    gap: var(--ct-space-10);
    padding: var(--ct-space-10);
    overflow: hidden;
    /* Plain fallback first, for engines without color-mix(). */
    background-image: linear-gradient(
      160deg,
      var(--ct-primary-700), var(--ct-primary-600), var(--ct-primary-800)
    );
    /* Then the real thing: ONE customer hex derives all three stops, because a
     * customer picks one colour, not a gradient. Precedence, highest first:
     *   explicit stop  →  --ct-auth-brand  →  the product accent
     * so an unbranded install needs no configuration at all. */
    background-image: linear-gradient(
      160deg,
      var(--ct-auth-panel-from, color-mix(in oklab, var(--_ct-auth-brand) 88%, #000000)),
      var(--ct-auth-panel-via,  var(--_ct-auth-brand)),
      var(--ct-auth-panel-to,   color-mix(in oklab, var(--_ct-auth-brand) 72%, #000000))
    );
    color: var(--ct-auth-on-panel, #FFFFFF);
    /* The panel can be taller than the viewport on a short laptop screen. */
    overflow-y: auto;
  }
}
@media (min-width: 80rem) {
  .ct-auth__panel { padding: var(--ct-space-16); }
}

/* A soft scrim adds contrast headroom over a light brand colour. It is not a
 * substitute for setting --ct-auth-on-panel correctly — it buys margin, it does
 * not guarantee a ratio. */
.ct-auth__panel::after {
  content: "";
  position: absolute;
  inset: 0;
  background-image: linear-gradient(to top, rgba(0, 0, 0, 0.22), rgba(0, 0, 0, 0.08));
  pointer-events: none;
}
.ct-auth__panel > * { position: relative; z-index: 1; }

.ct-auth__headline {
  margin: 0;
  font-size: var(--ct-text-4xl);
  font-weight: var(--ct-weight-bold);
  line-height: var(--ct-leading-tight);
}
.ct-auth__lede {
  margin: var(--ct-space-3) 0 0;
  font-size: var(--ct-text-md);
  color: var(--ct-auth-on-panel, #FFFFFF);
}

.ct-auth__features {
  display: flex;
  flex-direction: column;
  gap: var(--ct-space-6);
  margin: var(--ct-space-10) 0 0;
  padding: 0;
  list-style: none;
}
.ct-auth__feature { display: flex; align-items: flex-start; gap: var(--ct-space-4); }
.ct-auth__feature-icon {
  display: grid;
  place-items: center;
  inline-size: 40px;
  block-size: 40px;
  flex: none;
  border-radius: var(--ct-radius-md);
  background: color-mix(in oklab, var(--ct-auth-on-panel, #FFFFFF) 16%, transparent);
}
.ct-auth__feature-icon > svg { inline-size: 20px; block-size: 20px; }
.ct-auth__feature-title { margin: 0; font-weight: var(--ct-weight-semibold); }
.ct-auth__feature-desc {
  margin: var(--ct-space-1) 0 0;
  font-size: var(--ct-text-sm);
  color: var(--ct-auth-on-panel, #FFFFFF);
}

.ct-auth__panel-foot {
  margin: 0;
  font-size: var(--ct-text-sm);
  color: var(--ct-auth-on-panel, #FFFFFF);
}

/* NO opacity-tinted text on the panel. Fading a label to 70% of the foreground
 * is the usual way to get hierarchy, and on a brand gradient it is a contrast
 * trap: white at 78% over a mid-tone brand colour lands around 3.6:1, under the
 * 4.5:1 floor, and it fails silently because the headline above it still looks
 * fine. Hierarchy here comes from SIZE and WEIGHT; every panel label is the full
 * --ct-auth-on-panel. The feature icon tile keeps its alpha — it is a surface,
 * not text, so 3:1 applies. */

/* --- The form column -------------------------------------------------------
 * `safe center` centres the column but falls back to flush-start once the form
 * is taller than the viewport — plain `center` would push the top out of reach
 * with no way to scroll to it (a short laptop with the on-screen keyboard up). */
.ct-auth__main {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  align-content: center;
  align-content: safe center;
  gap: var(--ct-space-6);
  padding: var(--ct-space-6);
  min-inline-size: 0;
}
@media (min-width: 40rem) {
  .ct-auth__main { padding: var(--ct-space-10); }
}

.ct-auth__col {
  inline-size: 100%;
  max-inline-size: 24rem;
  display: flex;
  flex-direction: column;
  gap: var(--ct-space-5);
  min-inline-size: 0;
}

/* --- Brand mark ------------------------------------------------------------ */
.ct-auth__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--ct-space-3);
  text-decoration: none;
  color: inherit;
}
.ct-auth__brand img,
.ct-auth__brand svg { block-size: 32px; inline-size: auto; }

/* The panel carries the logo on desktop; below `lg` the panel is gone, so the
 * form column shows it instead. Without this the product is unbranded on every
 * phone — the most common way this layout is actually seen. */
.ct-auth__brand--compact { margin-block-end: var(--ct-space-2); }
@media (min-width: 64rem) {
  .ct-auth__brand--compact { display: none; }
}

/* --- Headings + form ------------------------------------------------------- */
.ct-auth__title {
  margin: 0;
  font-size: var(--ct-text-2xl);
  font-weight: var(--ct-weight-bold);
  color: var(--ct-text-strong);
}
.ct-auth__subtitle {
  margin: var(--ct-space-2) 0 0;
  font-size: var(--ct-text-sm);
  color: var(--ct-text-muted);
}

.ct-auth__form {
  display: flex;
  flex-direction: column;
  gap: var(--ct-space-4);
  min-inline-size: 0;
}

/* Row for "remember me" + "forgot password", which sit on one line until there
 * is no room for both. */
.ct-auth__form-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ct-space-3);
}

/* Optional branded submit. Unbranded, it falls back to --ct-primary-action and
 * its press shade — the two values the token layer GUARANTEES behind white text
 * (the -500 display hue is not one of them: it is a fill colour, AA only at the
 * 3:1 non-text threshold, and teal-500 lands at 3.5:1 under white). */
.ct-auth__cta {
  background-image: linear-gradient(
    to right, var(--ct-primary-action), var(--ct-primary-action-press));
  background-image: linear-gradient(
    to right,
    var(--ct-auth-cta-from, var(--_ct-auth-brand)),
    var(--ct-auth-cta-to,   color-mix(in oklab, var(--_ct-auth-brand) 78%, #000000))
  );
  color: var(--ct-auth-on-cta, #FFFFFF);
  border-color: transparent;
}

/* --- Light brand colours ---------------------------------------------------
 * A customer who picks #FFD400 gets 1.3:1 under white — a 1.4.3 failure that
 * ships unnoticed because it only breaks for that one customer.
 *
 * Setting --ct-auth-on-panel / --ct-auth-on-cta to a computed value is the full
 * answer. This class is the cheap one: apps that cannot compute a colour only
 * have to answer a BOOLEAN — "is the configured brand light?" — and flip the
 * panel to dark ink. A fixed near-black, not a theme token: the panel sits on
 * customer colour, so it must not follow light/dark mode. */
.ct-auth--on-light {
  --ct-auth-on-panel: #14161A;
  --ct-auth-on-cta:   #14161A;
}
/* The scrim darkens; over a light brand with dark ink it works against us. */
.ct-auth--on-light .ct-auth__panel::after {
  background-image: linear-gradient(to top, rgba(255, 255, 255, 0.20), rgba(255, 255, 255, 0.06));
}

/* --- Providers ------------------------------------------------------------- */
.ct-auth__divider {
  display: flex;
  align-items: center;
  gap: var(--ct-space-3);
  font-size: var(--ct-text-sm);
  color: var(--ct-text-muted);
}
.ct-auth__divider::before,
.ct-auth__divider::after {
  content: "";
  flex: 1 1 auto;
  block-size: 1px;
  background: var(--ct-border);
}

.ct-auth__providers {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--ct-space-2);
}
.ct-auth__provider-mark { inline-size: 18px; block-size: 18px; flex: none; }

/* --- Outcome states --------------------------------------------------------
 * "Sprawdź skrzynkę", "Link wygasł", "Hasło zmienione" are the same shape as an
 * empty state, so they reuse .ct-empty rather than duplicating it — with the
 * page padding dropped, because here it already sits inside the 24rem column. */
.ct-auth__col .ct-empty { padding: 0; }

/* --- Password requirements -------------------------------------------------
 * Stated UP FRONT, not only in an error after a failed submit (3.3.2). Tie the
 * list to the input with aria-describedby so it is read on focus; do NOT make
 * it a live region, or it announces on every keystroke. */
.ct-auth__rules {
  display: flex;
  flex-direction: column;
  gap: var(--ct-space-1);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--ct-text-sm);
  color: var(--ct-text-muted);
}
.ct-auth__rule { display: flex; align-items: center; gap: var(--ct-space-2); }
/* Met and unmet differ by GLYPH as well as colour — a green tick against a grey
 * dot, not green text against grey text (1.4.1). */
.ct-auth__rule-mark { inline-size: 16px; block-size: 16px; flex: none; }
.ct-auth__rule--met { color: var(--ct-success-text); }

/* --- Settings strip ---------------------------------------------------------
 * Language and the accessibility trigger, at the foot of the form column.
 *
 * Both belong ON this screen, not behind it. Language: the wrong-language case
 * is exactly the visitor who has not signed in yet. Accessibility: the panel is
 * needed most by a first-time user who cannot read the form at the default text
 * size — the one user who cannot have a saved preference yet. */
.ct-auth__settings {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ct-space-3);
  margin-block-start: var(--ct-space-2);
  padding-block-start: var(--ct-space-4);
  border-block-start: 1px solid var(--ct-border);
}

/* --- Footer + status ------------------------------------------------------- */
.ct-auth__foot {
  margin: 0;
  font-size: var(--ct-text-sm);
  color: var(--ct-text-muted);
  text-align: center;
}

/* Sign-in failures land here. A live region, so it is announced when it appears
 * rather than only being visible. */
.ct-auth__error { margin: 0; }

/* CloudTeam Design System — PublicShell (signed-out content pages)
 * ---------------------------------------------------------------------------
 * The third shell. There are exactly three, and choosing between them is the
 * whole decision:
 *
 *   .ct-app-shell    signed in            sidebar + content
 *   .ct-auth         signed out, a FORM   branded panel + form column
 *   .ct-public-shell signed out, CONTENT  thin header + reading column + footer
 *
 * This one is for the pages a visitor reads without an account: the terms, the
 * privacy policy, the accessibility statement, a public share link, and — the
 * one people forget — an error page reached while signed out, where the app
 * shell is not available to render it in.
 *
 *   <div class="ct-public-shell">
 *     <header class="ct-public-shell__header">
 *       <div class="ct-public-shell__bar">
 *         <a class="ct-public-shell__brand" href="/">…logo…</a>
 *         <div class="ct-public-shell__actions">…Zaloguj się…</div>
 *       </div>
 *     </header>
 *
 *     <main class="ct-public-shell__main" id="main">
 *       <div class="ct-prose">…</div>
 *     </main>
 *
 *     <div class="ct-public-shell__footer"><footer class="ct-footer">…</footer></div>
 *   </div>
 *
 * Requires: footer.css. Optionally prose.css, lang-select.css, button.css.
 * --------------------------------------------------------------------------- */

.ct-public-shell {
  display: flex;
  flex-direction: column;
  /* svh, not vh: on mobile Safari/Chrome 100vh exceeds the visible viewport and
   * pushes the footer under the URL bar. */
  min-block-size: 100svh;
  background: var(--ct-surface);
  min-inline-size: 0;
}

/* A reading measure, not the app measure. --ct-content-max (1400px) is right for
 * a data table and wrong for a policy: at that width a paragraph runs past the
 * point where the eye can find the next line. Raise it per page when the content
 * is not prose — see --wide. */
.ct-public-shell {
  --_measure: var(--ct-public-measure, 48rem);
}

/* --- Bands -----------------------------------------------------------------
 * Header and footer bleed to the viewport edges; their contents align to the
 * same measure as the main column, so the rules run full width while the text
 * stays in one line. */
.ct-public-shell__header {
  padding: var(--ct-space-4) var(--ct-space-6);
  border-block-end: 1px solid var(--ct-border);
}
.ct-public-shell__footer {
  padding-inline: var(--ct-space-6);
  border-block-start: 1px solid var(--ct-border);
}
/* The band already draws the rule, so the footer component must not draw a
 * second one a few pixels below it — and the footer aligns to the same measure
 * as the column above it, or its first link sits to the left of the body text. */
.ct-public-shell__footer .ct-footer {
  border-block-start: 0;
  inline-size: 100%;
  max-inline-size: var(--_measure);
  margin-inline: auto;
}

.ct-public-shell__bar {
  inline-size: 100%;
  max-inline-size: var(--_measure);
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--ct-space-3) var(--ct-space-4);
  min-inline-size: 0;
}

.ct-public-shell__brand {
  display: inline-flex;
  align-items: center;
  gap: var(--ct-space-3);
  text-decoration: none;
  color: inherit;
  font-weight: var(--ct-weight-semibold);
}
.ct-public-shell__brand img,
.ct-public-shell__brand svg { block-size: 28px; inline-size: auto; }

/* The way back in. Never optional — see the prompt doc. */
.ct-public-shell__actions {
  display: inline-flex;
  align-items: center;
  gap: var(--ct-space-3);
}

/* --- The column ------------------------------------------------------------- */
.ct-public-shell__main {
  flex: 1 1 auto;
  inline-size: 100%;
  max-inline-size: var(--_measure);
  margin-inline: auto;
  padding: var(--ct-space-10) var(--ct-space-6);
  min-inline-size: 0;
}

@media (min-width: 40rem) {
  .ct-public-shell__header,
  .ct-public-shell__footer,
  .ct-public-shell__main { padding-inline: var(--ct-space-12); }
}

/* For a public page that is not prose — a catalogue, a shared dashboard. */
.ct-public-shell--wide { --ct-public-measure: var(--ct-content-max); }

/* --- Print ------------------------------------------------------------------
 * A policy page is one of the few things in an app people actually print.
 * footer.css already hides .ct-footer; without this its band would print as an
 * empty ruled strip. */
@media print {
  .ct-public-shell__footer { display: none; }
  .ct-public-shell__header { border-block-end: 0; }
  .ct-public-shell__actions { display: none; }
}


/* CloudTeam Design System — Countdown (split-flap clock)
 * ---------------------------------------------------------------------------
 * Time remaining until a deadline, as per-digit cards with a centre seam.
 *
 * THE VISUAL CLOCK IS DECORATIVE. It is aria-hidden, and the accessible content
 * is the DEADLINE — an absolute <time datetime> — plus threshold announcements.
 * A ticking display put in a live region announces once per second, forever, and
 * makes the page unusable with a screen reader. See countdown.prompt.md, which
 * carries the rules that matter here (including WCAG 2.2.1, which decides
 * whether you are even allowed to run this clock without an extend control).
 *
 *   <div class="ct-countdown" role="group" aria-labelledby="cd-l">
 *     <p class="ct-countdown__label" id="cd-l">Zapisy zamykają się za</p>
 *     <p class="ct-countdown__deadline">
 *       <time datetime="2026-08-12T23:59:00+02:00">12 sierpnia 2026, 23:59</time>
 *     </p>
 *     <div class="ct-countdown__groups" aria-hidden="true">…</div>
 *     <p class="ct-countdown__status" role="status" aria-live="polite"></p>
 *   </div>
 *
 * Requires: nothing.
 * --------------------------------------------------------------------------- */

.ct-countdown {
  /* The face is a fixed dark pair — LITERAL values, not tokens.
   *
   * This is not a stylistic preference, it is the whole reason the defaults are
   * hard-coded: --ct-neutral-900 is #18181B in light mode, #EFF2F8 in dark and
   * #FFFFFF in high-contrast. A card built from it inverts to near-white while
   * the ink stays white — 1.05:1 in dark mode, 1:1 in high-contrast, i.e. the
   * digits vanish. A split-flap is a physical object and should no more invert
   * with the colour mode than a departure board does.
   *
   * Override per use if a brand demands it — and then you own the contrast
   * check against --ct-countdown-ink, in every mode. */
  --_face: var(--ct-countdown-face, #18181B);
  --_ink:  var(--ct-countdown-ink, #FFFFFF);
  /* One knob drives the whole clock's scale. */
  --_size: var(--ct-countdown-size, 3.5rem);

  display: flex;
  flex-direction: column;
  gap: var(--ct-space-2);
  min-inline-size: 0;
}

.ct-countdown__label {
  margin: 0;
  font-size: var(--ct-text-sm);
  color: var(--ct-text-muted);
}

/* The deadline in words. This is the accessible answer to "how long?" — an
 * absolute instant a reader can act on, rather than a number that was true a
 * second ago. Keep it visible: sighted users want it too, and it is the only
 * thing left once the clock hits zero. */
.ct-countdown__deadline {
  margin: 0;
  font-size: var(--ct-text-sm);
  font-weight: var(--ct-weight-medium);
  color: var(--ct-text-strong);
}

/* --- The clock -------------------------------------------------------------- */
.ct-countdown__groups {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--ct-space-2);
  min-inline-size: 0;
}

.ct-countdown__group {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--ct-space-1);
}

.ct-countdown__digits { display: flex; gap: 3px; }

/* One card per digit — so only the digit that changed animates, and the width
 * never depends on which numeral is showing. */
.ct-countdown__digit {
  position: relative;
  display: grid;
  place-items: center;
  inline-size: calc(var(--_size) * 0.72);
  block-size: var(--_size);
  border-radius: calc(var(--_size) * 0.12);
  background: var(--_face);
  color: var(--_ink);
  font-size: calc(var(--_size) * 0.62);
  font-weight: var(--ct-weight-bold);
  line-height: 1;
  /* Without tabular figures the card's contents shift on every tick even though
   * the card does not — the digits appear to jitter in place. */
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  /* A hairline lift so the card still reads as an object on a dark canvas,
   * where face and background are both near-black. Invisible on light. */
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--_ink) 14%, transparent);
}

/* The seam. A split-flap card is two halves; this is the hinge line. */
.ct-countdown__digit::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: 50%;
  block-size: 2px;
  background: rgba(0, 0, 0, 0.38);
  transform: translateY(-1px);
  pointer-events: none;
}

.ct-countdown__sep {
  align-self: center;
  padding-block-end: calc(var(--ct-space-4));
  font-size: calc(var(--_size) * 0.36);
  font-weight: var(--ct-weight-bold);
  color: var(--ct-text-subtle);
  user-select: none;
}

.ct-countdown__unit {
  font-size: var(--ct-text-xs);
  font-weight: var(--ct-weight-semibold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ct-text-muted);
}

/* --- States ----------------------------------------------------------------
 * Urgency is never colour alone: --urgent tints the face, and the copy in
 * __label / __status must say so in words as well (1.4.1). */
.ct-countdown--urgent { --_face: var(--ct-countdown-face-urgent, #9F1239); }

/* Reached zero. The clock stops being the message; the words are. */
.ct-countdown--expired { --_face: #3F3F46; }
.ct-countdown--expired .ct-countdown__digit { opacity: 0.55; }

/* --- Flip ------------------------------------------------------------------
 * The flip is an ENHANCEMENT. Apply .is-flipping to a digit for one frame when
 * its value changes; if you never do, the component still works — the numeral
 * simply swaps. */
@keyframes ct-countdown-flip {
  0%   { transform: perspective(200px) rotateX(0deg); }
  50%  { transform: perspective(200px) rotateX(-90deg); }
  100% { transform: perspective(200px) rotateX(0deg); }
}
.ct-countdown__digit.is-flipping {
  animation: ct-countdown-flip var(--ct-dur-normal) var(--ct-ease-out);
  transform-origin: center center;
}

/* base.css neutralises animation globally under prefers-reduced-motion; this
 * removes the transform outright so no partial rotation can be left painted. */
@media (prefers-reduced-motion: reduce) {
  .ct-countdown__digit.is-flipping { animation: none; transform: none; }
}
[data-reduced-motion="reduce"] .ct-countdown__digit.is-flipping { animation: none; transform: none; }

/* --- Status ---------------------------------------------------------------
 * Threshold announcements only — never the tick. Empty by default so it does
 * not reserve space. */
.ct-countdown__status {
  margin: 0;
  font-size: var(--ct-text-sm);
  color: var(--ct-text-muted);
}
.ct-countdown__status:empty { display: none; }

/* --- Responsive ------------------------------------------------------------
 * One knob. The groups wrap before they shrink, so digits stay legible rather
 * than collapsing to unreadable cards on a phone. */
@media (max-width: 30rem) {
  .ct-countdown { --_size: 2.5rem; }
}

@media print {
  .ct-countdown__groups { display: none; }
}

/* CloudTeam Design System — Reorder (ordered, nestable, draggable list)
 * ---------------------------------------------------------------------------
 * A list whose ORDER IS THE DATA: course modules, lessons inside a module, lab
 * instructions inside a lesson. Reorder by dragging a handle, and — always, not
 * optionally — by move up/down buttons.
 *
 * WCAG 2.5.7 (Dragging Movements, AA in 2.2) requires a single-pointer
 * alternative to any drag. The move buttons ARE that alternative; they are part
 * of the component, not a nicety to add later. A handle-only list fails.
 *
 *   <ol class="ct-reorder" aria-label="Moduły kursu">
 *     <li class="ct-reorder__item">
 *       <button class="ct-reorder__handle" aria-label="Przenieś: Wprowadzenie">…grip…</button>
 *       <div class="ct-reorder__main">…title + meta…</div>
 *       <div class="ct-reorder__moves">…up / down…</div>
 *       <ol class="ct-reorder ct-reorder--nested">…lessons…</ol>
 *     </li>
 *   </ol>
 *   <p class="ct-reorder__status" role="status" aria-live="polite"></p>
 *
 * Relationship to DetailView: .ct-detailview__rows is the FLAT case — a
 * collection whose order carries no meaning. Use this component instead the
 * moment the order is meaningful or the list nests. See reorder.prompt.md.
 *
 * Requires: button.css. Optionally dropdown.css (per-item menus).
 * --------------------------------------------------------------------------- */

.ct-reorder {
  display: flex;
  flex-direction: column;
  gap: var(--ct-space-2);
  margin: 0;
  padding: 0;
  list-style: none;          /* a real <ol>; the number is rendered by __index */
  min-inline-size: 0;
}

.ct-reorder__item {
  position: relative;
  display: grid;
  /* handle · index · main · moves · actions — main is the only flexible track */
  grid-template-columns: auto auto minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--ct-space-3);
  padding: var(--ct-space-3);
  background: var(--ct-surface);
  border: 1px solid var(--ct-border);
  border-radius: var(--ct-radius-md);
  min-inline-size: 0;
}

/* --- Handle ----------------------------------------------------------------
 * A real <button>, so it is reachable and operable from the keyboard even
 * before any drag library is wired up.
 *
 * touch-action: none is what lets a pointer-based drag work on touch — without
 * it the browser claims the gesture for scrolling and the drag never starts.
 * Scope it to the handle, never the row: the row must still scroll the page. */
.ct-reorder__handle {
  display: inline-grid;
  place-items: center;
  inline-size: var(--ct-target-min);
  block-size: var(--ct-target-min);
  padding: 0;
  border: 0;
  border-radius: var(--ct-radius-sm);
  background: none;
  color: var(--ct-text-subtle);
  cursor: grab;
  touch-action: none;
}
.ct-reorder__handle:hover { color: var(--ct-text-body); background: var(--ct-surface-hover); }
.ct-reorder__handle:focus-visible { outline: none; box-shadow: var(--ct-ring); }
.ct-reorder__handle:active { cursor: grabbing; }
.ct-reorder__handle > svg { inline-size: 18px; block-size: 18px; }

/* --- Position --------------------------------------------------------------
 * Decorative: the <ol> already conveys position to assistive tech, so
 * announcing it twice is noise. Mark it aria-hidden. */
.ct-reorder__index {
  min-inline-size: 1.75rem;
  font-size: var(--ct-text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--ct-text-muted);
  text-align: end;
}

.ct-reorder__main { min-inline-size: 0; }
.ct-reorder__title {
  margin: 0;
  font-size: var(--ct-text-sm);
  font-weight: var(--ct-weight-medium);
  color: var(--ct-text-strong);
  overflow-wrap: anywhere;
}
.ct-reorder__meta {
  margin: var(--ct-space-1) 0 0;
  font-size: var(--ct-text-xs);
  color: var(--ct-text-muted);
}

/* --- Move buttons ----------------------------------------------------------
 * The 2.5.7 alternative. Always rendered — never revealed on hover only, which
 * puts them out of reach of touch and of anyone who never hovers. */
.ct-reorder__moves { display: inline-flex; gap: var(--ct-space-1); }

.ct-reorder__actions { display: inline-flex; gap: var(--ct-space-1); }

/* --- Drag states -----------------------------------------------------------
 * .is-dragging goes on the lifted row; .is-over on the row a drop would land
 * before. Both are set by whatever drag library you wire up. */
.ct-reorder__item.is-dragging {
  box-shadow: var(--ct-shadow-md);
  border-color: var(--ct-primary);
  opacity: 0.96;
}
.ct-reorder__item.is-over::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(-1 * var(--ct-space-1) - 1px);
  block-size: 2px;
  border-radius: 2px;
  background: var(--ct-primary);
}

/* A row being persisted. Not `disabled` on the buttons — that would move focus
 * out of the row the user is working in. */
.ct-reorder__item.is-saving { opacity: 0.6; pointer-events: none; }

/* --- Nesting ---------------------------------------------------------------
 * A nested list is a full-width child of its parent row, indented to the depth
 * of the handle so children line up under the content, not under the grip. */
.ct-reorder--nested {
  grid-column: 1 / -1;
  margin-block-start: var(--ct-space-3);
  padding-inline-start: var(--ct-space-6);
  border-inline-start: 2px solid var(--ct-border);
  gap: var(--ct-space-2);
}
.ct-reorder--nested .ct-reorder__item { background: var(--ct-surface-sunken); }
/* Two levels of nesting is the practical limit for a drag surface. Past that
 * the drop targets are smaller than a fingertip and the tree stops being
 * legible — give the third level its own page. */
.ct-reorder--nested .ct-reorder--nested .ct-reorder--nested { padding-inline-start: 0; border: 0; }

/* --- Status ----------------------------------------------------------------
 * Every move must be announced: "Wprowadzenie — pozycja 2 z 5". A reorder that
 * only manifests visually is invisible to a screen-reader user who just pressed
 * the button that caused it (4.1.3). */
.ct-reorder__status {
  margin: var(--ct-space-2) 0 0;
  font-size: var(--ct-text-sm);
  color: var(--ct-text-muted);
}
.ct-reorder__status:empty { display: none; }

/* --- Narrow screens --------------------------------------------------------
 * The row keeps its handle and its moves — they are the function of the
 * component. The meta line wraps under the title instead. */
@media (max-width: 34rem) {
  .ct-reorder__item { grid-template-columns: auto minmax(0, 1fr) auto; }
  .ct-reorder__index { display: none; }
  .ct-reorder__actions { grid-column: 1 / -1; justify-content: flex-end; }
  .ct-reorder--nested { padding-inline-start: var(--ct-space-3); }
}

/* ===========================================================================
 * PORTAL DEVIATIONS — the ONLY place this file may diverge from the DS.
 * Everything above is verbatim, so re-syncing is a copy/paste and these
 * overrides survive it. Each entry says why, and whether it should go upstream.
 * =========================================================================== */

/* (Deviation 1 was retired on 2026-08-01. It pushed .ct-topbar__actions to the
 *  inline-end edge "because the DS relies on .ct-topbar__search to do it" — but
 *  the real cause was that layout/topbar.css had never been vendored at all, so
 *  neither rule was present. With the component in place the search's flex-grow
 *  does the job, exactly as the DS intends. A deviation that patches over a
 *  missing component is the shape this mistake takes; check the section exists
 *  before writing one.) */

/* 2. .ct-table-wrap needs `position: relative`.
 *    NOT because .ct-sr-only exists in a <th> — /labs has one and never overflows. The trigger is
 *    an absolutely-positioned descendant whose STATIC POSITION lands beyond the viewport, which
 *    happens once the table is wider than the screen. .ct-sr-only is position:absolute !important
 *    (base.css) with no containing block of its own, so offsetParent resolves to <body>: it is
 *    positioned against the INITIAL containing block, escapes the wrap's overflow-x clip, and
 *    joins the DOCUMENT's scrollable area.
 *    Measured at a 420px viewport, /company/users (6 columns, actions column at x≈937):
 *      without this rule  document.scrollWidth = 938  (page scrolls sideways)
 *      with this rule     document.scrollWidth = 420
 *    /labs is unaffected only because its actions column happens to land at x≈390, inside the
 *    viewport. Any table wide enough reproduces it. */
.ct-table-wrap { position: relative; }

/* (Deviations 4 and 5 were retired on 2026-07-29: the DS now owns footer
 *  placement via .ct-app-shell__content > .ct-footer, and the operator shell no
 *  longer has a topbar, so there is no hamburger left to re-assert.) */

/* 6. .ct-public-shell__main content is narrower than the header's and footer's.
 *    The measure and the gutter sit on the SAME element:
 *      .ct-public-shell__main { max-inline-size: var(--_measure); padding-inline: … }
 *    and box-sizing is border-box, so the gutter eats the measure — the content box comes out at
 *    measure MINUS twice the padding. The two bands do it the other way round: the padding is on
 *    .ct-public-shell__header / __footer and the measure on the inner __bar / .ct-footer, so their
 *    content box is the measure exactly.
 *    Measured with --wide (measure = --ct-content-max = 1400px), viewport ≥ 40rem:
 *      header / footer content   1400px
 *      main content              1304px   (1400 − 2 × --ct-space-12)
 *    The app shell has it right and is the reference the public shell should match:
 *    .ct-app-shell__main carries the padding, .ct-app-shell__content carries the measure and no
 *    padding, so its content box is --ct-content-max exactly. This restores that on the public
 *    shell by growing the border box instead of restructuring the markup.
 *    UPSTREAM: yes — the DS's own comment says the bands "align to the same measure as the main
 *    column", which is the behaviour this produces and not the one it ships. */
.ct-public-shell__main {
  max-inline-size: calc(var(--ct-public-measure, 48rem) + 2 * var(--ct-space-6));
}
@media (min-width: 40rem) {
  .ct-public-shell__main {
    max-inline-size: calc(var(--ct-public-measure, 48rem) + 2 * var(--ct-space-12));
  }
}

/* 7. .ct-a11y-panel must not set `display` (or `overflow`) outside :popover-open.
 *
 *    REWRITTEN 2026-08-05. The old deviation solved a real problem — the panel's Save and Reset
 *    scrolled out of sight — with a sticky action bar. The DS has since fixed the same problem the
 *    other way, and its way is better: three regions, header and actions `flex: none`, and only
 *    __body scrolling. A sticky bar overlays content it then has to compensate for (z-index, an
 *    opaque background, scroll-padding-block-end); a footer that is a SIBLING of the scrollport
 *    never overlaps anything and needs none of it. The DS's structure is now vendored above and
 *    the sticky bar is gone.
 *
 *    What remains is one property, and it is an upstream bug the portal has already been bitten by.
 *    The DS's .ct-a11y-panel rule sets `display: flex` and `overflow: hidden` unconditionally. A
 *    closed popover is hidden by the UA stylesheet's `[popover]:not(:popover-open) { display: none }`
 *    — a normal, non-important UA declaration, which any author declaration outranks by origin. So
 *    an unscoped `display: flex` un-hides the panel permanently, and because a closed popover is not
 *    in the top layer it renders inline, mid-page, on every layout. That is not a hypothesis: it is
 *    what shipped here on 2026-08-04 and had to be reverted.
 *
 *    `overflow` is scoped alongside it for the no-Popover-API case: with no `display: flex` the
 *    panel is a block box, and `overflow: hidden` against `max-height` would clip the option groups
 *    with no way to scroll to them. Scoped like this, a browser that cannot open the panel never
 *    sees either rule.
 *
 *    UPSTREAM: yes — scope both to `.ct-a11y-panel:popover-open` in a11y-panel.css. The other four
 *    properties in that rule (width, max-width, max-height, padding) are safe unscoped and stay
 *    where they are. Deviation implemented at the component above, not here, because it has to sit
 *    in the same cascade position as the rule it corrects. */

/* 8. Breathing room between the page and the footer's rule.
 *    The shell stacks .ct-app-shell__body over .ct-footer and pushes the footer
 *    down with margin-top:auto — which is ZERO the moment a page is taller than
 *    the viewport, so the last card sits flush against the footer's border-top.
 *    Nothing else in the chain owns that gap: the body has no bottom padding,
 *    the footer's own padding is BELOW its border, and page components
 *    (formview, listview) carry no trailing margin by design. Every page tall
 *    enough reproduces it; the users/create form is where it was noticed.
 *    UPSTREAM: yes — app-shell.css should give __body a block-end padding. */
.ct-app-shell__body { padding-block-end: var(--ct-space-8); }
