/* Fonts (spec 3.2) — self-hosted WOFF2 (wwwroot/fonts), no third-party requests. */
@font-face {
    font-family: "Source Serif 4";
    src: url("fonts/SourceSerif4-Regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Source Serif 4";
    src: url("fonts/SourceSerif4-Italic.woff2") format("woff2");
    font-weight: 400;
    font-style: italic;
    font-display: swap;
}

@font-face {
    font-family: "Source Serif 4";
    src: url("fonts/SourceSerif4-SemiBold.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Source Serif 4";
    src: url("fonts/SourceSerif4-Bold.woff2") format("woff2");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Source Sans 3";
    src: url("fonts/SourceSans3-Regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "Source Sans 3";
    src: url("fonts/SourceSans3-SemiBold.woff2") format("woff2");
    font-weight: 600;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: "JetBrains Mono";
    src: url("fonts/JetBrainsMono-Regular.woff2") format("woff2");
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

:root {
    --font-serif: "Source Serif 4", Georgia, "Times New Roman", serif;
    --font-ui: "Source Sans 3", system-ui, -apple-system, "Segoe UI", sans-serif;
    --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Code", Menlo, Consolas, monospace;
}

/* The colour tokens are not here: they are generated into /branding/theme.css from the site's
   branding folder (linked before this file), so this stylesheet holds no colour values of its
   own — bar the two marked regions BrandingThemeCssTests allow-lists: the drawer scrim directly
   below and MudBlazor's scrollbar copy at the end of this file. */

/* The one exception besides the marked scrollbar copy: the contents drawer's scrim (brief 07i
   item 3). Its alphas are fixed design constants — black 32% in light, 48% in dark — not
   site-brandable palette entries (the brandable tokens are exactly the 18 theme.css generates),
   so they live here as theme-keyed custom properties in modern CSS colour syntax: the drawer
   paints with var(--scrim) and no alpha value appears in any component or style attribute. The
   three blocks mirror theme.css's structure: explicit light, explicit dark, system dark. */
/* scrim-colour-values:begin */
:root { --scrim: color(srgb 0 0 0 / 0.32); }
:root[data-theme=dark] { --scrim: color(srgb 0 0 0 / 0.48); }
@media (prefers-color-scheme: dark) {
    :root:not([data-theme=light]) { --scrim: color(srgb 0 0 0 / 0.48); }
}
/* scrim-colour-values:end */

html, body {
    background-color: var(--bg);
    color: var(--text);
    font-family: var(--font-ui);
}

/* Base defaults. The reading pages load no MudBlazor stylesheet; these are the parts of its
   reset and element defaults that the shell and the pages were relying on (found by comparing
   computed styles and screenshots with and without it), set here from our own tokens. */
html {
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    -webkit-tap-highlight-color: transparent;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    font-size: .875rem;
    line-height: 1.43;
    letter-spacing: .01071em;
}

button {
    position: relative;
    justify-content: center;
    vertical-align: middle;
    user-select: none;
    font-family: inherit;
    font-size: inherit;
    line-height: inherit;
}

/* Keyboard focus: every focusable control shows an accent outline when focused from the keyboard
   (:focus-visible, so mouse and touch focus look as before). Outline and offset take no layout
   space. Controls that carry a MudBlazor class are left alone (MudBlazor loads on interactive
   documents only and draws its own focus state; its element resets such as button { outline: 0 }
   would otherwise win), as is anything with its own :focus-visible rule (post cards: class
   specificity beats this). The accent is --primary: at least 3:1 on the page and sidebar
   backgrounds in light and dark. */
:where(a[href], button, summary, [tabindex]:not([tabindex="-1"])):not([class*="mud-"]):focus-visible {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

/* Fluid root type (brief 03f item 1): the root font size grows linearly from 16px at 1440px to
   19px at 2560px, capped at both ends, so rem-sized surfaces scale with the type on big screens.
   At 1440px the middle term is 16px, at 2560px it is 19px, and between it rises ~3px over 1120px
   of viewport (17.3px at 1920px). Viewports ≤1440px (including phones) stay at 16px. */
html {
    font-size: clamp(16px, calc(16px + (100vw - 1440px) * 3 / 1120), 19px);
}

h1, h2, h3, h4, h5, h6 {
    font-family: var(--font-serif);
}

code, pre, kbd, samp {
    font-family: var(--font-mono);
}

/* WORKAROUND(W-015, MudBlazor): MudBlazor writes its typography variables as inline custom properties on html, hence !important */
/* Typography (spec §3.2): headings and post body in Source Serif 4, UI text in
   Source Sans 3, code in JetBrains Mono. MudBlazor injects its --mud-typography-*-family
   variables as inline custom properties on <html> at runtime, hence !important. */
:root {
    --mud-typography-default-family: var(--font-ui) !important;
    --mud-typography-h1-family: var(--font-serif) !important;
    --mud-typography-h2-family: var(--font-serif) !important;
    --mud-typography-h3-family: var(--font-serif) !important;
    --mud-typography-h4-family: var(--font-serif) !important;
    --mud-typography-h5-family: var(--font-serif) !important;
    --mud-typography-h6-family: var(--font-serif) !important;
    --mud-typography-subtitle1-family: var(--font-serif) !important;
    --mud-typography-subtitle2-family: var(--font-serif) !important;
    --mud-typography-body1-family: var(--font-ui) !important;
    --mud-typography-body2-family: var(--font-ui) !important;
    --mud-typography-button-family: var(--font-ui) !important;
    --mud-typography-caption-family: var(--font-ui) !important;
    --mud-typography-overline-family: var(--font-ui) !important;
}
/* END WORKAROUND(W-015) */

/* Article body 1.125rem/1.65 desktop, 1.0625rem on phones; hero title 2.5rem desktop / 1.875rem
   phone; subtitles Source Serif italic in --muted (spec §3.2). The sizes are rem so the reading
   text scales with the fluid root (brief 03f item 5): 18px/17px/40px/30px at a 16px root, larger
   above 1440px. */
.markdown-body {
    font-family: var(--font-serif);
    font-size: 1.125rem;
    line-height: 1.65;
}

.hero-title {
    font-family: var(--font-serif);
    font-size: 2.5rem;
    line-height: 1.15;
}

.subtitle {
    font-family: var(--font-serif);
    font-style: italic;
    color: var(--muted);
}

@media (max-width: 959.98px) {
    .markdown-body {
        font-size: 1.0625rem;
    }

    .hero-title {
        font-size: 1.875rem;
    }
}

/* Anchors speak in the accent colour (spec §3.1: crimson = act/current). */
a {
    color: var(--primary);
}

/* WORKAROUND(W-012, Blazor): no focus ring on h1: FocusOnNavigate focuses it programmatically */
/* h1 receives FocusOnNavigate's programmatic focus (Routes.razor); no ring for it. */
h1:focus {
    outline: none;
}
/* END WORKAROUND(W-012) */

/* Layout shell: a CSS grid with a sidebar area and a content area. Global
   because it styles <html> classes. */
.shell { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
.shell > .sidebar { position: sticky; top: 0; height: 100vh; overflow-y: auto; }
.shell > main { container-type: inline-size; min-width: 0; }
:root { --sidebar-w: 260px; }
html.sidebar-collapsed { --sidebar-w: 64px; }
@media (min-width: 960px) and (max-width: 1279.98px) { :root { --sidebar-w: 64px; } }
@media (max-width: 959.98px) {
  .shell { grid-template-columns: 1fr; }
  /* WORKAROUND(W-017, Safari): the phone drawer is sized with 100dvh (100vh fallback) and scrolls itself so Safari toolbar does not cover its last item */
  /* The closed overlay also gets visibility: hidden so its
     links leave the tab order. The delayed visibility transition keeps the slide-out visible; the
     open state resets the delay to 0 so the slide-in and the immediate focus move still work.
     The height is the dynamic viewport height (100dvh, with 100vh as the fallback) so the last
     footer item ("Log in"/"Log out") clears Safari's toolbar on iPhone, and the drawer scrolls
     itself (overflow-y: auto) when its content is taller than the screen. No bottom edge: the
     height comes from dvh sizing, not from top + bottom stretching. */
  .shell > .sidebar { position: fixed; inset: 0 auto auto 0; width: 280px; height: 100vh; height: 100dvh;
                      overflow-y: auto; transform: translateX(-100%);
                      transition: transform .2s, visibility 0s .2s; visibility: hidden; z-index: 1300; }
  /* END WORKAROUND(W-017) */
  /* WORKAROUND(W-017, Safari): safe-area-inset-bottom needs viewport-fit=cover; the drawer keeps its last item above the home indicator */
  /* Clear the home indicator: viewport-fit=cover (App.razor) makes env(safe-area-inset-bottom)
     non-zero on notched phones. The id selector outranks the scoped .sidebar padding (12px 10px),
     so the bottom keeps its 12px plus the inset. */
  #sidebar-nav { padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
  /* END WORKAROUND(W-017) */
  html.nav-open .shell > .sidebar { transform: none; visibility: visible; transition: transform .2s, visibility 0s; }
  /* Scroll lock: while the drawer is open the page behind it must not scroll. The drawer's own
     scrolling is on .sidebar itself (overflow-y: auto), not on the root, so it is unaffected. */
  html.nav-open { overflow: hidden; }
  .topbar { display: flex; }
}
@media (min-width: 960px) { .topbar { display: none; } }
/* rail: hide labels/counts, keep icons, tooltips via title. Gated to ≥1280px — the collapse rail
   exists only there, and blog-sidebar=collapsed stored at a wide width must not strip labels from
   the overlays below 1280px. */
@media (min-width: 1280px) {
  :root:is(.sidebar-collapsed) .sidebar .label { display: none; }
}
/* 960-1279px rail vs drawer: labels appear only in the drawer. The section headings and the
   brand copy keep their boxes in the rail (text hidden, not display:none) — the headings reserve
   their vertical space and the brand block its height — so opening the drawer adds only the
   labels beside the icons and no icon moves. */
@media (min-width: 960px) and (max-width: 1279.98px) {
  .sidebar .label { display: none; }
  html.nav-open .sidebar .label { display: inline; }
  html .sidebar .section-heading,
  html .sidebar .brand-name,
  html .sidebar .brand-tagline { display: block; visibility: hidden; }
  html.nav-open .sidebar .section-heading,
  html.nav-open .sidebar .brand-name,
  html.nav-open .sidebar .brand-tagline { display: block; visibility: visible; }
}

/* 960-1279px: html.nav-open widens the rail to the full sidebar as an overlay over the
   content (spec §4.1). */
@media (min-width: 960px) and (max-width: 1279.98px) {
  html.nav-open .shell > .sidebar {
    position: fixed; inset: 0 auto 0 0; width: 260px; height: 100vh; overflow-y: auto;
    transform: none; transition: transform .2s; z-index: 1300;
    box-shadow: 0 0 24px var(--fallback-wash);
  }
}

/* The scrim behind the overlay sidebar (phone and the 960-1279 overlay). */
.scrim { display: none; position: fixed; inset: 0; background: var(--fallback-wash); z-index: 1200; }
@media (max-width: 1279.98px) { html.nav-open .scrim { display: block; } }
/* MudBlazor's scrollbar rules, copied verbatim from the <style> block MudThemeProvider
   renders; MainLayout's DefaultScrollbar "true" turns that block off, so this copy is what
   keeps the look it produced. The copy stays byte-identical (line endings aside — this file
   is CRLF) to a fresh render of the block, asserted in MudThemeStyleCspTests. It is one of
   the two marked regions of app.css allowed to carry colour values (BrandingThemeCssTests):
   they are MudBlazor's rules, not this file's own. */
/* WORKAROUND(W-020, MudBlazor): verbatim copy of MudBlazor's scrollbar rules (a test compares it with a fresh render) */
/* mudblazor-scrollbar-copy:begin */
::-webkit-scrollbar {width: 8px;height: 8px;z-index: 1;}
::-webkit-scrollbar-track {background: transparent;}
::-webkit-scrollbar-thumb {background: #c4c4c4;border-radius: 1px;}
::-webkit-scrollbar-thumb:hover {background: #a6a6a6;}
html, body * {scrollbar-color: #c4c4c4 transparent;scrollbar-width: thin;}
/* mudblazor-scrollbar-copy:end */
/* END WORKAROUND(W-020) */
