/* =====================================================================
   base.css — Musikschule Tonleiter (WDM demo)
   Shared by EVERY page. Page-specific styling for the instrument pages
   lives in instrument.css; nothing else gets its own stylesheet.

   Order: fonts -> tokens -> reset -> layout -> type -> links/buttons ->
   header -> footer -> demo furniture -> utilities -> reduced motion.
   ===================================================================== */


/* ===== fonts =====
   Self-hosted, latin subset, zero external requests (DSGVO + performance).

   The fallback faces below are METRIC-MATCHED, measured in headless Chrome on
   2026-09-02, not guessed: the same German sentence was rendered at 100px in
   the real face and in the fallback, and size-adjust is the width ratio
   (webWidth / fallbackWidth). ascent/descent-override come from the real
   face's canvas fontBoundingBox, divided by size-adjust because the browser
   applies size-adjust to the overrides too. Verified afterwards: a 520px-wide
   German paragraph is byte-for-byte the same height in the real face and in
   the fallback (delta 0.00 px for body, bold UI and the serif display), so a
   font swap moves nothing and CLS stays 0.

   Two sans fallbacks, not one: Arial Bold is ~4.3% wider relative to
   Instrument Sans than Arial Regular is, so a single size-adjust would let
   the nav and the buttons jump on swap even while the body text held still. */

@font-face {
  font-family: "Instrument Serif";
  src: url("../fonts/instrumentserif-latin.woff2?v=9b8dd1ec") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+2122, U+2212;
}

@font-face {
  font-family: "Instrument Sans";
  src: url("../fonts/instrumentsans-var-latin.woff2?v=d4be1b60") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+2000-206F, U+2122, U+2212;
}

@font-face {
  font-family: "Instrument Sans Fallback";
  src: local("Arial"), local("Helvetica Neue"), local("Liberation Sans");
  font-weight: 100 450;
  size-adjust: 101.47%;
  ascent-override: 95.6%;
  descent-override: 24.64%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Instrument Sans Fallback";
  src: local("Arial Bold"), local("Arial"), local("Liberation Sans Bold");
  font-weight: 451 900;
  size-adjust: 97.16%;
  ascent-override: 99.83%;
  descent-override: 25.73%;
  line-gap-override: 0%;
}

@font-face {
  font-family: "Instrument Serif Fallback";
  src: local("Georgia"), local("Times New Roman"), local("Liberation Serif");
  font-weight: 400;
  size-adjust: 75.03%;
  ascent-override: 131.95%;
  descent-override: 41.32%;
  line-gap-override: 0%;
}


/* ===== tokens =====
   Fixed in BRIEF.md. Six colours, nothing else. Success and error states use
   brass and paper, never green/red, so the palette never breaks character.

   v2: these six are now the BÜHNE theme, and two more looks override the same
   names further down (see "themes"). Every colour in this site therefore has
   to come from a token — a hard-coded hex in a component is a colour that
   cannot follow the theme. The four that were left (the body wash, the poster
   haze, the timeline fade and the per-page --tint) are tokens now too.

   The role of each name is what stays fixed across the three themes, not its
   hue: --ink is always the page ground, --paper is always the text on it,
   --brass is always the single accent. That is why
   `background: var(--brass); color: var(--ink)` still reads correctly on
   paper and on silver without a single extra token: the accent is dark there
   and the ground is light, so the pairing simply inverts. */

:root {
  --ink: #0E0C0A;
  --stage: #17140F;
  --paper: #F3EBDD;
  --paper-60: rgba(243, 235, 221, .6);
  --brass: #D2A62C;
  --brass-deep: #9A7418;

  /* A hairline that reads on the near-black ground without becoming a box.
     Built from paper, not from a seventh colour. */
  --rule: rgba(243, 235, 221, .16);
  --rule-strong: rgba(243, 235, 221, .3);

  /* The three former hard-codes. --ground is the whole body background-image,
     not just its colour, because the studio look needs a vertical sweep where
     the stage look needs a pool at the top. */
  --ground: radial-gradient(120% 60% at 50% -10%, rgba(210, 166, 44, .07), transparent 62%);
  --poster-haze: rgba(243, 235, 221, .05);
  --brass-fade: rgba(210, 166, 44, .12);
  /* Emissive used by scene.js setHighlight(). Read through getComputedStyle,
     so the 3D highlight is the same accent as the anatomy list. */
  --highlight: #D2A62C;

  --sans: "Instrument Sans", "Instrument Sans Fallback", system-ui, sans-serif;
  --serif: "Instrument Serif", "Instrument Serif Fallback", Georgia, serif;

  /* One motion rhythm for the whole site. Anything that moves uses these two
     values, so nothing ever feels like it came from a different site. */
  --ease: cubic-bezier(.2, .7, .1, 1);
  --dur: 420ms;

  /* 2px on controls, 0 on cards. The round play button is the one exception
     and it sets its own radius. */
  --r: 2px;

  --gap: clamp(1rem, 2.2vw, 2rem);
  --section-y: clamp(4rem, 9vw, 8rem);
}


/* ===== themes =====
   Three looks, one markup, switched by data-theme on <html>. Only the colour
   tokens change: no theme moves a box, changes a font size or adds a rule, so
   a page laid out in one look is laid out identically in the other two and
   the switch cannot shift anything.

   BÜHNE is the :root block above and is deliberately NOT repeated here — a
   second copy would be a second thing to keep in sync, and the default look
   has to survive a visitor with data-theme missing entirely.

   Every value below was measured, not eyeballed: body text, links, the muted
   line and the accent all clear 4.5:1 against the ground they actually sit
   on, with rgba blended over the real background first (see docs/TESTS.md).

   PAPIER — direction A: paper, ink, and watercolour that the notes leave
   behind. The accent is a burnt sienna, the warm end of the same ink ramp the
   blots are drawn from, so the one accent and the note colours are the same
   family. Brass is impossible here: #D2A62C on paper measures 1.9:1. */

html[data-theme="papier"],
html[data-theme="skizze"] {
  --ink: #F7F3EA;                        /* ground: the sheet */
  --stage: #EFE9DC;                      /* the deeper sheet the stage sits on */
  --paper: #141311;                      /* text: ink, never pure black */
  --paper-60: rgba(20, 19, 17, .62);     /* 5.03:1 blended over the sheet */
  --brass: #A6431B;                      /* 5.52:1 — the one accent */
  --brass-deep: #7B3113;

  --rule: rgba(20, 19, 17, .15);
  --rule-strong: rgba(20, 19, 17, .32);

  --ground: radial-gradient(120% 55% at 50% -8%, rgba(166, 67, 27, .05), transparent 60%);
  --poster-haze: rgba(20, 19, 17, .035);
  --brass-fade: rgba(166, 67, 27, .14);
  --highlight: #A6431B;
}

/* STUDIO — direction C: a silver room, one signal colour. The signal in the
   render is #FF4D1F, which measures 2.86:1 on the wall and therefore cannot
   be text. The UI accent is its darker sibling (5.53:1); #FF4D1F stays where
   it belongs, on the oscilloscope ribbon in js/sound-visual.js, where it is a
   graphic and not a label. */

html[data-theme="studio"],
html[data-theme="riss"] {
  --ink: #EDEEF1;                        /* ground: the wall */
  --stage: #E4E5E9;
  --paper: #101214;
  --paper-60: rgba(16, 18, 20, .62);     /* 5.00:1 blended over the wall */
  --brass: #B32C00;                      /* 5.53:1 */
  --brass-deep: #8A2200;

  --rule: rgba(16, 18, 20, .14);
  --rule-strong: rgba(16, 18, 20, .30);

  /* Wall at the top, light at eye height, floor below — the sweep of the
     reference render, painted once across the whole document. */
  --ground: linear-gradient(180deg, #E4E5E9 0%, #F5F6F8 42%, #EDEEF1 100%);
  --poster-haze: rgba(16, 18, 20, .04);
  --brass-fade: rgba(179, 44, 0, .14);
  --highlight: #B32C00;
}


/* ===== the two illustrated looks =====
   SKIZZE and RISS are PAPIER and STUDIO with a drawing in the stage box
   instead of a lit 3D model, so they take the whole token set of their parent
   in the two selector lists above and only correct the sheet under the drawing
   here. That is deliberate and it is what keeps the measured contrast: no text
   colour, no accent and no rule changes between a look and its drawn sibling,
   so nothing on the page has to be re-measured. Only these two lines and the
   drawing tokens below are new. Full spec: docs/DRAWING-LANGUAGE.md §4.

   The --draw-* and --wash-* tokens are read by nothing on the page — only by
   svg/*.svg, which hard-codes no colour at all. That is why a look change
   re-colours twelve drawings with no JavaScript. */

html[data-theme="skizze"] {
  --ink: #F7F3EA;
  --stage: #F3EEE3;                      /* a shade above papier: the sheet
                                            the drawing is ON, not the one the
                                            page is printed on */

  /* The hand. One weight for the silhouette, one for interior detail, and no
     third — two weights across six drawings is what makes six agents look
     like one draughtsman. */
  --draw-line: #2A2622;
  --draw-line-soft: rgba(42, 38, 34, .42);
  --draw-fill: rgba(255, 253, 247, .9);  /* .72 let 28 % of a wash through: bodies went lavender/ochre; mockup A's are cream */
  --draw-w: 1.6;
  --draw-w-fine: .7;

  /* The watercolour behind the instrument. The instrument itself is never
     coloured; the contrast between an uncoloured object and a coloured ground
     is the whole look. */
  --wash-1: #A8C0A6;
  --wash-2: #B3A6CC;
  --wash-3: #E8B79A;
  --wash-4: #D9C77E;
  --wash-5: #9FB2CC;
  --wash-6: #D9A0A0;
}

html[data-theme="riss"] {
  --ink: #F4F5F7;
  --stage: #EDEEF1;

  --draw-line: #3A4048;
  --draw-line-soft: rgba(58, 64, 72, .38);
  /* A drawing office fills with a faint sheen, never a flat grey: the body
     carries a gradient between these two. */
  --draw-fill-hi: rgba(255, 255, 255, .72);
  --draw-fill-lo: rgba(226, 229, 234, .55);
  --draw-dim: rgba(58, 64, 72, .55);
  --draw-w: 1.1;
  --draw-w-fine: .55;
}


/* ===== reset =====
   Small on purpose: the parts of a normalize that this site actually uses. */

*, *::before, *::after { box-sizing: border-box; }

html {
  /* Native smooth scrolling is fine here (no scroll library), but it must be
     switched off for people who asked for less motion — see the block at the
     bottom of this file. */
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  font-size: clamp(1rem, .96rem + .18vw, 1.0625rem);
  line-height: 1.6;
  font-weight: 400;
  /* The stage is a dark room. A very slow warm pool at the top of the page
     stops the flat #0E0C0A from looking like an unstyled dark mode. Each
     theme brings its own: see --ground under "themes". */
  background-image: var(--ground);
  background-repeat: no-repeat;
}

img, svg { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }


/* ===== layout =====
   One wrapper, one 12-column grid. Sections declare their own vertical
   rhythm so a page never needs a margin utility. */

.wrap {
  max-width: 1200px;
  margin-inline: auto;
  padding-inline: clamp(1.25rem, 5vw, 3rem);
}

.grid {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gap);
}

/* One column on a phone, and this has to be a REAL RULE, not the absence of
   one. A .col-* child with no grid-column is auto-placed into a single 1/12
   track: 27 px wide at 390, which crushes body copy to one word per line and
   clips a heading to single letters. Nothing catches it either, because
   minmax(0, 1fr) tracks shrink rather than overflow, so scrollWidth stays
   equal to innerWidth while the section is unreadable. Measured on
   index.html so-laeuft-es and fuer-wen, 2026-09-02. */
.col-4, .col-5, .col-6, .col-7, .col-8, .col-12 { grid-column: 1 / -1; }

/* From 900px up the 12 columns are actually used. */
@media (min-width: 900px) {
  .col-4 { grid-column: span 4; }
  .col-5 { grid-column: span 5; }
  .col-6 { grid-column: span 6; }
  .col-7 { grid-column: span 7; }
  .col-8 { grid-column: span 8; }
  .col-12 { grid-column: span 12; }
}

section { padding-block: var(--section-y); }
section + section { border-top: 1px solid var(--rule); }


/* ===== type =====
   One typographic idea: Instrument Serif set large and tight for display,
   Instrument Sans doing every other job. No third font, no italics. */

h1, h2, h3 {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.04;
  letter-spacing: -.015em;
  margin: 0 0 .5em;
  text-wrap: balance;
}

h1 { font-size: clamp(2.4rem, 1.5rem + 3.6vw, 4.125rem); }
h2 { font-size: clamp(1.9rem, 1.2rem + 2.6vw, 3rem); }
h3 { font-size: clamp(1.25rem, 1.1rem + .8vw, 1.6rem); line-height: 1.15; }

p { margin: 0 0 1em; max-width: 62ch; }
p:last-child { margin-bottom: 0; }

/* The label above a heading. Small caps done with tracking, not with a
   different font, so it stays in the same voice. */
.eyebrow {
  font-size: .8125rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--brass);
  margin: 0 0 1.25rem;
  font-weight: 500;
}
.eyebrow::before {
  content: "";
  display: inline-block;
  width: 2rem;
  height: 1px;
  background: var(--brass);
  vertical-align: .3em;
  margin-right: .75rem;
}

.lead { font-size: clamp(1.0625rem, 1rem + .4vw, 1.3125rem); color: var(--paper); }
.muted { color: var(--paper-60); }


/* ===== heading reveal =====
   A line mask, never opacity. Contrast audits sample text mid-transition, and
   a fading heading fails them; a heading sliding out from behind a clip edge
   is at full contrast in every frame. */

.reveal {
  display: block;
  overflow: hidden;
  /* The mask cuts at the box edge, and at line-height 1.04 a descender hangs
     about 6 px below the last line box — "zum Ausprobieren da." lost the tail
     of its p. The padding gives the descender room and the margin gives the
     same space back, so the rhythm below the heading is unchanged. */
  padding-bottom: .18em;
  margin-bottom: .32em;
}
.reveal > span {
  display: block;
  /* 130%, not 105%: the line has to start below the padded box as well as
     below the text, or its top edge shows through before the animation runs. */
  transform: translateY(130%);
  animation: line-up var(--dur) var(--ease) forwards;
}
.reveal > span:nth-child(2) { animation-delay: 60ms; }
.reveal > span:nth-child(3) { animation-delay: 120ms; }

@keyframes line-up { to { transform: translateY(0); } }


/* ===== links and focus =====
   The focus ring is designed, not the browser default: a brass outline with a
   dark offset ring so it stays visible on the near-black ground and on brass
   buttons alike. */

a { color: var(--paper); text-decoration-color: var(--rule-strong); text-underline-offset: .22em; }
a:hover { color: var(--brass); text-decoration-color: var(--brass); }

:focus-visible {
  outline: 2px solid var(--brass);
  outline-offset: 3px;
  border-radius: var(--r);
}
/* The brass button already is brass, so a brass ring on it would vanish.
   Paper there instead. */
.btn-primary:focus-visible, .play-round:focus-visible { outline-color: var(--paper); }

.skip {
  position: absolute;
  left: 1rem;
  top: -4rem;
  z-index: 50;
  background: var(--brass);
  color: var(--ink);
  padding: .7rem 1.1rem;
  border-radius: var(--r);
  font-weight: 600;
  text-decoration: none;
  transition: top 180ms var(--ease);
}
.skip:focus { top: 1rem; color: var(--ink); }


/* ===== buttons =====
   Two weights only. Both are 2px-radius rectangles; nothing on this site is a
   pill except the one round play button, which earns it by being the single
   signature control. */

.btn {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  padding: .85rem 1.5rem;
  border: 1px solid var(--brass);
  border-radius: var(--r);
  font-weight: 600;
  font-size: .9375rem;
  letter-spacing: .01em;
  text-decoration: none;
  cursor: pointer;
  background: transparent;
  color: var(--brass);
  transition: background var(--dur) var(--ease), color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.btn:hover { background: var(--brass); color: var(--ink); }

.btn-primary { background: var(--brass); color: var(--ink); }
.btn-primary:hover { background: var(--paper); border-color: var(--paper); color: var(--ink); }

.btn-ghost { border-color: var(--rule-strong); color: var(--paper); }
.btn-ghost:hover { background: transparent; border-color: var(--paper); color: var(--paper); }

.btn-row { display: flex; flex-wrap: wrap; gap: .75rem; margin-top: 2rem; }

/* The arrow is drawn with a border, not an icon font and not an emoji. */
.btn .arr {
  width: .5rem; height: .5rem;
  border-top: 1.5px solid currentColor;
  border-right: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--dur) var(--ease);
}
.btn:hover .arr { transform: rotate(45deg) translate(2px, -2px); }


/* ===== header / nav =====
   Static markup on every page, never injected by JS: a crawler (and a reader
   with JS off) must see the whole link graph. No hamburger either — five
   links wrap onto a second row on a phone, which needs no script and cannot
   trap focus. */

.site-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .75rem 2rem;
  padding-block: 1.5rem;
  border-bottom: 1px solid var(--rule);
}

.brand {
  font-family: var(--serif);
  font-size: 1.5rem;
  letter-spacing: -.01em;
  text-decoration: none;
  color: var(--paper);
  margin-right: auto;
}
.brand:hover { color: var(--paper); }
/* The brass tick under the wordmark is the school's only mark. Drawn in CSS,
   so it costs no request and scales with the type. */
.brand b { font-weight: 400; border-bottom: 2px solid var(--brass); padding-bottom: .1em; }

.site-nav { display: flex; flex-wrap: wrap; gap: .4rem 1.5rem; align-items: baseline; }
.site-nav a {
  text-decoration: none;
  font-size: .9375rem;
  color: var(--paper);
  padding-block: .35rem;
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.site-nav a:hover { color: var(--brass); border-bottom-color: var(--brass); }
.site-nav a[aria-current="page"] { color: var(--brass); border-bottom-color: var(--brass); }

/* The language menu is the fifth item in the nav, so it is a flex item like the
   links and lands at the end of whichever row it wraps onto. js/i18n.js draws
   the button and the listbox inside this span; the only thing the demo owns is
   the box it sits in and the four colour variables below. */
.site-nav .lang-host { display: flex; align-items: center; }

/* js/i18n.js ships neutral defaults for its switcher (near-black pill, pale
   text). Those are invisible in the PAPIER and STUDIO looks, where the ground
   is light — so the four variables it reads are mapped onto this site's own
   tokens instead of a fixed colour, and the menu follows the Stimmung with
   everything else. --stage and not --ink: the open list has to be opaque
   against whatever it covers. */
:root {
  --wdm-lang-font: var(--sans);
  --wdm-lang-bg: var(--stage);
  --wdm-lang-fg: var(--paper);
  --wdm-lang-border: var(--rule-strong);
}


/* ===== footer =====
   Address block is real markup (address element), so a phone number is
   tappable and a screen reader announces it as contact details. */

.site-foot {
  border-top: 1px solid var(--rule);
  padding-block: 3.5rem 1rem;
  font-size: .9375rem;
  color: var(--paper-60);
}
.site-foot h2 { font-size: 1.25rem; color: var(--paper); margin-bottom: .6rem; }
.site-foot a { color: var(--paper-60); }
.site-foot a:hover { color: var(--brass); }

.foot-cols {
  display: grid;
  gap: 2.5rem var(--gap);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  padding-bottom: 2.5rem;
}
.foot-cols address { font-style: normal; }
.foot-links { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }

.foot-base {
  border-top: 1px solid var(--rule);
  padding-top: 1.25rem;
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.5rem;
  font-size: .875rem;
}


/* ===== demo furniture =====
   Required by the WDM demo kit: the visitor must always know this is a
   fictional example. It sits above the header so it never fights the hero,
   and it is dismissible but not remembered — a reload brings it back, because
   the notice is a disclosure, not a cookie banner. */

.demo-note {
  /* Stacked above the note overlay (z 20): the disclosure is the one thing on
     the page that may never be painted over by a decoration. */
  position: relative;
  z-index: 30;
  display: flex;
  align-items: center;
  gap: 1rem;
  background: var(--stage);
  border-bottom: 1px solid var(--rule);
  font-size: .875rem;
  padding: .7rem clamp(1.25rem, 5vw, 3rem);
  color: var(--paper-60);
}
.demo-note p { margin: 0; max-width: none; }
.demo-note strong { color: var(--paper); font-weight: 600; }
.demo-note a { color: var(--brass); }
.demo-note button {
  margin-left: auto;
  flex: none;
  width: 2rem; height: 2rem;
  background: transparent;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  color: var(--paper-60);
  cursor: pointer;
  line-height: 1;
  transition: color var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.demo-note button:hover { color: var(--paper); border-color: var(--rule-strong); }

/* Closing block that turns a demo visitor into a studio lead. Deliberately
   plain so it reads as an aside and not as part of the fiction. */
.studio-pitch { background: var(--stage); }
.studio-pitch p { color: var(--paper-60); }


/* ===== Stimmung switcher =====
   Three looks on one control, on every page. A native radio group and
   nothing else: arrow keys move between the three and space picks one for
   free, with no script, and a screen reader announces "Stimmung, Bühne, 1 of
   3" without a single aria attribute being invented here.

   Fixed, so it can never shift the layout. Right edge and vertically centred
   on a desktop; bottom right on a phone, which is the corner nothing else
   uses — the studio seal signs bottom LEFT and the CTA sits in the flow.

   THE LABEL DOES NOT FADE IN. Text on this site is never animated with
   opacity (contrast audits sample mid-transition and a half-faded word
   fails). It is revealed by width, which is a clip, and the word is in the
   accessibility tree the whole time. */

.theme-switch {
  position: fixed;
  z-index: 40;
  right: .55rem;
  bottom: 1rem;
}
@media (min-width: 900px) {
  .theme-switch { right: .75rem; bottom: auto; top: 50%; transform: translateY(-50%); }
}

.theme-switch fieldset {
  margin: 0;
  padding: .3rem;
  border: 1px solid var(--rule);
  border-radius: var(--r);
  background: var(--stage);
  display: grid;
  gap: .15rem;
}

.theme-switch label {
  display: flex;
  align-items: center;
  gap: .5rem;
  padding: .3rem;
  cursor: pointer;
  border-radius: var(--r);
  font-size: .8125rem;
  line-height: 1;
  color: var(--paper);
}
.theme-switch label:hover { color: var(--brass); }

/* The swatch is the theme itself: its ground with its accent on it. These
   five colours are hard-coded on purpose and are the only ones in the site
   that are — a swatch has to show the look you are NOT in, so it cannot come
   from a token that the current theme has already overridden. */
.theme-switch .sw {
  flex: none;
  width: 1.35rem; height: 1.35rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r);
  display: grid;
  place-items: center;
}
.theme-switch .sw::after {
  content: "";
  width: .42rem; height: .42rem;
  border-radius: 50%;
}
.theme-switch [data-sw="buehne"] { background: #17140F; }
.theme-switch [data-sw="buehne"]::after { background: #D2A62C; }
.theme-switch [data-sw="papier"] { background: #F7F3EA; }
.theme-switch [data-sw="papier"]::after { background: #A6431B; }
.theme-switch [data-sw="studio"] { background: #ECEDF0; }
.theme-switch [data-sw="studio"]::after { background: #FF4D1F; }
/* The two drawn looks carry their PARENT's sheet and their DRAWING HAND as
   the dot, not an accent: what makes Skizze different from Papier is that it
   is drawn, and a swatch that showed the same rust dot would say the opposite.
   Checked at the rendered 1.35rem, not in the hex. */
.theme-switch [data-sw="skizze"] { background: #F7F3EA; }
.theme-switch [data-sw="skizze"]::after { background: #2A2622; }
.theme-switch [data-sw="riss"] { background: #F4F5F7; }
.theme-switch [data-sw="riss"]::after { background: #3A4048; }

/* The chosen one is marked on the swatch, not by a background on the row: a
   filled row would turn three looks into three buttons. */
.theme-switch input:checked + .sw { border-color: var(--brass); box-shadow: 0 0 0 1px var(--brass); }
.theme-switch input:focus-visible + .sw { outline: 2px solid var(--brass); outline-offset: 2px; }

.theme-switch .tx {
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  transition: max-width var(--dur) var(--ease);
}
.theme-switch:hover .tx,
.theme-switch:focus-within .tx { max-width: 5rem; }

/* One flag on <html> hides the whole control for a client build. */
html[data-theme-switch="off"] .theme-switch { display: none; }

/* On a phone the five looks lie down.
   A five-row column in the bottom-right corner is 175 px tall and it sat on
   the last natural-note button on the trumpet page — measured, and it was
   already an open item at three rows. Laid out as one strip it is 40 px tall
   (4.7% of a 390x844 viewport, budget 25%) and centred, where the pages put
   text rather than controls.

   The word next to each swatch does not open on a phone: five words side by
   side do not fit in 390 px. It is clipped, not removed — max-width 0 is the
   same reveal-by-width mechanism as on desktop, so every radio keeps its
   label in the accessibility tree and the group still announces
   "Stimmung, Bühne, 1 von 5". */
@media (max-width: 899px) {
  .theme-switch {
    right: auto;
    left: 50%;
    transform: translateX(-50%);
    bottom: .5rem;
  }
  .theme-switch fieldset { grid-auto-flow: column; }
  .theme-switch label { padding: .25rem; gap: 0; }
  .theme-switch:hover .tx,
  .theme-switch:focus-within .tx { max-width: 0; }
}


/* ===== note overlay =====
   Where the sound is drawn. One canvas, fixed over the whole viewport,
   between the content (z auto) and the switcher (z 40) and the demo notice
   (z 30). It has no backing store and no rAF until the first note arrives,
   so a Lighthouse run — which never plays one — never pays for it.

   The blend mode is on only while the loop runs (data-on), because a
   permanently blended full-viewport layer costs a composite pass on every
   paint even when it is empty. */

.note-overlay {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  z-index: 20;
  pointer-events: none;
}
/* Motes on a dark stage add light; ink on paper only ever takes it away. */
.note-overlay[data-on][data-mode="buehne"] { mix-blend-mode: screen; }
/* The 0.35 is the contrast ceiling and it lives HERE, on the element, not in
   the drawing code. Blots are painted into the canvas at full strength and
   overlap freely; a group opacity then caps how much of the paper the whole
   layer can ever take, no matter how many notes are on it. The first attempt
   capped each blot instead, and a Hörprobe's thirty overlapping notes stacked
   to 0.93 — measured — which is a text-contrast bug the moment a blot lands
   under a paragraph. */
.note-overlay[data-on][data-mode="papier"] { mix-blend-mode: multiply; opacity: .35; }


/* ===== utilities ===== */

/* Visible to a screen reader, invisible on screen, and still focusable — the
   piano keys use this so a keyboard-only visitor can play the instrument that
   is otherwise only reachable inside a canvas. */
.vh {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
/* When one of those hidden controls takes focus it must become visible, or a
   sighted keyboard user watches focus disappear. */
.vh-focus:focus-within {
  position: static;
  width: auto; height: auto;
  margin: 0; clip: auto;
  white-space: normal;
}

.rule { border: 0; border-top: 1px solid var(--rule); margin: 0; }


/* ===== reduced motion =====
   Kills every transform and long transition, and lets the reveal headings
   start in place instead of animating in. Nothing here is decoration-only:
   each rule turns off something declared above. */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
  .reveal > span { transform: none; animation: none; }
  .btn:hover .arr { transform: rotate(45deg); }
  /* The switcher label still reveals, it just does it instantly — hiding it
     for good would take the word away from a sighted mouse user. */
  .theme-switch .tx { transition: none; }
}
