:root{
    --bg: #000;
    --bright: #f4f4f3;      /* headings / max contrast */
    --fg: #d0d1d0;          /* body text */
    --muted: #9b9b9b;       /* secondary text */
    --faint: #8a8a8a;       /* tertiary labels, tags, timestamps (AA on black) */
    --line: rgba(255,255,255,0.14);     /* hairline dividers / borders */
    --line-soft: rgba(255,255,255,0.07);
    --display: 'Space Grotesk', 'Courier New', monospace;
    --mono: 'Space Mono', 'Courier New', monospace;
    --ease: cubic-bezier(.22, 1, .36, 1);
    --radius: 2px;
    --tracking-display: 0.2em;   /* section titles / small caps headings */
    --tracking-label: 0.08em;    /* buttons, tags, card names, small caps chrome */
    --tracking-wordmark: -0.02em; /* the name itself: heavy, tight, anonymous */

    /* ---- Vertical rhythm ----
       Two values, because the page had none. Measured before this existed, the
       gap between a section heading and the thing it labels ran -8px, 8px,
       18px, 18px, 18px and 56px on one screen: six different answers to one
       question, so the page read as a stack of separate blocks rather than one
       composition. A decorative <hr> was doing the dividing instead, which is
       the chrome that gets deleted once space is allowed to do the job.

       --heading-air is the only gap under a section heading anywhere on the
       site. --section-air is the only gap between sections. Sections carry it
       on the BOTTOM only: <main> is a flex column, so margins there do not
       collapse, and the symmetric `margin: 4rem 0` this replaces was quietly
       paying twice — 128px between two sections that had asked for 64. If a
       new block needs a third value, the block is wrong. */
    --section-air: clamp(4rem, 7vw, 5.5rem);
    --heading-air: 2.75rem;

    /* ---- Type scale ----
       Five steps and two display clamps, and nothing else. The site used to
       carry seventeen sizes between 0.68rem and 1.1rem — 0.72, 0.74, 0.76,
       0.78, 0.80, 0.82, 0.84, 0.85, 0.88, 0.90, 0.92, 0.95, 1.02, 1.05, 1.10.
       Nobody can see 0.84 against 0.85; a scale that fine reads as unconsidered
       rather than restrained. Every rem font-size on the site resolves to one
       of these. If a new element does not fit a step, it is the element that is
       wrong, not the scale. Add a size only with a reason written here.

       On RANGE, which is the reason there are five steps and not four.
       Measured against goyard.com and rickowens.eu, both of which read as
       expensive: Goyard's median on-screen text is 12px across a 9px-to-32px
       spread, Rick Owens sets body at 10px. This site's four steps were all
       bunched between 11.5px and 16.8px, a 1.5x spread, which is what made it
       read as even rather than composed. Micro drops and headings rise: the
       spread is 1.9x now. The editorial contrast between a tiny tracked label
       and a large heading is doing work no single size can do.

       Worth being clear that this REVISES the note that stood here before,
       which argued headings and prose could share a step because uppercase at
       0.2em already reads larger. That is still true. It was just solving the
       wrong problem: the issue was never heading-versus-prose, it was that the
       whole scale occupied too little range to have a top or a bottom.

       --t-micro is for LABELS, not for anything anyone has to read. Fine print
       that carries real information (what is in a sample pack, the licence,
       the privacy notices) sits at --t-chrome deliberately: it was set at
       label size, which quietly worked against the decision to state those
       things plainly in the first place. */
    --t-micro:  0.66rem;  /* ~10.6px: tags, timestamps, status, legal, chrome labels */
    --t-chrome: 0.82rem;  /* ~13.1px: buttons, CTAs, form fields, readable fine print */
    --t-nav:    0.92rem;  /* ~14.7px: wordmarks, card titles, sub-copy */
    --t-lead:   1.05rem;  /* ~16.8px: reading prose, product and release names */
    --t-head:   1.25rem;  /* ~20px:   section headings, open-menu links */
}
html{
    /* Native form controls, scrollbars and UA chrome render dark to match the site */
    color-scheme: dark;
}
@media (prefers-reduced-motion: no-preference){
    html{
        scroll-behavior: smooth;
    }
}

/* Keyboard users get a way past the sticky header; invisible otherwise */
.skip-link{
    position: fixed;
    top: 0.6rem;
    left: 0.6rem;
    z-index: 200;
    transform: translateY(-250%);
    background: #000;
    color: var(--bright);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0.7rem 1.1rem;
    font-family: var(--mono);
    font-size: var(--t-micro);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    text-decoration: none;
}
.skip-link:focus-visible{
    transform: none;
}
*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: var(--mono);
    color: var(--fg);
}
body{
    background: var(--bg);
    /* Light type on a black ground reads lighter than it is and needs more
       room than the 1.2 the UA default was giving every paragraph on the
       site. Headings set their own tighter leading. */
    line-height: 1.6;
}
h1,h2,h3,.title,.brand,.shop-card-name{
    line-height: 1.2;
}

*::selection{
    background: rgba(244,244,243,0.22);
    color: #ffffff;
}

/* Remove the 300ms double-tap delay on touch devices */
a, button, .card, .shop-card, .sp-play, .musicvideo-lite, .sp-wave{
    touch-action: manipulation;
}

/* Visible keyboard focus for all interactive elements (mouse clicks stay clean) */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
.card:focus-visible,
.shop-card:focus-visible,
.musicvideo-lite:focus-visible,
.sp-play:focus-visible{
    outline: 2px solid var(--bright);
    outline-offset: 3px;
    border-radius: var(--radius);
}

/* The legacy `.hero` / `.hero-bg` lockup lived here for a page that no longer
   exists. 404.html is the only surface still using that treatment and it
   carries its own copy inline, so nothing here referenced it any more.
   Removed along with the `translateY(-50%)` that used to ride on every `h1`:
   that rule was written for the hero and was silently pulling the h1 up half
   its own height on privacy, terms, studio, get and sample-packs, none of
   which reset it. Two separate cancellation blocks existed downstream purely
   to undo it. Do not reintroduce a page-specific transform on a bare `h1`. */
/* Tracking is size-specific: letters read further apart as type grows, so a
   value that is right for a 1.1rem small-caps heading is wrong at 4.25rem.
   The 0.2em display tracking is a deliberate brand system for SECTION
   headings (see BRAND.md) and stays on those. A page-title h1 scaling to
   68px gets tight tracking and tight leading instead. */
h1{
    font-family: var(--display);
    font-weight: 500;
    color: var(--bright);
    font-size: clamp(1.4rem, 8vw, 4.25rem);
    letter-spacing: var(--tracking-wordmark);
    line-height: 1.05;
    text-wrap: balance;
}
main{
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-evenly;
    position: relative;
    overflow: hidden;
}
.card{
    width: 100%;
    max-width: 600px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 1rem;
    border-bottom: 1px solid var(--line);
    transition: background-color 300ms var(--ease);
    text-decoration: none;
}

/* Release & preview covers: monochrome at rest, full color on hover.

   On the 600ms, which every grayscale release on the site now shares.
   goyard.com carries a 1s ease; the site's colour releases were at 300-350ms.
   Expensive reads as unhurried, and a reveal is the one thing here that can
   afford to take its time — it is scenery, not an answer to an input.

   The distinction that matters, and the reason this is not a global slowdown:
   FEEDBACK stays fast and REVEALS get slow. Hover colour, button and border
   states are still 150-200ms, because those are the site answering a finger
   and latency there just reads as lag. The transform on the covers stays at
   350ms for the same reason. Only the colour bleeding back in is slow. */
.card img{
    width: 5rem;
    filter: grayscale(1);
    transition: filter 600ms var(--ease);
}
.card:hover img{
    filter: grayscale(0);
}

.releases{
    width: 100%;
    background: #000;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: space-around;
}

.releases h2,
.emailsignup h2{
    font-family: var(--display);
    font-weight: 500;
    letter-spacing: var(--tracking-display);
    text-transform: uppercase;
    margin: 0 0 var(--heading-air);
    color: var(--bright);
    font-size: var(--t-head);
}

/* The only <hr> left on the site sits on the three legal pages, between the
   document masthead (title + effective date) and the body. That is a rule
   separating two different kinds of content, which is structural. The ten that
   sat directly under a section heading were decoration and are gone: space is
   the divider now. Do not reintroduce one under a heading. It also carried no
   margin at all, because the global reset zeroes it and nothing put it back. */
hr{
    max-width: 600px;
    width: 100%;
    border: 0;
    border-top: 1px solid var(--line);
    margin-block: 1.5rem 2rem;
}

/* OUT NOW: one large featured cover */
.presave{
    margin-bottom: var(--section-air);
}
.presave .card{
    flex-direction: column-reverse;
    align-items: center;
    gap: 0.9rem;
    border: 0;
    padding: 0;
    max-width: 380px;
    margin: 0 auto;
    text-decoration: none;
}
.presave .card img{ width: 100%; height: auto; aspect-ratio: 1; object-fit: cover; }
.presave .card .title{ font-size: var(--t-lead); }

.title{
    font-family: var(--mono);
    letter-spacing: var(--tracking-label);
    font-weight: 400;
}

.card:hover{
    background: #181818b2;
    cursor: pointer;
}

h2,.title{
    text-align: center;
    text-wrap: balance;
}

section{
    margin: 0 0 var(--section-air);
    /* Must clear the sticky header or every in-page nav jump lands with the
       heading tucked underneath it. The header measures 52px wide-screen and
       65px once the wordmark wraps, so this tracks the taller case. */
    scroll-margin-top: 5rem;
}

/* RELEASES: covers float on black in a clean grid, no dividers */
#releases{
  --releases-gap: 2.4rem;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  gap: var(--releases-gap) 1.8rem;
  width: 100%;
  max-width: 1000px;
  margin: 0 auto;
  padding: 0 1rem;
  align-items: start;
}
/* The heading is a grid item, so the row gap below it is already paid once.
   Subtract it so this heading sits at exactly --heading-air like every other
   one, instead of the 56px it used to get for being in a grid. */
#releases h2{
  grid-column: 1 / -1;
  margin-bottom: calc(var(--heading-air) - var(--releases-gap));
}
#releases .card{
  flex-direction: column-reverse;
  align-items: stretch;
  gap: 0.7rem;
  padding: 0;
  border: 0;
  max-width: none;
}
#releases .card img{
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  object-fit: cover;
  transition: filter 600ms var(--ease), transform 350ms var(--ease);
}
#releases .card .title{ text-align: center; font-size: var(--t-chrome); }
#releases .card:hover{ background: transparent; }
#releases .card:hover img{ transform: scale(1.03); }

/* On a phone, minmax(200px) resolves to ONE column, so seven covers became
   seven full-width screens and pushed the shop to 5.4 screens down and the
   booking section to 8. Nearly all traffic here is a phone arriving from the
   Instagram bio link. Two columns keeps the catalogue complete and in the same
   artist-first position, and simply stops it running the page long: covers at
   ~160px are still bigger than they are in any streaming app.
   This changes layout only. Do not use it to move sections; order lives in the
   HTML by decision (see the comment above <main> in index.html). */
@media (max-width: 560px){
  #releases{
    --releases-gap: 1.5rem;
    grid-template-columns: repeat(2, 1fr);
    gap: var(--releases-gap) 1rem;
    padding: 0 1rem;
  }
  #releases .card .title{ font-size: var(--t-micro); }
}

.socials{
    height: 3rem;
    display: flex;
    align-items: center;
    justify-content: space-evenly;
    width: 100%;
    max-width: 300px;
    margin-bottom: 3rem;
    border-radius: var(--radius);
    background: transparent;
    padding: 0.5rem;
    border: 1px solid var(--line);
}

.socials.listen{
    margin-bottom: 0;
    margin-top: 2rem;
}


.socials a{
    height: 100%;
}
.socials a svg{
    fill: var(--bright) !important;
    height: 100%;
    aspect-ratio: 1/1;
    width: auto;
}

/* Streaming service logos: quiet & monochrome, color on hover */
.socials.listen img{
    filter: grayscale(1);
    opacity: 0.85;
    transition: filter 600ms var(--ease), opacity 600ms var(--ease);
}
.socials.listen a:hover img{
    filter: grayscale(0);
    opacity: 1;
}

.m-v-cont{
    aspect-ratio: 2/1 !important;
    width: 100%;
    max-width: 800px;
    margin: 0 auto;
    padding: 0 0.5rem;
}

.musicvideo{
    width: 100%;
    height: 100%;
    margin-top: 1rem;
    border-radius: var(--radius);
}

.musicvideo-lite{
    width: 100%;
    height: 100%;
    margin-top: 1rem;
    border-radius: var(--radius);
    border: 1px solid #222;
    background-color: #050505;
    background-position: center;
    background-size: cover;
    background-repeat: no-repeat;
    position: relative;
    overflow: hidden;
    padding: 0;
    display: grid;
    place-items: center;
    filter: grayscale(1);
    transition: transform 200ms var(--ease), border-color 200ms var(--ease), filter 600ms var(--ease);
}

.musicvideo-lite::before{
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(180deg, rgba(0,0,0,0.18), rgba(0,0,0,0.5));
}

.video-title{
    position: absolute;
    top: 0.7rem;
    left: 0.75rem;
    z-index: 2;
    font-size: var(--t-nav);
    letter-spacing: var(--tracking-label);
    color: var(--bright);
    text-shadow: 0 1px 2px rgba(0,0,0,0.8);
    background: rgba(0,0,0,0.45);
    padding: 0.22rem 0.45rem;
    border-radius: var(--radius);
}

.musicvideo-lite:hover,
.musicvideo-lite:focus-visible{
    transform: translateY(-2px);
    border-color: #333;
    filter: grayscale(0);
}

.youtube-play{
    width: 3.4rem;
    height: 3.4rem;
    border-radius: 50%;
    background: transparent;
    border: 1.5px solid rgba(255,255,255,0.92);
    position: relative;
    z-index: 2;
    transition: transform 180ms var(--ease), background-color 180ms var(--ease);
}

.musicvideo-lite:hover .youtube-play,
.musicvideo-lite:focus-visible .youtube-play{
    transform: scale(1.05);
    background: #fff;
}
.musicvideo-lite:hover .youtube-play-triangle,
.musicvideo-lite:focus-visible .youtube-play-triangle{
    border-left-color: #000;
}

.youtube-play-triangle{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-42%, -50%);
    width: 0;
    height: 0;
    border-top: 0.56rem solid transparent;
    border-bottom: 0.56rem solid transparent;
    border-left: 0.9rem solid #fff;
}

/* Both video sections must claim the full width. `main` is a centred flex
   column, so a section without an explicit width shrinks to fit and its
   `width:100%` child collapses with it. #featuredVideo was missing from this
   rule and was rendering the featured performance at 72x36px. */
#musicVideos,
#featuredVideo{
    width: 100%;
}

.socials img{
    height: 100%;
    width: auto;
    min-width: 0;
    flex: 1;
    border-radius: var(--radius);
}

.emailsignup{
    width: 100%;
    max-width: 800px;
}

.emailsignup iframe{
    width: 100%;
}

/* The signup heading and its sub-line used to be a second, ID-scoped copy of
   the section-heading rule with their own margins, including a 1.5rem left
   indent on a heading that `h2,.title` centres — so the one heading on the
   page that was not visually centred was the one nobody had noticed. It uses
   the shared rule now (see `.releases h2, .emailsignup h2`). */
.signup-sub{
    color: var(--muted);
    font-size: var(--t-nav);
    line-height: 1.6;
    margin: 0;
}

.events{
    margin: 2rem auto;
}

.text{
    flex: 1;
    align-items: center;
    justify-content: space-evenly;
    display: flex;
    flex-direction: column;
    text-align: center;
    margin-left: 1rem;
    height: 100%;
}

@media screen and (max-width: 340px){
    .row p{
        font-size: var(--t-chrome);
    }
}

/* The event-poster lightbox (.posterCard / .poster / #openPoster / #closePoster
   and the .text block) was removed. The poster cards that called openPoster()
   were taken off the page with the events section; the modal markup, its CSS
   and its JS were left behind as an orphan that nothing could open. */

button{
    background: #000;
    padding: 0.2rem 0.4rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    cursor: pointer;
}

/* Header / Navigation styles */
.site-header{
    position: sticky;
    top: 0;
    width: 100%;
    z-index: 50;
    background: linear-gradient(180deg, rgba(0,0,0,0.65), rgba(0,0,0,0.35));
    backdrop-filter: blur(6px);
    border-bottom: 1px solid var(--line-soft);
}
.site-header .header-inner{
    max-width: 1100px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.6rem 1rem;
    --nav-offset: calc(3.2rem + 1.2rem);
}
/* Identity lockup: sigil leads, wordmark supports (heavy, tight, anonymous) */
.brand{
    font-family: var(--display);
    color: var(--bright);
    text-decoration: none;
    font-weight: 700;
    letter-spacing: var(--tracking-wordmark);
    text-transform: uppercase;
    font-size: var(--t-nav);
    display: inline-flex;
    align-items: center;
    gap: 0.55rem;
}
.brand-sigil{
    width: 1.5rem;
    height: 1.5rem;
    flex: 0 0 auto;
    object-fit: contain;
}
/* A live local-time stamp, the one idea taken from blonded.co. It is pure
   atmosphere: it says the page is awake without saying anything. The
   visitor's own timezone, so it needs no label and cannot be misread.
   Updates on the minute, never per second — a counter ticking in the corner
   of a sticky header is a distraction, and WCAG 2.2.2 territory besides.
   Renders nothing at all with scripting off (the element ships `hidden`)
   rather than leaving a gap where a time should be. */
.site-clock{
    font-family: var(--mono);
    font-size: var(--t-micro);
    letter-spacing: var(--tracking-label);
    color: var(--faint);
    /* Fixed-width digits, so the header does not twitch as the minute rolls */
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    /* It yields before the wordmark and the nav ever do */
    flex: 0 1 auto;
    overflow: hidden;
}
.site-clock[hidden]{ display: none; }
/* The full stamp, the wordmark and the nav control used to touch with no gap
   at all below ~420px and the wordmark wrapped to two lines. The date is the
   part nobody came for, so it is the part that goes; the time survives and
   the header stays on one line down to 320px.

   The threshold moved 419 -> 385 when --t-micro dropped to 0.66rem, because a
   smaller stamp simply needs less room. Measured with the date forced on:
   320 and 360 still wrap the wordmark, 375 clears it by only 4px, 390 clears
   it by 12px. So 390-class phones — which is most of them now — get the whole
   date back, and only genuinely narrow screens lose it. */
@media (max-width: 385px){
    .site-clock .clock-date{ display: none; }
}
/* The Store dropdown (~90 lines of CSS and ~70 of JS) was removed: neither
   #storeToggle nor #storeDropdown has existed in any page's markup for some
   time, so all of it was dead. Shop is a plain nav link now. */

/* ---- The whole nav sits behind one word, at every width ----
   Taken from rickowens.eu, where an entire catalogue lives behind MENU.
   The header used to run two different navigations: a row of six links
   above 721px, a panel below it. It is one control now at every size, so a
   laptop behaves the way the phone does — and the phone is where nearly all
   of this site's traffic arrives from.

   The word rather than three lines, because it says what it does. The
   visible label IS the button's accessible name, so it stays truthful when
   it flips to Close; the old static aria-label="Toggle navigation" said the
   same thing in both states. */
.nav-toggle{
    background: transparent;
    border: 0;
    padding: 0.4rem 0.2rem;
    cursor: pointer;
    font-family: var(--mono);
    font-size: var(--t-chrome);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--fg);
    line-height: 1;
    position: relative;
    transition: color 180ms var(--ease);
    /* Menu and Close are different lengths. A floor on the inline size stops
       the header twitching as the label swaps. */
    display: inline-flex;
    justify-content: flex-end;
    min-width: 4.4rem;
}
.nav-toggle:hover{ color: var(--bright); }
/* The panel is a SIBLING of these inside .site-header, so its z-index would
   otherwise paint over the very button used to shut it. The mark, the clock
   and the control all stay above the open panel — you can always see where
   you are and how to get out. */
.site-header .brand,
.site-header .site-clock,
.site-header .nav-toggle{
    position: relative;
    z-index: 41;
}
.nav-toggle-close{ display: none; }
.nav-toggle[aria-expanded="true"] .nav-toggle-open{ display: none; }
.nav-toggle[aria-expanded="true"] .nav-toggle-close{ display: inline; }

/* The panel. `visibility: hidden` is load-bearing: it takes the links out of
   the tab order while closed, which clip-path alone does not. Sits below the
   header's z-index so the wordmark and the Close control stay on top of it. */
.main-nav{
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    width: 100%;
    z-index: 40;
    /* Solid, not the 0.98 this carried as a phone menu. Over a black page 2%
       transmission does not read as depth, it reads as a ghost of the
       wordmark sitting behind the links. */
    background: #000;
    /* Full height, so the open panel is the screen rather than a card that
       stops halfway down and lets the page resume underneath it. dvh so the
       mobile URL bar cannot leave a strip of page showing at the bottom. */
    height: 100vh;
    height: 100dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    clip-path: inset(0 0 100% 0);
    visibility: hidden;
    transition: clip-path 300ms cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 300ms;
}
.main-nav.open{
    visibility: visible;
    clip-path: inset(0 0 0% 0);
    transition: clip-path 300ms cubic-bezier(0.4, 0, 0.2, 1), visibility 0s linear 0s;
}
.main-nav .nav-links{
    list-style: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 0;
    /* Top padding clears the header the panel opens underneath */
    padding: 5rem 1rem 2rem;
}
.main-nav .nav-links > li{ width: 100%; }
.main-nav .nav-links a{
    display: block;
    width: 100%;
    max-width: 34rem;
    margin: 0 auto;
    padding: 1rem 1.5rem;
    text-align: center;
    border-bottom: 1px solid var(--line-soft);
    color: var(--fg);
    text-decoration: none;
    font-family: var(--mono);
    font-size: var(--t-nav);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    line-height: 1.2;
    transition: color 180ms var(--ease), background-color 180ms var(--ease);
}
.main-nav .nav-links a:hover{
    color: var(--bright);
    background: rgba(255,255,255,0.04);
}
/* Given a whole screen, the links take the room: heading scale and the
   display tracking, so an open panel reads as a page of its own rather than
   a phone menu that wandered onto a monitor. */
@media (min-width: 721px){
    .main-nav .nav-links{ padding: 7rem 1rem 4rem; }
    .main-nav .nav-links a{
        font-size: var(--t-head);
        letter-spacing: var(--tracking-display);
        padding: 1.25rem 1.5rem;
    }
}

/* ---- One button language: primary (solid) + secondary (outline) ----
   Sized at 0.82rem (~13px), not the 0.72rem (~11.5px) this group used to
   carry. These are the buy, book and download actions; uppercase mono at
   11.5px is label size, and it was being asked to do the work of a CTA.
   Metadata (prices, tags, fine print) deliberately stays small. */
.btn,
.sp-buy,
.store-btn,
.lessons-cta{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    text-decoration: none;
    font-family: var(--mono);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    font-size: var(--t-chrome);
    line-height: 1;
    padding: 0.85rem 1.5rem;
    border-radius: var(--radius);
    border: 1px solid var(--line);
    background: transparent;
    color: var(--fg);
    transition: background-color 200ms var(--ease), color 200ms var(--ease), border-color 200ms var(--ease);
    white-space: nowrap;
}
/* Secondary (outline) hover */
.store-btn:hover,
.lessons-cta:hover{
    border-color: var(--bright);
    color: var(--bright);
    background: rgba(255,255,255,0.04);
}
/* Primary (solid white) */
.sp-buy,
.store-btn-primary{
    background: #fff;
    color: #000;
    border-color: #fff;
}
/* Primary hover: invert to outline */
.sp-buy:hover,
.store-btn-primary:hover{
    background: transparent;
    color: var(--bright);
    border-color: var(--bright);
}

/* Sample pack previews */
.sp-previews .sp-sub{
    text-align: center;
    color: var(--faint);
    font-size: var(--t-chrome);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    /* Zero on top: the air under a heading is --heading-air's job, and a
       leading margin here quietly added to it. */
    margin: 0 0 1.4rem;
}
.sp-pack{
    width: 100%;
    max-width: 600px;
    margin: 0 auto 1.6rem;
}
.sp-phead{
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.8rem;
    border-bottom: 1px solid var(--line);
    padding: 0 0.25rem 0.6rem;
    margin-bottom: 0.3rem;
}
.sp-pname{
    color: var(--bright);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    font-size: var(--t-nav);
}
/* The pack names are h2s so the page has an outline below its single h1, but
   visually they stay the phead labels they always were. Without this override
   the `.releases h2` section-heading rule would inflate and centre them. */
.releases h2.sp-pname{
    font-family: var(--mono);
    font-weight: 400;
    font-size: var(--t-nav);
    letter-spacing: var(--tracking-label);
    margin: 0;
    text-align: left;
}

/* Buyer questions under the packs. Native <details> so it works with
   scripting off; the default disclosure triangle is replaced by a +/– that
   matches the mono chrome. */
.sp-faq{
    width: 100%;
    max-width: 600px;
    margin: 2.2rem auto 0;
}
.sp-faq h2{
    font-size: var(--t-nav);
    margin-bottom: 0.4rem;
}
.sp-faq details{
    border-bottom: 1px solid var(--line-soft);
}
.sp-faq summary{
    list-style: none;
    cursor: pointer;
    padding: 0.85rem 0.25rem;
    color: var(--fg);
    font-size: var(--t-chrome);
    letter-spacing: var(--tracking-label);
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 1rem;
}
.sp-faq summary::-webkit-details-marker{ display: none; }
.sp-faq summary::after{
    content: "+";
    color: var(--faint);
    flex: 0 0 auto;
}
.sp-faq details[open] summary::after{ content: "\2013"; }
.sp-faq summary:focus-visible{
    outline: 2px solid var(--bright);
    outline-offset: 2px;
}
.sp-faq details p{
    margin: 0;
    padding: 0 0.25rem 0.95rem;
    color: var(--faint);
    font-size: var(--t-chrome);
    line-height: 1.65;
}
.sp-buy{
    font-size: var(--t-chrome);
    padding: 0.6rem 1rem;
    letter-spacing: var(--tracking-label);
}
/* A price is always set smaller than the copy around it. Physical size reads as
   magnitude, so a big number gets judged as a bigger number. The word carries
   the weight, not the amount. Kept fully legible on purpose: a price someone
   has to hunt for becomes a refund. Applies site-wide, not just here. */
.sp-price{
    font-size: 0.82em;
    opacity: 0.85;
    margin-left: 0.4em;
}
/* What is actually in the pack. The page had previews and a price and never
   said what a buyer gets, so the two volumes looked identical at $40 when they
   are not: Vol. 1 ships mic stems and one-shots, Vol. 2 is loops only. Stating
   it plainly is the point, including what it is NOT. */
.sp-specs{
    margin: 0 0.25rem 0.4rem;
    padding: 0.55rem 0 0.7rem;
    color: var(--faint);
    font-size: var(--t-chrome);
    line-height: 1.65;
}
.sp-specs b{
    color: var(--muted);
    font-weight: 400;
}
/* Licence + guarantee, stated once at the top rather than repeated per pack. */
.sp-terms{
    max-width: 600px;
    margin: -0.6rem auto 1.8rem;
    padding: 0 0.25rem;
    text-align: center;
    color: var(--faint);
    font-size: var(--t-chrome);
    line-height: 1.7;
}

.sp-specs .sp-not,
.sp-terms .sp-not{
    display: block;
    margin-top: 0.3rem;
    color: var(--faint);
    opacity: 0.85;
}

.sp-row{
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 0.85rem 0.25rem;
    border-bottom: 1px solid var(--line-soft);
}
.sp-play{
    flex: 0 0 auto;
    width: 2.75rem;
    height: 2.75rem;
    border-radius: 50%;
    border: 1px solid var(--line);
    background: #0c0c0c;
    color: var(--bright);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0;
    transition: background-color 150ms var(--ease), color 150ms var(--ease), border-color 150ms var(--ease);
}
.sp-play:hover{ background: #fff; color: #000; border-color: #fff; }
.sp-play svg{ width: 1.05rem; height: 1.05rem; fill: currentColor; }
.sp-ic-pause{ display: none; }
.sp-row.sp-playing .sp-ic-play{ display: none; }
.sp-row.sp-playing .sp-ic-pause{ display: block; }
.sp-row.sp-playing .sp-play{ background: #fff; color: #000; border-color: #fff; }
.sp-meta{ flex: 1 1 auto; min-width: 0; }
.sp-tt{ display: flex; align-items: baseline; gap: 0.7rem; margin-bottom: 0.5rem; }
.sp-name{ color: var(--fg); letter-spacing: var(--tracking-label); text-transform: uppercase; font-size: var(--t-chrome); }
.sp-tag{ color: var(--faint); font-size: var(--t-micro); letter-spacing: var(--tracking-label); border: 1px solid var(--line-soft); padding: 0.1rem 0.4rem; border-radius: var(--radius); }
.sp-wave{
    position: relative;
    height: 2.7rem;
    cursor: pointer;
    -webkit-mask-size: 100% 100%;
    mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
}
.sp-base,.sp-prog{ position: absolute; inset: 0; height: 100%; }
.sp-base{ background: #2a2a2a; width: 100%; }
.sp-prog{ background: var(--bright); width: 0%; }
.sp-time{ flex: 0 0 auto; color: var(--faint); font-size: var(--t-micro); letter-spacing: var(--tracking-label); width: 2rem; text-align: right; }
/* Buy line under a pack's preview rows: the clips make the case, then the
   button. Wraps on narrow screens so the button drops below the copy instead
   of crushing it. */
.sp-cta{
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.8rem 1.2rem;
    margin: 1rem 0.25rem 0;
    color: var(--faint);
    font-size: var(--t-chrome);
    line-height: 1.65;
}
.sp-cta span{ flex: 1 1 16rem; }
.sp-cta .sp-buy{ flex: 0 0 auto; }

/* Store buttons. The .store-grid wrapper was dead: the only page using
   .store-btn (studio/index.html) supplies its own containers.
   `flex: 1 1 8rem` used to put the 8rem on the flex MAIN axis, which is only
   the width in a row. studio/index.html has one row container and one column
   container, and in the column that 8rem became a 128px-tall button. The size
   floor belongs on the inline axis, so it is a min-width now: identical in the
   row, correct in the column. */
.store-btn{
    flex: 1 1 auto;
    min-width: 8rem;
}
.store-btn-primary{
    flex-basis: 100%;
}

.sponsor-logos{
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 2rem;
    flex-wrap: wrap;
    padding: 0.5rem 1rem;
    margin-bottom: 1rem;
    margin-top: 2rem;
}

.sponsor-logos img{
    height: 15px;
    width: auto;
    object-fit: contain;
    opacity: .4;
    filter: grayscale(1);
    transition: opacity .2s var(--ease);
    display: block;
}

/* The logos link through to Porter's artist page on each brand's own site.
   The link is the whole affordance: no underline, no icon, no caption. It
   brightens exactly the way the bare logo already did on hover, so the row
   reads as it always did and only rewards the people who try it. */
.sponsor-logos a{
    position: relative;
    display: block;
    line-height: 0;
}
.sponsor-logos a::after{
    /* 44x44 hit area, centred, without touching the 13-15px logo itself. */
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 44px;
    height: 44px;
}
/* Only the linked logos brighten. The bare 64 Audio mark (no artist page to
   point at) used to brighten too, advertising a click that did nothing. */
.sponsor-logos a:hover img,
.sponsor-logos a:focus-visible img{
    opacity: .75;
}

.sponsor-logos .invert{
    filter: grayscale(1) invert(1);
}

@media(max-width:600px){
.sponsor-logos{
    gap: 1.4rem;
}

.sponsor-logos img{
    height: 13px;
}
}

/* ---- Lessons section ---- */
/* Prose gets the grotesque, not the mono. Mono is the site's chrome voice:
   labels, prices, nav, buttons, fine print. It is not a reading face, and
   these are the two or three places on the site where a visitor reads an
   actual sentence to be persuaded. Everything else stays mono, so the
   Space Mono character of the site is unchanged. */
.lessons-copy,
.signup-sub,
.slots-note{
  font-family: var(--display);
  font-weight: 400;
  letter-spacing: 0;
  text-wrap: pretty;
}
.lessons-copy{
  max-width: 600px;
  margin: 0 auto 1.5rem;
  padding: 0 1.2rem;
  text-align: center;
  line-height: 1.7;
  font-size: var(--t-lead);
  color: var(--muted);
}
/* Inherits the shared secondary-button language (sharp 2px corners, hairline
   border, uppercase mono) from the .btn group above, so it stays consistent
   with the store buttons instead of being the site's only pill. */
.lessons-cta{ text-decoration: none; }

/* ---- Book a lesson: Cal.com embed ---- */
.lesson-price{
    font-size: var(--t-chrome);
    color: var(--faint);
    letter-spacing: var(--tracking-label);
    white-space: nowrap;
}
.cal-embed{
    width: 100%;
    max-width: 760px;
    margin: 1.4rem auto 0;
    padding: 0 1rem;
    min-height: 40px;
}
.slots-note{
    max-width: 600px;
    margin: 1.1rem auto 0;
    padding: 0 1.2rem;
    text-align: center;
    color: var(--muted);
    font-size: var(--t-nav);
    line-height: 1.6;
}
.slots-note a{ color: var(--fg); text-decoration: underline; text-underline-offset: 2px; }
/* ---- end book a lesson ---- */

/* ---- Full-bleed graded photo breaks ---- */
.photo-break{
    position: relative;
    width: 100%;
    height: 72vh;
    max-height: 720px;
    margin: 0 0 var(--section-air);
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.photo-break::after{
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(180deg, #000 0%, transparent 16%, transparent 84%, #000 100%);
}
.photo-break.break-kit{ background-image: url(/images/motion-still.webp); background-position: center 35%; }

/* Motion break: live frame-diff render of a performance clip. The static
   break-kit photo stays as the base layer and remains the fallback for
   reduced-motion users and any browser where the effect can't run. */
.photo-break.break-kit{ position: relative; overflow: hidden; }
#motionCanvas{
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    background: #000;
    opacity: 0;
    transition: opacity 900ms var(--ease);
}
.photo-break.motion-live #motionCanvas{ opacity: 1; }
#motionVideo{ display: none; }
/* ---- end photo breaks ---- */

/* ---- Sample pack covers ---- */
.sp-cover{
    width: 3.4rem;
    height: 3.4rem;
    flex: 0 0 auto;
    background-size: cover;
    background-position: center;
    border-radius: var(--radius);
    filter: grayscale(1);
}
.sp-phead .sp-pname{ flex: 1 1 auto; }
/* ---- end sample pack covers ---- */


/* ---- Contact modal + form ---- */
.contact-modal{
    display: none;
    position: fixed;
    inset: 0;
    z-index: 100;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: rgba(0,0,0,0.72);
    backdrop-filter: blur(6px);
    overscroll-behavior: contain;
}
.contact-modal.open{ display: flex; }
.contact-card{
    position: relative;
    width: 100%;
    max-width: 460px;
    background: #0a0a0a;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 2.2rem 1.6rem 1.6rem;
    animation: contactIn 200ms var(--ease);
}
@keyframes contactIn{ from{ opacity: 0; transform: translateY(10px); } to{ opacity: 1; transform: none; } }
.contact-title{
    font-family: var(--display);
    font-weight: 500;
    letter-spacing: var(--tracking-display);
    text-transform: uppercase;
    color: var(--bright);
    font-size: var(--t-head);
    text-align: center;
    margin-bottom: 1.4rem;
}
.contact-form{ display: flex; flex-direction: column; gap: 0.7rem; }
.sr-only{
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0,0,0,0);
    white-space: nowrap;
    border: 0;
}
.contact-form input,
.contact-form textarea{
    width: 100%;
    background: transparent;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    color: var(--fg);
    font-family: var(--mono);
    font-size: var(--t-chrome);
    padding: 0.7rem 0.8rem;
}
.contact-form textarea{ resize: vertical; min-height: 6rem; }
.contact-form input::placeholder,
.contact-form textarea::placeholder{
    color: var(--faint);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    font-size: var(--t-micro);
}
.contact-form input:focus,
.contact-form textarea:focus{
    outline: none;
    border-color: var(--bright);
}
.contact-hp{ position: absolute; left: -9999px; width: 1px; height: 1px; opacity: 0; }
.contact-submit{
    margin-top: 0.3rem;
    font-family: var(--mono);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    font-size: var(--t-chrome);
    background: #fff;
    color: #000;
    border: 1px solid #fff;
    border-radius: var(--radius);
    padding: 0.85rem 1.5rem;
    cursor: pointer;
    transition: background-color 200ms var(--ease), color 200ms var(--ease), border-color 200ms var(--ease);
}
.contact-submit:hover{ background: transparent; color: var(--bright); border-color: var(--bright); }
.contact-submit:disabled{ opacity: 0.5; cursor: default; }
.contact-status{
    text-align: center;
    font-size: var(--t-micro);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--muted);
    min-height: 1rem;
}
.contact-close{
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    width: 2.2rem;
    height: 2.2rem;
    padding: 0.45rem;
    background: transparent;
    border: 0;
    cursor: pointer;
}
.contact-close svg{ width: 100%; height: 100%; fill: var(--fg); transition: fill 150ms var(--ease); }
.contact-close:hover svg{ fill: var(--bright); }
/* ---- end contact modal ---- */

/* ---- Shop-forward homepage ---- */
.home-intro{
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: 2rem 1rem 0.75rem;
}
.home-wordmark{
    font-family: var(--display);
    font-weight: 700;
    color: var(--bright);
    text-transform: uppercase;
    letter-spacing: var(--tracking-wordmark);
    font-size: clamp(1.9rem, 7.4vw, 3.6rem);
    /* cancel the global h1 hero offset so the sigil above it doesn't collide */
    position: static;
    transform: none;
}
/* Hero lockup: sigil above the name */
.home-sigil{
    width: clamp(3.6rem, 12vw, 5.4rem);
    height: auto;
    display: block;
    margin: 0 auto 1.6rem;
}

/* Home section order lives in the HTML, not here.
   These rules used flex `order` to show shop and signup before the music, which
   left the document and keyboard order telling a different story than the page.
   The sections are now in visual order in index.html; only the spacing those
   rules also carried survives. Do not reintroduce `order` on these: it desyncs
   Tab order from what a sighted visitor sees. */
main > .socials:not(.listen){ margin-bottom: 1rem; }
main > .socials.listen{ margin-top: 0.8rem; margin-bottom: var(--section-air); }
/* #shop used to override the section margin with 1.6rem of its own. With the
   rhythm on the bottom edge there is nothing left for it to correct. */

/* Shop product cards */
.shop-grid{
    width: 100%;
    max-width: 1300px;
    margin: 0 auto;
    padding: 0 2rem;
    display: grid;
    /* auto-fit rather than a fixed count so the row reflows as products are
       added or removed, instead of stranding the last card on its own row */
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    gap: 1.8rem;
}
@media (max-width: 680px){
    .shop-grid{ grid-template-columns: 1fr; padding: 0 1rem; gap: 1.4rem; }
}
.shop-card{ text-decoration: none; display: flex; flex-direction: column; gap: 0.7rem; }
.shop-card-img{
    width: 100%;
    aspect-ratio: 1;
    display: block;
    background-size: cover;
    background-position: center;
    /* No border. On black, a hairline around a product shot is the only thing
       making it read as a card in a container rather than a photograph on the
       page. The image is the surface. */
    filter: grayscale(1);
    transition: filter 600ms var(--ease), transform 350ms var(--ease);
}
.shop-card:hover .shop-card-img{ filter: grayscale(0); transform: scale(1.02); }
/* Clothing card: the product shot is already monochrome on a black ground,
   so its hover reveal is a brightness lift rather than a grayscale release */
/* ---- Shop card art ----
   These four were inline `background-image:url(...jpg)` on the spans, at
   552KB for thumbnails that render at ~310px. They are now classes so each
   can carry a WebP with a JPEG/PNG fallback via image-set(): 552KB -> 244KB
   on any browser of the last five years, 392KB on anything older.

   The plain url() line MUST come first in each pair. Browsers that do not
   understand image-set() ignore the second declaration and keep the first;
   browsers that do understand it override. Deleting the first line silently
   removes the image for old browsers.

   Safe to compress hard: every one of these is grayscaled or brightness-
   shifted by the rules below before anyone sees it. */
.shop-card-img-packs{
    background-image: url(/images/sp-cover-1.jpg);
    background-image: image-set(url(/images/sp-cover-1.webp) type("image/webp"),
                                url(/images/sp-cover-1.jpg) type("image/jpeg"));
}
.shop-card-img-lessons{
    background-image: url(/images/shop-lessons.jpg);
    background-image: image-set(url(/images/shop-lessons.webp) type("image/webp"),
                                url(/images/shop-lessons.jpg) type("image/jpeg"));
}
.shop-card-img-merch{
    background-image: url(/images/shop-merch.jpg);
    background-image: image-set(url(/images/shop-merch.webp) type("image/webp"),
                                url(/images/shop-merch.jpg) type("image/jpeg"));
    filter: grayscale(1) brightness(0.95);
}
.shop-card:hover .shop-card-img-merch{ filter: grayscale(0) brightness(1.35); transform: scale(1.02); }
/* The app icon is already the monochrome sigil, so a grayscale release would
   reveal nothing. It lifts in brightness instead, same as the clothing shot. */
.shop-card-img-studio{
    background-image: url(/images/studio-logo.png);
    background-image: image-set(url(/images/studio-logo.webp) type("image/webp"),
                                url(/images/studio-logo.png) type("image/png"));
    filter: brightness(0.9);
    background-size: cover;
}
.shop-card:hover .shop-card-img-studio{ filter: brightness(1.3); transform: scale(1.02); }
.shop-card-info{ display: flex; align-items: baseline; justify-content: space-between; gap: 0.6rem; }
.shop-card-name{ font-family: var(--display); text-transform: uppercase; letter-spacing: var(--tracking-label); color: var(--bright); font-size: var(--t-lead); }
.shop-card-meta{ color: var(--faint); text-transform: uppercase; letter-spacing: var(--tracking-label); font-size: var(--t-micro); white-space: nowrap; }
.shop-all{
    display: inline-block;
    margin: 1.6rem auto 0;
    font-family: var(--mono);
    text-transform: uppercase;
    letter-spacing: var(--tracking-label);
    font-size: var(--t-chrome);
    font-weight: 700;
    color: var(--fg);
    text-decoration: none;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0.85rem 1.7rem;
    transition: border-color 200ms var(--ease), color 200ms var(--ease), background-color 200ms var(--ease);
}
.shop-all:hover{ border-color: var(--bright); color: var(--bright); background: rgba(255,255,255,0.04); }

/* ---- end shop-forward homepage ---- */

/* ---- Accessibility: keyboard focus, reduced motion, press feedback ---- */

/* Visible focus ring for keyboard users only (focus-visible), so mouse
   users keep the clean look. The site previously had no visible focus state
   and removed the outline on form fields entirely. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
.card:focus-visible,
.shop-card:focus-visible,
.musicvideo-lite:focus-visible,
.sp-play:focus-visible{
    outline: 2px solid var(--bright);
    outline-offset: 3px;
}

/* Form fields keep their border shift on mouse focus and gain a ring on keys */
.contact-form input:focus-visible,
.contact-form textarea:focus-visible{
    outline: 2px solid var(--bright);
    outline-offset: 2px;
    border-color: var(--bright);
}

/* Tactile pressed feedback across the button language */
.sp-buy:active,
.store-btn:active,
.lessons-cta:active,
.shop-all:active,
.contact-submit:active,
.sp-play:active{
    transform: translateY(1px);
}

/* The media covers already animate (hover scale + the scroll-reveal both drive
   `transform`), so a press-scale there would fight the reveal. The streaming-logo
   row was the one conflict-free gap — no tap feedback. Subtle press-in, transform
   only, reduced to instant by the reduced-motion block below. */
.socials.listen a{
    transition: transform 160ms var(--ease);
}
.socials.listen a:active{
    transform: scale(0.9);
}

/* Press feedback on the cover art — scoped to the <img>, never the card, so it can't
   collide with the scroll-reveal (which transforms the card container). Declared after
   the hover rules so the press wins the tie; the covers' existing transform transition
   (200-350ms) carries the settle — on-brand for the site's smooth, cinematic feel.

   `filter` has to be restated here. This is the shorthand, so it replaces the
   whole transition it inherits from `.card img` rather than adding to it, and
   that quietly left the featured OUT NOW cover — the largest and most looked-at
   image on the homepage — snapping straight to colour with no fade while every
   other cover eased. It was a 300ms discrepancy before the reveals slowed down
   and a 600ms one after, which is what finally made it visible. */
.presave .card img{
    transition: transform 200ms var(--ease), filter 600ms var(--ease);
}
#releases .card:active img,
.presave .card:active img,
.shop-card:active .shop-card-img{
    transform: scale(0.97);
}

/* Respect users who ask for reduced motion */
@media (prefers-reduced-motion: reduce){
    html{ scroll-behavior: auto; }
    *,
    *::before,
    *::after{
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
    }
}

/* Reduced motion was the only accessibility preference the site answered.
   Three surfaces here are translucent blurred material (the sticky header
   and the contact dialog + its scrim), and blur behind text is exactly what
   these two settings exist to switch off. Both make the material solid. */
@media (prefers-reduced-transparency: reduce){
    .site-header{
        background: #000;
        backdrop-filter: none;
    }
    .contact-modal{
        background: rgba(0,0,0,0.94);
        backdrop-filter: none;
    }
}
@media (prefers-contrast: more){
    :root{
        --fg: #ffffff;
        --muted: #e0e0e0;
        --faint: #cfcfcf;
        --line: rgba(255,255,255,0.45);
        --line-soft: rgba(255,255,255,0.28);
    }
    .site-header{
        background: #000;
        backdrop-filter: none;
        border-bottom: 1px solid var(--line);
    }
    .contact-modal{
        background: rgba(0,0,0,0.96);
        backdrop-filter: none;
    }
}
/* ---- end accessibility ---- */

/* ---- 44px touch targets ----
   Pointer targets below ~44px are hard to hit and fail WCAG 2.5.8. The icons
   here are deliberately small and must STAY small, so the hit area is grown
   with a centred, invisible pseudo-element instead of by resizing anything.
   Nothing about the artwork, layout or spacing changes: only the area that
   responds to a finger. The pseudo-element is absolutely positioned, so it
   never takes a grid or flex slot. */
.nav-toggle,
.socials a{
    position: relative;
}
.nav-toggle::after,
.socials a::after{
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    width: 44px;
    height: 44px;
    /* Purely a hit area. Never paints, never affects layout. */
}

/* ---- Book a lesson: on-demand calendar ----
   The Cal.com embed used to mount on every page load, pulling a third-party
   script and iframe for every visitor whether or not they wanted a lesson.
   It now loads on request. These rules carry the button, the compact status
   line, and a fixed loading height so the page does not jump when it opens. */
.cal-wrap{
    width: 100%;
    max-width: 760px;
    margin: 1.4rem auto 0;
    padding: 0 1rem;
    display: flex;
    flex-direction: column;
    align-items: center;
}
.cal-open{
    font-family: var(--mono);
    cursor: pointer;
}
.cal-open[disabled]{
    opacity: 0.55;
    cursor: default;
}
.cal-status{
    /* Fixed height so swapping between loading and failure text cannot shift
       the sections underneath. */
    min-height: 3.2rem;
    display: flex;
    align-items: center;
    justify-content: center;
    max-width: 34rem;
    margin: 0.9rem auto 0;
    text-align: center;
    color: var(--faint);
    font-size: var(--t-chrome);
    line-height: 1.5;
}
.cal-status[hidden]{ display: none; }
.cal-embed[data-state="loading"]{
    /* Reserve the space the calendar is about to take. */
    min-height: 320px;
}
.cal-embed[data-state="ready"]{
    min-height: 40px;
}
.cal-embed[hidden]{ display: none; }

/* ---- Data notices ----
   A quiet line wherever the visitor is about to hand something to a third
   party. States what happens, links to the detail. */
.data-note{
    max-width: 34rem;
    margin: 0.9rem auto 0;
    padding: 0 1.2rem;
    text-align: center;
    color: var(--faint);
    font-size: var(--t-chrome);
    line-height: 1.6;
}
.data-note a{
    color: var(--muted);
    text-decoration: underline;
    text-underline-offset: 2px;
}
.data-note a:hover{ color: var(--bright); }
.contact-form .data-note{
    margin-top: 1.1rem;
    padding: 0;
    text-align: left;
}

/* ---- Site footer ----
   Deliberately quiet: a hairline, the mark, the ways to reach him, the privacy
   link. It closes the page without competing with anything above it. */
.site-footer{
    width: 100%;
    border-top: 1px solid var(--line-soft);
    margin-top: 3.5rem;
    padding: 2.4rem 1.2rem 3rem;
}
.footer-inner{
    max-width: 900px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 1.1rem;
}
.footer-id{
    display: flex;
    align-items: center;
    gap: 0.6rem;
    margin: 0;
}
.footer-sigil{
    width: 1.15rem;
    height: auto;
    display: block;
    opacity: 0.75;
}
.footer-name{
    font-family: var(--display);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: var(--tracking-wordmark);
    color: var(--fg);
    font-size: var(--t-nav);
}
/* No descriptor line, by decision. The name carries itself and a sentence
   explaining who he is would undercut it. Do not add one back. */
.footer-nav{
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 0.5rem 1.4rem;
}
.footer-nav a{
    position: relative;
    font-family: var(--mono);
    font-size: var(--t-micro);
    letter-spacing: var(--tracking-label);
    text-transform: uppercase;
    color: var(--faint);
    text-decoration: none;
    /* This claimed the 44px treatment but only had padding, and measured
       34.6px tall in the browser. The icon rows get their height from a
       centred ::after; this now does the same, so the type size is untouched
       and the target actually clears 44px. */
    display: inline-flex;
    align-items: center;
    min-height: 44px;
    transition: color 200ms var(--ease);
}
.footer-nav a:hover{ color: var(--bright); }
.footer-legal{
    margin: 0;
    color: var(--faint);
    font-family: var(--mono);
    font-size: var(--t-micro);
    letter-spacing: var(--tracking-label);
}

/* ---- Waveform seeking ----
   The waveform was a <div> with a click handler: reachable with a mouse, and
   with nothing at all from a keyboard or a screen reader. The bar is now
   decorative and a real range input sits over it, so seeking is keyboard
   operable and announces a position. The input is transparent, not hidden, so
   it keeps native drag-to-seek; the focus ring is drawn on the wrapper because
   the input itself has no visible box. */
.sp-seekwrap{
    position: relative;
}
.sp-seek{
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    opacity: 0;
    cursor: pointer;
    background: transparent;
    -webkit-appearance: none;
    appearance: none;
}
.sp-seekwrap:has(.sp-seek:focus-visible){
    outline: 2px solid var(--bright);
    outline-offset: 3px;
    border-radius: var(--radius);
}
.sp-seek:focus-visible{
    outline: none;
}

/* A section heading that happens to be the page's h1 (sample-packs). The global
   h1 rule is written for the hero lockup — huge, and pulled up 50% — so it is
   cancelled here and the ordinary section-heading look is restored. Visually
   identical to the h2 it replaces; only the outline level changed. */
.releases h1{
    font-family: var(--display);
    font-weight: 500;
    letter-spacing: var(--tracking-display);
    text-transform: uppercase;
    margin: 0 0 var(--heading-air);
    color: var(--bright);
    font-size: var(--t-head);
    text-align: center;
    position: static;
    transform: none;
}
