/* KarateShowCase tenant — tokens-fonts
 *
 * Generated by tools/consolidate-tenant-css.mjs from 2 source files,
 * concatenated in their original cascade order. Edit the sources, re-run the
 * tool; do not hand-edit this file.
 */

/* ══════════════════════════════════════════════════════════════════════
   dojo-fonts.v1.css
   ══════════════════════════════════════════════════════════════════════ */
/* Playfair Display and Raleway, served from this origin.
 *
 * Cinzel was retired with the design-system migration: it was the display face
 * of the legacy gold theme, and the platform now uses one type system built on
 * Raleway. Its 8 @font-face blocks and 2 woff2 files (40 KB) went once a
 * whole-repository scan showed zero consumers — declarations only.
 *
 * These were three <link> tags to fonts.googleapis.com, which is a stylesheet on
 * the critical path: a pending stylesheet blocks every classic <script> after it,
 * and this app has 38 of them, so the page could not finish parsing until Google
 * answered. Captured in a trace with time -1 and status -1 -- a request that
 * never completed -- while a navigation that normally takes 200ms timed out.
 *
 * Generated from the exact CSS Google serves a current browser, so the faces,
 * weights, unicode-ranges and font-display: swap are unchanged. A browser still
 * downloads only the files for the families and ranges it actually renders.
 *
 * All three families are SIL Open Font License 1.1, which permits redistribution.
 * Regenerate with tools/selfhost-fonts.mjs if a weight or family is added.
 */
/* latin-ext */

/* latin */

/* latin-ext */

/* latin */

/* latin-ext */

/* latin */

/* latin-ext */

/* latin */

/* cyrillic-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCAIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* cyrillic */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCkIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* vietnamese */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCIIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCMIT4ttDfCmxA.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;
}

/* latin */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyC0IT4ttDfA.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;
}

/* cyrillic-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCAIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* cyrillic */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCkIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* vietnamese */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCIIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCMIT4ttDfCmxA.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;
}

/* latin */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyC0IT4ttDfA.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;
}

/* cyrillic-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCAIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* cyrillic */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCkIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* vietnamese */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCIIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCMIT4ttDfCmxA.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;
}

/* latin */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyC0IT4ttDfA.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;
}

/* cyrillic-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCAIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* cyrillic */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCkIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* vietnamese */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCIIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCMIT4ttDfCmxA.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;
}

/* latin */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 600;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyC0IT4ttDfA.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;
}

/* cyrillic-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCAIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}

/* cyrillic */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCkIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* vietnamese */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCIIT4ttDfCmxA.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyCMIT4ttDfCmxA.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;
}

/* latin */
@font-face {
  font-family: 'Raleway';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/v37-1Ptug8zYS_SKggPNyC0IT4ttDfA.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;
}

/* cyrillic */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTjYgEM86xRbPQ.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* vietnamese */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTPYgEM86xRbPQ.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTLYgEM86xRbPQ.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;
}

/* latin */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTzYgEM86xQ.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;
}

/* cyrillic */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTjYgEM86xRbPQ.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* vietnamese */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTPYgEM86xRbPQ.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTLYgEM86xRbPQ.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;
}

/* latin */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 800;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTzYgEM86xQ.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;
}

/* cyrillic */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTjYgEM86xRbPQ.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}

/* vietnamese */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTPYgEM86xRbPQ.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}

/* latin-ext */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTLYgEM86xRbPQ.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;
}

/* latin */
@font-face {
  font-family: 'Playfair Display';
  font-style: normal;
  font-weight: 900;
  font-display: swap;
  src: url(/assets/fonts/v40-nuFiD-vYSZviVYUb_rj3ij__anPXDTzYgEM86xQ.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;
}


/* ══════════════════════════════════════════════════════════════════════
   dojo-global-theme-tokens.v1.css
   ══════════════════════════════════════════════════════════════════════ */
    /* ══════════════════════════════════════════════════════════════════════════
       THEME TOKENS — the semantic layer both themes are expressed in.

       dojo-global-foundation-reset-tokens.v1.css already owns the scales that do
       not change with the theme: spacing, radii, z-index, motion, and the raw
       brand palette. Nothing here duplicates those. What it does not have is a
       set of names for the *roles* a colour plays -- page background, card
       surface, hairline, primary text -- which is what makes one stylesheet able
       to render light or dark without knowing which it is in.

       The values are not invented. The light set is the palette proven in
       dojo-admin-dashboard-shell.v1.css, which is the most recent and most
       deliberate design pass in the repo; the dark set is the site's existing
       navy-and-gold, so a surface that opts in keeps looking like itself.

       Contract:
         html[data-theme="light"]   force light
         html[data-theme="dark"]    force dark
         no attribute               follow the OS, resolving to light
       Light is the default because it is the product's primary target. That only
       reaches a surface once its stylesheet consumes these names, so declaring
       them changes nothing on its own -- which is the point of landing the layer
       before anything depends on it.
       ══════════════════════════════════════════════════════════════════════════ */

    /* ── Light ──────────────────────────────────────────────────────────────── */
    /* THE TWO ADMIN WORKSPACES OPT IN HERE, DELIBERATELY LIGHT-ONLY.
     *
     * Decided 2026-08-07 against the alternative of a second dark migration.
     * The authenticated audit found ELEVEN contrast failures on the dark admin
     * tabs, worst 1.09:1 -- text that is invisible rather than dim. None of
     * those colours are in the file that was just migrated; they are in
     * dojo-tenant-admin-dashboard.v1.css, dojo-platform-owner-dashboard.v1.css,
     * ks-shipped-surfaces.v1.css and seventeen inline styles. Fixing them all
     * is a migration the size of the one just finished. The light workspace, by
     * contrast, is already coherent and tokenized.
     *
     * BY SELECTOR, NOT BY VALUE, and that is the whole point. Restating these
     * 37 declarations on #view-admin would create a parallel palette scoped to
     * the admin -- exactly what dojo-admin-dashboard-shell.v1.css records as its
     * own past failure, where --adm-ink-3 drifted to #98A2B3 and shipped labels
     * at 2.58:1. Naming the views here keeps one definition.
     *
     * Specificity is what makes it work: #view-admin is (1,0,0) and beats
     * :root[data-theme="dark"] and :root:not([data-theme]), both (0,2,0), in any
     * source order. Custom properties then inherit to the whole subtree.
     *
     * WHAT EACH SELECTOR ACTUALLY DOES -- they are not the same:
     *   #view-admin           a real behaviour change. It followed the theme
     *                         through the shell's --adm-* aliases; now it does
     *                         not.
     *   #view-platform-owner  light by construction since 2026-08-16: the
     *                         console's stylesheet reads only --ks-* semantic
     *                         tokens now, so this pin is what resolves them to
     *                         the light values regardless of the user's theme.
     *   #view-showroom        the same situation, found 2026-08-31. The product
     *                         tour paints its cards from var(--ks-white) and its
     *                         notice from the warm paper pair -- light by
     *                         construction -- while #view-showroom itself took
     *                         --ks-canvas and --ks-ink from the theme. In dark
     *                         mode that put near-white ink on hard-white cards:
     *                         .sr-athlete-name and .sr-block-lede measured
     *                         1.37:1, and tools/audit-rendered-contrast.mjs
     *                         counted 40 elements below threshold on
     *                         /demo-dojo/tour. This is the surface a PROSPECT is
     *                         shown, so it was the worst place on the platform to
     *                         be light by accident.
     *                         (It used to paint itself from 75 literals and be
     *                         light by accident; the pin predates the
     *                         migration and made it safe.)
     *
     * DELIBERATELY NOT COVERED: surfaces inside these views that are meant to be
     * dark in both themes keep their own treatment, because they are painted
     * from --ks-band-*, declared in a theme-independent :root block further
     * down. The admin topbar and the dashboard weekday strip are both that, and
     * both must stay inverse.
     *
     * TO UNDO: delete the two selectors and add 'dark' to SCHEMES in
     * tests/design/admin-dashboard-contrast.spec.ts. Nothing else changes.
     */
    :root,
    :root[data-theme="light"],
    #view-admin,
    #view-platform-owner,
    #view-showroom {
      /* Surfaces, back to front. Cool, not cream. The warm paper palette read as a dated brochure under
         a navy band -- yellow-grey next to blue is the one neighbourhood where
         warm neutrals look cheap. These are the same lightness steps in a cool
         hue, so every contrast pair the tool checks holds. */
      --ks-canvas: #F3F5F9;
      --ks-surface: #FFFFFF;
      --ks-surface-raised: #FFFFFF;
      --ks-surface-sunken: #EBEFF6;

      /* The band-adjacent tone. Named -warm from the cream era; it is the
         cool step now and the name is kept so no stylesheet has to change. */
      --ks-canvas-warm: #E7ECF4;

      /* Separators. -soft is for divisions inside a card, where a full hairline
         reads as a second card rather than a grouping. */
      --ks-line: #DCE3ED;
      --ks-line-soft: #E9EEF5;
      /* The edge of an INTERACTIVE control -- select, text input, outline
         button -- which has to read as a thing you can operate rather than as a
         division between sections. The admin shell drew that distinction with
         #D1D5DB against #E5E7EB and it is a real one, so the layer needs a name
         for it; without a third step the migration would flatten every control
         edge into a divider. Valued at the platform's existing --ks-n-300, the
         nearest system step to what the admin shell already painted, so this
         names current appearance rather than changing it.

         HONEST LIMIT: at 1.5:1 on white this does not meet WCAG 1.4.11's 3:1
         for non-text contrast, and neither did #D1D5DB. Naming the step does
         not fix that. Darkening it is a visual-design decision across every
         control on the platform, not something a token migration should do
         quietly. Recorded so the next pass can take it deliberately. */
      /* 3.40:1 on --ks-surface and 3.11:1 on --ks-canvas, and it is a control
         boundary rather than decoration — WCAG 1.4.11 wants 3:1 for the visual
         information that identifies a component. Nearly every use of this token
         is a text input, a select or an outlined button, and the FILL cannot
         carry that job: --ks-surface on --ks-canvas measures 1.09:1, so the
         border is the only thing telling a reader where the field is.
         #C9D1DE measured 1.54:1 and 1.41:1. This is the lightest value in the
         same blue-grey family that clears both adjacencies, chosen so the
         change buys the requirement and no more of the design than it must. */
      --ks-line-strong: #7F8CA3;

      /* Text, most to least prominent. -3 is for metadata, but it still clears
         4.5:1 on both the surface and the canvas: it is the quietest step that
         is safe at any size, not a step that is quiet by being unreadable. The
         admin shell's #98A2B3 manages only 2.58:1 and is not carried over. */
      --ks-ink: #101828;
      --ks-ink-2: #475467;
      --ks-ink-3: #5D6B82;
      --ks-ink-inverse: #FFFFFF;

      /* Brand — the DOJO's, not the platform's.
         These were the gold literals of the legacy theme, which meant every
         tenant shipped the same brand colour no matter what they chose. They
         now defer to the platform accent, which resolves from --tn-accent and
         falls back to the platform blue.

         The distinction the gold comment drew still holds and is why there are
         three names rather than one: --ks-accent is a FILL and can be any
         lightness; --ks-accent-strong is the same colour as TEXT on a light
         surface, so it is the lightness-capped form; --ks-on-accent is the ink
         that sits on the fill and is derived from the fill's own lightness. */
      --ks-accent: var(--accent);
      --ks-accent-strong: var(--accent-on-surface);
      --ks-accent-soft: color-mix(in oklab, var(--accent) 12%, transparent);
      --ks-on-accent: var(--accent-ink);

      /* Status. Carried as roles so a surface never reaches for a raw hex. */
      --ks-success: #12784A;
      --ks-warning: #B54708;
      --ks-danger: #B42318;
      --ks-info: #175CD3;
      /* ── Tinted states ──────────────────────────────────────────────────────
         Backgrounds and borders for the status and medal chips. They live here
         rather than in the sheets that use them because a chip painted with a
         fixed light tint is unreadable the moment the page is dark, and this is
         the one file where a value already knows which theme it is in.

         Named for the values already in the markup: data-medal="1" reads
         --ks-medal-1-*, so a chip cannot be tinted gold while claiming silver. */
      --ks-success-bg: #ECFDF5;
      --ks-success-line: #A7F3D0;
      --ks-warning-bg: #FFFBEB;
      --ks-warning-line: #FDE68A;
      --ks-danger-bg: #FEF2F2;
      --ks-danger-line: #FECACA;
      /* Completes the trio family for --ks-info, which existed alone — the
         platform console's informational notices are its first customer. */
      --ks-info-bg: #EFF6FF;
      --ks-info-line: #BFDBFE;
      --ks-medal-1-bg: #FDF6E3;
      --ks-medal-1-line: #E3C46B;
      --ks-medal-1-ink: #6B4E10;
      --ks-medal-2-bg: #F4F5F7;
      --ks-medal-2-line: #C7CDD6;
      --ks-medal-2-ink: #414B57;
      --ks-medal-3-bg: #FBF0E7;
      --ks-medal-3-line: #D9A87C;
      --ks-medal-3-ink: #6E3D18;

      /* Elevation. Three steps only: resting, hover, and floating (menus,
         popovers). More than three stops reading as depth and starts reading
         as noise. */
      --ks-shadow-1: 0 1px 2px rgba(16, 24, 40, .04), 0 1px 3px rgba(16, 24, 40, .06);
      --ks-shadow-2: 0 4px 8px -2px rgba(16, 24, 40, .08), 0 2px 4px -2px rgba(16, 24, 40, .04);
      --ks-shadow-3: 0 12px 24px -6px rgba(16, 24, 40, .12), 0 4px 8px -4px rgba(16, 24, 40, .06);

      /* The ring is its own token because focus has to stay visible against both
         a white card and a navy header, which no single surface colour does. */
      --ks-focus-ring: var(--accent-on-surface);

      color-scheme: light;
    }

    /* ── Dark ───────────────────────────────────────────────────────────────── */
    :root[data-theme="dark"] {
      --ks-canvas: #050A14;
      --ks-surface: #0A1324;
      --ks-surface-raised: #111C33;
      --ks-surface-sunken: #04080F;
      --ks-canvas-warm: #0A1324;

      --ks-line: rgba(255, 255, 255, .09);
      --ks-line-soft: rgba(255, 255, 255, .05);
      /* The control edge, one step up from the divider, same as the light set. */
      --ks-line-strong: rgba(255, 255, 255, .18);

      --ks-ink: #E0DCD2;
      --ks-ink-2: #9EADC6;
      /* Same rule as the light theme: the existing --dim (#5A6E8A) reaches only
         3.56:1 here, so the tertiary step is lightened until it is safe small. */
      --ks-ink-3: #7C8FAB;
      --ks-ink-inverse: #050A14;

      --ks-accent: var(--accent);
      --ks-accent-strong: var(--accent-on-inverse);
      --ks-accent-soft: color-mix(in oklab, var(--accent) 15%, transparent);
      --ks-on-accent: var(--accent-ink);

      /* Lifted off the light values: the darker greens and reds go muddy against
         a navy surface, so each is the lighter step of the same hue. */
      --ks-success: #5BD98A;
      --ks-warning: #F5A55B;
      --ks-danger: #FF8A80;
      --ks-info: #7CB8FF;
      /* Tinted states, dark. The same hues read as a wash over the dark canvas
         rather than as a light card sitting on it. */
      --ks-success-bg: #10251C;
      --ks-success-line: #2F6B4C;
      --ks-warning-bg: #2A1F10;
      --ks-warning-line: #6B4E1F;
      --ks-danger-bg: #2A1414;
      --ks-danger-line: #6B2F2F;
      --ks-info-bg: #0F1D33;
      --ks-info-line: #2F4A6B;
      --ks-medal-1-bg: #2A2314;
      --ks-medal-1-line: #6B5A2A;
      --ks-medal-1-ink: #E8C76E;
      --ks-medal-2-bg: #1E232A;
      --ks-medal-2-line: #4A5563;
      --ks-medal-2-ink: #C7CDD6;
      --ks-medal-3-bg: #2A1F16;
      --ks-medal-3-line: #6E4E30;
      --ks-medal-3-ink: #E0B48A;

      /* Shadow alone does not separate layers on a dark canvas, so these stay
         soft and the surface steps above do the real work. */
      --ks-shadow-1: 0 1px 2px rgba(0, 0, 0, .4);
      --ks-shadow-2: 0 4px 12px rgba(0, 0, 0, .45);
      --ks-shadow-3: 0 16px 40px rgba(0, 0, 0, .55);

      --ks-focus-ring: var(--accent-on-inverse);

      color-scheme: dark;
    }

    /* A reader who has asked their OS for dark and has expressed no preference
       here gets dark. An explicit data-theme is a choice and still wins, because
       this only matches when the attribute is absent. */
    @media (prefers-color-scheme: dark) {
      :root:not([data-theme]) {
        --ks-canvas: #050A14;
        --ks-surface: #0A1324;
        --ks-surface-raised: #111C33;
        --ks-surface-sunken: #04080F;
        --ks-canvas-warm: #0A1324;

        --ks-line: rgba(255, 255, 255, .09);
        --ks-line-soft: rgba(255, 255, 255, .05);
        --ks-line-strong: rgba(255, 255, 255, .18);

        --ks-ink: #E0DCD2;
        --ks-ink-2: #9EADC6;
        --ks-ink-3: #7C8FAB;
        --ks-ink-inverse: #050A14;

        --ks-accent: var(--accent);
        --ks-accent-strong: var(--accent-on-inverse);
        --ks-accent-soft: color-mix(in oklab, var(--accent) 15%, transparent);
        --ks-on-accent: var(--accent-ink);

        --ks-success: #5BD98A;
        --ks-warning: #F5A55B;
        --ks-danger: #FF8A80;
        --ks-info: #7CB8FF;
        /* Tinted states, dark. The same hues read as a wash over the dark canvas
           rather than as a light card sitting on it. */
        --ks-success-bg: #10251C;
        --ks-success-line: #2F6B4C;
        --ks-warning-bg: #2A1F10;
        --ks-warning-line: #6B4E1F;
        --ks-danger-bg: #2A1414;
        --ks-danger-line: #6B2F2F;
        --ks-info-bg: #0F1D33;
        --ks-info-line: #2F4A6B;
        --ks-medal-1-bg: #2A2314;
        --ks-medal-1-line: #6B5A2A;
        --ks-medal-1-ink: #E8C76E;
        --ks-medal-2-bg: #1E232A;
        --ks-medal-2-line: #4A5563;
        --ks-medal-2-ink: #C7CDD6;
        --ks-medal-3-bg: #2A1F16;
        --ks-medal-3-line: #6E4E30;
        --ks-medal-3-ink: #E0B48A;

        --ks-shadow-1: 0 1px 2px rgba(0, 0, 0, .4);
        --ks-shadow-2: 0 4px 12px rgba(0, 0, 0, .45);
        --ks-shadow-3: 0 16px 40px rgba(0, 0, 0, .55);

        --ks-focus-ring: var(--accent-on-inverse);

        color-scheme: dark;
      }
    }

    /* ── The band ───────────────────────────────────────────────────────────────
       The dark navy foundation the product is built on: the fixed nav, the band
       at the top of every screen, and the footer. Deliberately outside the
       light/dark pair above -- these are brand, like the footer already was, and
       they read the same in either theme. A page is a dark band, a bright
       surface to work on, and a dark foot.

       Restrained on purpose. The Americana is two low-opacity sweeps at the
       band's far edge and a gold hairline along its foot; anything stronger
       stops being a championship identity and starts being a flag. Nothing in
       this group is used on a content surface.
       ══════════════════════════════════════════════════════════════════════════ */
    :root {
      --ks-band: #0A1324;
      --ks-band-deep: #050A14;
      --ks-band-raised: #111C33;

      /* Text on the band. -3 still clears 4.5:1 on --ks-band, which the footer's
         old rgba(255,255,255,.7) did not at small sizes. */
      --ks-band-ink: #FFFFFF;
      --ks-band-ink-2: #C6D0E0;
      --ks-band-ink-3: #9DAAC0;

      --ks-band-line: rgba(255, 255, 255, .10);
      /* The hairline along the foot of every band, and the one accent edge in
         the system that is always visible. It was gold for every dojo; it is
         the tenant's own colour now, lightness-floored because it sits on the
         band. */
      --ks-band-edge: color-mix(in oklab, var(--accent-on-inverse) 45%, transparent);
      --ks-band-accent: var(--accent-on-inverse);
      --ks-band-accent-soft: color-mix(in oklab, var(--accent) 14%, transparent);

      /* The two Americana notes. Used only as large, soft, low-opacity washes
         inside a band -- never as a fill, a border or a text colour. */
      --ks-band-red: rgba(178, 34, 52, .30);
      --ks-band-blue: rgba(28, 62, 128, .34);
    }

    /* ── Role badges ────────────────────────────────────────────────────────────
       A stable categorical set, owned by the platform and identical at every
       dojo. A role is not a brand: a coach badge that changed colour from one
       tenant to the next would stop being an identifier and start being
       decoration, and somebody who has learned "green means competitor" would
       have to learn it again per dojo.

       Which is why admin is NOT the tenant accent. It was var(--gold) — the
       legacy theme token — so it moved with a theme rather than standing for a
       role. It is its own value now, in the same family, and fixed.

       All four are read on the nav band and clear AA there: admin 10.86:1,
       coach 7.23:1, competitor 6.67:1, parent 7.83:1.
       ═══════════════════════════════════════════════════════════════════════════ */
    :root {
      --ks-role-admin: #E8C76E;
      --ks-role-coach: #5DADE2;
      --ks-role-competitor: #3CB371;
      --ks-role-parent: #9EADC6;
    }

    /* ── The destructive mark ───────────────────────────────────────────────────
       A filled warning triangle, carried as a mask so it takes currentColor and
       is correct in both themes with one definition. It lives here rather than
       in the button sheet because it is the platform's symbol for "this
       destroys something", and the confirm dialog and inline warnings should be
       able to draw the same one.

       A mask rather than an <img>: an icon that cannot follow the text colour
       ends up black on a danger-coloured button the first time somebody themes
       it, and a destructive action is the worst place to discover that.
       ═══════════════════════════════════════════════════════════════════════════ */
    :root {
      --ks-danger-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='M12 2.2 1.2 21.1h21.6L12 2.2Zm-1.05 6.3h2.1v6.3h-2.1V8.5Zm0 7.9h2.1v2.1h-2.1v-2.1Z'/%3E%3C/svg%3E");
    }

    /* ── Type ───────────────────────────────────────────────────────────────────
       One scale, so a heading is chosen from a system rather than typed as a
       number. Every step is fluid with a deliberate maximum -- the previous
       sizes were uniformly small and light, which reads as a document rather
       than as a product, and no amount of layout fixes thin 13px text in a
       1440px column.

       Display is for flagship titles only (the band). Everything else is the
       application scale, and it starts at 1rem rather than the .82-.9rem the
       cards had been using.
       ══════════════════════════════════════════════════════════════════════════ */
    :root {
      --ks-display-1: clamp(2.3rem, 4.9vw, 3.7rem);
      --ks-display-2: clamp(1.9rem, 4vw, 3rem);
      --ks-title-1: clamp(1.4rem, 2.6vw, 1.95rem);
      --ks-title-2: clamp(1.1rem, 1.8vw, 1.35rem);
      --ks-body-lg: clamp(1rem, 1.4vw, 1.15rem);
      --ks-body: 1rem;
      --ks-body-sm: .9rem;
      --ks-label: .72rem;

      --ks-weight-display: 700;
      --ks-weight-title: 800;
      --ks-weight-action: 800;
    }

    /* ── Layout ─────────────────────────────────────────────────────────────── */
    :root {
      /* Content stops widening here. Full-bleed text on a wide monitor is the
         single clearest tell of a template, and the mockups all hold a column. */
      --ks-measure: 1200px;
      --ks-measure-narrow: 760px;

      /* One gutter that grows with the viewport, so a surface does not need a
         breakpoint just to breathe. */
      --ks-gutter: clamp(1rem, 4vw, 2.5rem);

      /* Vertical rhythm between major sections of a page. */
      --ks-section-gap: clamp(2.5rem, 6vw, 5rem);

      /* Height of the fixed site header. A band at the top of a view adds this
         to its own padding rather than trusting an ancestor to reserve it: the
         view shell's 72px does not reach the page, so a component that assumed
         it had its first line printed underneath the header. */
      --ks-nav-h: 72px;
    }
    /* Under the command-dock breakpoint the fixed header is 60px
       (dojo-responsive-command-dock-mobile.v1.css, `body>nav`), and a band
       that reserved 72 would print twelve blank pixels under it. */
    @media (max-width: 900px) {
      :root {
        --ks-nav-h: 60px;
      }
    }

    /* ── Breakpoints ────────────────────────────────────────────────────────────
       Custom properties cannot be used in a media query, so these are a written
       convention rather than a token. Existing stylesheets carry seven different
       widths between them; new and restyled surfaces use only these four, and a
       legacy file adopts them when it is next touched rather than in a sweep.

         (min-width:  640px)   sm   two-up grids
         (min-width:  960px)   md   horizontal nav, three-up grids, side rails
         (min-width: 1280px)   lg   content reaches --ks-measure
         (min-width: 1600px)   xl   wider gutters only, never more columns

       Mobile-first: style the phone case unprefixed, then add from sm up. The
       existing files are desktop-first with max-width queries, which is why the
       two must not be interleaved inside one stylesheet.
       ══════════════════════════════════════════════════════════════════════════ */
