/* Hallmark · macrostructure: Split Studio · H2 hero knobs: ratio=full, figure=none, divider=negative space
 * theme: custom · vibe: "compile-time, cool, precise, .NET" · paper: oklch(99% 0.006 277) · accent: oklch(47% 0.21 277)
 * display: Geist · body: Geist · mono: JetBrains Mono · axes: light / geometric-sans / cool
 * nav: N9 edge-aligned minimal · footer: Ft2 inline single line · enrichment: none · studied: no
 * contrast: pass (40-41) · slop: pass (42-45) · honest: pass (46) · chrome: pass (47)
 * tokens: pass (48) · responsive: pass (49) · mobile: pass (34, 49, 50-57)
 *
 * The hero used to be the 6/6 split this macrostructure is named for, with a code figure on the
 * right. It gave up the figure so the page could open on the pair the figure was half of: the
 * expression beside the HTML it renders. The splits below still carry the macrostructure -- four of
 * the five sections are one -- and the run of them now alternates pair / single / pair rather than
 * placing two pair sections together, which reads as a template whatever is inside them.
 */

/* The design system, extracted so it is portable and so app.css never carries a raw value.
   Every colour, font, size, easing and duration used by app.css is declared here and referenced by
   name. A literal oklch()/hex outside this file is a bug, not a shortcut.

   This file is linked from wwwroot/index.html as a separate stylesheet rather than @import-ed from
   app.css: an @import costs a second round trip that only starts once app.css has parsed, and the
   custom properties are needed by the first rule app.css applies. site.yml asserts the <link> for
   the same reason it asserts the other two -- losing the line would ship the site with every
   var() unresolved, which every other assertion in that workflow would report as green. */

/* The two families the site draws every character in, served from this origin rather than from
   fonts.googleapis.com (#252). Both are variable fonts, so one file spans every weight app.css asks
   for; the seven static faces the old URL requested were three times the bytes.

   font-weight states each file's real axis range rather than the weights in use, which is what
   tells the browser it never has to synthesise one. That widens code-block bold: highlight.css asks
   for mono 700 and the old URL never requested it, so those runs used to be drawn with the 600 face.

   font-display: swap so first paint is never blocked on a font; the fallbacks are the platform's own
   UI and mono stacks, named in the --font-* tokens below.

   unicode-range is what makes the latin-ext pair free: a page that draws none of those characters
   never fetches the file. index.html preloads only the two latin files, for the same reason.

   The @font-face rules live here rather than in a fourth stylesheet because this file already
   declares every font the page uses, and a new <link> in index.html would need its own
   hand-written assertion beside the three in eng/verify-site-prerender.sh. */
@font-face {
    font-family: "Geist";
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("../fonts/geist-1.800-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: "Geist";
    font-style: normal;
    font-weight: 100 900;
    font-display: swap;
    src: url("../fonts/geist-1.800-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: "JetBrains Mono";
    font-style: normal;
    font-weight: 400 800;
    font-display: swap;
    src: url("../fonts/jetbrains-mono-2.211-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: "JetBrains Mono";
    font-style: normal;
    font-weight: 400 800;
    font-display: swap;
    src: url("../fonts/jetbrains-mono-2.211-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;
}

:root {
    /* Resolves every light-dark() below, and tells the browser which way to draw what it renders
       for itself: scrollbars, the canvas behind the page, and a form control's default appearance.
       Those two jobs are one declaration, so it lives with the tokens rather than in app.css --
       moving it away would leave a reader's explicit choice applying to the palette and not to the
       scrollbar beside it. */
    color-scheme: light dark;

    /* --- Colour -------------------------------------------------------------
       Anchor hue 277 (violet). Derived from the .NET brand violet #512BD4, whose OKLCH chroma is
       ~0.245; the accent below is held at 0.21, the top of the range this design system allows,
       which is indistinguishable side by side and keeps the accent from overpowering the ink.
       Every neutral is tinted toward the same hue: an untinted grey beside a violet accent reads
       wrong in a way most people cannot name.

       Each pair is light-dark(light, dark), so a colour that differs between schemes is still one
       declaration and one place to change. Only colour is paired: type, space, shape, motion,
       layers and measure are the same design in either scheme.

       The page inverts but the slab does not. --color-slab and the five code colours on it are
       already a dark surface with its own contrast budget, so they stay single-valued and the paper
       is taken *below* them instead: 15% against the slab's 21%. That keeps the figure reading as a
       raised panel, which is the same signal it carries in light mode, and it holds this file's own
       rule that elevation on a dark surface is lightness rather than shadow.

       The light slab is the case that used to need a second code palette here and no longer does.
       `.slab--light` draws on --color-paper-2 and pins `color-scheme: light`, so the generated
       pairs in css/highlight.css resolve to their paper half on that figure whatever the page
       theme is; the dark slab pins `dark` and takes the other half. One generated stylesheet
       therefore serves both surfaces, and the hand-written `-on-light` family that used to serve
       the light one is gone.

       css/highlight.css pairs the same way for prose code fences. It is generated, and
       ColorCodeTheme is where its two palettes are declared -- see the note in that file for why the
       values there are hex rather than a var() reference to this one. */
    --color-paper: light-dark(oklch(99% 0.006 277), oklch(15% 0.015 277));
    --color-paper-2: light-dark(oklch(97% 0.008 277), oklch(19% 0.017 277));
    --color-paper-3: light-dark(oklch(94.5% 0.010 277), oklch(24% 0.019 277));

    --color-ink: light-dark(oklch(21% 0.020 277), oklch(94% 0.008 277));
    --color-ink-2: light-dark(oklch(38% 0.016 277), oklch(82% 0.010 277));
    --color-muted: light-dark(oklch(52% 0.014 277), oklch(70% 0.012 277));

    --color-rule: light-dark(oklch(90% 0.010 277), oklch(30% 0.018 277));
    --color-rule-2: light-dark(oklch(94% 0.008 277), oklch(25% 0.016 277));

    /* The accent crosses the middle: light enough to carry text on paper, which makes it too light
       to carry text itself, so --color-accent-ink turns dark to stay legible on it. */
    --color-accent: light-dark(oklch(47% 0.21 277), oklch(76% 0.15 277));
    --color-accent-strong: light-dark(oklch(41% 0.20 277), oklch(84% 0.12 277));
    --color-accent-soft: light-dark(oklch(96% 0.022 277), oklch(30% 0.05 277));
    --color-accent-ink: light-dark(oklch(99% 0.006 277), oklch(15% 0.020 277));
    --color-focus: light-dark(oklch(52% 0.22 277), oklch(78% 0.16 277));

    /* The warning block, and the only hue on this site that is not the accent or the diagnostic id.
       It carries one meaning -- this can produce a security defect in a reader's own page -- and
       ':::warning' is the only construct that reaches it (site/README.md §Warnings).

       The hue is what buys the rank, and it has to be the hue rather than the drawing. The block is
       shaped exactly like a code figure, so a reader who cannot separate red from violet still reads
       it as a block rather than as a louder aside. The design is monochrome at 277, so violet would
       have made it the stale notice in another weight. Red at 28 is the one colour a reader does not
       have to be taught.

       Three tokens rather than two, because the chip under an inline `code` span cannot stay on
       --color-paper-3. That grey is tinted toward 277 and goes muddy on a warm ground. The chip
       moves away from its ground in both schemes, which is what paper-3 does against paper:
       darker on paper, lighter on the dark page. */
    --color-warn: light-dark(oklch(51% 0.18 28), oklch(74% 0.15 28));
    --color-warn-soft: light-dark(oklch(96.5% 0.022 28), oklch(24% 0.040 28));
    --color-warn-chip: light-dark(oklch(92.5% 0.030 28), oklch(30% 0.048 28));

    /* The dark slab that carries code. Elevation on a dark surface is lightness, never shadow. */
    --color-slab: oklch(21% 0.024 277);
    --color-slab-2: oklch(26% 0.022 277);
    --color-slab-ink: oklch(93% 0.008 277);
    --color-slab-muted: oklch(70% 0.012 277);
    --color-slab-rule: oklch(31% 0.020 277);

    /* The build error on the landing page, which is a terminal message rather than code: where the
       error is, and which error it is. Two colours, on the dark slab, at ~4.6:1 and ~7:1.

       They are their own names rather than borrowed code colours. The figure is the one on that
       page that is not a code sample, and a comment colour standing in for "a file and a line"
       coupled it to a vocabulary it does not belong to.

       There are no --color-code-* tokens any more. Code on this site is coloured by the generated
       css/highlight.css, whose two palettes are declared in ColorCodeTheme -- see the note in that
       file for why the values there are hex rather than a var() reference to this one. Declaring
       them here as well would be a copy no rule renders, so nothing could observe it drifting from
       the hex that actually paints. */
    --color-diag-loc: oklch(66% 0.020 277);
    --color-diag-id: oklch(78% 0.12 350);

    /* --- Type ---------------------------------------------------------------
       Two families plus a mono outlier. The mono carries exactly one role -- machine text -- and
       that role covers the wordmark (a C# namespace), code, file and route paths, the install
       command, and figures. Anything that is prose, a label, or a heading stays on the body face,
       or the outlier quietly becomes a third body font. */
    --font-display: "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
    --font-body: "Geist", ui-sans-serif, system-ui, -apple-system, sans-serif;
    --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

    /* Major third (1.25). Five sizes carry the page; hierarchy past that comes from weight. */
    --text-xs: 0.75rem;
    --text-sm: 0.8125rem;
    --text-base: 1rem;
    --text-md: 1.125rem;
    --text-lg: 1.375rem;
    --text-xl: 1.75rem;
    --text-2xl: 2.25rem;
    --text-display: clamp(2.5rem, 4.5vw + 1rem, 4.25rem);
    --text-display-s: clamp(2rem, 3vw + 1rem, 3rem);

    --leading-display: 1.06;
    --leading-heading: 1.2;
    --leading-body: 1.6;
    --leading-code: 1.65;

    --tracking-display: -0.028em;
    --tracking-label: 0.08em;

    /* --- Space --------------------------------------------------------------
       4pt base, named by role. */
    --space-3xs: 0.125rem;
    --space-2xs: 0.25rem;
    --space-xs: 0.5rem;
    --space-sm: 0.75rem;
    --space-md: 1rem;
    --space-lg: 1.5rem;
    --space-xl: 2.5rem;
    --space-2xl: 4rem;
    --space-3xl: 6rem;
    --space-4xl: 9rem;

    /* --- Shape --------------------------------------------------------------
       Tight radii. This is an instrument panel, not a consumer app. */
    --radius-xs: 3px;
    --radius-sm: 5px;
    --radius-md: 8px;
    --rule-hairline: 1px;

    /* --- Motion -------------------------------------------------------------
       Exponential ease-out. The browser default `ease` reads as uncrafted. */
    --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
    --ease-in: cubic-bezier(0.7, 0, 0.84, 0);
    --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
    --dur-micro: 120ms;
    --dur-short: 220ms;
    --dur-long: 420ms;

    /* --- Layers -------------------------------------------------------------
       Six named levels. An ad-hoc z-index is a bug. */
    --z-base: 1;
    --z-raised: 10;
    --z-dropdown: 100;
    /* In-page sticky boxes and the top bar are separate levels. Two sticky elements briefly overlap
       during a scroll, and the bar has to be the one that paints over. */
    --z-sticky: 200;
    --z-sticky-nav: 300;
    --z-modal: 400;
    --z-toast: 500;

    /* --- Measure ------------------------------------------------------------
       em, not ch: `ch` is the advance width of the current font's "0" glyph, so a reading column
       sized with it resizes the instant font-display: swap replaces the fallback stack with Geist
       -- not a sub-pixel glyph-metrics nudge but every consumer's max-width recomputing at once
       (#481). em tracks font-size the same way ch does, without depending on which face is painted;
       43.75em reproduces 66ch's width at Geist's real metrics (700px at the 16px base size).       */
    --measure: 43.75em;
    --shell-max: 76rem;
    --rail-width: 16rem;
    /* The banner's own height, so anything else that sticks can dock beneath it rather than
       sliding under it. Kept in step with .site-nav's padding-block plus its line box. */
    --nav-height: 4.25rem;
}

/* --- The reader's own choice -----------------------------------------------
   Without these two rules the palette above would answer only the operating system, and a reader
   who wants this site drawn the other way round would have no way to say so.

   They set color-scheme rather than re-declaring colours. Every light-dark() pair above resolves
   against it, so the whole palette follows from one property and the dark values stay written once.
   A @media (prefers-color-scheme) block cannot do this: media queries answer the system and cannot
   see a choice made on the page.

   The absent attribute is the third state, and the default: no attribute means no override, so the
   pairs fall back to `light dark` on :root and follow the system as before. wwwroot/index.html
   applies a stored choice before first paint and cycles it through these three on a click.

   css/highlight.css needs no counterpart. Its rules are light-dark() pairs too, so they resolve
   against the same color-scheme these rules set. */
:root[data-theme="light"] {
    color-scheme: light;
}

:root[data-theme="dark"] {
    color-scheme: dark;
}
