/* Written by scripts/22_detail.py. Shared by every page under docs/repo/.

   Deliberately not docs/pages.css, which scripts/20_landing.py writes: two stages writing one file
   means the later run silently deletes the earlier one's rules.

   The two blocks below are `19_pages.py`'s, character for character, and that is an invariant rather
   than a coincidence: a reader who follows a link from the index to one of these pages must not see the
   palette change under them. `tests/theme_test.py` asserts the three copies are byte-identical, which
   is the only reason it is safe to keep them as copies instead of a shared file.

   The reasoning behind the values -- which accents are lifted and why --onbar inverts between the two
   modes -- is written out once, beside the blocks in `19_pages.py`. It is deliberately not repeated
   here; three copies of a rationale is three things to go stale, and the previous palette's did. */
:root{
  --surface:#090A0D; --plane:#101217; --band:#20242D; --ink:#F7F8FA; --ink2:#D0D5DD;
  --muted:#9BA5B3; --grid:#3A414D; --link:#78B7F4; --bar:#D6A034;
  --good:#5BD5AA; --warn:#EF7D86; --off:#9BA5B3; --onbar:#090A0D;
  --accent-sky:#78B7F4; --accent-mint:#5BD5AA; --accent-gold:#E7B64D;
  --accent-coral:#EF7D86; --accent-violet:#A99AF7;
  /* THE STRUCTURAL HALF OF A THEME. Not colours, so not contrast-bearing, and not declared per mode
     either -- a typeface and a blur radius do not change when the lights go out. Graphite's values are
     the page as it shipped, with one change since: the face is Geist, embedded from `assets/fonts/`
     (see scripts/sitewide.py), so every reader gets the same type rather than whichever sans their
     platform has. No wash, an opaque panel and no backdrop filter. --mono is Geist Mono, for code
     and the build tags. A reader who never opens Settings cannot otherwise tell this block exists.
     --bdf is `none` rather than `blur(0px)` on purpose: a non-none backdrop-filter makes the element a
     containing block and a stacking context, which would quietly re-parent the mascot's speech bubble
     and re-rank the pinned bar for every reader, including the ones who chose nothing. */
  --ui:"Geist","Segoe UI",system-ui,-apple-system,Helvetica,Arial,sans-serif;
  --mono:"Geist Mono",ui-monospace,"Cascadia Mono",Consolas,"Liberation Mono",monospace;
  --wash:none; --panel:var(--plane); --bdf:none;
}
html[data-theme=light]{
  --surface:#F2F4F7; --plane:#FAFBFC; --band:#E7EAF0; --ink:#14171C; --ink2:#353C47;
  --muted:#596574; --grid:#CAD1DB; --link:#1D5E9E; --bar:#6557C8;
  --good:#187557; --warn:#875A19; --off:#596574; --onbar:#FFFFFF;
  --accent-sky:#1D5E9E; --accent-mint:#187557; --accent-gold:#9A6718;
  --accent-coral:#B6465E; --accent-violet:#6557C8;
}
/* THE THREE OPT-IN THEMES, six blocks, and the pattern is the same for each: `html[data-skin=X]` carries
   the dark values and the structural tokens, `html[data-skin=X][data-theme=light]` carries the light
   ones. Both selectors are one attribute stronger than nothing and the pair is stronger than the light
   block above, so the cascade resolves skin-then-mode without a single `!important`.

   The light block restates all thirteen colours and nothing else. It does not restate --ui or --bdf
   because those are the same in both modes and the skin block above already applies at both, and it
   does restate --panel and --wash because the alpha that reads as frosted over near-black is a smear
   over near-white.

   --panel IS translucent, and it is the one token that can be without giving up the measurement. Every
   panel on this page sits on --surface and is filled from --plane, and the rgba below is --plane's own
   channels: so whatever the compositor produces lies between two backdrops that ARE in the checked set,
   and every text token clears 4.5:1 on both ends of that range. `theme_test.py` asserts the channels
   match --plane rather than trusting this paragraph, because a hand-typed rgba is exactly the kind of
   copy that drifts one digit and stops being a blend of anything. */
html[data-skin=glass]{
  --surface:#070912; --plane:#12141D; --band:#1C1E26; --ink:#F4F7FF; --ink2:#CBD5E8;
  --muted:#95A2BE; --grid:#3D4152; --link:#7CC4FF; --bar:#6FE3C4;
  --good:#86E0A0; --warn:#FF9BA8; --off:#95A2BE; --onbar:#04121A;
  --accent-sky:#7CC4FF; --accent-mint:#6FE3C4; --accent-gold:#F3C46A;
  --accent-coral:#FF9BA8; --accent-violet:#B3A6FF;
  --ui:"Geist","Inter","Segoe UI",system-ui,-apple-system,Helvetica,Arial,sans-serif;
  --wash:radial-gradient(1100px 520px at 12% -12%,rgba(80,140,255,.22),transparent 70%),
         radial-gradient(900px 480px at 88% 0%,rgba(110,227,196,.14),transparent 72%);
  --panel:rgba(18,20,29,.72); --bdf:blur(14px) saturate(1.25);
}
html[data-skin=glass][data-theme=light]{
  --surface:#EEF2FA; --plane:#F8FAFF; --band:#E2E8F5; --ink:#111726; --ink2:#323B4F;
  --muted:#56627A; --grid:#C3CCDE; --link:#0F5FA6; --bar:#0B6E7A;
  --good:#136B4E; --warn:#8A4A2B; --off:#56627A; --onbar:#FFFFFF;
  --accent-sky:#0F5FA6; --accent-mint:#0B6E7A; --accent-gold:#8A5A00;
  --accent-coral:#A63F52; --accent-violet:#4B44B8;
  --wash:radial-gradient(1100px 520px at 12% -12%,rgba(60,120,255,.14),transparent 70%),
         radial-gradient(900px 480px at 88% 0%,rgba(0,150,130,.10),transparent 72%);
  --panel:rgba(248,250,255,.74);
}
/* Terminal is the one theme that changes the typeface, and that is the whole of it: a monospace stack,
   phosphor greens for the foreground and amber for the action. Amber rather than a brighter green
   because --bar is drawn as text as well as as a fill, and a green action beside the green --good is two
   meanings in one hue -- the mark of a verdict and the colour of a button should not be the same thing. */
html[data-skin=terminal]{
  --surface:#050B0D; --plane:#0A1315; --band:#132220; --ink:#D8F5E6; --ink2:#A8D8C4;
  --muted:#84AC99; --grid:#2D4741; --link:#6FD0FF; --bar:#FFB627;
  --good:#3FE08C; --warn:#FF8B7A; --off:#84AC99; --onbar:#05100B;
  --accent-sky:#6FD0FF; --accent-mint:#3FE08C; --accent-gold:#FFB627;
  --accent-coral:#FF8B7A; --accent-violet:#C2A6FF;
  --ui:"Geist Mono","SF Mono",ui-monospace,"Cascadia Mono",Consolas,"Liberation Mono",monospace;
  --wash:radial-gradient(1200px 620px at 50% -20%,rgba(63,224,140,.12),transparent 70%);
  --panel:rgba(10,19,21,.86); --bdf:blur(3px);
}
html[data-skin=terminal][data-theme=light]{
  --surface:#EEF4EF; --plane:#F9FCF9; --band:#E0EBE3; --ink:#0D1A14; --ink2:#2B3E34;
  --muted:#4F6659; --grid:#BFCFC4; --link:#0C5F8F; --bar:#7A5300;
  --good:#166B45; --warn:#9A3D22; --off:#4F6659; --onbar:#FFFFFF;
  --accent-sky:#0C5F8F; --accent-mint:#166B45; --accent-gold:#7A5300;
  --accent-coral:#9A3D22; --accent-violet:#5A3FA8;
  --wash:radial-gradient(1200px 620px at 50% -20%,rgba(20,140,90,.10),transparent 70%);
  --panel:rgba(249,252,249,.86);
}
/* Prism is the maximal one: five washes at five positions so the page is never one colour, magenta for
   the action, and the deepest blur of the four. It is also where the constraint above shows its teeth --
   the hues are all in the accents and the wash, and the thirteen tokens underneath them are as plain and
   as measured as graphite's. Colour on this page is allowed to be loud in the background and never in
   the contract between ink and its backdrop. */
html[data-skin=prism]{
  --surface:#0B0718; --plane:#17112C; --band:#241A3F; --ink:#FBF7FF; --ink2:#DCD2F0;
  --muted:#ADA0C8; --grid:#4A3A70; --link:#8FD0FF; --bar:#FF9BD2;
  --good:#63E6B8; --warn:#FFB36B; --off:#ADA0C8; --onbar:#14061B;
  --accent-sky:#8FD0FF; --accent-mint:#63E6B8; --accent-gold:#FFD166;
  --accent-coral:#FF9BD2; --accent-violet:#C0A8FF;
  --ui:"Geist","Inter","Segoe UI",system-ui,-apple-system,Helvetica,Arial,sans-serif;
  --wash:radial-gradient(760px 430px at 6% -8%,rgba(255,155,210,.26),transparent 68%),
         radial-gradient(680px 400px at 96% 2%,rgba(143,208,255,.22),transparent 68%),
         radial-gradient(900px 520px at 50% 108%,rgba(99,230,184,.18),transparent 70%),
         radial-gradient(520px 320px at 78% 54%,rgba(255,209,102,.15),transparent 70%),
         radial-gradient(560px 340px at 18% 62%,rgba(192,168,255,.20),transparent 70%);
  --panel:rgba(23,17,44,.70); --bdf:blur(18px) saturate(1.4);
}
html[data-skin=prism][data-theme=light]{
  --surface:#F3EFFC; --plane:#FDFBFF; --band:#E8E0F8; --ink:#170B26; --ink2:#3A2B52;
  --muted:#5E4F7A; --grid:#C7B9E4; --link:#1A4FB0; --bar:#9A2C7E;
  --good:#0F6B52; --warn:#8A4410; --off:#5E4F7A; --onbar:#FFFFFF;
  --accent-sky:#1A4FB0; --accent-mint:#0F6B52; --accent-gold:#8A4410;
  --accent-coral:#9A2C7E; --accent-violet:#6A3FB8;
  --wash:radial-gradient(760px 430px at 6% -8%,rgba(214,60,150,.16),transparent 68%),
         radial-gradient(680px 400px at 96% 2%,rgba(40,120,220,.14),transparent 68%),
         radial-gradient(900px 520px at 50% 108%,rgba(0,170,120,.12),transparent 70%),
         radial-gradient(520px 320px at 78% 54%,rgba(230,160,20,.10),transparent 70%),
         radial-gradient(560px 340px at 18% 62%,rgba(130,90,230,.13),transparent 70%);
  --panel:rgba(253,251,255,.72);
}
/* THE FOUR LIGHTER-HEARTED ONES, eight more blocks in exactly the pattern above, and held to exactly the
   same arithmetic -- `theme_test.py` runs `audit()` over all sixteen sets. The fun is spent where it is
   free: the wash, the typeface and the accents. None of them moves. Every --wash here is a static
   gradient, so a reader who asked the OS for reduced motion has nothing to ask these to stop doing, and
   none of them sets an --fx-* token on the homepage, which is the only place a theme can animate.

   Sherbet is an ice-cream parlour: blackcurrant and cream, strawberry for the action, and a rounded face
   where the platform has one. `ui-rounded` resolves on Apple platforms only; everywhere else the stack
   falls through to Nunito if installed and then to the page's own Segoe UI, so the shape of the page is
   never at the mercy of a font nobody has. */
html[data-skin=sherbet]{
  --surface:#1E1830; --plane:#272040; --band:#32294F; --ink:#FFF1E6; --ink2:#EEDCE6;
  --muted:#C4B3D0; --grid:#554872; --link:#A8B4FF; --bar:#FF7AA2;
  --good:#5FE0B4; --warn:#FFB36B; --off:#C4B3D0; --onbar:#1A1026;
  --accent-sky:#A8B4FF; --accent-mint:#5FE0B4; --accent-gold:#FFD166;
  --accent-coral:#FF9A8B; --accent-violet:#D0B3FF;
  --ui:ui-rounded,"SF Pro Rounded","Nunito","Geist","Segoe UI",system-ui,-apple-system,Helvetica,Arial,sans-serif;
  --wash:radial-gradient(900px 480px at 8% -10%,rgba(255,122,162,.16),transparent 70%),
         radial-gradient(820px 460px at 94% 104%,rgba(95,224,180,.12),transparent 70%),
         radial-gradient(520px 300px at 70% 8%,rgba(255,209,102,.08),transparent 70%);
  --panel:rgba(39,32,64,.80); --bdf:blur(12px) saturate(1.2);
}
html[data-skin=sherbet][data-theme=light]{
  --surface:#FFF7EE; --plane:#FFFCF8; --band:#FBEADF; --ink:#2B2140; --ink2:#4A3D5E;
  --muted:#6A5A78; --grid:#E8CFC3; --link:#3A48C9; --bar:#C22D62;
  --good:#11704F; --warn:#8E4E00; --off:#6A5A78; --onbar:#FFFFFF;
  --accent-sky:#3A48C9; --accent-mint:#11704F; --accent-gold:#8A5A00;
  --accent-coral:#B23A3A; --accent-violet:#6B42C2;
  --wash:radial-gradient(900px 480px at 8% -10%,rgba(232,70,124,.10),transparent 70%),
         radial-gradient(820px 460px at 94% 104%,rgba(60,207,160,.10),transparent 70%),
         radial-gradient(520px 300px at 70% 8%,rgba(255,209,102,.14),transparent 70%);
  --panel:rgba(255,252,248,.82);
}
/* Riso is a risograph zine: two inks on off-white paper, and the second one printed a hair off register.
   That is the wash -- a pink and a blue blob that overlap without lining up. Fluoro yellow is the third
   drum and appears only as --accent-gold in dark, where it is ink on black; on paper it is a mustard,
   because yellow on cream is 1.1:1 and a zine you cannot read is not a zine. Paper is nearly opaque, so
   the panel is too, with just enough blur that a line of text behind the open Settings menu reads as a
   smudge rather than as a second sentence. */
html[data-skin=riso]{
  --surface:#16161F; --plane:#1E1E29; --band:#282835; --ink:#F5F0E6; --ink2:#DCD6CA;
  --muted:#AAA59C; --grid:#46465A; --link:#6FB4EA; --bar:#FF5CB8;
  --good:#3FD08A; --warn:#FFB84D; --off:#AAA59C; --onbar:#16161F;
  --accent-sky:#6FB4EA; --accent-mint:#3FD08A; --accent-gold:#FFE800;
  --accent-coral:#FF7F7F; --accent-violet:#B9A2FF;
  --ui:"Space Grotesk","Geist","Inter","Segoe UI",system-ui,-apple-system,Helvetica,Arial,sans-serif;
  --wash:radial-gradient(700px 420px at 14% -6%,rgba(255,72,176,.16),transparent 70%),
         radial-gradient(700px 420px at 17% -3%,rgba(0,120,191,.14),transparent 70%),
         radial-gradient(640px 380px at 92% 100%,rgba(255,232,0,.07),transparent 70%);
  --panel:rgba(30,30,41,.96); --bdf:blur(6px);
}
html[data-skin=riso][data-theme=light]{
  --surface:#F5F0E6; --plane:#FBF8F1; --band:#ECE5D6; --ink:#1A1A2E; --ink2:#34344A;
  --muted:#5A5864; --grid:#CFC6B3; --link:#B0126E; --bar:#0066A6;
  --good:#00703F; --warn:#A33A1F; --off:#5A5864; --onbar:#FFFFFF;
  --accent-sky:#0066A6; --accent-mint:#00703F; --accent-gold:#7A6100;
  --accent-coral:#B0126E; --accent-violet:#5B3FB0;
  --wash:radial-gradient(700px 420px at 14% -6%,rgba(255,72,176,.14),transparent 70%),
         radial-gradient(700px 420px at 17% -3%,rgba(0,120,191,.12),transparent 70%),
         radial-gradient(640px 380px at 92% 100%,rgba(255,232,0,.16),transparent 70%);
  --panel:rgba(251,248,241,.96);
}
/* Blueprint is a drafting sheet, and dark is the one it was drawn for: chalk on cyanotype blue with a
   24px ruled grid and safety orange for the action. The blue is far lighter than any other dark surface
   here, which is what the theme is and also what it costs -- every foreground had to be lifted to clear
   the band, and the plane and band sit closer to the surface than the other themes' do so the orange can
   still reach 4.5:1 on the lightest of the three. The grid is two `repeating-linear-gradient`s rather
   than an image, so it needs no request and no `background-size`. */
html[data-skin=blueprint]{
  --surface:#0B3D91; --plane:#0D4399; --band:#104BA6; --ink:#F4F8FF; --ink2:#DCE8FF;
  --muted:#C0D4F7; --grid:#4677D2; --link:#B5ECFF; --bar:#FFB27A;
  --good:#9CF2C4; --warn:#FFB3C1; --off:#C0D4F7; --onbar:#0A2352;
  --accent-sky:#B5ECFF; --accent-mint:#9CF2C4; --accent-gold:#FFE066;
  --accent-coral:#FFB8B0; --accent-violet:#D8CCFF;
  --ui:"IBM Plex Sans","Geist","Segoe UI",system-ui,-apple-system,Helvetica,Arial,sans-serif;
  --wash:repeating-linear-gradient(0deg,rgba(234,242,255,.07) 0 1px,transparent 1px 24px),
         repeating-linear-gradient(90deg,rgba(234,242,255,.07) 0 1px,transparent 1px 24px);
  --panel:rgba(13,67,153,.96); --bdf:blur(6px);
}
html[data-skin=blueprint][data-theme=light]{
  --surface:#F3F7FF; --plane:#FBFDFF; --band:#E4ECFA; --ink:#0B2A5C; --ink2:#233F72;
  --muted:#4A6290; --grid:#BFCDE8; --link:#1650B8; --bar:#B5470F;
  --good:#0F6B4A; --warn:#A3245A; --off:#4A6290; --onbar:#FFFFFF;
  --accent-sky:#1650B8; --accent-mint:#0F6B4A; --accent-gold:#7D5A00;
  --accent-coral:#B5470F; --accent-violet:#5A3FB8;
  --wash:repeating-linear-gradient(0deg,rgba(31,95,209,.07) 0 1px,transparent 1px 24px),
         repeating-linear-gradient(90deg,rgba(31,95,209,.07) 0 1px,transparent 1px 24px);
  --panel:rgba(251,253,255,.96);
}
/* Aurora is the northern lights over a midnight sky: one teal-to-violet curtain across the top of the
   window and nothing else. Teal is the action, so the verdict green is pushed toward lime to keep "this
   is a button" and "this runs here" in two different hues, the same separation terminal makes with
   amber. The curtain is `fixed` like every wash, so it hangs in the window rather than scrolling away. */
html[data-skin=aurora]{
  --surface:#0A1128; --plane:#101936; --band:#172244; --ink:#F1F5FF; --ink2:#CFD7F0;
  --muted:#9DA8CC; --grid:#35416B; --link:#8CC8FF; --bar:#2EC4B6;
  --good:#A8EF7A; --warn:#FFB5E8; --off:#9DA8CC; --onbar:#06121F;
  --accent-sky:#8CC8FF; --accent-mint:#A8EF7A; --accent-gold:#FFD98A;
  --accent-coral:#FFB5E8; --accent-violet:#C39BF0;
  --ui:"Geist",system-ui,-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;
  --wash:linear-gradient(172deg,rgba(46,196,182,.20),rgba(114,242,165,.10) 14%,rgba(157,78,221,.16) 26%,
         transparent 40%),radial-gradient(900px 420px at 80% -10%,rgba(255,181,232,.12),transparent 70%);
  --panel:rgba(16,25,54,.78); --bdf:blur(16px) saturate(1.3);
}
html[data-skin=aurora][data-theme=light]{
  --surface:#F4F6FF; --plane:#FBFCFF; --band:#E6EAF8; --ink:#1B1F3B; --ink2:#373C5E;
  --muted:#595F82; --grid:#C6CCE4; --link:#2447B0; --bar:#0E6E68;
  --good:#3D6A0F; --warn:#A3316E; --off:#595F82; --onbar:#FFFFFF;
  --accent-sky:#2447B0; --accent-mint:#3D6A0F; --accent-gold:#805A00;
  --accent-coral:#A3316E; --accent-violet:#6A2CB0;
  --wash:linear-gradient(172deg,rgba(18,122,115,.14),rgba(60,160,90,.07) 14%,rgba(123,44,191,.11) 26%,
         transparent 40%),radial-gradient(900px 420px at 80% -10%,rgba(220,90,170,.08),transparent 70%);
  --panel:rgba(251,252,255,.80);
}
/* Holo is the one theme the Settings menu does not offer until it is unlocked from the admin panel (see
   `docs/assets/atlas-admin.js`), and the head script will not wear it before then either. Clean lines,
   texture and light, in three layers of --wash, top first: a film-grain tile -- an SVG fractal-noise
   filter in a data URI, so it costs no request -- then a 32px hairline grid, then an iridescent violet,
   cyan and rose light falling from the top of the window. Violet is the action and cyan the link, so a
   button and a link are never one hue, and the thirteen tokens underneath are as plain and as measured as
   graphite's: all the colour is in the wash and the accents, where it costs no contrast. */
html[data-skin=holo]{
  --surface:#07090F; --plane:#0D1018; --band:#161A25; --ink:#F2F5FB; --ink2:#C8D0DF;
  --muted:#939EB5; --grid:#353D52; --link:#7AD7FF; --bar:#A99BFF;
  --good:#5FE3B1; --warn:#FF93A6; --off:#939EB5; --onbar:#0B0820;
  --accent-sky:#7AD7FF; --accent-mint:#5FE3B1; --accent-gold:#FFD38A;
  --accent-coral:#FF93A6; --accent-violet:#C6B8FF;
  --ui:"Geist",system-ui,-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;
  --wash:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 .07 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"),
         repeating-linear-gradient(0deg,rgba(122,215,255,.05) 0 1px,transparent 1px 32px),
         repeating-linear-gradient(90deg,rgba(122,215,255,.05) 0 1px,transparent 1px 32px),
         radial-gradient(900px 460px at 88% -10%,rgba(169,155,255,.24),transparent 70%),
         radial-gradient(760px 420px at 4% -8%,rgba(122,215,255,.18),transparent 70%),
         radial-gradient(640px 360px at 50% -14%,rgba(255,147,166,.10),transparent 70%);
  --panel:rgba(13,16,24,.74); --bdf:blur(16px) saturate(1.5);
}
html[data-skin=holo][data-theme=light]{
  --surface:#F3F5FA; --plane:#FCFDFF; --band:#E7EBF3; --ink:#0D1220; --ink2:#2C3446;
  --muted:#525D73; --grid:#C0C7D6; --link:#0B57C2; --bar:#5A3FD1;
  --good:#0E6B4C; --warn:#A32F4F; --off:#525D73; --onbar:#FFFFFF;
  --accent-sky:#0B57C2; --accent-mint:#0E6B4C; --accent-gold:#7A5600;
  --accent-coral:#A32F4F; --accent-violet:#5A3FD1;
  --wash:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 .055 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E"),
         repeating-linear-gradient(0deg,rgba(90,63,209,.055) 0 1px,transparent 1px 32px),
         repeating-linear-gradient(90deg,rgba(90,63,209,.055) 0 1px,transparent 1px 32px),
         radial-gradient(900px 460px at 88% -10%,rgba(90,63,209,.13),transparent 70%),
         radial-gradient(760px 420px at 4% -8%,rgba(11,87,194,.10),transparent 70%),
         radial-gradient(640px 360px at 50% -14%,rgba(163,47,79,.07),transparent 70%);
  --panel:rgba(252,253,255,.78);
}
*{box-sizing:border-box}
/* --ui and --wash are the structural tokens declared in the block above; graphite resolves them to this
   file's own font stack and to `none`, so a reader who has chosen no theme gets what was written here
   before. The 1.6 line height is this page's own and stays -- it is prose, not a table. */
body{margin:0;background:var(--surface);background-image:var(--wash);
  background-attachment:fixed;color:var(--ink);font:15px/1.6 var(--ui)}
a{color:var(--link);text-decoration:none}
a:hover{text-decoration:underline}
button{font:inherit;cursor:pointer}
header{background:var(--panel);backdrop-filter:var(--bdf);border-bottom:1px solid var(--grid);
  padding:18px 20px 22px}
/* Narrower than the 1500px the table surfaces use. This one is prose, and a 44-character-per-line
   measure is what a blurb and a licence sentence want; 1500px would set the lead paragraph 180
   characters wide, which is unreadable for the one thing on the page a reader has to read. */
.wrap{max-width:1000px;margin:0 auto}
main{padding:0 20px 64px}
footer{border-top:1px solid var(--grid);background:var(--plane);padding:22px 20px;
  color:var(--muted);font-size:13px}
h1{margin:0 0 4px;font-size:30px;letter-spacing:-.02em;overflow-wrap:anywhere}
h2{margin:0 0 10px;font-size:15px;text-transform:uppercase;letter-spacing:.07em;color:var(--muted)}
.crumb{font-size:13px;color:var(--muted);margin-bottom:14px}
.top{display:flex;justify-content:space-between;align-items:flex-start;gap:20px;flex-wrap:wrap}
.top>div{min-width:0}
/* word-break rather than overflow-wrap, because an owner/name pair breaks nowhere: a slash is not a
   break opportunity in Chrome or Safari, so a 52-character nwo would otherwise widen the document. */
.nwo{margin:0 0 12px;color:var(--muted);font-size:13px;word-break:break-all}
.lead{margin:0 0 14px;color:var(--ink2);font-size:16px;max-width:68ch}
.lead.none,.none{color:var(--muted);font-style:italic}
/* Each fact its own element rather than text separated by dots, so that when the row wraps on a phone
   the separator never ends up orphaned at the start of a line. */
.facts{display:flex;flex-wrap:wrap;gap:6px 18px;margin:0 0 18px;font-size:13px;color:var(--muted)}
.facts span{white-space:nowrap}
/* The live pair starts hidden and detail.js reveals it once live.json has answered. Hidden rather than
   showing a placeholder, because "Stars —" that never fills in reads as a broken page, whereas a fact
   that simply is not there reads as a fact that is not there. */
.facts .live{display:none}
.facts .live.on{display:inline;color:var(--ink2)}
.facts .live b{color:var(--ink);font-variant-numeric:tabular-nums}
.acts{display:flex;flex-wrap:wrap;gap:10px 16px;align-items:center;margin:0}
/* The one link that has to be found. Filled with the accent, which is why it needs --onbar for ink. */
.cta{display:inline-block;background:var(--bar);color:var(--onbar);font-weight:600;
  border-radius:8px;padding:10px 18px}
.cta:hover{text-decoration:none;filter:brightness(1.08)}
.alt{font-size:14px}
.chip{display:inline-block;background:var(--band);color:var(--ink2);border:1px solid var(--grid);
  border-radius:999px;padding:5px 12px;font-size:13px;white-space:nowrap;margin:0 5px 5px 0}
.chip:hover{border-color:var(--bar);color:var(--ink);text-decoration:none}
.chip.cat{border-color:var(--bar);color:var(--ink)}
.chips{margin-top:2px}
.util{flex-shrink:0}
section{margin:34px 0 0;border-top:1px solid var(--grid);padding-top:22px}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0,0,0,0);white-space:nowrap;border:0}
/* The reader is a document surface inside the Atlas, not a separately themed application. Its aliases
   point directly at the shared product tokens so a theme change cannot leave the Markdown behind. */
.reader{
  --read-bg:var(--surface);--read-panel:var(--plane);--read-raised:var(--band);--read-ink:var(--ink);
  --read-muted:var(--muted);--read-line:var(--grid);--read-link:var(--link);--read-accent:var(--bar);
  --read-warn:var(--warn);--read-code:var(--surface);--read-good:var(--good);--read-quote:var(--band);
  overflow:hidden;border:1px solid var(--read-line);border-radius:14px;background:var(--read-panel);
  color:var(--read-ink);box-shadow:0 20px 55px rgba(0,0,0,.3),0 0 0 1px color-mix(in srgb,var(--bar) 5%,transparent)}
.reader [hidden]{display:none!important}
html[data-theme=light] .reader{
  --read-panel:var(--band);--read-raised:var(--plane);
  --read-code:color-mix(in srgb,var(--band) 82%,var(--grid));--read-quote:var(--surface);
  box-shadow:0 18px 46px rgba(25,35,50,.11),0 0 0 1px color-mix(in srgb,var(--bar) 4%,transparent)}
.preview-intro{display:flex;align-items:flex-end;justify-content:space-between;gap:20px;margin-bottom:14px}
.preview-intro h2{margin-bottom:6px;color:var(--ink);font-size:19px;text-transform:none;letter-spacing:-.01em}
.preview-intro .lead{margin:0;font-size:14.5px}
.eyebrow{margin:0 0 3px;color:var(--bar);font-size:11px;font-weight:700;letter-spacing:.13em;
  text-transform:uppercase}
.source-live{display:inline-flex;align-items:center;gap:7px;color:var(--muted);font-size:12px;
  white-space:nowrap;margin-bottom:3px}
.source-live i{width:7px;height:7px;border-radius:50%;background:var(--good);
  box-shadow:0 0 0 4px color-mix(in srgb,var(--good) 14%,transparent)}
.reader-chrome{min-height:55px;display:flex;align-items:center;gap:12px;padding:9px 12px;
  background:var(--read-bg);border-bottom:1px solid var(--read-line)}
.window-dots{display:flex;gap:6px;flex:0 0 auto}
.window-dots i{width:9px;height:9px;border-radius:50%;background:var(--read-muted)}
.window-dots i:nth-child(2){background:var(--read-warn)}
.window-dots i:nth-child(3){background:var(--read-good)}
.file-picker{min-width:0;flex:1;position:relative}
.file-picker:after{content:"⌄";position:absolute;right:11px;top:50%;transform:translateY(-55%);
  color:var(--read-muted);pointer-events:none;font-size:15px}
.file-picker select{width:100%;height:35px;appearance:none;border:1px solid var(--read-line);
  border-radius:7px;background:var(--read-raised);color:var(--read-ink);padding:0 32px 0 11px;
  font:12.5px/1 var(--mono);
  text-overflow:ellipsis;cursor:pointer}
.file-picker select:focus,.viewtab:focus-visible,.open-source:focus-visible{
  outline:2px solid var(--read-link);outline-offset:2px}
.file-picker select:disabled{cursor:wait;color:var(--read-muted)}
.reader-tabs{display:flex;padding:3px;border-radius:8px;background:var(--read-raised);flex:0 0 auto}
.viewtab{border:0;border-radius:6px;padding:6px 10px;background:transparent;color:var(--read-muted);
  font-size:12px;line-height:1.2}
.viewtab:hover{color:var(--read-ink)}
.viewtab.on{background:var(--read-panel);color:var(--read-ink);box-shadow:0 1px 4px rgba(0,0,0,.18)}
.open-source{color:var(--read-link);font-size:12px;white-space:nowrap}
.reader-status{min-height:240px;display:flex;align-items:center;justify-content:center;gap:11px;
  padding:34px;text-align:center;color:var(--read-muted);font-size:14px}
.reader[data-state=error] .reader-status{min-height:190px;flex-direction:column}
.reader-status a{color:var(--read-link);font-weight:600}
.loader{width:18px;height:18px;border:2px solid var(--read-line);border-top-color:var(--read-link);
  border-radius:50%;animation:reader-spin .8s linear infinite}
@keyframes reader-spin{to{transform:rotate(360deg)}}
.markdown-body{max-height:760px;overflow:auto;padding:30px clamp(18px,4vw,42px) 46px;
  background:var(--read-panel);color:var(--read-ink);
  font:16px/1.72 Charter,"Bitstream Charter","Sitka Text",Cambria,Georgia,serif;
  scrollbar-color:var(--read-line) var(--read-bg)}
.markdown-body>*:first-child{margin-top:0!important}
.markdown-body>*:last-child{margin-bottom:0!important}
.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,
.markdown-body h5,.markdown-body h6{color:var(--read-ink);line-height:1.22;margin:1.55em 0 .65em;
  font-family:"Aptos Display","Segoe UI Variable Display","Segoe UI",system-ui,sans-serif;
  font-weight:720;letter-spacing:-.025em;text-transform:none}
.markdown-body h1{font-size:2em;padding-bottom:.42em;border-bottom:1px solid var(--read-line)}
.markdown-body h2{font-size:1.52em;padding-bottom:.35em;border-bottom:1px solid var(--read-line)}
.markdown-body h3{font-size:1.23em;color:var(--read-accent)}
.markdown-body h4{font-size:1.05em;color:var(--read-warn)}
.markdown-body p,.markdown-body ul,.markdown-body ol{margin:0 0 1em}
.markdown-body li+li{margin-top:.28em}
.markdown-body li::marker{color:var(--read-accent);font-weight:700}
.markdown-body a{color:var(--read-link);text-decoration:underline;text-decoration-thickness:.07em;
  text-underline-offset:.17em}
.markdown-body a:hover{color:var(--read-accent)}
.markdown-body strong{color:var(--read-ink);font-weight:750}
.markdown-body blockquote{margin:1.2em 0;padding:.7em 1.05em;border-left:4px solid var(--read-accent);
  border-radius:0 8px 8px 0;background:var(--read-quote);color:var(--read-muted)}
.markdown-body blockquote>:last-child{margin-bottom:0}
.markdown-body code,.markdown-body kbd{font:13px/1.55 var(--mono)}
.markdown-body :not(pre)>code{padding:.16em .38em;border:1px solid var(--read-line);border-radius:5px;
  background:var(--read-code);color:var(--read-warn);font-size:.84em}
.markdown-body pre{overflow:auto;margin:1.15em 0;padding:16px 18px;border:1px solid var(--read-line);
  border-radius:9px;background:var(--read-code);color:var(--read-ink);line-height:1.55;
  box-shadow:inset 3px 0 0 var(--read-accent)}
.markdown-body pre code{display:block;min-width:max-content;color:inherit;background:transparent}
/* GitHub's renderer emits Primer-compatible `pl-*` spans for highlighted fenced blocks. The source
   owns the tokenisation; this small palette supplies the colour without importing all of Primer. */
.markdown-body .pl-c,.markdown-body .pl-c1{color:var(--read-muted)}
.markdown-body .pl-k,.markdown-body .pl-ent,.markdown-body .pl-pds{color:var(--read-accent)}
.markdown-body .pl-s,.markdown-body .pl-s1,.markdown-body .pl-smi{color:var(--read-good)}
.markdown-body .pl-en,.markdown-body .pl-v,.markdown-body .pl-e{color:var(--read-link)}
.markdown-body .pl-c1,.markdown-body .pl-mh,.markdown-body .pl-mi{color:var(--read-warn)}
.markdown-body hr{height:1px;margin:2em 0;border:0;background:var(--read-line)}
.markdown-body img{max-width:100%;height:auto;border-radius:7px;background:var(--read-bg)}
.markdown-body table{display:block;width:max-content;max-width:100%;overflow:auto;margin:1.2em 0;
  border-collapse:collapse;font:13.5px/1.5 "Aptos","Segoe UI",system-ui,sans-serif}
.markdown-body th,.markdown-body td{padding:8px 11px;border:1px solid var(--read-line);text-align:left}
.markdown-body th{background:var(--read-raised);color:var(--read-ink)}
.markdown-body tr:nth-child(even) td{background:color-mix(in srgb,var(--read-raised) 42%,transparent)}
.markdown-body details{margin:1em 0;padding:10px 13px;border:1px solid var(--read-line);
  border-radius:8px;background:var(--read-bg)}
.markdown-body summary{cursor:pointer;color:var(--read-link);font:600 14px/1.4 "Aptos","Segoe UI",sans-serif}
.markdown-body kbd{display:inline-block;padding:1px 5px;border:1px solid var(--read-line);
  border-bottom-width:2px;border-radius:4px;background:var(--read-raised);font-size:.75em}
.source-code{max-height:760px;min-height:280px;overflow:auto;margin:0;padding:25px 28px 40px;
  border:0;background:var(--read-code);color:var(--read-ink);tab-size:2;white-space:pre;
  font:13px/1.65 var(--mono);
  scrollbar-color:var(--read-line) var(--read-bg)}
.reader-note{margin:9px 2px 0;color:var(--muted);font-size:12px}
/* The install command, and the button that takes it away. Selecting wrapped monospace text without
   catching the paragraph above it is the thing this replaces. */
.cmdrow{display:flex;gap:10px;align-items:flex-start}
.cmd{flex:1;min-width:0;font:13.5px/1.6 var(--mono);color:var(--ink);
  background:var(--band);border:1px solid var(--grid);border-radius:6px;padding:10px 12px;
  overflow-wrap:anywhere;display:block}
.copy{background:var(--band);color:var(--ink2);border:1px solid var(--grid);border-radius:6px;
  padding:10px 14px;font-size:13px;white-space:nowrap}
.copy:hover{border-color:var(--bar);color:var(--ink)}
figure.shot{margin:0}
/* A fixed 2:1 box with the image contained inside it. The src is somebody else's CDN and its
   dimensions are unknown at build time, so height cannot be declared on the img -- and without a box
   the whole page below would jump when it finally arrives. `contain` rather than `cover` because these
   are arbitrary images: cropping a social card to fill a frame cuts the repository name off it. */
.shot img{width:100%;aspect-ratio:2/1;object-fit:contain;border-radius:10px;background:var(--band);
  border:1px solid var(--grid);display:block}
figcaption{color:var(--muted);font-size:12.5px;margin-top:8px;max-width:70ch}
table.os{width:100%;border-collapse:collapse;font-size:14px}
table.os caption{text-align:left;color:var(--muted);font-size:12.5px;margin-bottom:10px;
  max-width:70ch}
table.os th[scope=col]{text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.07em;
  color:var(--muted);padding:8px 10px;border-bottom:1px solid var(--grid);font-weight:600}
table.os th[scope=row]{text-align:left;padding:10px;border-bottom:1px solid var(--grid);
  font-weight:600;white-space:nowrap}
table.os td{padding:10px;border-bottom:1px solid var(--grid);vertical-align:top}
table.os .why{color:var(--ink2);font-size:13px}
/* The five platform marks -- scripts/osicons.py owns the geometry; these are the only rules that
   apply to all of them. `fill:currentColor` is the whole reason they can sit inside a coloured verdict:
   a green Y and a grey dash get a green and a grey mark without either being named here. */
.oi{width:1.15em;height:1.15em;fill:currentColor;vertical-align:-.19em;flex:none}
/* A mark and its word, when both are shown, should not be separable by a line break. */
.oiw{white-space:nowrap}
/* The legend -- see `legend()` for why a `title` is not enough. Sized and coloured like the caption it is,
   and it wraps, because five pairs do not fit on one line at 335px. */
.oikey{display:flex;flex-wrap:wrap;gap:4px 14px;margin:0 0 12px;font-size:12.5px;color:var(--muted)}
.mk{display:inline-grid;place-items:center;width:28px;height:28px;flex:none;
  vertical-align:-7px;margin-right:2px}
.brand{display:inline-flex;align-items:center;gap:7px;text-decoration:none;color:inherit;
  font-weight:600;letter-spacing:-.01em;white-space:nowrap}
.brand:hover span{text-decoration:underline}
/* The brand's own line, on the page families that have no breadcrumb to sit in. Named rather than
   reusing `.crumb`, which is `22_detail.py`'s class and already carries a rule there: a shared
   stylesheet that redefines a generator's own selector wins or loses on source order, which is not a
   thing to leave to chance across four files. */
.brandbar{margin:0 0 14px}
.brand .mk{vertical-align:baseline;margin-right:0}
.mk svg{width:30px;height:30px;display:block;overflow:visible;
  filter:drop-shadow(0 0 calc(5px * var(--fx-glow,1)) rgba(214,160,52,.45));
  animation:mkbreath calc(6s * var(--fx-pulse,1)) ease-in-out infinite}
@keyframes mkbreath{
  0%,100%{filter:drop-shadow(0 0 calc(3px * var(--fx-glow,1)) rgba(214,160,52,.32))}
  50%{filter:drop-shadow(0 0 calc(9px * var(--fx-glow,1)) rgba(214,160,52,.60))}}
@media(prefers-reduced-motion:reduce){.mk svg{animation:none}}

/* Bigger than the shared 1.15em, because this is the one surface where the mark stands beside its own
   word rather than in place of one: at the table's 14px that is ~17px, which matches the cap height of
   the word next to it instead of sitting under it. The margin is on top of the literal space in the
   markup -- a space alone is 4px at this size, which reads as one word with a picture stuck to it. */
table.os th[scope=row] .oi{width:1.2em;height:1.2em;margin-right:.2em}
/* Prefixed, on the same reasoning as 20_landing.py's .vY: a class name has to be a valid CSS
   identifier, and these are tone names rather than the verdict characters for exactly that reason. */
.vgood{color:var(--good);font-weight:700;white-space:nowrap}
.vwarn{color:var(--warn);white-space:nowrap}
.voff{color:var(--off);white-space:nowrap}
ul.lists{list-style:none;padding:0;margin:0;display:flex;flex-wrap:wrap;gap:8px}
ul.lists li{background:var(--band);border:1px solid var(--grid);border-radius:6px;padding:6px 12px;
  font-size:13.5px}
/* Two columns of cards on a desktop, one on a phone, without a media query: auto-fill collapses to a
   single track as soon as 320px stops fitting twice. */
ul.kinlist{list-style:none;padding:0;margin:0;display:grid;gap:10px;
  grid-template-columns:repeat(auto-fill,minmax(320px,1fr))}
ul.kinlist li{border:1px solid var(--grid);border-radius:10px;padding:12px 14px;background:var(--plane)}
ul.kinlist a{font-weight:600;overflow-wrap:anywhere}
ul.kinlist .nwo{display:block;margin:2px 0 0;font-size:12px}
ul.kinlist .desc{display:block;color:var(--ink2);font-size:13px;margin-top:6px}
/* The directory page. Three or four columns of names, which is the only layout in which 1,294 links
   are scannable rather than a mile of scrolling. */
ul.dir{list-style:none;padding:0;margin:0;columns:4 220px;column-gap:26px;font-size:13.5px}
ul.dir li{margin:0 0 5px;break-inside:avoid;overflow-wrap:anywhere}
.ct{color:var(--muted);font-weight:400}
.jump{font-size:13px;color:var(--muted);line-height:2;margin:14px 0 0}
.more{margin:14px 0 0;font-size:13.5px}
@media(max-width:640px){
  header{padding:14px 14px 18px}
  main{padding:0 14px 48px}
  footer{padding:18px 14px}
  h1{font-size:23px}
  .lead{font-size:15px}
  section{margin-top:26px;padding-top:18px}
  /* The button under the command rather than beside it. At 335px a flex row leaves the code element
     about 210px, which wraps a 145-character npm line into nine lines. */
  .cmdrow{flex-direction:column}
  .copy{align-self:flex-start}
  .cta{flex:1;text-align:center}
  .preview-intro{align-items:flex-start;flex-direction:column;gap:5px}
  .source-live{margin:0}
  .reader-chrome{align-items:stretch;flex-wrap:wrap;padding:10px}
  .window-dots{align-items:center}
  .file-picker{order:2;flex-basis:100%}
  .reader-tabs{margin-left:auto}
  .open-source{align-self:center}
  .markdown-body{max-height:680px;padding:22px 17px 34px;font-size:15px}
  .markdown-body h1{font-size:1.65em}
  .markdown-body h2{font-size:1.35em}
  .source-code{max-height:680px;padding:20px 17px 32px;font-size:12px}
  /* Four columns of names in 307px is 77px a column. One column, and let it scroll. */
  ul.dir{columns:1}
  /* `.util{flex-shrink:0}` is right on a desktop, where the nav must not be squeezed by a long owner/name
     pair beside it, and wrong here: it pins the nav at max-content, which is 390px once there are four
     links in it. A flex item that cannot shrink does not wrap either, so the whole document grew a 44px
     sideways scroll -- on the narrowest surface, where sideways scroll is worst. Shrinking is safe because
     the automatic minimum size still floors it at the longest word. */
  .util{flex-shrink:1}
  /* The evidence sentence is the first thing to go: three columns do not fit, and the verdict word
     without its reason is still the answer to "does this run on my machine". The reason stays in the
     document for a reader who asks for the desktop site, and it is never the only place a fact is. */
  table.os .why,table.os th[scope=col]:last-child{display:none}
}
@media(prefers-reduced-motion:reduce){.loader{animation:none}}
@font-face{font-family:"Geist";src:url(../assets/fonts/geist.woff2) format("woff2");font-weight:100 900;font-style:normal;font-display:optional}
@font-face{font-family:"Geist Mono";src:url(../assets/fonts/geist-mono.woff2) format("woff2");font-weight:400;font-style:normal;font-display:optional}
@font-face{font-family:"Geist Mono";src:url(../assets/fonts/geist-mono-600.woff2) format("woff2");font-weight:500 800;font-style:normal;font-display:optional}
@font-face{font-family:"Atlas Icons";src:url(../assets/fonts/geist-mono.woff2) format("woff2");unicode-range:U+F400-F5FF;font-display:swap}
/* The build tags in the footer. See `buildtags()` in scripts/sitewide.py. Two classes deep so that
   `footer a`, which every page family has, does not outrank them. */
.btags{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 14px;font:600 11px/1 var(--mono)}
.btags .btag{display:inline-flex;align-items:center;gap:6px;min-height:24px;padding:4px 9px;border-radius:7px;
  color:var(--ink);background:var(--band);border:1px solid var(--grid);text-decoration:none;
  letter-spacing:.01em}
.btags .btag:hover{border-color:var(--link);text-decoration:none}
.btags .btag b{font-weight:600}
.btags .btag .bv{color:var(--muted);font-weight:500}
.btags .btag .bico{color:var(--muted);font:400 12px/1 "Atlas Icons",var(--mono)}
.btags .btag .bico:empty{display:none}
.bdot{width:7px;height:7px;flex:none;border-radius:50%;background:var(--off)}
.btags .btag[data-state=ok] .bdot{background:var(--good);box-shadow:0 0 8px var(--good)}
.btags .btag[data-state=bad] .bdot{background:var(--warn);box-shadow:0 0 8px var(--warn)}
.btags .btag[data-state=run] .bdot{background:var(--link);box-shadow:0 0 8px var(--link)}
.btags .btag[data-state=ok] .bv{color:var(--good)}
.btags .btag[data-state=bad] .bv{color:var(--warn)}
.btags .btag[data-state=run] .bv{color:var(--link)}
@media (prefers-reduced-motion:no-preference){
  .btags .btag[data-state=run] .bdot{animation:bpulse 1.6s ease-in-out infinite}
  @keyframes bpulse{50%{opacity:.35}}
}
@media print{.btags{display:none}}
