/* =====================================================================
   instrument.css — every content component of this site.

   base.css holds the tokens, the type and the page chrome (header, footer,
   buttons). This file holds everything that goes between them: the playable
   stage, the anatomy list, the price rows, the teacher card, the instrument
   grid and the timeline.

   EVERY page links exactly these two files, in this order, and no page has a
   stylesheet of its own. The only per-page value is --tint on .stage-box,
   which shifts the colour of the poster glow so six instrument pages do not
   look like the same photograph.

   Order: stage -> poster -> play button -> anatomy -> facts -> lists and
   cards -> instrument grid -> timeline -> studio pitch -> reduced motion.
   ===================================================================== */


/* ===== the stage =====
   Desktop: copy on 5 columns, instrument on 7, and the instrument column is
   sticky across both rows of the grid — so the piano stays lit and reachable
   while the visitor reads the parts list underneath the headline. Sticky
   inside a grid area works because the area spans both rows and the box only
   fills the first one.

   Mobile: one column, and the canvas is a square directly under the H1, bled
   out to both screen edges by cancelling the wrapper padding. Not 100vw —
   that includes the scrollbar and is a horizontal-overflow bug waiting to
   happen. */

.itop { padding-block: clamp(2.5rem, 6vw, 5rem) var(--section-y); }
/* The two blocks inside are real <section>s with their own data-sid, so the
   site-wide section spacing and divider have to be switched off here. */
.itop > section { padding-block: 0; border-top: 0; }
/* Not "section + section": the stage div sits between the two sections in the
   DOM, so an adjacent-sibling rule never matches and the anatomy heading ends
   up glued to the Hörprobe. */
.itop-anat { margin-top: clamp(3rem, 6vw, 5rem); }

.stage-art { margin-inline: calc(-1 * clamp(1.25rem, 5vw, 3rem)); margin-block: 1.5rem; }

@media (min-width: 900px) {
  .itop {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    grid-template-rows: auto auto;
    column-gap: var(--gap);
  }
  .itop-copy { grid-column: 1 / span 5; grid-row: 1; }
  .itop-anat { grid-column: 1 / span 5; grid-row: 2; }
  .itop-stage {
    grid-column: 6 / span 7;
    grid-row: 1 / 3;
    position: sticky;
    top: 1.5rem;
    align-self: start;
    margin-inline: 0;
    margin-block: 0;
  }
}

/* The box reserves its own height before anything loads, so the canvas
   arriving later moves nothing. This is where CLS 0 is won. */
.stage-box {
  position: relative;
  aspect-ratio: 1 / 1;
  background: var(--stage);
  overflow: hidden;
  isolation: isolate;
  --tint: 210 166 44;          /* brass, per page overridable */
}
@media (min-width: 900px) {
  .stage-box { aspect-ratio: 4 / 3; border: 1px solid var(--rule); }
}

/* The poster's glow follows the theme, and it has to do so AT FIRST PAINT —
   the poster is what every visitor sees before three.js is allowed to load,
   so JS is too late to colour it. That is the whole reason for !important
   here: every page writes its own --tint as an inline style (§6), inline
   beats a stylesheet, and the six per-page tints are a BÜHNE idea. Paper gets
   a wash of its own ink, the studio a neutral silver; the six pages stop
   differing there, which is correct — a warm brass pool in a cool silver room
   was the one thing that read as a mistake in every test render. */
html[data-theme="papier"] .stage-box { --tint: 166 67 27 !important; }
html[data-theme="studio"] .stage-box { --tint: 120 128 140 !important; }
/* The drawn looks take their parent's tint for the same reason: the glow is
   the light in the room behind the instrument, and Skizze is the paper room. */
html[data-theme="skizze"] .stage-box { --tint: 166 67 27 !important; }
html[data-theme="riss"] .stage-box { --tint: 120 128 140 !important; }


/* ===== poster =====
   What the visitor sees before three.js is allowed to load, and the ambient
   light behind it afterwards. Two layers on purpose: the glow stays forever
   (the WebGL canvas is transparent and borrows it as its backdrop), the line
   drawing fades out once the real instrument is standing there. */

.poster {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.poster::before {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(58% 42% at 50% 74%, rgb(var(--tint) / .20), transparent 70%),
    radial-gradient(90% 70% at 50% 30%, var(--poster-haze), transparent 72%);
}
/* The stage floor: one hairline, brighter in the middle where the light is. */
.poster::after {
  content: "";
  position: absolute;
  left: 8%; right: 8%; bottom: 21%;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgb(var(--tint) / .5), transparent);
}
.poster-art {
  position: relative;
  width: min(66%, 26rem);
  color: var(--brass);
  opacity: .85;
  transition: opacity 600ms var(--ease);
}
.poster-art svg { width: 100%; height: auto; }

.stage-box canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 700ms var(--ease);
  /* Vertical touch keeps scrolling the page, horizontal drag turns the
     instrument. Without this the canvas swallows the scroll on a phone. */
  touch-action: pan-y;
  cursor: grab;
}
.stage-box[data-stage="ready"] canvas { opacity: 1; }
.stage-box[data-stage="ready"] .poster-art { opacity: 0; }


/* ===== the illustrated stage =====
   Skizze and Riss put a drawing in the box instead of a renderer. The file is
   svg/<slug>-<look>.svg and it hard-codes no colour at all: every stroke and
   fill in it is one of the --draw-* / --wash-* tokens from css/base.css, which
   is why switching between the two looks re-colours it with no JavaScript.
   js/svg-stage.js mounts it and sets data-stage="svg"; the contract the twelve
   files follow is docs/DRAWING-LANGUAGE.md. */

.stage-svg {
  position: absolute;
  inset: 0;
  /* The parallax has to have a vanishing point, and perspective applies to an
     element's CHILDREN — so it belongs on the box the drawing fills, not on
     the drawing. 900px is a long lens: three degrees of yaw reads as a tilt
     and never as a shear. */
  perspective: 900px;
  /* Same bargain the canvas makes: a vertical drag still scrolls the page. */
  touch-action: pan-y;
}
.stage-svg svg {
  display: block;
  width: 100%;
  height: 100%;
}
/* The poster's line art is the stand-in for an instrument that has not
   arrived; once the drawing is there it would show through as a second,
   fainter one. The glow behind it stays. */
.stage-box[data-stage="svg"] .poster-art { opacity: 0; }
/* The poster's floor hairline is the 3D room's floor at 79% height. A drawing
   brings its own ground (riss) or has none (skizze, seen from above), and the
   hairline crossed the skizze keyboard as a stray pencil line. */
.stage-box[data-stage="svg"] .poster::after { opacity: 0; }

/* The watercolour blooms of the Skizze look. multiply is what makes two
   overlapping washes darken the way wet paint does instead of covering each
   other, and it is set here rather than in the file so the drawings stay pure
   geometry. */
.stage-svg .wash { mix-blend-mode: multiply; }

/* Highlight: the same accent the anatomy list uses, on the line only. Never on
   the fill — a part that turns solid stops being a drawing. */
.stage-svg .lit,
.stage-svg .lit * {
  stroke: var(--brass);
  stroke-width: calc(var(--draw-w) + .6);
}
/* Riss adds the signal orange as a glow around the brass stroke, never
   instead of it: #FF4D1F measures 2.86:1 on this ground and cannot be the
   only thing that marks a part. */
html[data-theme="riss"] .stage-svg .lit { filter: drop-shadow(0 0 3px #FF4D1F); }

/* Press: the part moves along its own natural direction for 140 ms. The
   default is straight down by 4% of the part's OWN height (transform-box:
   fill-box), which is a key dipping; a <g> that moves differently — a valve, a
   drum head — sets --press-x / --press-y on itself in the SVG file. */
.stage-svg [data-id] { transition: transform 90ms var(--ease); }
.stage-svg .prest {
  transform-box: fill-box;
  transform: translate(var(--press-x, 0), var(--press-y, 4%));
}

@media (prefers-reduced-motion: reduce) {
  /* The highlight alone carries the feedback: the class still lands, so an
     assistive reading of the page is unchanged, but nothing moves. */
  .stage-svg .prest { transform: none; }
  .stage-svg svg { transform: none !important; }
}

/* ===== "Spiel mich" =====
   The one thing that has to be obvious in the first second: this picture is
   an object and it answers a finger.

   It is HTML, not something drawn into the WebGL canvas, for three reasons a
   texture cannot give: it is real text at the page's own font size, it is
   readable by a screen reader, and it exists before three.js does — it sits
   on the CSS poster from the first paint.

   Absolutely positioned inside .stage-box, which already reserves its height
   with aspect-ratio, so the prompt cannot move anything: CLS stays 0. It is
   translated into place by js/page.js, which projects the instrument's
   primary part every frame, so the label follows the model while it wobbles
   and while the visitor turns it. */

/* The box IS the ring, and page.js centres it on the projected part with
   translate(x, y) translate(-50%, -50%). The label hangs off it absolutely,
   so it can never pull the ring off the part it is pointing at. */
.prompt {
  position: absolute;
  left: 0; top: 0;
  width: 1.15rem; height: 1.15rem;
  z-index: 3;
  pointer-events: none;
  will-change: transform;
}
.prompt[hidden] { display: none; }

.prompt-ring {
  position: absolute;
  inset: 0;
  border: 1.5px solid var(--brass);
  border-radius: 50%;
}
/* The pulse is the invitation. It is a ring, never the word: a fading label
   would be text animated with opacity. */
.prompt-ring::after {
  content: "";
  position: absolute;
  inset: -1.5px;
  border: 1.5px solid var(--brass);
  border-radius: 50%;
  animation: prompt-pulse 1.9s var(--ease) infinite;
}
@keyframes prompt-pulse {
  from { transform: scale(1); opacity: .85; }
  to   { transform: scale(2.4); opacity: 0; }
}

.prompt-text {
  position: absolute;
  left: calc(100% + .5rem);
  top: 50%;
  transform: translateY(-50%);
  background: var(--stage);
  color: var(--paper);
  border: 1px solid var(--rule-strong);
  border-radius: var(--r);
  padding: .3rem .55rem;
  font-size: .8125rem;
  line-height: 1;
  white-space: nowrap;
}
/* Near the right edge of the frame the label swaps sides instead of being
   clipped by .stage-box's overflow. */
.prompt.flip .prompt-text { left: auto; right: calc(100% + .5rem); }

/* The instruction line under the stage. The keyboard half is hidden on touch
   devices, where there is no keyboard to press. */
.stage-hint {
  margin: 1rem 0 0;
  font-size: .875rem;
  color: var(--paper-60);
  max-width: none;
}
.stage-hint kbd {
  font: inherit;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r);
  padding: .05em .4em;
  margin-right: .15em;
  color: var(--paper);
}
/* The width test is there as well as the pointer test: a narrow window on a
   desktop still has a keyboard, but a phone-sized viewport that reports a
   fine pointer (every headless browser, and some tablets with a mouse) would
   otherwise show the hint where there is no room for it. */
.only-keyboard { display: none; }
@media (min-width: 900px) and (hover: hover) and (pointer: fine) { .only-keyboard { display: inline; } }


/* ===== the round play button =====
   The one round thing on the site. It is round because it is the single
   signature control: press it and the instrument plays by itself. */

.hoerprobe { display: flex; align-items: center; gap: 1.25rem; margin-top: 2rem; }

.play-round {
  flex: none;
  width: 5rem; height: 5rem;
  border-radius: 50%;
  border: 0;
  background: var(--brass);
  color: var(--ink);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease);
}
.play-round:hover { transform: scale(1.06); background: var(--paper); }

.play-ico { position: relative; display: block; width: 1.6rem; height: 1.6rem; }
/* A triangle from borders, not an icon font and not an emoji. */
.play-ico::before {
  content: "";
  position: absolute;
  inset: 0; margin: auto;
  width: 0; height: 0;
  border-left: 1.05rem solid currentColor;
  border-top: .62rem solid transparent;
  border-bottom: .62rem solid transparent;
  transform: translateX(.2rem);
}
.play-ico i { display: none; width: .22rem; background: currentColor; }

/* While the phrase runs the triangle becomes three moving bars, so the state
   is visible and not only announced. */
.play-round[aria-pressed="true"] .play-ico { display: flex; align-items: center; justify-content: center; gap: .22rem; }
.play-round[aria-pressed="true"] .play-ico::before { display: none; }
.play-round[aria-pressed="true"] .play-ico i { display: block; animation: bars 560ms var(--ease) infinite alternate; }
.play-ico i:nth-child(2) { animation-delay: 180ms; }
.play-ico i:nth-child(3) { animation-delay: 90ms; }
@keyframes bars { from { height: .45rem; } to { height: 1.45rem; } }

.hoerprobe-text { font-size: .9375rem; }
.hoerprobe-text b { display: block; font-weight: 600; font-size: 1.0625rem; }
.hoerprobe-text span { color: var(--paper-60); }


/* ===== anatomy list =====
   The list and the model are the same control seen twice: hovering, focusing
   or tapping either one lights the part in the other. The buttons are real
   buttons so the whole thing works from the keyboard. */

.anatomy { list-style: none; margin: 0; padding: 0; }
.anatomy li + li { border-top: 1px solid var(--rule); }

.anatomy button {
  position: relative;
  width: 100%;
  display: flex;
  gap: 1rem;
  align-items: baseline;
  background: none;
  border: 0;
  padding: 1.05rem .25rem 1.05rem 1.1rem;
  text-align: left;
  cursor: pointer;
  color: var(--paper);
  transition: color var(--dur) var(--ease);
}
/* A brass bar that grows out of the left edge instead of a hover background:
   a filled row would turn the list into a stack of cards. */
.anatomy button::before {
  content: "";
  position: absolute;
  left: 0; top: 50%;
  width: 2px; height: 0;
  background: var(--brass);
  transform: translateY(-50%);
  transition: height var(--dur) var(--ease);
}
.anatomy button:hover, .anatomy button[aria-pressed="true"] { color: var(--brass); }
.anatomy button:hover::before, .anatomy button[aria-pressed="true"]::before { height: 70%; }
.anatomy .n { font-family: var(--serif); color: var(--brass); font-size: 1.05rem; min-width: 1.6ch; }
.anatomy .t { font-size: 1.0625rem; }

/* The fact line. Its height is reserved for two lines so a longer sentence
   arriving does not push the page down. */
.factline {
  margin-top: 1.5rem;
  min-height: 4.2em;
  color: var(--paper-60);
  border-left: 1px solid var(--rule-strong);
  padding-left: 1.1rem;
}
@media (min-width: 900px) { .factline { min-height: 3.2em; } }


/* ===== the screen-reader keyboard =====
   88 keys inside a canvas are a picture. This row is the same instrument as
   real buttons: 13 notes, labelled in German, focusable, and audible. It is
   off screen until one of them takes focus, and then it appears. */

.keyrow { display: flex; flex-wrap: wrap; gap: .35rem; margin-top: 1.25rem; }
.keyrow button {
  border: 1px solid var(--rule-strong);
  background: transparent;
  color: var(--paper);
  border-radius: var(--r);
  padding: .45rem .7rem;
  font-size: .8125rem;
  cursor: pointer;
}
.keyrow button:hover { border-color: var(--brass); color: var(--brass); }


/* ===== plain lists and cards =====
   No card sits inside another card and nothing has a filled background: on a
   near-black stage a hairline is enough to separate two things, and a filled
   panel immediately reads as a template. */

.facts { display: grid; gap: 0; margin: 0; }
.facts > div { display: grid; gap: .35rem; padding: 1.4rem 0; border-top: 1px solid var(--rule); }
.facts > div:last-child { border-bottom: 1px solid var(--rule); }
.facts dt { font-size: .8125rem; letter-spacing: .14em; text-transform: uppercase; color: var(--brass); }
.facts dd { margin: 0; max-width: 58ch; }
@media (min-width: 720px) {
  .facts > div { grid-template-columns: 12rem 1fr; gap: 2rem; align-items: baseline; }
}

.prices { display: grid; margin: 0 0 1.5rem; }
.prices > div {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: .4rem 1.5rem;
  padding: 1.2rem 0;
  border-top: 1px solid var(--rule);
}
.prices > div:last-child { border-bottom: 1px solid var(--rule); }
.prices .what { flex: 1 1 14rem; }
.prices .what small { display: block; color: var(--paper-60); font-size: .875rem; }
.prices .amount { font-family: var(--serif); font-size: 1.9rem; color: var(--brass); line-height: 1; }

/* One teacher, no photo. The monogram is type in a brass square: a stock
   portrait of a person who does not exist would be worse than no picture. */
.teacher { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: flex-start; }
.mono {
  flex: none;
  width: 4.5rem; height: 4.5rem;
  display: grid; place-items: center;
  border: 1px solid var(--brass);
  color: var(--brass);
  font-family: var(--serif);
  font-size: 1.6rem;
  letter-spacing: .04em;
}
.teacher-body { flex: 1 1 20rem; }
.teacher-body h3 { margin-bottom: .2rem; }
.teacher-role { color: var(--brass); font-size: .875rem; letter-spacing: .12em; text-transform: uppercase; margin-bottom: .9rem; }


/* ===== instrument grid (home) =====
   Six line drawings, drawn as SVG paths rather than set as icons. Separated
   by hairlines, not boxed: six boxed tiles is the exact look this site is
   trying not to have. */

.instr-grid { display: grid; gap: 0; grid-template-columns: 1fr; }
@media (min-width: 640px) { .instr-grid { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .instr-grid { grid-template-columns: repeat(3, 1fr); } }

.instr {
  display: block;
  text-decoration: none;
  color: var(--paper);
  padding: 2rem 1.5rem 2rem 0;
  border-top: 1px solid var(--rule);
  transition: color var(--dur) var(--ease);
}
/* Brass on ink, dimmed at rest and full on hover. Opacity is fine here and
   forbidden on text: these are aria-hidden drawings, not content. */
.instr svg { width: 100%; max-width: 12rem; height: auto; color: var(--brass); opacity: .72; transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease); }
.instr:hover { color: var(--brass); }
.instr:hover svg { opacity: 1; transform: translateX(6px); }
.instr h3 { margin: 1.5rem 0 .3rem; }
.instr p { color: var(--paper-60); font-size: .9375rem; margin: 0; max-width: 30ch; }


/* ===== timeline (home) =====
   Three steps as one continuous brass rule with numbers on it, not as three
   identical cards with three identical icons. */

.steps { list-style: none; margin: 0; padding: 0; position: relative; }
.steps::before {
  content: "";
  position: absolute;
  left: 1.1rem; top: .6rem; bottom: 2.4rem;
  width: 1px;
  background: linear-gradient(180deg, var(--brass), var(--brass-fade));
}
.steps li { position: relative; padding: 0 0 2.75rem 4rem; }
.steps li:last-child { padding-bottom: 0; }
.steps .n {
  position: absolute;
  left: 0; top: 0;
  width: 2.25rem; height: 2.25rem;
  display: grid; place-items: center;
  border: 1px solid var(--brass);
  background: var(--ink);
  color: var(--brass);
  font-family: var(--serif);
  font-size: 1.1rem;
}
.steps h3 { margin-bottom: .35rem; }
.steps p { color: var(--paper-60); margin: 0; }


/* ===== studio pitch and CTA ===== */

.cta-band { text-align: left; }
.cta-band .btn-row { justify-content: flex-start; }


/* ===== reduced motion =====
   base.css already flattens durations. These two rules undo the transforms
   that would otherwise leave an element parked in the wrong place. */

@media (prefers-reduced-motion: reduce) {
  .play-round:hover { transform: none; }
  .instr:hover svg { transform: none; }
  /* The prompt stays — it is the affordance, not decoration. Only its pulse
     stops, and the ring keeps its resting size instead of parking at 2.4x. */
  .prompt-ring::after { animation: none; transform: none; opacity: .5; }
}


/* ===== v3 drawings: trompete valve state (.vdown) =====
   A trumpet valve is a switch that STAYS down, not a key that springs back:
   js/instruments/trompete.js (svg.press) puts .vdown on the drawn valve
   groups and takes it off again on the next tap, so the drawing always shows
   the fingering that is sounding. Only the piston moves (class="piston" in
   svg/trompete-*.svg); the casing it rides in stays where it is. Each valve
   group carries its own travel in drawing units (--vd-x / --vd-y), because
   the valve axis points a different way in every view: the skizze is turned
   on the sheet, the riss stands upright, detail A is drawn at 2:1.
   The transition sits on every piston, not only on the pressed ones, so the
   way back up is as quick as the way down - 55 ms, the module's own valve
   speed. Under reduced motion the state still shows (a valve that is down has
   to look down); only the travel between the two positions is dropped. */
.stage-svg [data-part="ventile"] .piston { transition: transform 55ms var(--ease); }
.stage-svg .vdown .piston { transform: translate(var(--vd-x, 0px), var(--vd-y, 4px)); }

@media (prefers-reduced-motion: reduce) {
  .stage-svg [data-part="ventile"] .piston { transition: none; }
}


/* ===== v3 drawing looks: the stage hint must not tell a drawing to turn =====
   In skizze/riss there is no orbit drag on the stage (no 3D, nothing to turn),
   so the sentence that tells the visitor to drag the model would be false in
   those two looks. .hint-orbit wraps only that sentence (index.html +
   instrumente/*.html, DE+EN); hide it in the two drawn looks only. */
[data-theme="skizze"] .hint-orbit, [data-theme="riss"] .hint-orbit { display: none; }
