/* Self-hosted type.
 *
 * These loaded from fonts.googleapis.com until iteration 27. That is remote
 * CSS parsed inside this origin on every page load, including the wizard at
 * /onboard/:token, which is the page where a client types their social media
 * passwords. SRI cannot protect it: the css2 endpoint returns different bytes
 * per User-Agent, so any integrity hash breaks on the next browser release.
 *
 * It also disclosed every client's IP address to a US company on every visit,
 * which is an APP 8 cross-border disclosure nobody had assessed or told them
 * about, and it broke fonts in the installed app because the service worker
 * correctly refuses to cache cross-origin requests.
 *
 * Seven weights, latin subset, 160kb total, served from the host we already
 * pay for. The shared-CDN-cache argument for hosted fonts died in 2020 when
 * browsers partitioned the HTTP cache by origin.
 *
 * swap, not block: text is readable immediately in a fallback and reflows
 * when the real face arrives, which is the right trade on a slow connection.
 */
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 400; font-display: swap; src: url('/assets/fonts/inter-latin-400-normal.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 500; font-display: swap; src: url('/assets/fonts/inter-latin-500-normal.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 600; font-display: swap; src: url('/assets/fonts/inter-latin-600-normal.woff2') format('woff2'); }
@font-face { font-family: 'Inter'; font-style: normal; font-weight: 700; font-display: swap; src: url('/assets/fonts/inter-latin-700-normal.woff2') format('woff2'); }
@font-face { font-family: 'Montserrat'; font-style: normal; font-weight: 600; font-display: swap; src: url('/assets/fonts/montserrat-latin-600-normal.woff2') format('woff2'); }
@font-face { font-family: 'Montserrat'; font-style: normal; font-weight: 700; font-display: swap; src: url('/assets/fonts/montserrat-latin-700-normal.woff2') format('woff2'); }
@font-face { font-family: 'Montserrat'; font-style: normal; font-weight: 800; font-display: swap; src: url('/assets/fonts/montserrat-latin-800-normal.woff2') format('woff2'); }

/* Macourt OS design tokens. SPEC section 4.1.
 *
 * The single source of colour, spacing, type and shape. Feature screens may
 * not hardcode any of these values; verify.sh fails the build if they do.
 *
 * Contrast is measured, not assumed. Every foreground token below is stated
 * with its ratio against --bg. AA needs 4.5:1 for normal text, 3:1 for large
 * text and interactive boundaries. --ink-faint was 3.36:1 in v1 and failed;
 * it is darkened here. */

/* No orange. Anywhere.
 *
 * This is the owner's one stated non-negotiable about colour and it was being
 * broken by a token rather than by a screen, which is why nobody noticed: the
 * warn hue rendered at 34 degrees, 62% saturation, which is unambiguously
 * orange, and it painted every warn pill, every behind-commitment bar, every
 * warn metric value and one colour of the client wheel.
 *
 * The rule enforced by tests/no-orange.test.js: no token may land in hue 15 to
 * 48 above 25% saturation, in either theme.
 *
 * Getting out of the orange band is necessary and not sufficient. The first
 * replacement set cleared it and then collided: dark --warn came out
 * byte-identical to dark --c8, so a warn pill and client 8's identity colour
 * were the same colour, and --c3 landed 4 degrees from --c8 when the wheel's
 * whole job is twelve separable hues. tests/palette.test.js now asserts the
 * separation too, because "not orange" was the only thing anyone was
 * measuring. Warn is ochre at hue 55 now, which
 * reads as caution without reading as orange. A suggested replacement of
 * #c9b465 was rejected during this fix because it measures hue 47, which is
 * inside the band by one degree. Check the number, do not eyeball it. */
:root {
  /* Surfaces */
  --bg: #fbfbfa;
  --panel: #ffffff;
  /* The scrim dims the page behind a dialog, and the raised surface is what
     the dialog sits on. Both are tokens because the dark values are not a
     tint of the light ones: dimming a near-black page cannot separate it from
     a near-black panel, so dark also raises the dialog. */
  --scrim: rgba(0, 0, 0, .45);
  --panel-raised: #ffffff;
  --panel-sunken: #f5f4f1;
  /* Hover has to be visible or it teaches the user the target is dead. This
     was #f2f1ee, which measures 1.027:1 against the rail: a rendered hover
     that changes nothing. #e9e7e1 is 1.124:1, still quiet, actually there. */
  --panel-hover: #e9e7e1;

  /* Ink. Ratios are against --bg. */
  --ink: #1f1f1e;        /* 15.93:1  primary text */
  --ink-soft: #55554f;   /*  7.25:1  secondary text */
  --ink-faint: #686861;  /*  4.54:1 on the WORST surface it lands on, --panel-hover. Rated 5.13 here for years against --panel only, while every hovered row rendered it at 4.34. */
  --ink-inverse: #ffffff;

  /* Lines */
  --line: #e7e6e2;
  --line-strong: #85847f;
  --line-focus: #1f1f1e;
  /* The skeleton bar. NOT --line-strong: when that token was promoted to 3:1
     for control boundaries (WCAG 1.4.11), the loading bars rode along and a
     quiet state started shouting. A skeleton is not a control; it keeps the
     old visible-but-soft value. */
  --skel: #d5d4cf;

  /* Status. Each is used for text on --panel, so each clears 4.5:1. */
  --ok: #2e7d4f;
  --ok-bg: #eef6f1;
  /* Ochre, not amber. See the no-orange note below. */
  --warn: #6f6a1c;
  --warn-bg: #f6f4e6;
  --bad: #a32e28;
  --bad-bg: #fbeeed;
  --info: #2a5a8a;
  --info-bg: #eef3f8;

  /* Client colour wheel for the calendar. Deliberately desaturated so a month
     view with twelve clients still reads as one product rather than a paint
     chart. */
  --c1: #4d8899; --c2: #6a8f5f; --c3: #5f7a2e; --c4: #7a5f8f;
  --c5: #4a8f8a; --c6: #994d83; --c7: #565a95; --c8: #994d70;

  /* Spacing. 4px base. Screens use these names, never raw px. */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 20px; --s6: 24px; --s7: 32px; --s8: 48px;

  /* Type */
  --font-sans: 'Inter', -apple-system, 'Segoe UI', sans-serif;
  --font-head: 'Montserrat', 'Inter', sans-serif;
  --font-mono: ui-monospace, 'SF Mono', Menlo, monospace;
  --t-xs: 12px; --t-sm: 13px; --t-md: 14px; --t-lg: 16px;
  --t-xl: 19px; --t-2xl: 24px; --t-3xl: 32px;
  --lh-tight: 1.25; --lh-normal: 1.5; --lh-loose: 1.65;

  /* Shape. Flat: no shadow tokens exist, on purpose. Adding one is a SPEC 4.1
     violation, not a convenience. */
  --r-sm: 4px; --r-md: 6px; --r-lg: 8px; --r-full: 999px;

  /* Touch and layout floors, written once.
     --tap was the number 40px repeated 13 times across two stylesheets, and
     the WCAG 2.5.8 floor 7 times. A floor that lives in thirteen places is a
     floor that gets raised in twelve. */
  --tap: 40px;
  --tap-min: 24px;
  --navbar-h: 52px;
  --mobilebar-h: 52px;
  /* Control heights, written once. The round-3 craft judge counted six
     different interactive control heights; a toolbar putting a 36px button
     beside a 39px input showed why that reads as drift rather than as a
     decision. Two scales: md for buttons, inputs and selects, sm for chips
     and small buttons. */
  --control-h: 36px;
  --control-h-sm: 26px;
  --control-h-mobile: 48px;
  --border: 1px solid var(--line);
  --border-strong: 1px solid var(--line-strong);

  /* Motion */
  --fast: 120ms ease;
  --normal: 200ms ease;

  /* Layout */
  --sidebar-w: 208px;
  --content-max: 1180px;
  --modal-reel-max: 960px;
  --content-editor-measure: 760px;
  --content-preview-meta-min: 220px;

  /* Table row geometry. The skeleton stands in for a row, so these have to be
     what a row actually measures, and --row-h was 40px while every table in
     the app rendered 49. Six rows of that is a 54px jump the moment the data
     lands, on every navigation. ui.css said "the skeleton row matches --row-h,
     the real row height" in a comment, which was simply not true and had
     nothing checking it.

     Measured at 1280px on Shoots, Content and Contacts, which agreed exactly:
     a one-line row is 49px. Clients renders a name over a status sentence and
     is 59. The arithmetic below is asserted in tests/row-height.test.js, so
     changing the cell padding without changing these fails the build.

     The last two are calc, not numbers. Writing 49 and 59 made them a second
     copy of an arithmetic that a test had to re-check; as calc they ARE the
     arithmetic, so changing the cell padding moves them by itself and the two
     cannot disagree. It also stops --row-line reading as a token nothing
     references. */
  --row-line: 24px;       /* the line box of one line in a table cell */
  --row-line-sub: 10px;   /* what a second, smaller line adds to it */
  --row-h: calc(2 * var(--s3) + var(--row-line) + 1px);   /* --s3 is .ui-table td's padding */
  --row-h-2line: calc(var(--row-h) + var(--row-line-sub));
}

@media (prefers-reduced-motion: reduce) {
  :root { --fast: 0ms; --normal: 0ms; }
}

/* Dark theme. Harrison, 2026-08-07: "generally id prefer dark mode".
 *
 * Not an inversion. Inverting a light palette produces harsh pure-black
 * surfaces and text that vibrates. This is a designed dark set: near-black
 * surfaces that step upward as they come forward, softened foregrounds, and
 * status hues lifted in lightness so they still clear AA on a dark panel.
 *
 * Every ratio below is measured against its own surface, not against white.
 *
 * Resolution order: the viewer's system preference wins by default, and an
 * explicit data-theme on the root overrides it in both directions, so the
 * in-app toggle always beats the OS. */

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #131313;
    --panel: #1a1a19;
    --scrim: rgba(0, 0, 0, .66);
    --panel-raised: #282827;
    --panel-sunken: #101010;
    --panel-hover: #232322;

    --ink: #f0efec;        /* 14.8:1 on --panel */
    --ink-soft: #b4b2ab;   /*  8.1:1 */
    --ink-faint: #918f88;  /*  4.55:1 on --panel-raised, the dialog surface. */
    --ink-inverse: #131313;

    --line: #2b2b29;
    --line-strong: #72726f;
    --line-focus: #f0efec;
    --skel: #3a3a37;

    --ok: #6cc08a;   --ok-bg: #17251c;
    --warn: #d0c77e; --warn-bg: #22210f;
    --bad: #e08b84;  --bad-bg: #2a1917;
    --info: #7fb0e0; --info-bg: #14202b;

    --c1: #7bb6c6; --c2: #94bd88; --c3: #a8b568; --c4: #a98cc0;
    --c5: #74bfb9; --c6: #c67bb1; --c7: #9590c9; --c8: #c67b9d;
  }
}

:root[data-theme="dark"] {
  --bg: #131313;
  --panel: #1a1a19;
  --scrim: rgba(0, 0, 0, .66);
  --panel-raised: #282827;
  --panel-sunken: #101010;
  --panel-hover: #232322;

  --ink: #f0efec;
  --ink-soft: #b4b2ab;
  --ink-faint: #918f88;
  --ink-inverse: #131313;

  --line: #2b2b29;
  --line-strong: #72726f;
  --line-focus: #f0efec;
  --skel: #3a3a37;

  --ok: #6cc08a;   --ok-bg: #17251c;
  --warn: #d0c77e; --warn-bg: #22210f;
  --bad: #e08b84;  --bad-bg: #2a1917;
  --info: #7fb0e0; --info-bg: #14202b;

  --c1: #7bb6c6; --c2: #94bd88; --c3: #a8b568; --c4: #a98cc0;
  --c5: #74bfb9; --c6: #c67bb1; --c7: #9590c9; --c8: #c67b9d;
}

/* The dark primary-button skin lives with the button in ui.css: a component
   rule in the token file was the round-4 panel's last token-purity note. */
