/* ─────────────────────────────────────────────
   Diffusion — DJ page
   Sections follow document order.
   #4A4757 is a BORDER value only, never a text color.
   ───────────────────────────────────────────── */

/* ── fonts ──────────────────────────────────────────────────────────────────
   @font-face rules inlined from Google Fonts rather than linked. A stylesheet
   in <head> is render-blocking, so linking fonts.googleapis.com put a
   third-party round trip in front of first paint. That matters here beyond
   general load time: the wordmark's entrance is a one-shot 3s animation whose
   clock starts at document load, so paint delayed past 3s means the entrance is
   spent before anything is on screen.
   The font files themselves still come from fonts.gstatic.com, which is
   preconnected in the HTML and is not render-blocking.
   Regenerate by fetching the css2 URL with a modern browser User-Agent. */
/* vietnamese */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400 900;
  font-stretch: 62% 125%;
  font-display: optional;
  src: url(https://fonts.gstatic.com/s/archivo/v25/k3kQo8UDI-1M0wlSfdboLmvDIaK18A.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400 900;
  font-stretch: 62% 125%;
  font-display: optional;
  src: url(https://fonts.gstatic.com/s/archivo/v25/k3kQo8UDI-1M0wlSfdfoLmvDIaK18A.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'Archivo';
  font-style: normal;
  font-weight: 400 900;
  font-stretch: 62% 125%;
  font-display: optional;
  src: url(https://fonts.gstatic.com/s/archivo/v25/k3kQo8UDI-1M0wlSfdnoLmvDIaI.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwgknk-6nFg.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwgknk-6nFg.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxPcwgknk-6nFg.woff2) format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx_cwgknk-6nFg.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwgknk-6nFg.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwgknk-4.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/* cyrillic-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx3cwgknk-6nFg.woff2) format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
/* cyrillic */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxTcwgknk-6nFg.woff2) format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
/* greek */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxPcwgknk-6nFg.woff2) format('woff2');
  unicode-range: U+0370-0377, U+037A-037F, U+0384-038A, U+038C, U+038E-03A1, U+03A3-03FF;
}
/* vietnamese */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx_cwgknk-6nFg.woff2) format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
/* latin-ext */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPx7cwgknk-6nFg.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
/* latin */
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url(https://fonts.gstatic.com/s/jetbrainsmono/v24/tDbv2o-flEEny0FZhsfKu5WU4zr3E_BX0PnT8RD8yKwBNntkaToggR7BYRbKPxDcwgknk-4.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}


:root {
    --void:   #08070C;
    --panel:  #12111A;
    --line:   #1D1B26;
    --bone:   #E8E4DC;
    --grey:   #7E7A88;
    --violet: #6E64FF;

    --gut: 40px;

    /* Content measure. Without this the section headings span the full viewport
       on a wide monitor while the grid sits in a narrow column, which reads
       lopsided. --edge centers everything on one measure and collapses back to
       the plain gutter once the viewport is narrower than --page, so nothing
       changes on laptops and phones. It resolves against the element it is used
       on, so it belongs on full-width blocks only. */
    --page: 1480px;
    --edge: max(var(--gut), calc((100% - var(--page)) / 2));
    --mono: 'JetBrains Mono', ui-monospace, monospace;
}

*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
    background: var(--void);
    color: var(--bone);
    font-family: 'Archivo', system-ui, sans-serif;
    font-size: 16px;
    line-height: 1.5;
    -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }

:focus-visible {
    outline: 2px solid var(--violet);
    outline-offset: 3px;
}

/* ── nav ── */
.nav {
    position: sticky;
    top: 0;
    z-index: 10;
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 20px;
    /* Gutter, not --edge. The nav is a full-bleed bar: its background and border
       already span the viewport, so insetting its contents to the content measure
       left the wordmark and links floating well inside the bar on wide screens. */
    padding: 20px var(--gut);
    background: rgba(8, 7, 12, .86);
    backdrop-filter: blur(14px);
    border-bottom: 1px solid var(--line);
}

.nav-mark {
    font-weight: 700;
    font-stretch: 108%;
    font-size: 15px;
    letter-spacing: .3em;
    color: var(--bone);
    white-space: nowrap;
}

.nav-links { display: flex; gap: 30px; }

.nav-links a {
    font-family: var(--mono);
    font-size: 10.5px;
    letter-spacing: .24em;
    text-transform: uppercase;
    color: var(--grey);
    transition: color .18s;
}

.nav-links a:hover { color: var(--bone); }

/* ── hero ── */
.hero {
    position: relative;
    min-height: calc(100vh - 40px);
    min-height: calc(100dvh - 40px);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 80px var(--edge);
}

.hero::before {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(44% 38% at 50% 50%, rgba(110, 100, 255, .10), transparent 70%);
}

.hero-mark {
    position: relative;
    /* 13.5vw, not 16.5vw. The cap is a straight 1.5x (92px -> 138px), but the
       slope is not: "DIFFUSION" measures 6.083em wide at wght800/wdth110, so a
       1.5x slope overflows the viewport at 375px, 430px and 768px. 13.5vw is the
       widest slope that clears every width from 320px up with real margin. */
    font-size: clamp(2.5rem, 13.5vw, 8.625rem);
    line-height: .92;
    font-weight: 800;
    font-stretch: 110%;
    letter-spacing: -.025em;
    color: var(--bone);
}

/* Entrance, plays once on load: the wordmark resolves out of noise, left to
   right — the D clears first, the N last.

   Three stacked renderings of the word, each revealed through its own mask:
   full noise on top, half-resolved beneath it, clean text at the bottom. The
   masks are offset from one another, so what travels across the word is a band
   of partial resolution — clean behind it, half-formed inside it, pure noise
   ahead of it. A two-layer version could only crossfade between "noise" and
   "clean"; the middle layer is what gives genuinely intermediate states.

   Each mask is 300% wide, so its element sees one third at a time: at position
   100% the right third, at 0% the left third. Both filters are static, so they
   rasterize once — only mask-position animates.

   The clean layer's mask is on .mark-text and the middle layer's is on
   .mark-mid::before. .mark-mid itself carries no mask deliberately: a mask on it
   would also mask .mark-text inside it, and the layers need independent masks.

   The first 12% holds at pure noise before the front sets off, so it reads as
   starting from noise rather than already resolving. */
.hero-mark::before,
.mark-mid::before {
    content: 'DIFFUSION';
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    color: var(--bone);
    pointer-events: none;
    -webkit-mask-size: 300% 100%;
            mask-size: 300% 100%;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
}

/* top: a field of pure noise, cleared from the left.
   Three mask layers intersected: the travelling wipe, plus a vertical and a
   horizontal feather so the field has no hard edge anywhere. Without the
   feathers the noise is a plain rectangle, which is the box that kept showing
   up around the lettering. Only the first layer's position animates. */
.hero-mark::before {
    z-index: 3;
    filter: url(#mark-noise-full);
    -webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 52%, #E8E4DC 66%, #E8E4DC 100%),
                        linear-gradient(180deg, transparent 0%, #E8E4DC 20%, #E8E4DC 80%, transparent 100%),
                        linear-gradient(90deg, transparent 0%, #E8E4DC 8%, #E8E4DC 92%, transparent 100%);
            mask-image: linear-gradient(90deg, transparent 0%, transparent 52%, #E8E4DC 66%, #E8E4DC 100%),
                        linear-gradient(180deg, transparent 0%, #E8E4DC 20%, #E8E4DC 80%, transparent 100%),
                        linear-gradient(90deg, transparent 0%, #E8E4DC 8%, #E8E4DC 92%, transparent 100%);
    -webkit-mask-size: 300% 100%, 100% 100%, 100% 100%;
            mask-size: 300% 100%, 100% 100%, 100% 100%;
    -webkit-mask-composite: source-in;
            mask-composite: intersect;
    animation: mark-front-feathered 3.4s cubic-bezier(.35,0,.5,1) both,
               mark-retire 3.45s step-end both;
}

/* Only the travelling layer's position moves; the two feathers are pinned. A
   feathered layer must use this and not mark-front, whose single position would
   slide the feathers along with the band. */
@keyframes mark-front-feathered {
    0%, 12% { -webkit-mask-position: 100% 0, 0 0, 0 0; mask-position: 100% 0, 0 0, 0 0; }
    100%    { -webkit-mask-position: 0% 0, 0 0, 0 0;   mask-position: 0% 0, 0 0, 0 0; }
}

/* middle: half-resolved, visible only as a travelling band.
   Feathered like the noise layer above, and for the same reason: the blur and
   displacement throw ink well outside the element box, the box is exactly the
   width of the word because .hero is a centring flex column, and without the
   feathers that overspill is cut square — a grain-filled rectangle straddling
   the last N. */
.mark-mid::before {
    z-index: 2;
    filter: url(#mark-noise-mid);
    -webkit-mask-image: linear-gradient(90deg, transparent 0%, transparent 36%, #E8E4DC 48%, #E8E4DC 54%, transparent 66%, transparent 100%),
                        linear-gradient(180deg, transparent 0%, #E8E4DC 20%, #E8E4DC 80%, transparent 100%),
                        linear-gradient(90deg, transparent 0%, #E8E4DC 8%, #E8E4DC 92%, transparent 100%);
            mask-image: linear-gradient(90deg, transparent 0%, transparent 36%, #E8E4DC 48%, #E8E4DC 54%, transparent 66%, transparent 100%),
                        linear-gradient(180deg, transparent 0%, #E8E4DC 20%, #E8E4DC 80%, transparent 100%),
                        linear-gradient(90deg, transparent 0%, #E8E4DC 8%, #E8E4DC 92%, transparent 100%);
    -webkit-mask-size: 300% 100%, 100% 100%, 100% 100%;
            mask-size: 300% 100%, 100% 100%, 100% 100%;
    -webkit-mask-composite: source-in;
            mask-composite: intersect;
    animation: mark-front-feathered 3.4s cubic-bezier(.35,0,.5,1) both,
               mark-retire 3.45s step-end both;
}

/* bottom: the real text, arriving from the left */
.mark-text {
    display: block;
    position: relative;
    z-index: 1;
    -webkit-mask-image: linear-gradient(90deg, #E8E4DC 0%, #E8E4DC 34%, transparent 50%, transparent 100%);
            mask-image: linear-gradient(90deg, #E8E4DC 0%, #E8E4DC 34%, transparent 50%, transparent 100%);
    -webkit-mask-size: 300% 100%;
            mask-size: 300% 100%;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    animation: mark-front 3.4s cubic-bezier(.35,0,.5,1) both;
}

@keyframes mark-front {
    0%, 12% { -webkit-mask-position: 100% 0; mask-position: 100% 0; }
    100%    { -webkit-mask-position: 0% 0;   mask-position: 0% 0; }
}

/* Retiring these layers needs opacity and display, NOT visibility. The noise
   filter is pure feTurbulence — it generates its output without reading
   SourceGraphic — so `visibility: hidden` empties the element's own paint while
   the filter keeps emitting noise, and in that state the mask stops suppressing
   it. The result was a hard-edged noise rectangle appearing after the entrance
   finished. opacity applies to the filtered result, so it hides it for certain;
   display:none additionally stops the filter rasterizing at all. */
@keyframes mark-retire {
    from { opacity: 1; display: block; }
    to   { opacity: 0; display: none; }
}

/* Edge sweep: a violet outline of the wordmark, revealed through a narrow
   travelling mask, so a lit hairline runs across the letterforms. Only the
   stroke is ever lit — the bone fill underneath never changes.
   The mask gradient's colour is irrelevant (an alpha mask reads only opacity);
   it uses the bone triple rather than black to keep the stylesheet palette-pure.
   The h1 carries aria-label="Diffusion", so the duplicated text in this
   pseudo-element is never announced twice.
   Delayed to start when the entrance ends, with fill-mode backwards so it
   holds its opening frame during the delay instead of rendering its base
   mask position. See the entrance block above. */
.hero-mark::after {
    content: 'DIFFUSION';
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    color: transparent;
    -webkit-text-stroke: 1.1px var(--violet);
    pointer-events: none;
    -webkit-mask-image: linear-gradient(100deg, transparent 38%, rgba(232, 228, 220, 1) 50%, transparent 62%);
            mask-image: linear-gradient(100deg, transparent 38%, rgba(232, 228, 220, 1) 50%, transparent 62%);
    -webkit-mask-size: 320% 100%;
            mask-size: 320% 100%;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
    animation: edge-sweep 5.5s linear 3.2s infinite backwards;
}

@keyframes edge-sweep {
    from { -webkit-mask-position: 170% 0;  mask-position: 170% 0; }
    to   { -webkit-mask-position: -70% 0;  mask-position: -70% 0; }
}

.hero-tag {
    position: relative;
    margin-top: 36px;
    font-family: var(--mono);
    font-size: 16.5px;
    letter-spacing: .34em;
    text-transform: uppercase;
    color: var(--grey);
}

/* Decorative separator: aria-hidden in the markup, so it is pure decoration
   and exempt from WCAG 1.4.3. Kept as a bone tint rather than a new hex so the
   stylesheet holds only palette values. Do not "fix" this to a brighter value. */
.hero-tag span { color: rgba(232, 228, 220, .34); margin: 0 .8em; }

.hero-soc {
    position: relative;
    margin-top: 42px;
    display: flex;
    gap: 33px;
}

.hero-soc a { color: var(--bone); opacity: .82; transition: color .18s, opacity .18s; }
.hero-soc a:hover { color: var(--violet); opacity: 1; }
.hero-soc svg { display: block; }

/* ── section shell ── */
/* scroll-margin-top ≈ the sticky nav's rendered height (20+20 padding + ~22.5px
   line box + 1px border ≈ 63.5px). .sec's padding-top absorbs the rounding, so
   anchored headings clear the nav. At 600px the nav drops the wordmark and
   measures 44px, so the margin comes down to match.
   The top padding is shorter than the bottom on purpose: it ties a header to the
   rule above it rather than leaving it adrift between two sections. */
.sec {
    padding: 52px var(--edge) 74px;
    border-top: 1px solid var(--line);
    scroll-margin-top: 62px;
}

.sechead {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 16px;
    margin-bottom: 30px;
}

.sechead h2 {
    font-family: var(--mono);
    font-size: 13px;
    font-weight: 500;
    letter-spacing: .34em;
    text-transform: uppercase;
    color: var(--bone);
}

.sechead .count {
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: .2em;
    text-transform: uppercase;
    color: var(--grey);
}

/* ── sets ── */
.set-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 28px 24px;
}

.tile { display: block; }

.tile-art {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 1 / 1;
    object-fit: cover;
    border-radius: 2px;
    background: var(--panel);
    transition: transform .25s cubic-bezier(.4, 0, .2, 1), box-shadow .25s;
}

.tile:hover .tile-art,
.tile:focus-visible .tile-art {
    transform: translateY(-3px);
    box-shadow: 0 14px 44px rgba(8, 7, 12, .55);
}

.tile-cap {
    display: block;
    margin-top: 13px;
    font-size: 14px;
    font-weight: 500;
    line-height: 1.35;
    color: var(--bone);
}

.tile-meta {
    display: block;
    margin-top: 5px;
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--grey);
    transition: color .18s;
}

.tile:hover .tile-meta,
.tile:focus-visible .tile-meta { color: var(--violet); }

/* ── photos ── */
/* .sec--bleed must stay AFTER .sec in this file. Both are single-class selectors,
   so source order alone decides which padding wins. Do not reorder these blocks. */
.sec--bleed { padding-left: 0; padding-right: 0; }
.sec--bleed .sechead { padding: 0 var(--edge); }

.rail {
    display: flex;
    align-items: stretch;
    gap: 14px;
    overflow-x: auto;
    padding: 0 var(--edge) 16px;
    /* No scroll-snap here on purpose. The photos are mixed crops, so the snap
       points sit at irregular intervals; with only ~300px of scrollable width
       the second point is further away than the rail can travel, so proximity
       snapping dragged the view back to the start on every pause. */
    /* `none`, not `contain`. Both stop scroll chaining to the page, but `contain`
       still allows the local rubber-band, and with only ~295px of travel a
       trackpad flick hits the limit instantly and springs back — indistinguishable
       from the snap bug this rail already had once. */
    overscroll-behavior-x: none;
}

.rail::-webkit-scrollbar { height: 3px; }
.rail::-webkit-scrollbar-track { background: var(--panel); }
.rail::-webkit-scrollbar-thumb { background: rgba(232, 228, 220, .2); }

.shot {
    flex: 0 0 auto;
    position: relative;
    height: 330px;
    border-radius: 2px;
    overflow: hidden;
    background: var(--panel);
}

.shot img {
    display: block;
    height: 100%;
    width: auto;
    object-fit: cover;
}

.shot figcaption {
    position: absolute;
    left: 12px;
    bottom: 10px;
    font-family: var(--mono);
    font-size: 9px;
    letter-spacing: .2em;
    color: rgba(232, 228, 220, .62);
    text-shadow: 0 1px 3px rgba(8, 7, 12, .9);
}

.shot-btn {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    display: block;
    height: 100%;
    cursor: pointer;
}

.shot-btn img { transition: filter .18s; }

.shot-btn:hover img,
.shot-btn:focus-visible img { filter: brightness(1.12); }

/* ── lightbox ──
   Reuses the show dialog's backdrop and close-button treatment for a single
   enlarged photo. Unlike a show, there's no panel box around the content, so
   the close control anchors to the viewport corner rather than a panel edge. */
.lightbox {
    position: fixed;
    inset: 0;
    z-index: 20;
    display: flex;
    padding: var(--gut);
    background: rgba(8, 7, 12, .82);
    backdrop-filter: blur(14px);
    opacity: 0;
    transition: opacity .26s;
}

.lightbox[hidden] { display: none; }

.lightbox.is-open { opacity: 1; }

body.lightbox-open { overflow: hidden; }

.lightbox-img {
    margin: auto;
    display: block;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    border-radius: 2px;
    transform: translateY(12px);
    transition: transform .26s cubic-bezier(.4, 0, .2, 1);
}

.lightbox.is-open .lightbox-img { transform: none; }

.lightbox-close {
    position: fixed;
    top: 16px;
    right: 18px;
    z-index: 1;
    appearance: none;
    background: none;
    border: 0;
    padding: 6px;
    cursor: pointer;
    font-family: var(--mono);
    font-size: 18px;
    line-height: 1;
    color: var(--grey);
    transition: color .18s;
}

.lightbox-close:hover,
.lightbox-close:focus-visible { color: var(--bone); }

/* ── played ── */
/* The header row shares the gig grid, so PLAYED sits over the date column and
   LOCATION exactly over the city column. It was previously a flex row with
   space-between while the rows below were a 3-column grid, so the right-hand
   label missed the city column by the width of .gig's horizontal padding. */
.sechead--cols {
    display: grid;
    grid-template-columns: 104px minmax(0, 1fr) 140px;
    gap: 18px;
    /* Overrides .sechead's baseline alignment: PLAYED is 13px and LOCATION is
       10px, so a shared baseline left LOCATION's cap band sitting low. */
    align-items: center;
}

.sechead--cols .count {
    grid-column: 3;
    text-align: right;
}

.gig {
    display: grid;
    grid-template-columns: 104px minmax(0, 1fr) 140px;
    gap: 18px;
    /* Centred, not baseline: the mono labels are 10px caps against a 15px
       mixed-case title, so a shared baseline leaves their cap band sitting a
       pixel and a half low. Centring closes that to a quarter pixel. */
    align-items: center;
    /* No horizontal padding — it would offset every cell from the header above. */
    padding: 20px 0;
}

.gig-date,
.gig-city {
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: .15em;
    text-transform: uppercase;
    color: var(--grey);
}

.gig-city { text-align: right; }

.gig-venue { font-size: 15px; font-weight: 500; color: var(--bone); }

/* ── upcoming ── */
/* Shares the played grid, so a show moves between the two sections by editing
   its date and dropping .gig--next. .gig-note is its own cell rather than a
   child of .gig-venue so the mobile stack keeps the played order: venue, city,
   then the ticket line. */
.gig--next { row-gap: 5px; }

.gig-note {
    grid-column: 2;
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--grey);
    transition: color .18s;
}

.gig--next:hover .gig-note,
.gig--next:focus-visible .gig-note { color: var(--violet); }

/* ── shows ── */
.gig--show .gig-venue {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    font-size: 15px;
    font-weight: 500;
    text-align: left;
    justify-self: start;
    cursor: pointer;
    text-decoration: underline;
    text-decoration-color: rgba(232, 228, 220, .28);
    text-decoration-thickness: 1px;
    text-underline-offset: 5px;
    transition: text-decoration-color .18s;
}

.gig--show .gig-venue:hover,
.gig--show .gig-venue:focus-visible { text-decoration-color: var(--violet); }

.show {
    position: fixed;
    inset: 0;
    z-index: 20;
    display: flex;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: var(--gut);
    background: rgba(8, 7, 12, .72);
    backdrop-filter: blur(14px);
    opacity: 0;
    transition: opacity .26s;
}

/* The display: flex above would otherwise beat the user agent's [hidden] rule. */
.show[hidden] { display: none; }

.show.is-open { opacity: 1; }

body.show-open { overflow: hidden; }

/* margin: auto centres the panel when it fits and lets the backdrop scroll
   when it does not, where align-items: center would clip the top. The
   min-height is for a show with nothing in it yet: the blank under the title
   is the intended empty state, and the title alone would make a strip. */
.show-panel {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 40px;
    width: min(100%, 1400px);
    min-height: 360px;
    margin: auto;
    padding: 40px;
    background: var(--void);
    border: 1px solid var(--line);
    border-radius: 2px;
    transform: translateY(12px);
    transition: transform .26s cubic-bezier(.4, 0, .2, 1);
}

.show-panel:not(:has(.show-grid)) {
    grid-template-columns: minmax(0, 1fr);
    width: min(100%, 720px);
}

.show.is-open .show-panel { transform: none; }

.show-close {
    position: absolute;
    top: 16px;
    right: 18px;
    z-index: 1;
    appearance: none;
    background: none;
    border: 0;
    padding: 6px;
    cursor: pointer;
    font-family: var(--mono);
    font-size: 18px;
    line-height: 1;
    color: var(--grey);
    transition: color .18s;
}

.show-close:hover { color: var(--bone); }

.show-lead {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
}

.show-title {
    margin-top: 16px;
    /* Deliberate 2px hang outside the panel's padding, chosen by eye. The
       title no longer shares the left edge of the link beneath it. */
    margin-left: -2px;
    font-size: clamp(1.25rem, 3vw, 1.75rem);
    font-weight: 700;
    font-stretch: 108%;
    letter-spacing: -.015em;
    line-height: 1.15;
    color: var(--bone);
}

.show-lead > :first-child { margin-top: 0; }

.show-link {
    font-family: var(--mono);
    font-size: 10px;
    letter-spacing: .16em;
    text-transform: uppercase;
    color: var(--grey);
    text-decoration: none;
    transition: color .18s;
}

.show-link:hover,
.show-link:focus-visible { color: var(--violet); }

.show-embed {
    display: block;
    width: 100%;
    border: 0;
    border-radius: 2px;
    background: var(--panel);
}

.show-embed--video { aspect-ratio: 16 / 9; height: auto; }
.show-embed--audio { height: 166px; margin-top: 16px; }

.show-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px 24px;
    align-content: start;
}

.show-tile figcaption { margin-top: 10px; }

/* ── booking ── */
.book { text-align: center; padding-top: 72px; padding-bottom: 96px; }

.book-label {
    font-family: var(--mono);
    font-size: 13px;
    font-weight: 500;
    letter-spacing: .34em;
    text-transform: uppercase;
    color: var(--grey);
}

.book-mail {
    display: inline-block;
    margin-top: 22px;
    font-size: clamp(1.15rem, 4.4vw, 1.875rem);
    font-weight: 700;
    font-stretch: 108%;
    letter-spacing: -.015em;
    color: var(--bone);
    word-break: break-word;
    transition: color .18s;
}

.book-mail:hover { color: var(--violet); }

/* ── footer ── */
.foot {
    display: flex;
    justify-content: space-between;
    gap: 16px;
    padding: 24px var(--edge) 36px;
    border-top: 1px solid var(--line);
    font-family: var(--mono);
    font-size: 9.5px;
    letter-spacing: .2em;
    text-transform: uppercase;
    color: var(--grey);
}

/* ── responsive ── */
/* Column ladder: 4 -> 3 -> 2 -> 1. These blocks must stay in descending
   max-width order; they are equal specificity, so a later block wins and
   reordering would strand the grid at the wrong count. */
@media (max-width: 1200px) {
    .set-grid { grid-template-columns: repeat(3, 1fr); }
}

@media (max-width: 900px) {
    .set-grid { grid-template-columns: repeat(2, 1fr); }
    .nav-links { gap: 20px; }
    .shot { height: 260px; }
    .show-panel { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 600px) {
    /* The middle layer's displacement and blur are absolute lengths, so at phone
       glyph sizes the stock filter tears the letters apart. Swap in the half-scale
       twin; the top layer is pure turbulence and needs nothing. */
    .mark-mid::before { filter: url(#mark-noise-mid-sm); }

    :root { --gut: 20px; }

    /* Three places instead of two overruns the measure at the full size and
       wraps. Shrunk to hold one line at 320 too, and to stay narrower than the
       wordmark above it, which is 263px of ink there. */
    .hero-tag { font-size: 13.5px; letter-spacing: .26em; }

    .nav { flex-direction: column; align-items: flex-start; gap: 10px; padding-top: 14px; padding-bottom: 14px; }
    /* The wordmark is the first thing in the hero anyway, so on a phone the nav
       copy of it only costs header height. Hiding it also drops the tap target
       that returned to #top. */
    .nav-mark { display: none; }
    .nav-links { gap: 0; justify-content: space-between; width: 100%; }
    /* Five links spread across a 320px viewport leave no gap at the full-size
       tracking, so the labels touch. */
    .nav-links a { font-size: 10px; letter-spacing: .14em; }
    .sec { scroll-margin-top: 42px; padding-top: 38px; padding-bottom: 56px; }
    .sechead { margin-bottom: 24px; }

    /* Two columns, not one: six full-width squares stack to more than 2000px
       and lose the grid the desktop page is built around. */
    .set-grid { grid-template-columns: repeat(2, 1fr); gap: 22px 14px; }
    .tile-cap { font-size: 13px; margin-top: 10px; }
    .shot { height: 220px; }

    /* Keep the desktop's three columns so LOCATION still sits over the cities.
       The venue column is the one that gives; a long name wraps rather than
       pushing the city onto its own line. */
    .sechead--cols,
    .gig { grid-template-columns: 68px minmax(0, 1fr) auto; gap: 14px; }

    .show-panel { padding: 20px; gap: 28px; }
    .show-close { top: 10px; right: 12px; }
    .show-grid { gap: 22px 14px; }

    .foot { flex-direction: column; gap: 8px; }
}

/* ── reduced motion ── */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }

    *, *::before, *::after {
        transition: none !important;
        animation: none !important;
    }

    .tile:hover .tile-art,
    .tile:focus-visible .tile-art {
        transform: none;
    }

    /* `animation: none` alone would freeze the sweep mid-travel, leaving a violet
       outline stuck across part of the wordmark, and would strand the entrance
       filter at its opening frame — a permanent field of noise where the wordmark
       should be. Remove the sweep and the filter outright; the h1 then simply
       renders as plain text, immediately. */
    .hero-mark::after,
    .hero-mark::before, .mark-mid::before { display: none; }
    .mark-text { mask-image: none; -webkit-mask-image: none; animation: none; }
}
