/* Michael Victory — site. Ink leads; the About section is the light exception.
   Every colour and every size comes from styles.css. */
*{box-sizing:border-box}
/* --veil lives on html, not body: the tuning panel sets it as an inline style on
   documentElement, and a declaration on body would shadow the inherited value
   and silently swallow every change. */
/* :root, NOT html — and that is load-bearing, not style. :root is a pseudo-class
   at specificity (0,1,0); html is a type selector at (0,0,1). styles.css declares
   these on :root, so an html{} block here LOSES to it no matter which stylesheet
   loads later. This block was written as html{} and the --t-hero override below
   therefore never applied: the hero rendered at the foundation's 110px, and only
   looked like 68px because the tuning panel wrote an inline style over the top.
   Delete site-tune.js before launch — as intended — and the hero silently jumped
   back to 110px. Matching the selector makes the site's own rung real without the
   panel present. */
/* --veil baked to 63% 2026-07-26 from Mike's saved panel values. Lower veil =
   LESS white over the footage, so more of the frame shows; it is paired with
   --fx-fade .78 below, which flattens that extra image so the type still wins.
   Declared on :root, not html — :root is (0,1,0) and beats html's (0,0,1), so a
   later html{} rule for this property would silently lose. */
:root{scroll-behavior:smooth;--veil:63%;
  /* The site's display rung is the SMALLER of the ladder's two. Declared as a
     token override so the h1 keeps governing off --t-hero and the type-scale
     presets have one variable to drive. */
  --t-hero:var(--t-step);
  /* One light weight for all display type. 300 rather than 100: at 26px a 100
     stem is 0.73px, under the ink floor, while 300 measures 1.43px. */
  --w-display-light:300}
body{margin:0;background:var(--ink);color:var(--on-ink);--edge:color-mix(in srgb,var(--on-ink) 22%,transparent)}
img{max-width:100%;display:block}
a{color:var(--signal);text-decoration:none;border-bottom:1px solid currentColor;padding-bottom:1px}
a:hover{color:var(--on-ink)}
.hair{display:block;width:100%;height:1px;border:0;margin:0;background:var(--edge)}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:-.02em}
.quiet{color:var(--on-ink-dim)}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}

/* ---------- nav: absent until the hero is behind you ---------- */
/* Concealed means concealed to everyone: transform alone leaves these five
   links in the tab order, painted above the viewport with no visible focus. */
/* Sized and glazed to match shopwisdom.ca and opencommunications.consulting:
   .85rem vertical padding on a 36px logo rather than 14px on a 22px one, and
   their glass — blur(16px) over a 50% ground, not blur(8px) over 88%. The lower
   opacity is the point: at 88% almost nothing showed through and it read as a
   solid bar. Both values live here as tokens so the mobile panel reads the same
   two and cannot drift. */
.nav{--glass-bg:color-mix(in srgb,var(--ink) 50%,transparent);--glass-blur:blur(16px);
  position:fixed;top:0;left:0;right:0;z-index:40;display:flex;align-items:center;justify-content:space-between;
  gap:26px;padding:.85rem var(--axis);background:var(--glass-bg);
  -webkit-backdrop-filter:var(--glass-blur);backdrop-filter:var(--glass-blur);box-shadow:0 1px 0 var(--edge);
  transition:transform .45s cubic-bezier(.2,.7,.2,1),visibility .45s,
             background-color .45s ease,box-shadow .45s ease;
  transform:translateY(-100%);visibility:hidden;pointer-events:none}
.nav.on{transform:none;visibility:visible;pointer-events:auto}
.nav-logo{height:36px;width:auto;transition:filter .45s ease}
.nav ul{display:flex;gap:26px;list-style:none;margin:0;padding:0}
.nav a{font-size:var(--t-cap);font-weight:var(--w-bold);text-transform:uppercase;letter-spacing:.14em;
  color:var(--on-ink);border-bottom:1px solid transparent;padding-bottom:2px}
.nav a:hover{color:var(--signal);border-bottom-color:currentColor}

/* ---------- the transport ----------
   Sits between the logo and the links (space-between centres it) and borrows the
   nav's own type wholesale: .np-now is an <a> inside .nav, so the size, tracking,
   hover and .on-dark flip all arrive from the rules above without restating them.
   Only the buttons need their own colour story, and it is the links' story —
   dark-on-light with --signal hover, flipped to --pink hover over a dark ground. */
.nav-player{display:flex;align-items:center;gap:14px;min-width:0}
/* an author display beats [hidden]'s UA display:none, so hidden is restated */
.nav-player[hidden]{display:none}
.np-now{display:inline-flex;align-items:baseline;gap:10px;white-space:nowrap;
  min-width:0;overflow:hidden;text-overflow:ellipsis}
.np-b{display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:28px;height:28px;padding:0;border:0;background:none;cursor:pointer;
  color:var(--on-ink)}
.np-b svg{width:10px;height:10px;fill:currentColor}
.np-b:hover{color:var(--signal)}
.np-b:disabled{opacity:.35;cursor:default}
.np-b:disabled:hover{color:var(--on-ink)}
.np-b:focus-visible{outline:2px solid var(--pink);outline-offset:2px}
.nav.on-dark .np-b{color:var(--ink)}
.nav.on-dark .np-b:hover{color:var(--pink)}
.nav.on-dark .np-b:disabled:hover{color:var(--ink)}
/* A phone bar is the logo, this, and the toggle. The word goes first, then the
   skip buttons — stop is the ask on a small screen ("click stop if they want"),
   and the full playlist walk is a desktop luxury. The label never wraps; if a
   ≤360px screen still runs out, the ellipsis takes the loss instead of the bar. */
@media (max-width:760px){
  .nav-player{gap:8px}
  .np-b{width:24px;height:24px}
  .np-word{display:none}
}
@media (max-width:480px){
  .np-b.np-prev,.np-b.np-next{display:none}
}
/* On desktop the transport doesn't float in the bar's leftover space — its left
   edge sits on the thumbnail axis, the line where every EP's tracks begin.
   --np-left is measured and published by site-eps.js (the line is a layout
   result, not a stylable position). Absolute pulls it out of the flex flow,
   which is also why the bar doesn't reflow when it appears; the nav is the
   containing block (fixed) and spans the viewport edge-to-edge, so viewport x
   and `left` agree. Only at ≥1200px, where the EP numeral column pushes the
   axis clear of the logo — below that the tracks start at the section padding,
   which is where the logo already is. */
@media (min-width:1200px){
  .nav-player{position:absolute;left:var(--np-left,50%);top:50%;transform:translateY(-50%)}
}

/* ---------- footage: one clip per panel ----------
   Every panel owns its own optimised video, sitting inside the panel rather than
   behind the whole document. It plays only while its panel is on screen, so the
   page is never decoding five clips at once, and each panel can be graded and cut
   for the content it carries.
   The veil is a child of the footage layer, not a background on the panel: a
   background paints BEHIND its own children, so a panel-level background would
   bury the video it is supposed to sit over. */
.vid{position:absolute;inset:0;z-index:0;overflow:hidden;
  background:radial-gradient(120% 90% at 50% 30%,#ffffff 0%,#f4f2ee 52%,#eceae4 100%)}
/* The still sits UNDER the video and carries the identical grade (see the shared
   .vid-el,.vid-still filter rule below), so when the clip fades in over it there
   is no change in sharpness, saturation or contrast — only motion starting.
   It is what the panel shows whenever playback is refused: iOS Low Power Mode,
   a background tab, reduced-motion. */
.vid-still{position:absolute;inset:0;z-index:0;display:block;background-size:cover;background-position:center;
  background-repeat:no-repeat}
.vid-el{position:absolute;inset:0;z-index:1;width:100%;height:100%;object-fit:cover;display:block;
  opacity:0;transition:opacity 1.1s linear;will-change:transform}
.vid.has .vid-el{opacity:1}
.vid-veil{position:absolute;inset:0;z-index:2;display:block;pointer-events:none;
  background:color-mix(in srgb,var(--ink) var(--veil),transparent)}
.page{position:relative;z-index:1}

/* the hero is a type lockup over the ground. The scrim's left column runs to
   effectively solid ink behind the text, because the foundation is explicit that
   body copy never sits over a moving frame — the footage stays visible above and
   to the right of the column, not under the paragraph. */
/* `gap` is load-bearing, not cosmetic. With space-between alone the clearance
   between the logo row and the lockup is LEFTOVER SPACE — so it is generous at
   the shipping rung (measured 71px at 68px type) and collapses to exactly 0 once
   the type grows enough to consume the remainder, at which point the headline
   butts into the row above and reads as clipped. text-box-trim compounds it by
   removing the half-leading that used to disguise the collision.
   gap sets a FLOOR that space-between then distributes extra space on top of, so
   the rhythm can grow but never vanish. At the largest rungs the hero simply
   becomes taller than one screen, which is honest — the type genuinely does not
   fit, and scrolling is the correct answer to that rather than a collision. */
.hero{position:relative;min-height:100svh;display:flex;flex-direction:column;justify-content:space-between;
  gap:var(--gap-block);
  padding:var(--axis);background:var(--ink);--on-ink-dim:var(--on-ink)}
/* the hero's footage carries no flat veil — this directional scrim is its veil,
   which is why it has to sit ABOVE the clip rather than behind it */
.hero > .vid{z-index:0}
.hero > .hero-top,.hero > .hero-mid,.hero > .hero-bot{position:relative;z-index:2}
.hero::before{content:'';position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(96deg,color-mix(in srgb,var(--ink) 96%,transparent) 0%,
      color-mix(in srgb,var(--ink) 92%,transparent) 44%,
      color-mix(in srgb,var(--ink) 40%,transparent) 66%,transparent 82%),
    linear-gradient(180deg,color-mix(in srgb,var(--ink) 78%,transparent) 0%,transparent 26%),
    linear-gradient(0deg,color-mix(in srgb,var(--ink) 70%,transparent) 0%,transparent 20%)}
.hero-top{display:flex;flex-wrap:wrap;gap:26px var(--gap-block);align-items:flex-start;justify-content:space-between}
.hero-logo{width:min(16rem,30vw);height:auto}
/* The locator: map above the text, both bound to the text's width. The map is
   1.19:1, so this width is what keeps the lockup inside one viewport. */
/* THE STATION PLATE.
   White ink has nowhere to live on this site: the ground is white and the veils
   paint white, so the only place light type can sit is over unveiled footage —
   which is bright. Measured under this exact locator, white ran 1.09:1 at the
   brightest pixel. A halo patches that; a plate solves it, and a plate is what
   transit signage actually is. The type is not floating over a photograph, it is
   mounted on something.

   B Line #333333 is the only one of the three Vignelli greys that can carry white
   type: white measures 12.63:1 on it, against 3.25:1 on Stations #928E8B and
   2.94:1 on L Line #949797. Both of those fail small text outright, so the choice
   is the set's own ink rather than a preference. The pink marker holds too —
   #DA39AF on #333333 is 3.12:1, over the 3:1 bar a graphic needs.

   Square corners, no radius: Vignelli's system has no rounded plates. */
/* min-width:min-content is what stops "VANCOUVER BC" hanging off the plate.
   28vw is 77px of content box at phone width, and that token is unbreakable —
   the &nbsp; holding city to province is deliberate — so at 124.8px it simply
   overflowed the charcoal by 47.7px and sat on the footage.
   Sizing the floor off min-content rather than picking a bigger vw means the
   plate fits whatever the label says: change the city, or the type ladder, and
   it still fits. The vw cap keeps it from dominating a wide hero. */
.hero-loc{display:flex;flex-direction:column;align-items:stretch;gap:10px;
  width:min(10.5rem,28vw);min-width:min-content;padding:14px;background:var(--line-b);
  text-align:right;box-shadow:0 2px 14px -4px rgba(0,0,0,.45)}
/* Shading, now that there is a surface to shade against. Restrained — the plate
   is doing the contrast work, so this only has to give the white a little relief
   rather than rescue it. */
.hero-loc .label,.hero-loc .label.quiet{line-height:1.5;max-width:none;color:#fff;
  text-shadow:0 1px 2px rgba(0,0,0,.55)}
.hero-loc img{width:100%;height:auto;
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.5))}

/* The lockup runs on the ladder's own rungs: the display, sub, heading and body
   classes each carry their gap, so no extra spacing is added here. No max-width
   on the group — the body measure belongs to the paragraph (styles.css gives p
   its own), and clamping the whole lockup to it wraps the display rungs.
   The name sits on --t-step (68px), the smaller of the ladder's two display
   rungs, not --t-hero (110px). */
.hero-mid{max-width:none}
.hero-mid h1{text-transform:uppercase}
/* Weight 300 is not one of the weight tokens (--w-body 400, --w-bold 700), but at
   40px+ the stem clears the ink floor comfortably. The presets still swap to bold
   if the rung resolves small. */
.hero-mid .display-sub{font-weight:var(--sub-weight,var(--w-display-light))}
.hero-mid > :last-child{margin-bottom:0}
.hero-bot{display:flex;flex-direction:column;gap:12px;align-items:flex-start}

/* ---------- sections: panels that pass OVER the ground, not erase it ----------
   A thin veil and quiet colour are mutually exclusive: over a bright graded frame
   there is no lightness left to be quiet with, and the goldenrod accent fails
   outright (4.43:1 at 78%, 2.85:1 at 60%). So inside these panels the neutral inks
   carry the text at full strength and subordinate themselves by case, tracking,
   measure and position — never by lightness — while anything that NEEDS the accent
   gets its own opaque fill instead of borrowing the panel's. Measured over the
   worst-case bright frame at the 60% floor: --on-ink 5.45:1, --on-paper 6.00:1. */
.sec{position:relative;padding:var(--gap-block) var(--axis);--on-ink-dim:var(--on-ink);background:none}
/* The New Music section carries the site's paper wash as a flat band. Flat,
   not the radial the footage panels use, because GR2's numeral plays
   tone-on-tone against it — a relationship a gradient would break at some
   scroll position. The white GR1/GR3 knockouts need the tint to exist at all:
   they would vanish on the page's plain white.
   overflow-x:clip because GR2's white band below is a 100vw pseudo-element,
   and on Windows 100vw includes the scrollbar's width — unclipped, that is a
   permanent horizontal scrollbar. clip, not hidden: it doesn't turn the
   section into a scroll container. */
.sec.eps{background:var(--ink-2);overflow-x:clip}
/* GR2 GETS THE INVERSE BAND: a full-bleed white stripe behind the middle
   group, so the section reads wash / white / wash and GR2's wash-toned
   numeral whispers against white the way GR1/GR3's white whispers against
   wash. A pseudo-element rather than a background on the row because the row
   is narrower than the viewport (the wrap caps at 1360px) and a background
   would stop mid-screen on anything wide. isolation makes the row its own
   stacking context, without which the z-index:-1 paint sinks BENEATH the
   section's wash and never shows. The stripe borrows half the inter-row gap
   on each side so the white gets air around the content while the rhythm of
   the three rows stays exactly where it was. */
.ep:nth-child(2){isolation:isolate}
.ep:nth-child(2)::before{content:'';position:absolute;z-index:-1;
  top:calc(var(--gap-block)/-2);bottom:calc(var(--gap-block)/-2);
  left:50%;width:100vw;transform:translateX(-50%);background:#fff}
.sec > *:not(.vid){position:relative;z-index:1}
/* Links carry themselves with weight and a rule, not with the accent, which is
   unreadable at a thin veil. Goldenrod returns on hover, where it is a signal
   rather than the only thing holding the link up. Button-styled anchors are
   excluded: they own an opaque fill and their own colour pairing. */
.sec a:not(.buy){color:var(--signal);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px;border-bottom:0}
.sec a:not(.buy):hover{color:var(--on-ink)}
.sec-head{display:flex;flex-direction:column;gap:var(--gap-sub);margin-bottom:var(--gap-block)}
.sec-top{display:flex;flex-wrap:wrap;gap:8px var(--gap-block);align-items:baseline;justify-content:space-between}
/* Section markers run large and light. On --t-sub, not a literal 40px: a fixed
   size sits outside the type-scale presets, so the bump would shrink every other
   rung and leave the eyebrow the biggest type on the page. */
.sec-top .label:first-child{font-size:var(--t-sub);font-weight:var(--w-display-light);line-height:1.1}
.sec-h{font-size:var(--t-sub);font-weight:var(--w-bold);line-height:1;letter-spacing:-.025em;margin:0}
.sec-p{font-size:var(--t-body);line-height:1.55;max-width:var(--measure);margin:0;text-wrap:pretty}

/* ---------- music ---------- */
/* Secondary text in the panels leans on case, tracking and measure rather than
   lightness, so nothing here needs a tint to sit back. */
.rel{display:grid;grid-template-columns:minmax(260px,380px) 1fr;gap:var(--gap-block)}
.rel-art{position:relative;aspect-ratio:1;background:var(--ink-2);box-shadow:inset 0 0 0 1px var(--edge);
  display:grid;place-items:center;overflow:hidden}
.rel-art img{width:100%;height:100%;object-fit:cover}
.rel-art .await{padding:26px;text-align:center;font-size:var(--t-cap);color:var(--on-ink-dim);text-wrap:pretty}
.rel-art .await b{display:block;font-weight:var(--w-bold);color:var(--signal);margin-bottom:6px}
.rel-art[data-has] .await{display:none}
.rel-body{display:flex;flex-direction:column;gap:var(--gap-sub);min-width:0}
.rel-title{font-size:var(--t-sub);font-weight:var(--w-bold);line-height:1;letter-spacing:-.025em;margin:0}
.rel-facts{display:grid;grid-template-columns:auto 1fr;gap:6px 26px;font-size:var(--t-cap);margin:0}
.rel-facts dt{font-weight:var(--w-bold);text-transform:uppercase;letter-spacing:.14em;white-space:nowrap}
.rel-facts dd{margin:0;color:var(--on-ink-dim)}
.buy{align-self:flex-start;display:inline-flex;align-items:center;gap:12px;font-size:var(--t-cap);font-weight:var(--w-bold);
  text-transform:uppercase;letter-spacing:.14em;padding:13px 24px;border:0;cursor:pointer;font-family:inherit;
  /* A button label is a single unit — it never breaks. Space Grotesk runs wider than Barlow
     wider than Barlow at the same rung, which pushed "Buy the CD — $23.23 CAD"
     onto two lines the moment the face changed. */
  white-space:nowrap;
  color:var(--on-ink);
  /* THE FILL SWEEP — replaces the magnetic pull as the CTA's attention move.
     Magnetic separated the label from the shell (that lag was the whole effect)
     and at any real strength it reads as the button coming apart. This animates
     one background instead: nothing inside the button moves relative to anything
     else, so there is nothing to pull apart. Charcoal wipes across the cyan from
     the left — the same direction a route travels — and the button reads as
     arming rather than wobbling. */
  background-image:linear-gradient(90deg,var(--on-ink) 0 50%,var(--signal-fill) 50% 100%);
  background-size:200% 100%;background-position:100% 0;
  transition:background-position .34s cubic-bezier(.22,.8,.28,1),
             color .34s cubic-bezier(.22,.8,.28,1),
             transform .34s cubic-bezier(.22,.8,.28,1)}
/* the lift moves the WHOLE element, so it cannot separate from its own label */
.buy:hover,.buy:focus-visible{background-position:0 0;color:var(--ink);transform:translateY(-1px)}
@media (prefers-reduced-motion:reduce){
  .buy{transition:none}
  .buy:hover,.buy:focus-visible{transform:none}
}
.buy[data-nolink]{background:none;color:var(--on-ink-dim);box-shadow:inset 0 0 0 1px var(--on-ink-dim);cursor:not-allowed}

/* tracklist: number, title, running time */
.tracks{list-style:none;margin:0;padding:0;display:flex;flex-direction:column}
.tracks li{display:grid;grid-template-columns:3ch 1fr auto;gap:16px;align-items:baseline;padding:11px 0;
  box-shadow:inset 0 -1px 0 var(--edge)}
.tracks li:first-child{box-shadow:inset 0 1px 0 var(--edge),inset 0 -1px 0 var(--edge)}
.t-n{font-size:var(--t-cap);font-weight:var(--w-bold);color:var(--on-ink-dim);font-variant-numeric:tabular-nums}
.t-name{font-size:var(--t-body);min-width:0}
.t-dur{font-size:var(--t-cap);color:var(--on-ink-dim);font-variant-numeric:tabular-nums}
/* Holes must look like holes. Lightness is unavailable at a thin veil, so an
   unfilled slot borrows the page's existing "needed" idiom: signal colour, upper
   case and tracking — the same language as the disabled Bandcamp button. */
/* The chips bring their own opaque ground, so the accent never has to survive
   the panel alpha — goldenrod on solid ink is 6.9:1 at any veil setting. */
.tracks li[data-await] .t-name{display:inline-block;justify-self:start;color:var(--signal);
  font-size:var(--t-cap);font-weight:var(--w-bold);text-transform:uppercase;letter-spacing:.14em;
  line-height:1;padding:6px 10px;background:var(--ink);box-shadow:inset 0 0 0 1px var(--signal)}
/* An outlined chip, not another goldenrod label: .earlier b directly above owns
   that treatment, and as adjacent siblings they would merge into one block. */
.earlier .t[data-await]{align-self:flex-start;color:var(--signal);font-size:var(--t-cap);
  font-weight:var(--w-bold);text-transform:uppercase;letter-spacing:.14em;line-height:1;
  padding:7px 11px;background:var(--ink);box-shadow:inset 0 0 0 1px var(--signal)}
/* more than one release, so they need air between them */
.rel + .rel{margin-top:var(--gap-block);padding-top:var(--gap-block);box-shadow:inset 0 1px 0 var(--edge)}

/* earlier work, as Sect */
.earlier{display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:26px 42px;margin:0;padding:0}
.earlier div{display:flex;flex-direction:column;gap:6px;padding-top:16px;box-shadow:inset 0 1px 0 var(--edge)}
.earlier b{font-size:var(--t-cap);font-weight:var(--w-bold);text-transform:uppercase;letter-spacing:.14em;color:var(--on-ink)}
.earlier .t{font-size:var(--t-head);font-weight:var(--w-bold);line-height:1.15;letter-spacing:-.005em}
.earlier p{font-size:var(--t-cap);line-height:1.55;color:var(--on-ink-dim);margin:0;text-wrap:pretty}

/* ---------- about: the light exception ----------
   Uses var(--veil) like its siblings so the slider reaches it too. Secondary
   text here is full strength for the same reason it is in the ink panels: over a
   dark frame, muted paper ink fails at 2.4:1 by 60% veil, whereas full-strength
   paper ink holds to roughly 50%. Subordination comes from case and tracking. */
.about{color:var(--on-paper);
  --on-ink-dim:var(--on-paper);
  --edge:color-mix(in srgb,var(--on-paper) 24%,transparent)}
.about a:not(.buy){color:var(--signal);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px}
.about a:not(.buy):hover{color:var(--on-paper)}
.about > .vid{background:var(--chalk-2)}
.about .vid-veil{background:color-mix(in srgb,var(--chalk) var(--veil),transparent)}
.about .sec-p,.about .quiet{color:var(--on-paper)}
.about .sec-top .label{color:var(--on-paper)}
/* the bio sits tighter under its heading than the other sections — --gap-hero
   rather than --gap-block, which is the 42px you pulled it up by */
.about .sec-head{margin-bottom:var(--gap-hero)}
.bio{display:grid;grid-template-columns:1fr minmax(0,42ch);gap:var(--gap-block)}
/* The lead sits on --t-head with the shared light weight — not a 20px literal and
   not a second light weight. */
.bio p.bio-lead{font-size:var(--t-head);font-weight:var(--w-display-light);line-height:1.35;margin:0 0 var(--gap-sub);text-wrap:pretty}
.bio p{font-size:var(--t-body);line-height:1.6;margin:0 0 1em;max-width:var(--measure);text-wrap:pretty}
.bio p:last-child{margin-bottom:0}
/* margin:0 is load-bearing: the UA gives blockquote 1em 40px, which pushes the
   hairline off the page's single left axis and crushes the measure. */
.pull{align-self:start;margin:0;padding-top:16px;box-shadow:inset 0 1px 0 var(--edge)}
/* Weight 100 is only safe at 42px — below that the stem drops under the 1px ink
   floor — so the quote steps DOWN the ladder rather than thinning at a size the
   foundation does not allow. The narrow band is 901–1100px, where the grid is
   still two columns but the second track is too tight for 42px to rag well. */
/* One treatment at two sizes, rather than light-at-42 and bold-at-26. The narrow
   band is 901–1100px, where the grid is still two columns but the second track is
   too tight for 42px to rag well. */
.pull p{font-size:var(--t-sub);font-weight:var(--w-display-light);line-height:1.1;letter-spacing:-.02em;margin:0;text-wrap:balance}
@media (min-width:901px) and (max-width:1100px){
  .pull p{font-size:var(--t-head);line-height:1.25;letter-spacing:-.005em}
}
.pull cite{display:block;font-size:var(--t-cap);font-weight:var(--w-bold);text-transform:uppercase;
  letter-spacing:.14em;color:var(--on-paper);font-style:normal;margin-top:16px}

/* ---------- list + contact ---------- */
.two{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:var(--gap-block)}
.form{display:flex;flex-direction:column;gap:16px;max-width:42ch}
.form label{font-size:var(--t-cap);font-weight:var(--w-bold);text-transform:uppercase;letter-spacing:.14em}
.form input{font:inherit;font-size:var(--t-body);color:var(--on-ink);background:var(--ink-2);
  border:0;box-shadow:inset 0 0 0 1px var(--edge);padding:14px 16px;width:100%}
.form input:focus-visible{outline:1px solid var(--signal);outline-offset:2px}
.form .fine{font-size:var(--t-cap);color:var(--on-ink-dim);margin:0;text-wrap:pretty}
.contact-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px;font-size:var(--t-body)}
.contact-list b{display:block;font-size:var(--t-cap);font-weight:var(--w-bold);text-transform:uppercase;
  letter-spacing:.14em;color:var(--on-ink-dim);margin-bottom:2px}

/* ---------- footer ----------
   The only region that always sits directly on moving footage, so it takes the
   same treatment as the panels: neutral inks at full strength, links carried by
   weight and a rule rather than the accent. */
.foot{position:relative;overflow:hidden;background:var(--ink);--on-ink-dim:var(--on-ink)}
.foot a:not(.buy){color:var(--signal);text-decoration:underline;text-decoration-thickness:1px;text-underline-offset:3px;border-bottom:0}
.foot a:not(.buy):hover{color:var(--on-ink)}
.foot-stage{position:relative;min-height:420px;display:flex;flex-direction:column;justify-content:flex-end}
/* 0deg runs bottom-to-top. The strong end has to cover the whole content band,
   not just the last few pixels: the wordmark sits 103-199px off the bottom of a
   420px stage, and at the old 30%-at-58% stop it was standing on almost bare
   footage at 2.57:1. Strong ink now holds to 46%, past the top of the mark, and
   the loop reads in the clear upper half. Cream over the worst-case bright frame
   measures 9.8:1 anywhere content sits. */
.foot-scrim{position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(0deg,color-mix(in srgb,var(--ink) 92%,transparent) 0%,
    color-mix(in srgb,var(--ink) 88%,transparent) 46%,
    color-mix(in srgb,var(--ink) 26%,transparent) 76%,transparent 100%)}
.foot-in{position:relative;z-index:3;padding:var(--gap-block) var(--axis) var(--axis);display:flex;flex-direction:column;gap:var(--gap-sub)}
.foot-mark{display:flex;flex-wrap:wrap;align-items:flex-end;gap:26px var(--gap-block);justify-content:space-between}
.foot-mark img{width:min(38vw,300px);height:auto}
.foot-meta{display:flex;flex-wrap:wrap;gap:8px 42px;font-size:var(--t-cap);color:var(--on-ink-dim)}
/* ---------- text transitions ----------
   The keyframes and the curves are the ones the Text reveal and Rules-as-grouping
   cards use, at the sizes they use them: a masked 110% rise at 880ms, a 0.5rem
   lift at 700ms, a clip wipe at 760ms, a hairline drawing from scaleX(0) at 850ms.
   Delays are folded in through --rv-d rather than declared separately, because the
   animation shorthand would wipe an animation-delay declaration.

   The hidden states only apply while html carries data-rvon — set by the reveal
   script alone — so with no JS, or under reduced motion, every line is present. */
@keyframes rv-rise{from{transform:translate3d(0,110%,0);opacity:0}to{transform:translate3d(0,0,0);opacity:1}}
@keyframes rv-fade{from{transform:translate3d(0,.5rem,0);opacity:0}to{transform:translate3d(0,0,0);opacity:1}}
@keyframes rv-wipe{from{clip-path:inset(-30% 100% -30% -30%)}to{clip-path:inset(-30%)}}
@keyframes rv-draw{from{transform:scaleX(0)}to{transform:scaleX(1)}}
/* Two additions to the card set, both chosen because they suit type over film.
   BLUR resolves out of defocus — the same gesture as a lens finding focus, which
   is the one piece of camera language this site can borrow without pretending the
   type is a physical object. SETTLE opens tracked-out and tightens to its real
   letter-spacing; it reads as the word composing itself rather than moving, so it
   works where a translation would fight the footage drifting behind it. */
@keyframes rv-blur{from{filter:blur(12px);opacity:0}to{filter:blur(0);opacity:1}}
@keyframes rv-settle{from{letter-spacing:.34em;opacity:0}to{opacity:1}}
html[data-rvon] .rv{--rv-t:780ms;--rv-d:0ms}
html[data-rvon] .rv[data-m=fade],html[data-rvon] .rv[data-m=rise],html[data-rvon] .rv[data-m=lines],
html[data-rvon] .rv[data-m=blur],html[data-rvon] .rv[data-m=settle]{opacity:0}
html[data-rvon] .rv[data-m=blur].in{animation:rv-blur calc(var(--rv-t) * 1.05) cubic-bezier(.22,.8,.28,1) var(--rv-d) both}
/* Tracking animates the box width, so this is only ever applied to display-level
   type where the line has room to breathe; on a paragraph it would reflow. The
   role map is what keeps it there. */
html[data-rvon] .rv[data-m=settle].in{animation:rv-settle calc(var(--rv-t) * 1.2) cubic-bezier(.16,.84,.28,1) var(--rv-d) both}
html[data-rvon] .rv[data-m=wipe]{clip-path:inset(-30% 100% -30% -30%)}
html[data-rvon] .rv[data-m=draw]{transform:scaleX(0);transform-origin:0 50%}
html[data-rvon] .rv[data-m=fade].in{animation:rv-fade var(--rv-t) cubic-bezier(.22,.8,.28,1) var(--rv-d) both}
html[data-rvon] .rv[data-m=wipe].in{animation:rv-wipe calc(var(--rv-t) * .975) cubic-bezier(.22,.8,.28,1) var(--rv-d) both}
html[data-rvon] .rv[data-m=draw].in{animation:rv-draw calc(var(--rv-t) * 1.09) cubic-bezier(.16,.84,.28,1) var(--rv-d) both}
/* the block rise needs its own mask, and gives it back the moment it lands — a
   permanent overflow:hidden would clip a focus ring or a hover shadow forever */
html[data-rvon] .rv[data-m=rise]{overflow:hidden}
html[data-rvon] .rv[data-m=rise].in{animation:rv-rise calc(var(--rv-t) * 1.13) cubic-bezier(.16,.86,.24,1) var(--rv-d) both}
/* per-line masks: the line clips, the inner span rises inside it. The
   padding/negative-margin pair keeps descenders off the clip edge at rest. */
html[data-rvon] .rv[data-m=lines]{opacity:1}
html[data-rvon] .rv[data-m=lines] .rv-line{display:block;overflow:hidden;padding-bottom:.14em;margin-bottom:-.14em}
html[data-rvon] .rv[data-m=lines] .rv-line > i{display:block;font-style:inherit;opacity:0}
html[data-rvon] .rv[data-m=lines].in .rv-line > i{font-style:inherit;
  animation:rv-rise calc(var(--rv-t) * 1.13) cubic-bezier(.16,.86,.24,1) calc(var(--rv-d) + var(--rv-ld,0ms)) both}
/* .done is the finished state and has to outrank the .in animation shorthand that
   put the element there — hence the extra [data-m] on the selector: without it the
   `both` fill on a paused animation clock keeps painting the FROM frame. */
html[data-rvon] .rv.done[data-m]{animation:none;opacity:1;transform:none;clip-path:none;overflow:visible}
html[data-rvon] .rv.done[data-m] .rv-line > i{animation:none;opacity:1;transform:none}

@media (max-width:900px){
  .rel{grid-template-columns:1fr}
  .bio{grid-template-columns:1fr}
  .nav ul{gap:16px}
}
/* The nav links used to be simply hidden below 560px — no menu, no way to reach
   any section from a phone. The hamburger replaces that, so the rule is gone
   rather than left to fight the panel: it set display:none, the panel rule sets
   position and flex-direction but not display, so the panel opened with correct
   opacity and visibility and still measured 0x0. */
@media (prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
}

/* ==========================================================================
   ATMOSPHERE — grain, vignette, and the footage grade
   From the web-effects library (08 · Texture · film-grain-overlay), adapted.

   These live HERE, in the site, not in the tuning panel. The panel only writes
   the custom properties below; delete site-tune.js before launch and every one
   of these keeps rendering at whatever value was baked in. An effect that only
   exists while its dial is loaded is not an effect, it is a demo.

   Everything here is off or neutral by default. Film grain used to ship at .10
   and has been removed entirely — see the note on the tokens below.
   ========================================================================== */
html{
  /* FILM GRAIN REMOVED 2026-07-26. It existed to stop large flat near-black
     fields from banding and to give the footage a projected finish — both
     arguments belong to the dark film direction. On a white transit ground it
     does the opposite: it fights the "crisp, high-definition" requirement and
     muddies the hairlines the diagrams are made of. The whole layer, its
     keyframes and its three controls are gone rather than defaulted to zero, so
     nothing can quietly switch it back on. */
  /* GRADE BAKED 2026-07-26 from Mike's saved panel values on staging
     (mv-site-tune-v3: veil 63, fade 78, sat 70, soft 5, progress 1).
     These are now the authored look, not a default waiting to be dialled — the
     tuning panel writes the same properties, so it can still move them, and
     deleting site-tune.js changes nothing. */
  --fx-vignette:0;       /* 0 → .7 edge darkening                                */
  --fx-fade:.78;         /* 0 → 1 — narrows the footage's dynamic range          */
  --fx-sat:.7;           /* footage saturation                                   */
  --fx-soft:5px;         /* footage blur — pushes the frame back behind the type */
  --fx-magnet:0;         /* 0 → 1 pointer lean on buttons (desktop only)         */
  --fx-progress:1px;     /* 0 → 4px scroll-progress hairline                     */
}

/* ---------- scroll progress — web-effects 01 · Scroll · css-scroll-driven ----
   animation-timeline: scroll() means the browser drives this off the scroller
   itself: no listener, no rAF, nothing to throttle, and it cannot fall out of
   sync with the page. Where it is unsupported the bar simply keeps its scaleX(0)
   and is invisible — which is why height, not opacity, is the off switch. */
/* F Line magenta, NOT the cyan accent. Two reasons, and the second is the good
   one. First: a progress rule and a call to action are different jobs, and giving
   them the same colour teaches the reader that cyan means both "act" and "you are
   here". Second: F Line is one of the two TRAP colours — nothing is legible on it,
   so it can never be a button or a chip. A hairline is pure fill with nothing on
   it, which makes it the one place a trap colour costs nothing to spend. Magenta
   also sits opposite cyan on the wheel, so the two never read as a set. */
.progress{position:fixed;inset:0 0 auto 0;height:var(--fx-progress);z-index:70;
  background:var(--line-f);transform:scaleX(0);transform-origin:0 50%;pointer-events:none}
@supports (animation-timeline:scroll()){
  .progress{animation:progress-grow linear;animation-timeline:scroll(root block)}
}
@keyframes progress-grow{from{transform:scaleX(0)}to{transform:scaleX(1)}}

/* One element, two fixed layers: vignette below, grain above. pointer-events is
   load-bearing — this sits over the nav, and without it the whole page is dead
   to the mouse. */
.atmos{position:fixed;inset:0;z-index:60;pointer-events:none}

/* Vignette. A radial darkening that also buys back contrast at the margins,
   which is exactly where the hero's directional scrim is weakest. */
.atmos::before{content:"";position:absolute;inset:0;display:block;
  background:radial-gradient(120% 85% at 50% 45%,transparent 42%,
    color-mix(in srgb,#000 calc(var(--fx-vignette) * 100%),transparent) 100%)}

/* Atmosphere is a screen finish; it muddies print and wastes toner. */
@media print{.atmos{display:none}}

/* The footage grade. Fade narrows the dynamic range — the "full bleed faded /
   narrow dynamic range" look — by pulling contrast down and lifting the blacks
   slightly, rather than by dropping opacity, which would just show more ink.

   --fx-vscale is the blur's scale compensation, and it belongs HERE rather than
   in the drift code: a filtered element fades out over roughly 1.5x its blur
   radius, so a softened frame shows a feathered edge unless it is grown past its
   panel. Drift could do that arithmetic, but drift never runs under reduced
   motion, which would leave exactly the visitors who disabled animation looking
   at a broken edge. 3r of cover against a conservative 600px panel — over-
   scaling a tall panel only crops a few more pixels, under-scaling shows the
   seam, so the constant errs the safe way. When drift IS running it composes
   the same base into its inline transform. */
.vid-el,.vid-still{filter:saturate(var(--fx-sat))
              contrast(calc(1 - var(--fx-fade) * .45))
              brightness(calc(1 + var(--fx-fade) * .10))
              blur(var(--fx-soft));
        transform:scale(var(--fx-vscale,1.025))}


/* A sleeve can be faded back when it is louder than its place in the hierarchy.
   Opacity rather than a filter: on a white ground the page itself is what shows
   through, so the art recedes toward the paper instead of turning grey. */
.rel-art--fade img{opacity:var(--art-fade,.62)}

/* The hero's second heading in F Line pink. It sits at --t-head (22.8px) and
   bold, which clears WCAG's large-text bar at 3:1 — the authored #DA39AF measures
   4.05:1 on white and is legitimate here. It would NOT be at label size, which is
   why the locator below uses the darkened cut instead. */
.heading--pink{color:var(--pink)}
/* the divider is structure, not speech — it should not carry the same weight as
   the two counts it separates */
.heading--pink .bar{color:var(--on-ink-dim);font-weight:var(--w-body);
  padding:0 .18em;opacity:.55}

/* With the list hidden for launch, .two has one visible child and auto-fit hands
   it the whole row — a two-line contact list stretched across 1336px. Cap it at
   the body measure so it keeps the same column width it had beside the form. */
.two:has([data-launch-hidden]) #contact{max-width:var(--measure)}

/* ---------- Sect: records visible, appearances folded ----------
   <details> rather than a scripted toggle. It is keyboard-operable, announced as
   expandable by screen readers, and browsers open it automatically when the user
   runs find-in-page — none of which a div and a click handler give you for free. */
.appears{margin-top:var(--gap-sub);border-top:1px solid var(--edge)}
.appears summary{cursor:pointer;list-style:none;padding:14px 0;display:flex;
  align-items:baseline;justify-content:space-between;gap:16px;
  font-size:var(--t-cap);font-weight:var(--w-bold);text-transform:uppercase;letter-spacing:.14em}
.appears summary::-webkit-details-marker{display:none}
/* the disclosure mark is drawn, not a glyph — a rotating rule reads as transit
   signage where a triangle reads as an operating system */
.appears summary::after{content:"";width:11px;height:1px;background:currentColor;
  flex:0 0 auto;position:relative;transition:transform .3s cubic-bezier(.22,.8,.28,1)}
.appears summary::before{content:"";width:1px;height:11px;background:currentColor;
  flex:0 0 auto;margin-right:-6px;transform-origin:50% 50%;
  transition:transform .3s cubic-bezier(.22,.8,.28,1)}
.appears[open] summary::before{transform:scaleY(0)}
.appears summary:hover{color:var(--signal)}
.appears-yr{font-weight:var(--w-body);color:var(--on-ink-dim);margin-left:auto;padding-right:10px}

.appears-list{list-style:none;margin:0 0 var(--gap-sub);padding:0;
  display:flex;flex-direction:column}
.appears-list li{display:grid;grid-template-columns:4.5ch minmax(0,1.4fr) minmax(0,1fr) minmax(0,1.6fr);
  gap:8px 16px;align-items:baseline;padding:9px 0;font-size:var(--t-cap);
  box-shadow:inset 0 -1px 0 var(--edge)}
.a-y{font-variant-numeric:tabular-nums;font-weight:var(--w-bold)}
.a-t{font-weight:var(--w-bold)}
.a-l{color:var(--on-ink-dim)}
.a-k{color:var(--on-ink-dim);font-style:italic}
.a-k[data-unknown]{opacity:.5;font-style:normal}
@media (max-width:700px){
  .appears-list li{grid-template-columns:4.5ch 1fr;gap:2px 12px}
  .a-l,.a-k{grid-column:2}
}

/* ---------- upcoming: the three EPs ----------
   The masters are 720x1280 portrait. Every tile here is a DOWNSCALE of that and
   is capped so it stays one: max-width holds the tile at 190px however much room
   the row has, because a 720px-wide phone video stretched across a desktop column
   is the one thing this section must never do. The tiles shrink on narrow screens
   and never grow past the cap. */
/* Three EPs across on a desktop, folding to two and then one as the room runs
   out. auto-fit rather than a hard 3-column rule with a breakpoint: the track
   floor is the real constraint (an EP is two tiles wide and stops being legible
   below ~290px), so the layout changes when the CONTENT needs it to rather than
   at a width someone guessed. The 396px ceiling is 2 x 190px tiles + the row
   gap, so a column never grows wider than the artwork it holds and the three
   groups stay evenly spread instead of drifting apart on a wide screen. */
.eps-wrap{display:grid;grid-template-columns:repeat(auto-fit,minmax(290px,1fr));
  gap:var(--gap-block) var(--gap-sub);align-items:start;
  /* 3 x 396px of artwork + 2 gaps. A fixed 396px TRACK cannot be used — three of
     them plus the gaps overflow a 1280 viewport's content box by 27px, so
     auto-fit drops to two columns. Flexible tracks with the grid itself capped
     gets three across at 1280 and stops the columns sprawling on a wide screen. */
  max-width:calc(396px * 3 + var(--gap-sub) * 2)}
.ep{display:flex;flex-direction:column;gap:var(--gap-sub);position:relative}
/* Twelve tiles in a 3x4 grid is a lot to hold at once, so EP 1 and EP 3 take a
   wash and EP 2 stays on the page ground. The rhythm works either way round the
   layout folds: side by side on a desktop it reads as three columns, stacked on
   a phone it reads as three bands.

   Painted by a pseudo-element bled outside the box, NOT by padding on the odd
   ones — padding would shrink those columns' content and the tiles inside them
   would come out smaller than EP 2's, which is the opposite of the alignment
   this is meant to help.

   The tint is the L Line grey at 8%, which lands near #f6f7f7 over white: the
   lightest step that still separates from the ground. Body text over it measures
   16.1:1, so nothing gets harder to read. */
.ep:nth-child(odd)::before{content:'';position:absolute;
  inset:calc(var(--gap-sub) * -1) calc(var(--gap-sub) * -.7);
  background:color-mix(in srgb,var(--line-l) 8%,transparent);z-index:-1}

.ep-head{display:flex;align-items:center;gap:14px}
/* Lightened to the WCAG minimum for LARGE text, which is 3:1 — not the 4.5:1 a
   body rung would need. At 224px on a desktop and 36.8px bold on a phone this
   qualifies as large text on both, so 3:1 is the real floor and anything darker
   was simply heavier than it had to be.
   --station is the Vignelli Stations grey and measures 3.03:1 on the washed rows
   and 3.25:1 on plain white, so it lands on the floor by construction rather than
   by a hand-picked hex. The WASH is the binding case, not the white: --line-l
   looks like the same sort of grey and fails there at 2.74:1.
   The live state stays pink at 3.77:1 on the wash, which still clears it. */
.ep-n{font-size:var(--t-sub);font-weight:var(--w-bold);line-height:1;letter-spacing:-.025em;margin:0;
  color:var(--station);
  /* LEAVING: 500ms back to the resting colour. This duration lives on the base
     rule because that is the rule in force once .is-live is removed — the
     element transitions using the timing of the state it is arriving AT, not
     the one it left. */
  transition:color 500ms ease}
/* Desktop numerals go tonal against the section's wash: GR1 and GR3 knock out
   in white, GR2 sits one register BELOW the ground — #eceae4, Mike's swatch,
   which is the deepest stop of the site's own wash gradient rather than a new
   grey — so it reads as a whisper of tone against the #f4f2ee band instead of
   vanishing into it. Its name is still carried by the GR2.x tags and read to
   screen readers regardless of paint.
   Scoped to the desktop layout: on a phone the numeral is a working heading,
   not display furniture. Kept at (0,3,0) specificity and BEFORE the .is-live
   rule, so the playing pink still wins the tie by order. */
@media (min-width:1200px){
  .ep:nth-child(1) .ep-n,.ep:nth-child(3) .ep-n{color:#fff}
  .ep:nth-child(2) .ep-n{color:#eceae4}
}
/* The EP whose cut is audible names itself in the F Line magenta. At --t-sub
   bold this is large text, where 3:1 is the bar — measured 4.05:1 on white. The
   same magenta is NOT used for the small button text below, where the bar is
   4.5:1 and it would fail. */
/* ARRIVING: 250ms to pink. Faster in than out, so the highlight lands promptly
   when a cut starts and lets go gently when it stops — the same asymmetry the
   audio fade already uses (FADE_IN 50ms against FADE_OUT 180ms). */
.ep.is-live .ep-n{color:var(--pink);transition:color 250ms ease}

/* One flat run of tiles per group — the A SIDE / B SIDE framing is retired, so
   there is no .ep-side wrapper any more. Wrapping is what keeps the phone at
   two tiles a line: a 40% basis fits two per row and never three. */
.ep-row{display:flex;flex-wrap:wrap;gap:var(--gap-sub);align-items:flex-start}

.cut{margin:0;display:flex;flex-direction:column;gap:6px;flex:1 1 40%;min-width:0;max-width:190px}
.cut-cap{display:flex;flex-direction:column;gap:1px}
/* Slot label and its spectrum on one line. The label is sized by content and the
   meter takes whatever is left, so a two-character slot and the bars always fill
   the tile width exactly however the type ladder is rescaled. */
/* baseline, not flex-end. A canvas takes its bottom margin edge as its baseline,
   so aligning on the baseline sits the foot of the bars exactly on the foot of
   the "A1" — flex-end would line it up with the bottom of the text's LINE BOX,
   which is the descender depth lower and reads as a misalignment. */
.cut-slot{display:flex;align-items:baseline;gap:9px}
/* the tag IS the title until the tracks are named — so it carries display weight */
/* .776, measured not guessed: "GR1.1" runs 98.7px where "G1.1" ran 76.5 at the
   same size, and the meter beside the tag takes whatever width the tag leaves.
   Scaling the type by 76.5/98.7 hands the longer string the same box the short
   one had, so the visualizer keeps its size. calc off the ladder var, so the
   tune panel still drives it. */
.cut-n{font-size:calc(var(--t-sub)*.776);font-weight:var(--w-bold);line-height:.95;letter-spacing:-.03em;
  display:block;flex:none;font-variant-numeric:tabular-nums}
/* The two ends of the frequency ramp, read by site-eps.js. Low frequencies take
   the F Line magenta, high frequencies the site's link blue — the blue a visitor
   has already met on every anchor. Both are read from here rather than hard-coded
   in the script, so the palette stays in one place.
   --viz-high is --signal (#007a93, 5.0:1 on white), NOT the brighter CTA fill
   #00add0: that measures 2.66:1 and these are 1-2px bars. */
/* flex-basis 0, not auto: a <canvas> carries an intrinsic 300px default width,
   and an auto basis takes that as its starting size — so the row wants to be
   wider than the tile and the bars run past the right edge of the artwork.
   From zero it takes exactly the space left over and no more. */
.cut-viz{--viz-low:var(--pink);--viz-high:var(--signal);
  flex:1 1 0;width:0;min-width:0;height:22px;display:block}
.cut-t{font-size:var(--t-body);line-height:1.4}

.cut-media{position:relative;aspect-ratio:9/16;overflow:hidden;background:var(--ink-2);
  box-shadow:inset 0 0 0 1px var(--edge)}
.cut-el{width:100%;height:100%;object-fit:cover;display:block}
/* an <audio> element has no picture to give; the plate beside it is the picture */
audio.cut-el{display:none}
.cut-media--plate{background:var(--line-b)}
/* The mark stands in for the frame that was never shot, turned on its side and
   run nearly the full height of the plate.
   The 153% is not a guess. The tile is locked to 9:16, so its height is 16/9 of
   its width; filling 86% of that height with a mark rotated a quarter turn means
   a PRE-rotation width of 0.86 x 16/9 = 1.529 of the tile width. Percentages
   resolve against width, so this needs no container queries — but it is only
   correct while .cut-media stays 9:16.
   The logo ships with #1a1612 baked into its paths, so it is used as a MASK and
   the colour comes from the background — an <img> could not be recoloured. */
.cut-logo{position:absolute;top:50%;left:50%;
  transform:translate(-50%,-50%) rotate(-90deg);
  width:153%;aspect-ratio:1133.99/359.97;
  background:var(--station);
  -webkit-mask:url(assets/logo.svg) center/contain no-repeat;
          mask:url(assets/logo.svg) center/contain no-repeat}

/* The tagging strip under every tile: year and status. Under, not above, so an
   annotated cut cannot shove its artwork out of line with the three beside it. */
.cut-below{display:flex;flex-direction:column;gap:2px;margin-top:2px}
/* Label bones without the .label class: that class uppercases, and the data's
   casing is the point — DEMO is set in caps in the data, Concept is not, and
   flattening both to caps would erase the distinction. */
.cut-tag{font-size:var(--t-cap);font-weight:var(--w-bold);letter-spacing:.14em;
  line-height:1.4;color:var(--on-ink-dim)}

/* The live tile is outlined rather than tinted: a tint over footage changes what
   the footage looks like, which is the one thing a picker must not do.
   outline, not an inset box-shadow. The tile's width is fractional (flex of a
   190px cap), and an inset shadow is rasterised inside that fractional box, so
   its edges land on different subpixels and read as 1px on two sides and 3px on
   the others. An outline is drawn on the border box and stays even; the negative
   offset pulls it inside so it does not shift the layout. It also replaces the
   1px edge rather than sitting on top of it, so the live tile does not gain a
   second hairline. */
.cut.is-live .cut-media{outline:1px solid var(--pink);outline-offset:-1px;box-shadow:none}

/* Charcoal fill, white text — 12.63:1 — in every state. The pink lives in the dot
   and the outline, never under the words: white on this magenta measures 4.05:1
   and the label is small text, which needs 4.5:1. */
.cut-sound{position:absolute;top:8px;right:8px;display:inline-flex;align-items:center;gap:7px;
  padding:6px 10px;border:0;cursor:pointer;background:var(--line-b);color:#fff;
  font-family:inherit;font-size:var(--t-cap);font-weight:var(--w-bold);
  letter-spacing:.08em;text-transform:uppercase;line-height:1}
.cut-sound:hover{background:#000}
.cut-sound:focus-visible{outline:2px solid var(--pink);outline-offset:2px}
.cut-ico{width:11px;height:11px;flex:none;display:block;fill:currentColor}
/* 3.12:1 against the charcoal fill — above the 3:1 bar for a graphical object */
.cut-sound[aria-pressed="true"] .cut-ico{fill:var(--pink)}

/* THE STATION PLATE ON A PHONE.
   At 375px the plate was 152.8 x 184.6 — 41% of the screen's width and the
   loudest thing above the name. Everything here scales together, because the
   plate has no width of its own: min-width:min-content means the LABEL sets it,
   so shrinking the type is what shrinks the plate, and the map follows at 100%.

   How small it can honestly go: "VANCOUVER BC" is one unbreakable token, and at
   14px it is 124.8px. Even at 9px with the tracking pulled in it is 71.6px,
   which with padding still makes an 85.6px plate — so a literal half-width 76px
   plate is not reachable without type below 9px. 10px with .06em lands the plate
   at ~95px and ~116px tall: 62% of the width and 39% of the AREA, which is the
   "half the size" you actually see. Going further means shortening the label,
   which is Mike's copy, not mine to cut. */
@media (max-width:640px){
  .hero-loc{width:min(6rem,26vw);padding:8px;gap:6px}
  .hero-loc .label,.hero-loc .label.quiet{font-size:10px;letter-spacing:.06em;line-height:1.35}
}

@media (max-width:640px){
  .ep-head{gap:10px}
  .ep-row{gap:12px}
  .cut-n{font-size:var(--t-head)}
  .cut-slot{gap:7px}
  .cut-viz{height:17px}
}
/* Reduced motion is handled in site-eps.js, not here: the tile has to keep
   showing its poster frame, and a <video> only paints its poster while it is
   NOT playing. So the fix is to never start it — display:none on the element
   would leave an empty box, since nothing sets that poster as a background. */

/* ---------- mobile nav ----------
   The frosted glass is declared ONCE, as --glass-bg / --glass-blur on .nav, and
   the panel reuses those same two values. Mike asked the panel to inherit the
   bar's treatment, and re-typing color-mix(...) 88% and blur(8px) in a second
   place is how two things that must match quietly stop matching. */

/* THE TOGGLE.
   Three strokes leaning the way the logo's diagonals lean — the mark is a run of
   repeated parallel strokes descending left to right, so the closed state is a
   small quotation of it rather than a generic hamburger. Open stands them
   upright. Not the usual collapse-to-X: an X says "close", three verticals say
   "the strokes moved", which is the same gesture the mark itself makes.

   The bars are drawn VERTICAL and rotated -45deg to lean, so the open state is
   the untransformed rest position — rotate(0). Less to go wrong than animating
   between two transforms, and the geometry is legible from the CSS. */
.nav-toggle{display:none;width:40px;height:40px;padding:0;margin:0;border:0;background:none;
  cursor:pointer;align-items:center;justify-content:center;gap:6px;color:var(--on-ink);
  -webkit-tap-highlight-color:transparent}
.nav-toggle:focus-visible{outline:2px solid var(--pink);outline-offset:2px}
.nav-stroke{display:block;width:2px;height:19px;background:currentColor;flex:none;
  transform:rotate(-45deg);transform-origin:50% 50%;
  transition:transform .38s cubic-bezier(.2,.7,.2,1)}
.nav-toggle[aria-expanded="true"] .nav-stroke{transform:rotate(0deg)}
.nav-toggle:hover{color:var(--signal)}

@media (max-width:760px){
  .nav-toggle{display:inline-flex}
  /* the bar keeps only the logo and the toggle; the links move to .nav-panel */
  .nav ul{display:none}
}

/* ---------- nav over a dark ground ----------
   Same mechanism as shopwisdom.ca: sections declare data-bg, a probe point decides
   which section is under the bar, and .on-dark flips the foreground. Declared
   rather than pixel-sampled — sampling a playing video every frame to pick a text
   colour is a lot of work to answer a question the layout already knows.

   The logo is recoloured with brightness(0) invert(1) rather than swapped for a
   second file. brightness(0) crushes it to black whatever it started as, invert
   then takes it to pure white — so it is exact, and there is no reversed asset to
   keep in step with the original. */
.nav.on-dark{--glass-bg:color-mix(in srgb,var(--on-ink) 46%,transparent);
  box-shadow:0 1px 0 color-mix(in srgb,var(--ink) 16%,transparent)}
.nav.on-dark .nav-logo{filter:brightness(0) invert(1)}
.nav.on-dark a{color:var(--ink)}
.nav.on-dark a:hover{color:var(--pink)}
.nav.on-dark .nav-toggle{color:var(--signal-fill)}

/* THE TOGGLE'S COLOUR.
   Blue at rest, pink the instant it opens. transform is the only transitioned
   property, so the strokes EASE upright while the colour SNAPS — which is what
   Mike asked for, and it reads as two separate signals rather than one slow blend.
   --signal, not --signal-fill: --signal is the blue every link on this site already
   renders as, and at 5.0:1 on white it holds. --signal-fill (#00add0) is a button
   FILL, and as a 2px stroke on white it measures 2.66:1 — under the 3:1 a control
   needs. Pink open is 4.05:1, which clears it. */
.nav-toggle{color:var(--signal)}
.nav-toggle:hover{color:var(--signal)}
.nav-toggle[aria-expanded="true"],
.nav-toggle[aria-expanded="true"]:hover{color:var(--pink)}
.nav.on-dark .nav-toggle[aria-expanded="true"]{color:var(--pink)}


/* ---------- the mobile panel ----------
   A SIBLING of .nav, not a child, and that is load-bearing rather than tidy.
   An element with backdrop-filter becomes a backdrop root, so a descendant's own
   backdrop-filter has nothing left to sample and silently draws nothing. Inside
   <nav> this panel's blur computed correctly and rendered flat — invisible at 88%
   background, obvious at the 50% copied from SW, which is why shopwisdom.ca
   closes </header> before opening its menu.

   Values are hard-coded here rather than read from .nav's --glass-* tokens for the
   same structural reason: custom properties inherit down the tree, and this
   element is no longer in that tree. They are stated once, next to the note
   explaining why, so a change to one is an obvious prompt to change the other. */
.nav-panel{display:none;position:fixed;top:var(--nav-h,63px);left:0;right:0;z-index:39;
  flex-direction:column;padding:2px var(--axis) 12px;
  background:color-mix(in srgb,var(--ink) 50%,transparent);
  -webkit-backdrop-filter:blur(16px);backdrop-filter:blur(16px);
  box-shadow:0 1px 0 var(--edge);
  opacity:0;transform:translateY(-6px);
  transition:opacity .3s ease,transform .3s cubic-bezier(.2,.7,.2,1),
             background-color .45s ease,box-shadow .45s ease}
/* display and opacity are separate steps: display cannot be transitioned, so it
   is flipped first and the fade runs on the frame after. */
.nav-panel.open{display:flex;opacity:1;transform:none}
.nav-panel a{display:block;padding:13px 0;border-bottom:0;border-top:1px solid var(--edge);
  font-size:var(--t-cap);font-weight:var(--w-bold);text-transform:uppercase;
  letter-spacing:.14em;color:var(--on-ink);text-decoration:none}
.nav-panel a:first-child{border-top:0}
.nav-panel a:hover{color:var(--signal)}
/* judged on its own bottom edge, so it can be dark while the bar above is light */
.nav-panel.on-dark{background:color-mix(in srgb,var(--on-ink) 52%,transparent);
  box-shadow:0 1px 0 color-mix(in srgb,var(--ink) 16%,transparent)}
.nav-panel.on-dark a{color:var(--ink);border-top-color:color-mix(in srgb,var(--ink) 20%,transparent)}
.nav-panel.on-dark a:hover{color:var(--pink)}
@media (min-width:761px){ .nav-panel{display:none !important} }

/* The logo is a link home again, with no rule under it in any state.
   Two rules put one there: the global a{} adds border-bottom:1px solid
   currentColor, and .nav a:hover turns the nav's transparent one visible. Both
   have to be answered — killing only the hover leaves a line on tap, because a
   touch device fires :hover on press and holds it there until you tap elsewhere.
   padding-bottom goes too, or the mark sits a pixel high in the bar. */
.nav a.nav-home,.nav a.nav-home:hover,.nav a.nav-home:focus{border-bottom:0;padding-bottom:0;
  display:block;flex:none;line-height:0}
/* Removing the border removes the only focus affordance it had, so give the
   keyboard one back explicitly rather than leaving it invisible. */
.nav a.nav-home:focus-visible{outline:2px solid var(--pink);outline-offset:4px}
.nav.on-dark a.nav-home:focus-visible{outline-color:var(--signal-fill)}


/* ---------- new music, on a desktop ----------
   One group per row: an oversized group number standing on its side down the
   left, then the four cuts in one flat, evenly spaced run. Phones keep what
   they had — three stacked groups, two tiles a line — because two tiles is
   already the full width there.

   The number is set with writing-mode rather than transform:rotate. A rotated
   element keeps its ORIGINAL layout box, so a rotated 148px line still reserves a
   148px-tall, text-width-wide slot and the grid column comes out the wrong shape
   entirely. vertical-rl makes the box genuinely tall and narrow, so the column
   sizes itself to the type; the 180deg turn then flips it from reading downward to
   reading upward, which is the 90deg counter-clockwise being asked for.

   TILE AND NUMERAL ARE ONE DERIVATION, sized to each other and to the 1200px
   floor, then locked as constants (no clamp, no vw — an earlier vw numeral kept
   growing after the tiles capped and ended up dictating the row height):
     - the numeral spans the tile column's FULL stack — from the top of the
       GR1.1 tag text to the bottom of the year tag — not just the artwork;
     - its height in vertical writing is its glyph-advance sum: "GR1" runs
       1.7641px per font px in this face and tracking;
     - the stack is 60.7px of type and gaps (caption row, two column gaps, the
       tag strip — none of it tile-width-dependent) plus the 9:16 artwork, so a
       tile W wide gives a span of 60.7 + W x 16/9;
     - the floor check, measured not assumed: the wrap offers 1099px at a
       1200px viewport; solving column (.78F) + 66.6px grid gap + 4W + three
       12px minimum gaps <= 1099 with F = span/1.7641 lands W at 202 -> span
       419.8 -> font-size 237px = 418.1px, a hair inside the span, 3.5px of
       slack at the floor. space-between spends every wider viewport on the
       three gaps alone.
   Re-derive rather than nudge if the label, the aspect, or the floor changes. */
@media (min-width:1200px){
  .eps-wrap{grid-template-columns:1fr;gap:var(--gap-block);
    /* Bounds how far the run can spread. Sizes are locked; only the space
       BETWEEN the tiles is allowed to breathe, and only this far. */
    max-width:1360px}
  /* auto | 1fr — the number takes only what the type needs, the tile run takes
     the rest. Auto-placement puts both on one row already. */
  .ep{display:grid;grid-template-columns:auto 1fr;
    gap:12px var(--gap-block);align-items:start}
  /* flex-end, then pulled back up by whatever hangs below the year tags, so
     the numeral rests on the tag line rather than the run's raw bottom. */
  .ep-head{align-self:stretch;display:flex;align-items:flex-end;justify-content:center;
    padding-right:6px;padding-bottom:var(--ep-tail,0px)}
  .ep-n{writing-mode:vertical-rl;transform:rotate(180deg);
       font-size:237px;line-height:.78;letter-spacing:-.05em;
       /* TABULAR FIGURES, and this is the actual lock.
          In vertical writing mode the box height is the sum of the glyph
          ADVANCES, so proportional digits made each group a different length at
          the identical font size — "1" is narrower than "2" and "3". Three
          groups, three geometries. Tabular figures give every digit the same
          advance — and the shared G-R prefix is identical anyway — so all three
          numerals are exactly the same height and the alignment holds for all
          of them at once. */
       font-variant-numeric:tabular-nums;
    white-space:nowrap}
  /* Not sides of a record any more: four tiles share one run, evenly spread
     edge to edge. space-between makes the three gaps equal at every width;
     the 12px gap is only the floor they cannot collapse under. */
  .ep-row{flex-wrap:nowrap;justify-content:space-between;gap:12px}
  /* LOCKED, not flexible. flex:1 1 0 let the tiles shrink with the window, which
     changed the row height, which is the thing the numeral is sized against — so
     the type/artwork relationship drifted at every width. Fixed tiles make the
     artwork height constant, which is what lets the numeral be a constant too. */
  .cut{flex:0 0 202px;width:202px;max-width:202px}
}
