/* ═══════════════════════════════════════════════════════════════
   COLOR TREE COURSE — shared stylesheet
   Loaded by every lesson before its own <style> block.

   Everything here was byte-identical across Lessons 1–6. It is now
   in one place so that a fix lands once instead of six times, and
   so the degree colors below cannot drift apart between lessons.

   THE DEGREE COLORS ARE THE COURSE. Same note, same color, every
   lesson, start to finish. Change them here and nowhere else.

   Lesson-specific rules stay in the lesson. Load order is:
   this file, then the page's own <style>, so a lesson can always
   override anything below without !important.
   ═══════════════════════════════════════════════════════════════ */

:root{
  /* the twelve, by position on the circle of fifths — not by letter name */
  --deg-1:#e4419b; --deg-5:#f0453c; --deg-2:#f4703a; --deg-6:#f79235;
  --deg-3:#f7b733; --deg-7:#f2e13c; --deg-b5:#b9de3a; --deg-b2:#57c64a;
  --deg-b6:#35c1a6; --deg-b3:#3d9be9; --deg-b7:#8f80f2; --deg-4:#b558e0;
  --root-rgb:228,65,155;

  /* surfaces */
  --ink:#101018; --paper:#d9dade;
  --on-ink:#e8e6ee; --on-ink-dim:#8d8ba0;
  --on-paper:#191921; --on-paper-dim:#5c5c6b;
  --rule:rgba(232,230,238,.12);

  /* measure */
  --maxw:1180px; --narrow:640px; --gutter:clamp(20px,5vw,64px);
}

/* ── the measure, as a position ─────────────────────────────
   Anything absolutely positioned against a FULL-BLEED parent — a
   pinned stage, the hero — has no way to find the text column on its
   own: `left:var(--gutter)` is measured from the window edge, while
   the copy beside it is inside .wrap, which is capped at --maxw and
   centred. Below about 1300px the two agree and nothing looks wrong.
   Above it they part company by half the overflow, which on a 1600px
   screen is a couple of hundred pixels of daylight between a scroll
   cue and the column it belongs to.

   This is the inset of .wrap's TEXT from the edge of a full-bleed
   parent: the gutter on a narrow screen, the centred column's own edge
   on a wide one. The measure is centred, so it is the same distance in
   from the left as from the right — one number, usable as either
   `left:` or `right:`.

   --edge is the full measure (.wrap: figures, the piano, the vocab
   panel, the whole tree, and the head and foot of every pinned stage).
   --edge-narrow is the reading column (.wrap.narrow: all body prose,
   and the hero). */
:root{
  --edge:max(var(--gutter), calc(50% - var(--maxw)/2 + var(--gutter)));
  --edge-narrow:max(var(--gutter), calc(50% - var(--narrow)/2 + var(--gutter)));
}

/* ── base ──────────────────────────────────────────────────── */
/* Every height in the course is struck from this one number, which
   JS measures in pixels and republishes. The svh here is only a
   fallback for a browser that never runs the script. */
:root{--ct-vh:calc(var(--ct-vh)*1)}
*{box-sizing:border-box}
/* Scroll anchoring is the browser trying to be helpful: when content
   above the viewport changes size it shifts the scroll to compensate.
   Every stop of every applet rewrites the name and the caption inside
   a pinned stage, so the browser is forever recompensating — and it
   re-picks its anchor when the scroll direction changes, which is
   exactly when the page was seen to jump. We keep our own place
   (CT.keepPlace); we do not want a second opinion. */
html{scroll-behavior:smooth; -webkit-text-size-adjust:100%; overflow-anchor:none}
body{overflow-anchor:none}

/* ── type ──────────────────────────────────────────────────── */
body{margin:0; background:var(--ink); color:var(--on-ink); font-family:"Instrument Sans",ui-sans-serif,system-ui,-apple-system,sans-serif; font-size:clamp(16px,1.05vw + 13px,19px); line-height:1.62; -webkit-font-smoothing:antialiased; overflow-x:hidden}
h1,h2,h3{font-family:"Fraunces",ui-serif,Georgia,serif; font-weight:430; font-variation-settings:"SOFT" 20,"WONK" 1; letter-spacing:-.018em; line-height:1.06; margin:0}
p{margin:0 0 1.15em}
p:last-child{margin-bottom:0}
strong{font-weight:600}
.mono{font-family:"IBM Plex Mono",ui-monospace,"SF Mono",Menlo,monospace}
.eyebrow{font-family:"IBM Plex Mono",ui-monospace,monospace; font-size:.72rem; letter-spacing:.19em; text-transform:uppercase; color:var(--on-ink-dim); margin:0 0 1.4rem}

/* ── layout ────────────────────────────────────────────────── */
.wrap{max-width:var(--maxw); margin-inline:auto; padding-inline:var(--gutter)}
.narrow{max-width:640px}

/* ── reveal on scroll ──────────────────────────────────────── */
.reveal{opacity:0; transform:translateY(22px); transition:opacity .8s cubic-bezier(.2,.7,.2,1),transform .8s cubic-bezier(.2,.7,.2,1)}
.reveal.in{opacity:1; transform:none}
.reveal.d1{transition-delay:.09s}
.reveal.d2{transition-delay:.18s}
.reveal.d3{transition-delay:.27s}

/* ── blocks ────────────────────────────────────────────────── */
.band{padding:clamp(96px,calc(var(--ct-vh)*0.16),190px) 0}
.lede{font-family:"Fraunces",ui-serif,Georgia,serif; font-size:clamp(1.6rem,3.4vw,2.5rem); line-height:1.24; font-weight:380; letter-spacing:-.015em}
.note{font-size:.9rem; color:var(--on-ink-dim); line-height:1.5; border-left:2px solid var(--rule); padding-left:1rem}

/* ── controls ──────────────────────────────────────────────── */
.pill{appearance:none; border:1px solid var(--rule); background:transparent; color:var(--on-ink); font-family:"IBM Plex Mono",monospace; font-size:.68rem; letter-spacing:.14em; text-transform:uppercase; padding:.62rem .95rem; border-radius:999px; cursor:pointer; transition:.25s ease; white-space:nowrap}
.pill:hover{border-color:var(--deg-1)}
.pill:focus-visible{outline:2px solid var(--deg-b3); outline-offset:3px}

/* ── hero ──────────────────────────────────────────────────── */
.hero{min-height:calc(var(--ct-vh)*1); display:flex; flex-direction:column; justify-content:center; position:relative; padding-block:calc(var(--ct-vh)*0.14) calc(var(--ct-vh)*0.1); overflow:hidden}
/* NOTE (not a fix — a finding, left alone deliberately): .hero is a flex
   column, so .inner is a flex item, and a flex item with `auto` margins
   in the cross axis is NOT stretched — the auto margins absorb the free
   space instead. .wrap carries `margin-inline:auto`, so the hero's copy
   block shrink-wraps to its widest child and centres, rather than
   filling the 1180px measure the way every other .wrap on the page
   does. Because it is sized by its content, and each lesson sets its
   own .hero h1 max-width (11ch in Lesson 5, 12ch in 2, 13ch in 6, 14ch
   in 1), the hero's left edge lands somewhere slightly different in
   every lesson above ~1300px. Adding `width:100%` here would put the
   hero on the 1180 measure — but that is 270px left of the centred
   640px column the prose below it uses, so it is a composition
   decision, not a bug fix. Below ~1300px none of this is visible. */
.hero .inner{position:relative; z-index:2}
/* The contents button is fixed in the top right, and the hero is the
   one place in the lesson where it is deliberately on screen. On a
   narrow phone the eyebrow and subhead run the full measure and pass
   underneath it, so the top of the hero keeps a lane clear. */
@media (max-width:700px){
  .hero .inner > .eyebrow, .hero .inner > .sub{padding-right:54px}
}
.cue i{display:block; width:1px; height:34px; background:linear-gradient(var(--on-ink-dim),transparent); animation:drip 2.4s ease-in-out infinite}
@keyframes drip{0%,100%{opacity:.25; transform:scaleY(.6); transform-origin:top}50%{opacity:1; transform:scaleY(1)}}

/* ── going-deeper panel ────────────────────────────────────── */
.dive{border:1px solid var(--rule); border-radius:10px; padding:clamp(18px,3vw,30px); display:grid; grid-template-columns:auto 1fr; gap:clamp(14px,2.4vw,26px); align-items:center}
.dive .play{width:52px; height:52px; border-radius:999px; border:1px solid var(--rule); display:grid; place-items:center; color:var(--deg-1); font-size:.8rem}
.dive h3{font-size:1.15rem; margin-bottom:.3rem}
.dive p{font-size:.92rem; color:var(--on-ink-dim); margin:0}

/* ── next-lesson footer ────────────────────────────────────── */
.next{display:flex; align-items:center; justify-content:space-between; gap:1rem; border-top:1px solid var(--rule); padding-top:1.6rem; margin-top:clamp(48px,calc(var(--ct-vh)*0.09),90px)}
a.next{text-decoration:none; color:inherit; transition:border-color .25s ease}
a.next:hover{border-top-color:var(--deg-1)}
a.next:hover .arrow{transform:translateX(5px)}
.next h3{font-size:clamp(1.3rem,3.2vw,2rem)}

/* ── the start gate ────────────────────────────────────────── */
/* Applied by ct-core.js, never by the markup, so a script failure
   leaves an ordinary scrolling page rather than a trapped one. */
html.ct-locked, html.ct-locked body{overflow:hidden; height:100%; overscroll-behavior:none}
html.ct-locked .cue{opacity:0; pointer-events:none}
.cue{transition:opacity .6s ease}

.start{appearance:none; border:1px solid var(--deg-1); background:transparent;
  color:var(--on-ink); font-family:"IBM Plex Mono",ui-monospace,monospace;
  font-size:clamp(.72rem,2.2vw,.82rem); letter-spacing:.18em; text-transform:uppercase;
  padding:clamp(.85rem,2.4vw,1.05rem) clamp(1.6rem,5vw,2.4rem);
  border-radius:999px; cursor:pointer; white-space:nowrap;
  transition:background .25s ease, color .25s ease, opacity .35s ease}
.start:hover{background:var(--deg-1); color:var(--ink)}
.start:focus-visible{outline:2px solid var(--deg-b3); outline-offset:3px}
.start[disabled]{opacity:.45; cursor:default; background:transparent; color:var(--on-ink)}

/* ── going-deeper panel on narrow screens ──────────────────── */
/* At tablet widths the auto/1fr split squeezed the button column
   until its label overflowed and pushed the page sideways. */
@media (max-width:700px){
  .dive{grid-template-columns:1fr; gap:1.1rem}
  .dive .play{justify-self:start}
}

/* ── short viewports (phone landscape) ─────────────────────── */
/* .hero h1 is sized in vw, so a landscape phone — wide but short —
   grew the headline until it pushed the Start button below the fold.
   With the page locked until Start is pressed, that made the lesson
   unreachable. These rules are scoped with `body` so they outrank the
   per-lesson .hero h1 without needing !important. */

  html.ct-h560 .hero{padding-block:calc(var(--ct-vh)*0.05) calc(var(--ct-vh)*0.04)}
  html.ct-h560 body .hero h1{font-size:clamp(1.8rem,calc(var(--ct-vh)*0.064),2.9rem); max-width:22ch; line-height:1.06}
  html.ct-h560 body .hero .sub{margin-top:.75rem; font-size:.9rem; max-width:56ch}
  html.ct-h560 body .hero .eyebrow{margin-bottom:.3rem}
  html.ct-h560 body .hero .inner > p:last-child{margin-top:1.1rem !important}
  html.ct-h560 .cue{display:none}


/* ── the replay hint ───────────────────────────────────────── */
/* It sits absolutely at the bottom of a stage, so on short screens it
   landed on top of whatever the applet had drawn down there. It is a
   caption, not a control: never take clicks, and carry enough of a
   glow to stay legible over a canvas. */
.hint{pointer-events:none;
  text-shadow:0 0 10px var(--ink), 0 0 4px var(--ink), 0 1px 2px var(--ink)}

  html.ct-h620 .hint{font-size:.62rem !important}


/* ── the replay hint, given its own room ───────────────────── */
/* It used to sit on top of the canvas at inset:auto 0 -2px, which put
   it over whatever the applet had drawn near the bottom — spellings,
   fret numbers, the bottom of a circle. Reserving a strip of padding
   inside .st-body shrinks the canvas by exactly the height of the
   caption, so the two can no longer occupy the same pixels. Scoped
   through .stage so it outranks the per-lesson copies of these rules. */
.stage .st-body{padding-bottom:1.45rem}
.stage .st-body canvas{height:100%}
.stage .hint{inset:auto 0 0; line-height:1.45rem}

  html.ct-h620 .stage .st-body{padding-bottom:1.15rem}
  html.ct-h620 .stage .hint{line-height:1.15rem}


/* ── vocabulary cards ──────────────────────────────────────── */
/* One of these lands in every lesson, always in the same place —
   just before the close — and always with the same silhouette: a
   hairline frame, a ruled index-card line under the term, a mono
   count in the corner. The repetition is the point. By Lesson 4 the
   reader should recognise the shape before reading a word of it and
   know they are being handed a name for something they have already
   heard. Lesson 5's "two terms" panel uses this same component so
   the family stays closed.                                          */
.vocab{border-block:1px solid var(--rule); padding-block:clamp(46px,calc(var(--ct-vh)*0.08),84px)}
/* The cards used to inherit the full 1180px measure, which on a desktop
   stretched two of them to nearly 600px each — wide, flat and reading
   as unstyled markup rather than as a component. The grid gets its own
   measure, and a lone card is capped so it can't span the page. */
.vocab-set{max-width:920px; margin-inline:auto}
.vocab-head{display:flex; align-items:center; gap:1rem; margin-bottom:clamp(20px,calc(var(--ct-vh)*0.034),34px)}
.vocab-head .eyebrow{margin:0; color:var(--on-ink)}
.vocab-head i{flex:1; height:1px; background:var(--rule)}
.vocab-grid{display:grid; gap:clamp(14px,2.2vw,22px);
  grid-template-columns:repeat(auto-fit,minmax(min(100%,300px),1fr))}
.vocab-grid.one{max-width:560px}

.vcard{position:relative; border:1px solid rgba(232,230,238,.13); border-radius:14px;
  overflow:hidden; padding:clamp(20px,2.8vw,30px);
  background:
    radial-gradient(120% 90% at 0% 0%,
      color-mix(in srgb, var(--vc,var(--deg-1)) 13%, transparent), transparent 62%),
    linear-gradient(180deg,rgba(232,230,238,.055),rgba(232,230,238,.016))}
/* the spine, and a matching hairline along the top edge, so the card
   is framed by its degree colour rather than merely tagged with it */
.vcard::before{content:""; position:absolute; inset:0 auto 0 0; width:3px;
  background:var(--vc,var(--deg-1))}
.vcard::after{content:""; position:absolute; inset:0 0 auto 0; height:1px;
  background:linear-gradient(90deg,
    var(--vc,var(--deg-1)), color-mix(in srgb, var(--vc,var(--deg-1)) 6%, transparent))}

/* the figure sits in a well, so cards that carry one still line up
   with cards that don't */
.vcard-fig{border:1px solid rgba(232,230,238,.09); border-radius:9px;
  background:rgba(9,9,15,.34); padding:clamp(10px,1.6vw,15px);
  margin-bottom:1.05rem; display:grid; place-items:center}
.vcard canvas{display:block; width:100%; max-width:180px; aspect-ratio:1/1;
  color:var(--vc,var(--deg-1))}       /* the painter reads its hue from here */
.vcard canvas.wide{max-width:none; aspect-ratio:16/7}

/* Kept for the few cards that genuinely caption their figure — Lesson 5
   names the shape it has drawn. The "Lesson 3 · 02" counting that used
   to live here is gone: it numbered the furniture, not the idea. */
.vcard-n{display:block; font-family:"IBM Plex Mono",ui-monospace,monospace;
  font-size:.58rem; letter-spacing:.22em; text-transform:uppercase;
  color:var(--on-ink-dim); margin-bottom:.15rem}
.vcard-term{font-family:"Fraunces",ui-serif,Georgia,serif; font-weight:430;
  font-variation-settings:"SOFT" 20,"WONK" 1; letter-spacing:-.018em;
  font-size:clamp(1.4rem,3.2vw,1.9rem); line-height:1.08;
  color:var(--vc,var(--deg-1)); margin:.3rem 0 .75rem}
/* the ruled line is the card's signature — dashed, never solid */
.vcard-rule{height:1px; border-top:1px dashed rgba(232,230,238,.17); margin-bottom:.9rem}
.vcard-def{font-size:.93rem; line-height:1.6; color:var(--on-ink-dim); margin:0}
.vcard-def a{color:var(--on-ink); text-decoration:underline;
  text-decoration-color:color-mix(in srgb, var(--vc,var(--deg-1)) 62%, transparent);
  text-decoration-thickness:1px; text-underline-offset:3px; transition:color .2s ease}
.vcard-def a:hover{color:var(--vc,var(--deg-1))}
.vcard-def a:focus-visible{outline:2px solid var(--deg-b3); outline-offset:2px; border-radius:2px}


  html.ct-h560 .vocab{padding-block:clamp(30px,calc(var(--ct-vh)*0.06),54px)}
  html.ct-h560 .vcard{padding:clamp(15px,2.4vw,22px)}
  html.ct-h560 .vcard-fig{padding:9px; margin-bottom:.8rem}
  html.ct-h560 .vcard canvas{max-width:124px}


/* ── the pinned-section cue ────────────────────────────────── */
/* This used to be a hairline rail down the right margin of each
   stage. Two progress bars running in two different directions —
   one across the top for the lesson, one down the side for the
   section — was more confusing than either on its own.

   So the section's progress now runs across the top too, directly
   under the lesson bar and the same 2px tall, but ticked rather than
   solid so the two can never be mistaken for one another. It fades
   in when a section pins and out again when it lets go. One element
   for the whole page, created by CT.pinCues().                      */
#pinbar{position:fixed; top:2px; left:0; width:100%; height:2px; z-index:89;
  background:rgba(232,230,238,.085); pointer-events:none;
  opacity:0; transition:opacity .42s ease}
#pinbar.on{opacity:1}
#pinbar b{display:block; height:100%; width:0;
  background:repeating-linear-gradient(90deg,
    var(--pin,var(--deg-1)) 0 7px, rgba(232,230,238,.16) 7px 11px);
  transition:background .5s ease}

/* ── "scroll ↓", beside the step counter ───────────────────── */
/* A pinned stage looks like a page that has stopped responding until
   you realise the scroll is driving it. The counter is where the eye
   already goes to find out where it is in the section, so the
   instruction sits next to it rather than somewhere else again.
   CT.pinCues() wraps the counter in .cuebox and puts this before it. */
/* Two lines, always, right-aligned: the counter on top and the
   instruction under it.

   It used to sit them side by side and only stack on a narrow
   portrait phone, which meant the head was one line at some widths
   and two at others — and at the widths in between, the counter
   wrapped inside itself ("STEP 7 / OF 12") while the cue wrapped
   separately, giving four ragged lines in the corner. Several of the
   eyebrows opposite already run to two lines, so two deliberate lines
   here match them instead of fighting them, and nothing depends on
   how much room happens to be left over.

   column-reverse rather than a reordered DOM, so the reading order
   stays counter-then-instruction for a screen reader.              */
.cuebox{display:flex; flex-direction:column-reverse; align-items:flex-end;
  justify-content:flex-start; gap:.28rem; min-width:0; flex:0 0 auto;
  text-align:right}
.cuebox > .counter, .cuebox > .hz{margin:0; white-space:nowrap; text-align:right}
.scue{display:inline-flex; align-items:center; gap:.34rem;
  font-family:"IBM Plex Mono",ui-monospace,monospace; font-size:.6rem;
  letter-spacing:.2em; text-transform:uppercase; color:var(--on-ink-dim);
  white-space:nowrap; pointer-events:none; line-height:1;
  opacity:0; transition:opacity .5s ease}
.scue.on{opacity:1}
/* centred on the cap height of the word beside it rather than sitting
   on the baseline, which is what left the arrow hanging low */
.scue u{text-decoration:none; display:inline-block; font-size:.78rem;
  line-height:1; letter-spacing:0; transform-origin:50% 50%;
  animation:nudge 2.3s ease-in-out infinite}
@keyframes nudge{0%,100%{transform:translateY(-1px); opacity:.45}
                 50%{transform:translateY(2px); opacity:1}}
@media (max-width:560px){
  .scue{font-size:.55rem; letter-spacing:.14em}
}
@media (prefers-reduced-motion: reduce){ .scue u{animation:none} }

/* ── the head of a pinned stage ────────────────────────────── */
/* Eyebrow on the left, counter and cue on the right. Each lesson
   defined this for itself, so the two sides were sized by whatever
   was left over and a long eyebrow squeezed the counter until it
   wrapped. Now the right column is only as wide as it needs to be
   and the eyebrow takes the rest, wrapping to two lines when it has
   to — which is the same two lines the counter and cue already use. */
.st-head{display:flex; justify-content:space-between; align-items:flex-start;
  gap:clamp(.8rem,2vw,1.6rem); max-width:var(--maxw); width:100%; margin-inline:auto}
.st-head > .eyebrow{margin:0; flex:1 1 auto; min-width:0}
.st-head > .cuebox, .st-head > .counter, .st-head > .hz{flex:0 0 auto}

/* ── the head and foot, on the same measure as the prose ─────
   There were two measures in the course and they were a gutter apart.
   .wrap caps its BORDER box at --maxw and then pads a gutter in on each
   side, so prose is 1180 minus two gutters wide. A stage head caps its
   own box at --maxw with no padding of its own — the gutter belongs to
   .stage — so it was a full 1180 wide. Both are centred, so above about
   1300px the head and foot of every pinned stage sat one gutter (64px)
   wider than the paragraph the reader had just finished, on both sides.

   Taking the gutters off the cap makes the two measures identical at
   every width: the eyebrow of a pinned stage now starts on the same
   pixel as the prose above it, and the counter opposite ends on the
   same pixel as the prose's right edge. The canvas between them is
   deliberately left full-bleed — a drawing wants the room, a line of
   type does not.

   Each lesson restates these rules in its own <style>, which loads
   after this file, so these are scoped through the stage to outrank
   them rather than repeating the fix in eight files. */
.stage > .st-head,        .stage > .st-foot,
.spiral-stage > .ss-head, .spiral-stage > .ss-foot,
.tree-stage > .ts-head,   .tree-stage > .ts-foot,
.string-stage > .string-head, .string-stage > .string-foot{
  max-width:calc(var(--maxw) - var(--gutter) * 2)}

/* ── a browser that cannot run the lesson ──────────────────── */
.ctold{position:relative; z-index:97; margin:0;
  background:var(--deg-b7,#8f80f2); color:#0c0c14;
  padding:.9rem clamp(20px,5vw,64px)}
.ctold p{margin:0; max-width:70ch; font-size:.92rem; line-height:1.5}

/* ── the replay hint, once you no longer need it ───────────── */
/* "Click to hear it again" is an instruction for the first screen of
   a pinned run, not for all twelve. It goes once a quarter of the
   section is behind you. */
.hint, .pluck-hint{transition:opacity .6s ease}
.hint.faded, .pluck-hint.faded{opacity:0}

/* ── the contents menu ─────────────────────────────────────── */
/* Fixed top-right on every lesson, over dark bands and light ones
   alike, which is why it carries its own backdrop. Later this panel
   is where anything else about the course goes; for now it is the
   table of contents and nothing more. */
.ctmenu-btn{position:fixed; z-index:96; appearance:none; cursor:pointer;
  top:clamp(12px,calc(var(--ct-vh)*0.021),22px); right:clamp(12px,2.4vw,26px);
  width:42px; height:42px; padding:0; border-radius:999px;
  display:grid; align-content:center; justify-items:center; gap:4px;
  border:1px solid rgba(232,230,238,.16); background:rgba(14,14,22,.62);
  -webkit-backdrop-filter:blur(7px); backdrop-filter:blur(7px);
  transition:border-color .25s ease, background .25s ease}
.ctmenu-btn i{display:block; width:15px; height:1.5px; border-radius:2px;
  background:var(--on-ink); transition:transform .3s cubic-bezier(.2,.7,.2,1), opacity .2s ease}
/* A fixed button in the top right corner is in the way of exactly one
   thing: prose, whose lines run to the right edge on a phone. So it
   retires while the reader is moving down the page and comes back the
   moment they move up or reach the top — the two times anyone wants
   navigation. It never hides while the panel is open. */
.ctmenu-btn{transition:border-color .25s ease, background .25s ease,
  opacity .3s ease, transform .3s cubic-bezier(.2,.7,.2,1)}
.ctmenu-btn.away{opacity:0; transform:translateY(-16px); pointer-events:none}
.ctmenu-btn:hover{border-color:var(--deg-1)}
.ctmenu-btn:focus-visible{outline:2px solid var(--deg-b3); outline-offset:3px}
.ctmenu-btn[aria-expanded="true"] i:nth-child(1){transform:translateY(5.5px) rotate(45deg)}
.ctmenu-btn[aria-expanded="true"] i:nth-child(2){opacity:0}
.ctmenu-btn[aria-expanded="true"] i:nth-child(3){transform:translateY(-5.5px) rotate(-45deg)}

.ctmenu{position:fixed; inset:0; z-index:95; background:rgba(8,8,14,.7);
  -webkit-backdrop-filter:blur(8px); backdrop-filter:blur(8px);
  opacity:0; transition:opacity .3s ease}
.ctmenu.open{opacity:1}
.ctmenu-panel{position:absolute; inset:0 0 auto auto; height:100%;
  width:min(430px,100%); overflow-y:auto; overscroll-behavior:contain;
  background:var(--ink); border-left:1px solid var(--rule);
  padding:clamp(64px,calc(var(--ct-vh)*0.1),96px) clamp(22px,4vw,40px) clamp(32px,calc(var(--ct-vh)*0.07),64px);
  transform:translateX(20px); opacity:0;
  transition:transform .34s cubic-bezier(.2,.7,.2,1), opacity .34s ease}
.ctmenu.open .ctmenu-panel{transform:none; opacity:1}
.ctmenu-panel .eyebrow{margin-bottom:1.1rem}
.ctrow{display:grid; grid-template-columns:auto 1fr; gap:.9rem; align-items:baseline;
  padding:.85rem 0; border-top:1px solid var(--rule); text-decoration:none;
  color:inherit; transition:padding-inline .2s ease, color .2s ease}
.ctrow:last-child{border-bottom:1px solid var(--rule)}
.ctrow:hover{padding-inline:.4rem; color:var(--deg-1)}
.ctrow:focus-visible{outline:2px solid var(--deg-b3); outline-offset:2px; border-radius:3px}
.ctrow b{font-family:"IBM Plex Mono",ui-monospace,monospace; font-weight:400;
  font-size:.72rem; letter-spacing:.12em; color:var(--on-ink-dim)}
.ctrow span{font-family:"Fraunces",ui-serif,Georgia,serif; font-size:1.02rem; line-height:1.25}
.ctrow[aria-current="page"]{color:var(--deg-1)}
.ctrow[aria-current="page"] b{color:var(--deg-1)}

  html.ct-h520 .ctmenu-panel{padding-top:clamp(48px,calc(var(--ct-vh)*0.12),72px)}
  html.ct-h520 .ctrow{padding:.6rem 0}

/* The step counter lives in the top right of a pinned stage, which is
   the one place the menu button also has to be. On a phone there isn't
   room for both to claim the corner, so the head gives up the width and
   the counter sits immediately to the left of the button instead of
   underneath it. Desktop has room to spare and is left alone.

   BUT ONLY WHILE THE BUTTON IS ACTUALLY THERE. The reserve used to be
   unconditional, so every pinned head on every phone paid 46px for a
   button that CT.toc() hides the moment the reader is more than half a
   screen down the page — which is to say everywhere a pinned stage can
   possibly be. The counter and the "scroll ↓" under it were sitting 66px
   in from the right while the eyebrow opposite them sat on the 20px
   gutter, and the head read as lopsided for the whole of every applet.

   Gated on the button's own `away` class, the head is flush with the
   measure exactly when the corner is free and gives the width back only
   for the two moments the button is on screen — the title card and the
   close — where no stage is pinned anyway. :has() is not universal; a
   browser without it simply never applies the reserve, which is the
   same position the head is in for all the time the button is hidden. */
/* The width is 1200px rather than the 900px it used to be. The reserve
   is only needed while the head's right edge is close enough to the
   corner to run under the button, and the head stays hard against the
   gutter until the measure starts pulling it inward at about 1190px —
   so 901–1190px was a window where the two genuinely could overlap and
   nothing was reserved. Above 1200px the head is already 74px or more
   clear of a button that ends 68px in, so no reserve is wanted there. */
@media (max-width:1200px){
  body:has(.ctmenu-btn:not(.away)) .st-head,
  body:has(.ctmenu-btn:not(.away)) .ts-head,
  body:has(.ctmenu-btn:not(.away)) .ss-head,
  body:has(.ctmenu-btn:not(.away)) .string-head{padding-right:46px}
}
/* and it gives the width up and takes it back on the same beat the
   button leaves and returns on, rather than snapping */
.st-head, .ts-head, .ss-head, .string-head{
  transition:padding-right .3s cubic-bezier(.2,.7,.2,1)}
@media (prefers-reduced-motion: reduce){
  .ctmenu, .ctmenu-panel, .ctmenu-btn i{transition:none}
  .st-head, .ts-head, .ss-head, .string-head{transition:none}
}


/* ══ SHARED LESSON FURNITURE ══════════════════════════════════
   Every lesson was carrying its own copy of the rules below, and
   the copies had quietly drifted apart — a transition present in
   four files and missing from five, a z-index in eight of nine.
   They live here now, so a change to the look of a counter or a
   step caption happens once. A lesson that means something
   different still says so in its own <style> block, which loads
   after this file and wins.                                    */

#scrub{position:fixed; inset:0 auto auto 0; height:2px; width:0;
  background:var(--deg-1); z-index:90; transition:background .5s ease}

.hero h1{font-size:clamp(2.6rem,7.4vw,5.4rem); max-width:13ch}

.hero .sub{margin-top:1.6rem; max-width:46ch; color:var(--on-ink-dim)}

#heroCv{position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
  opacity:.4; filter:blur(3px); pointer-events:none; z-index:1}

.track{position:relative}

.stage{position:sticky; top:0; height:calc(var(--ct-vh)*1); display:grid;
  grid-template-rows:56px 1fr var(--foot,132px); gap:clamp(10px,calc(var(--ct-vh)*0.02),20px);
  padding:clamp(18px,calc(var(--ct-vh)*0.05),48px) var(--gutter) clamp(22px,calc(var(--ct-vh)*0.06),52px); background:var(--ink)}

.st-body{position:relative; min-height:0; overflow:hidden}

.st-body canvas{width:100%; height:100%; display:block; touch-action:pan-y; cursor:pointer}

.st-foot{max-width:var(--maxw); width:100%; margin-inline:auto; display:grid; grid-template-columns:1fr auto;
  gap:clamp(14px,3vw,40px); align-items:end; height:var(--foot,132px)}

/* A lone control in the foot — the Hold Fundamental pill in Lessons 6
   and 7 — is a direct grid child rather than being wrapped the way the
   other lessons wrap theirs. On a phone the foot collapses to a single
   1fr column, and a grid item's default justify-self is `stretch`, so
   that pill was drawn as a slab the full width of the screen while the
   same control in every other lesson sat pinned to the right edge. It
   is a button, not a banner: it takes its own width and keeps the edge
   the rest of the course puts it on. A no-op in the two-column layout,
   where its column is already content-sized. */
.st-foot > .pill, .ss-foot > .pill, .ts-foot > .pill, .string-foot > .pill{
  justify-self:end}

/* The scroll cue under a hero. It is positioned against .hero, which is
   full-bleed, so `left:var(--gutter)` measured it from the window while
   the headline above it was inside .wrap — capped at --maxw and centred.
   The two agree up to 1180px and part company above it, which put the
   cue out in the left margin on any desktop wider than that, unattached
   to the column it belongs to. Left on the gutter for now — see the
   note on .hero .inner above: where this cue belongs depends on where
   the hero's copy block is settled, and the two should move together. */
.cue{position:absolute; left:var(--gutter); bottom:clamp(24px,calc(var(--ct-vh)*0.05),48px); display:flex; align-items:center; gap:.7rem; z-index:2;
  font-family:"IBM Plex Mono",monospace; font-size:.68rem; letter-spacing:.18em; text-transform:uppercase; color:var(--on-ink-dim)}

/* Lesson 1's clicks-to-pitch stage is the one pinned section in the
   course whose scroll cue is not inside an .st-head, so it was never
   capped to the measure: `right:var(--gutter)` pinned it to the window
   corner, while the cue on the string stage immediately below it — and
   on every pinned stage in every other lesson — sits on the measure. On
   a 1600px screen those two were over 200px apart, in the same lesson,
   one section after the other. It lives here rather than in the lesson
   because this file already owns where every other cue sits, and it is
   scoped through .fuse-stage so it outranks the lesson's own copy of
   the rule without needing !important. */
.fuse-stage > .fuse-cue{right:var(--edge)}

.counter{font-family:"IBM Plex Mono",monospace; font-size:.78rem;
  letter-spacing:.12em; color:var(--on-ink-dim)}

.counter b{color:var(--hue,var(--deg-1)); font-weight:500; transition:color .4s ease}

.step-name{font-family:"Fraunces",ui-serif,Georgia,serif; font-size:clamp(1.35rem,3.4vw,2.15rem);
  line-height:1.1; color:var(--hue,var(--deg-1)); transition:color .4s ease; margin-bottom:.45rem; height:1.15em}

.step-copy{max-width:48ch; color:var(--on-ink-dim); font-size:clamp(.92rem,1.6vw,1.02rem); line-height:1.55; height:4.65em; overflow:hidden}

.hint{position:absolute; inset:auto 0 -2px; text-align:center; font-family:"IBM Plex Mono",monospace;
  font-size:.66rem; letter-spacing:.16em; text-transform:uppercase; color:var(--on-ink-dim); opacity:.7;
  pointer-events:none; transition:opacity .5s ease}

.hint.gone{opacity:0}

.pill[aria-pressed="true"]{background:var(--deg-1); border-color:var(--deg-1); color:#1b0410}

.next .arrow{transition:transform .25s ease; font-family:"IBM Plex Mono",monospace;
  color:var(--deg-b3); font-size:.8rem; letter-spacing:.14em}

.figure{width:100%; display:block; margin:2.4rem 0 0}