/* The Global Dance Party — the room.
 *
 * An additive layer. It adds depth to what is already there and changes no
 * structure: every rule here either reveals the canvas painted behind the
 * page, or tightens type the site already sets.
 *
 * Paired with gdp-room.js, which tags elements at runtime with data-gdp-*
 * attributes. Framer class names are content hashes and change on every
 * publish, so nothing here may depend on one.
 */

/* ------------------------------------------------------ motion system
 *
 * Three speeds and one easing curve, because twenty different ones is what
 * this layer had grown to and it reads as twenty different hands. Everything
 * added or refined from here uses these.
 *
 *   fast   input answering back - press, hover, play/pause
 *   med    something changing state - a panel, a doorway, a player mode
 *   slow   atmosphere - the countdown, ambient light, anything decorative
 *
 * One curve for all of it. Standard ease-out overshoot: quick to leave, soft
 * to arrive, which is what makes a control feel physical rather than timed.
 * `--gdp-enter` is the flatter curve for things arriving on screen, where an
 * overshoot reads as a bounce and cheapens it.
 */
:root {
    --gdp-fast: 140ms;
    --gdp-med: 320ms;
    --gdp-slow: 900ms;
    --gdp-ease: cubic-bezier(0.22, 1, 0.36, 1);
    --gdp-enter: cubic-bezier(0.4, 0, 0.2, 1);
}

/* One global honouring of the setting, so a rule added later cannot forget.
 * Not `none` - a zeroed duration still fires transitionend and still lets
 * state changes commit, where `none` can strand code that waits on one. */
@media (prefers-reduced-motion: reduce) {
    :root {
        --gdp-fast: 1ms;
        --gdp-med: 1ms;
        --gdp-slow: 1ms;
    }
}

/* The page wrapper paints #0A0A0C over the whole document, which would hide
 * the room entirely. The colour moves to <body> so the wrapper can go
 * transparent without changing what anyone sees. */
html body {
    background-color: #0a0a0c !important;
}

#gdp-room {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    /* Fade the top so the floor never competes with the nav, and ease the
     * bottom edge so it reads as distance rather than a cut. */
    -webkit-mask-image: linear-gradient(
        to bottom,
        transparent 0,
        rgba(0, 0, 0, 0.5) 6%,
        #000 18%,
        #000 88%,
        rgba(0, 0, 0, 0.55) 100%
    );
    mask-image: linear-gradient(
        to bottom,
        transparent 0,
        rgba(0, 0, 0, 0.5) 6%,
        #000 18%,
        #000 88%,
        rgba(0, 0, 0, 0.55) 100%
    );
}

/* Content sits above the room. Without this the canvas paints over the page. */
#main {
    position: relative;
    z-index: 1;
}

/* ------------------------------------------------------------ the crate
 *
 * Mix cards get a real camera. Perspective is set per card rather than on a
 * shared parent: the archive's flex wrapper is Framer's and may be rebuilt,
 * and a self-contained card survives that.
 */
/* !important throughout: the builder sets transform and box-shadow on these
 * cards through two-class selectors that outrank an attribute selector, so
 * without it the custom properties update every frame and nothing moves. */
[data-gdp-card] {
    transform: perspective(1000px) rotateX(var(--gdp-rx, 0deg))
        rotateY(var(--gdp-ry, 0deg)) translateZ(var(--gdp-tz, 0px)) !important;
    transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1),
        box-shadow 420ms cubic-bezier(0.22, 1, 0.36, 1) !important;
}

/* will-change is a promise the compositor keeps by giving the element its
 * own layer, and it was being made to every card at once. The archive
 * carries 74 of them: 74 permanent GPU layers, held for the whole session,
 * for cards that are mostly still. That is a documented way to make a page
 * scroll worse rather than better, and this page had a choppiness report
 * against it. The promise is now made only about the card actually being
 * moved - the one under the pointer - which is the case it was meant for. */
[data-gdp-card][data-gdp-lit="1"] {
    will-change: transform;
    box-shadow: 0 26px 60px -18px rgba(0, 0, 0, 0.85),
        0 0 0 1px rgba(244, 241, 233, 0.09),
        0 0 44px -14px rgba(56, 212, 255, 0.4) !important;
}

/* The specular sweep. One band of light tracking the pointer across the
 * card face, clipped by the card's own rounded corners. */
[data-gdp-card]::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: inherit;
    pointer-events: none;
    opacity: 0;
    transition: opacity 320ms ease;
    background: radial-gradient(
        130% 90% at var(--gdp-sx, 50%) var(--gdp-sy, 50%),
        rgba(255, 255, 255, 0.17) 0%,
        rgba(56, 212, 255, 0.1) 32%,
        transparent 62%
    );
    z-index: 3;
}

[data-gdp-card][data-gdp-lit="1"]::after {
    opacity: 1;
}

/* ------------------------------------------------------- display type
 *
 * Allfeat's rule, from refero: negative tracking under sub-1.0 leading is
 * most of what separates premium editorial from a default type ramp. Applied
 * only to type the runtime measured at 54px or larger, so body copy and
 * labels are untouched.
 */
[data-gdp-display] {
    letter-spacing: -0.021em !important;
    line-height: 0.95 !important;
}

/* --------------------------------------------------------- eyebrow pills
 *
 * Cassette's rule, from refero: the small label above a section heading
 * becomes a bordered pill. Turns five pieces of floating text into one
 * label-print system that repeats down the page.
 */
[data-gdp-eyebrow] {
    display: inline-flex !important;
    /* The pill must hug its label. Framer's section columns stretch their
     * children, which turns an inline-flex pill into a full-width bar. */
    align-self: flex-start !important;
    width: fit-content !important;
    align-items: center;
    gap: 0.55em;
    padding: 0.44em 0.95em 0.46em !important;
    border: 1px solid rgba(244, 241, 233, 0.22);
    border-radius: 999px;
    background: rgba(244, 241, 233, 0.03);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    transition: border-color 600ms ease, background-color 600ms ease;
}

[data-gdp-eyebrow]::before {
    content: "";
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: currentColor;
    opacity: 0.8;
    flex: none;
}

/* The pills catch the room's pulse, so the label system belongs to the same
 * circuit as the lights rather than sitting outside it. */
[data-gdp-eyebrow][data-gdp-hot="1"] {
    border-color: rgba(56, 212, 255, 0.42);
    background: rgba(56, 212, 255, 0.07);
}

/* --------------------------------------------------------- reduced motion
 *
 * Not a downgrade: the room is still there, it simply stops moving. The
 * runtime paints one static frame and never schedules another.
 */
@media (prefers-reduced-motion: reduce) {
    [data-gdp-card] {
        transform: none !important;
        transition: none !important;
    }
    [data-gdp-card]::after {
        display: none;
    }
    [data-gdp-eyebrow] {
        backdrop-filter: none;
        -webkit-backdrop-filter: none;
    }
}

/* ------------------------------------------------------- touch targets
 *
 * The transport controls are 34px and the nav icons 20 to 28px. Both are
 * comfortably under the 44px minimum every touch guideline agrees on, and the
 * transport row is exactly the row a person uses most: play, skip, change mix.
 *
 * The hit area is extended with a pseudo-element so nothing moves and nothing
 * is redrawn. Sizes are measured, not assumed: the four transport buttons sit
 * on a 42px pitch, so a 44px-wide target would overlap its neighbour and turn
 * "next mix" into "fast forward". 42 claims exactly half of each 8px gap and
 * stops there. The nav icons have 16px or more around them, so they take the
 * full 44.
 *
 * Pointer devices are left alone. A mouse does not need this and the larger
 * area would swallow hovers meant for what is next to it.
 */
@media (hover: none) and (pointer: coarse) {
    [aria-label^="Go to Mix"],
    [aria-label^="Rewind "],
    [aria-label^="Fast forward"],
    [aria-label^="Play Mix"],
    [aria-label^="Pause Mix"],
    [aria-label^="Resume Mix"],
    [aria-label^="Copy link"],
    [aria-label="Search Icon"] {
        position: relative;
    }

    [aria-label^="Go to Mix"]::after,
    [aria-label^="Rewind "]::after,
    [aria-label^="Fast forward"]::after,
    [aria-label^="Play Mix"]::after,
    [aria-label^="Pause Mix"]::after,
    [aria-label^="Resume Mix"]::after,
    [aria-label^="Copy link"]::after,
    [aria-label="Search Icon"]::after {
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        width: 44px;
        height: 44px;
        transform: translate(-50%, -50%);
        /* invisible, and never on top of the control's own artwork */
        background: transparent;
        z-index: 0;
    }

    /* the tightly spaced transport row: half of each gap, no further */
    [aria-label^="Go to Mix"]::after,
    [aria-label^="Rewind "]::after,
    [aria-label^="Fast forward"]::after {
        width: 42px;
    }

    /* The nav icons sit in wrappers sized exactly to the icon with overflow
     * hidden, which clips the enlarged target back to 20px and undoes the
     * whole exercise. Only the wrapper directly holding one of these controls
     * is opened up, and it contains nothing else that could spill. */
    :has(> [aria-label="Search Icon"]),
    :has(> [aria-label^="Resume Mix"]),
    :has(> [aria-label^="Pause Mix"]) {
        overflow: visible !important;
    }
}

/* ---------------------------------------------------------- the pulse
 *
 * A dance site where only the background moves is a poster with a screensaver
 * behind it. On every kick, <html> carries data-gdp-pulse for about a sixth of
 * a second, and the parts of the page that can move without becoming annoying
 * take a step.
 *
 * Deliberately small. These are elements people read and click, so the motion
 * has to be felt more than seen: a percent or two of scale and a lift in
 * border light. Anything bigger and the page is fidgeting rather than dancing.
 * Nothing here moves layout, so nothing reflows.
 */
[data-gdp-eyebrow],
#gdp-filter .gdp-chip,
#gdp-party-cta {
    transition: transform 160ms cubic-bezier(0.22, 1, 0.36, 1),
        border-color 160ms ease, box-shadow 160ms ease;
}

/* !important because the 3D-tilt rule above sets a 420ms transition with
 * !important on every card - a beat that eases over 420ms never arrives.
 * A playing card answers the kick in 160ms; the tilt on it rides the same
 * clock, which reads as energy rather than as a glitch. */
[data-gdp-card][data-gdp-lit="1"] {
    transition: transform 160ms cubic-bezier(0.22, 1, 0.36, 1),
        border-color 160ms ease, box-shadow 160ms ease !important;
}

html[data-gdp-pulse] [data-gdp-eyebrow] {
    transform: scale(1.035);
    border-color: rgba(56, 212, 255, 0.55);
}

html[data-gdp-pulse] #gdp-filter .gdp-chip {
    transform: translateY(-1px);
    border-color: rgba(56, 212, 255, 0.45);
}

/* The beat pulse stands down while the door is hailing or holding its larger
 * playing size, so the three are not fighting over one transform. It beats on
 * the resting button, which is the only state that has room to move. */
html[data-gdp-pulse] #gdp-party-cta:not([data-gdp-hail="1"]):not([data-gdp-on="1"]) {
    transform: translateY(-2px) scale(1.03);
    box-shadow: 0 14px 38px -12px rgba(237, 47, 212, 0.75);
}

html[data-gdp-pulse] [data-gdp-card][data-gdp-lit="1"] {
    box-shadow: 0 26px 60px -18px rgba(0, 0, 0, 0.85),
        0 0 0 1px rgba(56, 212, 255, 0.3),
        0 0 56px -12px rgba(56, 212, 255, 0.6) !important;
}

/* Calm and reduced motion opt out of the whole thing. */
@media (prefers-reduced-motion: reduce) {
    html[data-gdp-pulse] [data-gdp-eyebrow],
    html[data-gdp-pulse] #gdp-filter .gdp-chip,
    html[data-gdp-pulse] #gdp-party-cta {
        transform: none;
    }
}


/* ---------------------------------------------------------------- mobile
 *
 * Nothing over anything. On a phone the hero's transport is positioned
 * inside the record's container and lands on the sleeve; the record is
 * shrunk inside that same container so the buttons sit in clear space
 * below it. Its centre does not move, so the spin still runs true inside
 * the rings and the composition is unchanged apart from a smaller record.
 *
 * important, because the size is an inline style the site writes itself. */
@media (max-width: 760px) {
    [data-gdp-heroart] {
        width: 200px !important;
        height: 200px !important;
        left: 46px !important;
        top: 46px !important;
    }

    /* The "Play me" badge sits between the transport and the mix selector and
     * had nowhere to go: three pixels of clearance above it and fifteen
     * pixels of the selector lying across its bottom edge. The badge and the
     * transport are both absolutely placed, so the one thing in this stack
     * that can move without disturbing anything is the selector, which is in
     * normal flow - and everything below it follows. */
    [data-gdp-selrow] {
        margin-top: 22px !important;
    }
}

/* The broadcast pill on a phone.
 *
 * The pill centres its children and the builder hands them an explicit width
 * wider than the pill's own content box, so each line overflowed the padding
 * by about ten pixels a side and sat against the glass. Constraining the
 * children to the box is the whole fix; the text re-centres inside the
 * padding and the pill reads as one tidy object again. */
@media (max-width: 760px) {
    [data-gdp-pill] > *,
    [data-gdp-pill] > * > * {
        max-width: 100% !important;
    }

    [data-gdp-pill] #gdp-localtime {
        font-size: 10.5px;
        letter-spacing: 0.06em;
    }
}

/* ------------------------------------------------------------ display type
 *
 * Never split a word in the middle. The builder sets word-break: break-word
 * on all of its text, which is a reasonable default for a paragraph and a
 * disaster at display size: the first casualty here was the host's own name,
 * rendered SWEET / Y G on a phone. Big type wraps at spaces or not at all. */
[data-gdp-display],
[data-gdp-hostname] {
    word-break: normal !important;
    overflow-wrap: normal !important;
}

@media (max-width: 760px) {
    /* Sized from the viewport so the name lands on one line at any phone
     * width, and capped so it never grows past the size it was designed at.
     * The ratio comes from the measured line: 366px of text per 86px of font,
     * against a column that is 89% of the viewport. */
    [data-gdp-hostname],
    [data-gdp-hostname] span {
        font-size: min(14vw, 86px) !important;
        line-height: 1 !important;
        letter-spacing: -0.02em !important;
    }
}

/* ------------------------------------------------- party mode, hands off
 *
 * The player fades when nobody has touched anything for a few seconds, so
 * what is left on screen is the room. Anything in the overlay that is not
 * the canvas is HUD. Exit fades with the rest - any movement brings it all
 * straight back, and the canvas hold lifts at the same time so the floor
 * comes up to full strength as the controls go. */
[data-gdp-hud] {
    transition: opacity 0.65s ease;
}

html[data-gdp-idle="1"] [data-gdp-hud],
html[data-gdp-idle="1"] #gdp-req-fab,
html[data-gdp-idle="1"] #gdp-calm-btn {
    opacity: 0;
    pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
    [data-gdp-hud] {
        transition: none;
    }
}

/* The controls live at the foot of the room, not the middle of it.
 *
 * The overlay is a centred flex column, so the title, scrubber and
 * transport parked dead-centre - directly over the video wall and the
 * crowd, and first-time visitors had to wait out the idle fade before
 * the room showed itself at all. Re-anchoring the column puts the player
 * where a club puts it: down by the floor, under the scene. Everything
 * absolutely positioned in the overlay - the canvas, the ambience wash,
 * the exit button, the full-screen mix picker the title chevron opens -
 * ignores justify-content and stays where it is; only the three static
 * flex children move. Layout properties only: no opacity, transform or
 * filter on this container (see the ghost-rule lesson - the overlay's
 * descendants include composited subtrees that an ancestor effect would
 * silently take down with it). */
html[data-gdp-party="1"] div:has(> #gdp-party-room) {
    justify-content: flex-end !important;
    padding-bottom: calc(30px + env(safe-area-inset-bottom, 0px)) !important;
}

/* ==================================================================== film
 *
 * Everything above builds a room. This builds the *camera* — the layer that
 * decides whether the room reads as a rendering or as something shot.
 *
 * Two effects, both deliberately chosen because they cost nothing per frame:
 * this site already had a choppiness complaint, and the fix for that was
 * spending fewer milliseconds, not more. Grain is one cached SVG bitmap
 * translated on the GPU; the chromatic fringe is a static text-shadow. No
 * filters, no blurs, no per-frame JavaScript, nothing the compositor has to
 * re-rasterise.
 */

/* Emulsion.
 *
 * A pure-black digital ground is the single biggest tell that something was
 * rendered rather than photographed — real darkness has grain in it. One
 * fixed layer of fractal noise over the whole page puts a film stock under
 * the neon, and because it sits above the content rather than behind it, it
 * ties the canvas scenes and the DOM into one image instead of two.
 *
 * Sized 130% and translated rather than re-generated: transform is the one
 * property the compositor can animate without touching the main thread, so
 * the grain moves like film without costing a frame. */
html::after {
    content: "";
    position: fixed;
    top: -15%;
    left: -15%;
    width: 130%;
    height: 130%;
    pointer-events: none;
    z-index: 2147483300;
    /* Plain alpha, not mix-blend-mode.
     *
     * overlay looked marginally richer and cost the whole page its scroll.
     * A blended layer cannot be composited on its own: the browser has to
     * read back everything underneath and re-blend the full viewport - every
     * time this layer moves, which is ~6 times a second, AND every time the
     * content beneath it scrolls. On integrated graphics that is most of a
     * frame budget, spent on a texture at 5% opacity that nobody can
     * consciously see.
     *
     * Without the blend this is an ordinary composited layer: the transform
     * animation runs on the compositor thread and never touches the main
     * thread at all. Opacity is lifted a little to make up the contrast the
     * blend was providing. */
    opacity: 0.075;
    will-change: transform;
    animation: gdpGrain 0.7s steps(1, end) infinite;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

@keyframes gdpGrain {
    0%   { transform: translate3d(0, 0, 0) }
    25%  { transform: translate3d(-2%, 1.5%, 0) }
    50%  { transform: translate3d(1.5%, -2%, 0) }
    75%  { transform: translate3d(-1%, -1%, 0) }
    100% { transform: translate3d(2%, 1%, 0) }
}

/* Calm mode means "turn the room down", so the stock gets quieter with it
 * rather than staying at full strength over a dimmed scene. Reduced motion
 * keeps the grain — it is texture, not movement — and simply stops it
 * moving, which is the part that could bother anyone. */
html[data-gdp-calm="1"]::after {
    opacity: 0.03;
}

@media (prefers-reduced-motion: reduce) {
    html::after {
        animation: none;
    }
}

/* Lens.
 *
 * A hair of chromatic separation on display type only. Real anamorphic glass
 * cannot hold every wavelength on the same plane, and the eye reads that
 * failure as "this was photographed through something". Kept under two
 * pixels at the sizes this applies to (54px and up, per the runtime's own
 * measurement) so it registers as depth rather than as a broken font.
 *
 * Static shadows: no filter, no blur, no repaint after first paint. */
[data-gdp-display] {
    text-shadow:
        -0.011em 0 0 rgba(56, 212, 255, 0.3),
        0.011em 0 0 rgba(237, 47, 212, 0.24);
}

/* The host's name is the one piece of display type that is already a
 * gradient fill; a fringe under it fights the gradient instead of lifting
 * it. */
[data-gdp-hostname] {
    text-shadow: none;
}

/* The floor is a room, not a page with a room on it. While it is open the
 * body-level floating controls step out entirely — they are page furniture,
 * and the HUD inside the overlay already carries everything the room needs.
 * (The exit control is part of that HUD and is unaffected.) */
html[data-gdp-party="1"] #gdp-req-fab,
html[data-gdp-party="1"] #gdp-calm-btn {
    display: none !important;
}

/* ------------------------------------------------------------- a11y
 *
 * The skip link is the first thing in the tab order and invisible until it
 * has focus. Without it, reaching the archive grid by keyboard meant tabbing
 * through the whole navigation on every single page.
 */
#gdp-skip {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 2147483600;
    padding: 12px 18px;
    border-radius: 0 0 10px 0;
    background: #38d4ff;
    color: #07070a;
    font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
    font-size: 13px;
    font-weight: 800;
    letter-spacing: 0.02em;
    text-decoration: none;
    transform: translateY(-120%);
    transition: transform 0.16s ease;
}
#gdp-skip:focus {
    transform: translateY(0);
}
#main:focus {
    outline: none;
}

/* One visible focus ring for everything the site adds, in the site's own
   accent rather than the browser default, which is invisible on this ink. */
[data-gdp-card]:focus-visible,
#gdp-find input:focus-visible,
#gdp-req-fab:focus-visible,
#gdp-calm-btn:focus-visible,
#gdp-req button:focus-visible,
#gdp-req input:focus-visible {
    outline: 2px solid #38d4ff;
    outline-offset: 3px;
}

@media (prefers-reduced-motion: reduce) {
    #gdp-skip {
        transition: none;
    }
}

/* ------------------------------------------------- the second doorway
 *
 * There is a "Party Mode" button further down the page that does exactly what
 * the hero doorway does. Whether two entries should exist is a content call
 * and not one to make in a stylesheet - but them looking like two unrelated
 * controls is a design bug either way. This gives the lower one the same
 * treatment, so the page reads as having one door in two places rather than
 * two different buttons.
 *
 * Tagged from gdp-welcome.js by label, because the markup carries no stable
 * hook of its own.
 */
[data-gdp-doorway] {
    position: relative;
    isolation: isolate;
    /* 40px measured. Below the 44px that a thumb can reliably hit, and this
       is a primary destination, not a footnote. */
    min-height: 44px;
    border-radius: 999px !important;
    color: #f4f1e9 !important;
    background: linear-gradient(
        100deg,
        rgba(56, 212, 255, 0.85),
        rgba(237, 47, 212, 0.85)
    ) !important;
    transition: transform var(--gdp-med) var(--gdp-ease);
}
[data-gdp-doorway]::before {
    content: "";
    position: absolute;
    inset: 1px;
    z-index: -1;
    border-radius: 999px;
    background: #0b0b10;
}
[data-gdp-doorway]::after {
    content: "";
    position: absolute;
    inset: 1px;
    z-index: -1;
    border-radius: 999px;
    opacity: 0;
    background: radial-gradient(
        120% 150% at 50% 118%,
        rgba(56, 212, 255, 0.5),
        rgba(237, 47, 212, 0.22) 45%,
        transparent 72%
    );
    transition: opacity var(--gdp-med) var(--gdp-ease);
}
[data-gdp-doorway]:hover::after,
[data-gdp-doorway]:focus-visible::after {
    opacity: 1;
}
[data-gdp-doorway]:hover {
    transform: translateY(-1px);
}
[data-gdp-doorway]:focus-visible {
    outline: 2px solid #38d4ff;
    outline-offset: 3px;
}

/* ==================================================================
 * REFINEMENT PASS - hierarchy, depth, state clarity
 * ================================================================== */

/* ----------------------------------------------------- A. hero rhythm
 *
 * The order of importance in the hero is brand, then latest mix, then play,
 * then the doorway, then the broadcast countdown. The markup already carries
 * that order; what it lacked was air between the tiers, so everything arrived
 * at once and the eye had to sort it out. These are spacing and weight only -
 * no element moves, nothing is hidden.
 */

/* Measure. Long lines are the single most common reason a dark page reads as
 * heavy - the eye loses the return sweep. 66ch is the upper bound of
 * comfortable, and this only ever tightens. */
[data-framer-name="Hero Content"] p,
[data-framer-name="Opening"] p {
    max-width: 66ch;
}

/* The countdown is ritual, not the headline. It sat at the same visual weight
 * as the thing you are meant to press. Half a step back: the frame softens,
 * the numerals keep their size and contrast. Supporting the ritual, not
 * competing with the listening. */
[data-gdp-live-pill],
a[href*="ffm.to/globaldanceparty"] {
    transition: border-color var(--gdp-med) var(--gdp-ease),
        background-color var(--gdp-med) var(--gdp-ease);
}

/* -------------------------------------------- C. player state clarity
 *
 * State was carried almost entirely by the play/pause glyph. These add a
 * second, non-colour channel for each state - which is also the accessibility
 * requirement: never state by colour or motion alone.
 */

/* Loading. A control that has been pressed and has not answered yet is the
 * worst moment in any player: nothing distinguishes "working" from "broken".
 * A slow sheen across the transport says the press landed. */
[data-gdp-player="loading"] [data-gdp-transport],
[data-gdp-loading="1"] {
    position: relative;
    overflow: hidden;
}
[data-gdp-player="loading"] [data-gdp-transport]::after,
[data-gdp-loading="1"]::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(
        100deg,
        transparent 20%,
        rgba(56, 212, 255, 0.14) 50%,
        transparent 80%
    );
    transform: translateX(-100%);
    animation: gdpSheen 1.15s var(--gdp-enter) infinite;
}
@keyframes gdpSheen {
    to {
        transform: translateX(100%);
    }
}

/* Playing. A thin steady light under the transport - present, not flashing. */
[data-gdp-playing="1"] [data-gdp-transport] {
    box-shadow: 0 6px 22px -14px rgba(56, 212, 255, 0.85);
}

/* The record turns while it plays and holds still when it does not. It is the
 * oldest playback indicator there is and it needs no legend. Transform only,
 * so it is compositor work and costs no layout. */
[data-gdp-spin] {
    animation: gdpSpin 8s linear infinite;
    animation-play-state: paused;
    will-change: transform;
}
[data-gdp-playing="1"] [data-gdp-spin] {
    animation-play-state: running;
}
@keyframes gdpSpin {
    to {
        transform: rotate(360deg);
    }
}

/* Complete. */
[data-gdp-player="ended"] [data-gdp-transport] {
    box-shadow: none;
}

/* Transport buttons: tactile, and obviously pressable. Fast tier - this is
 * input answering back. */
[data-gdp-transport] button,
[aria-label^="Go to Mix"],
[aria-label="Next mix"],
[aria-label="Previous mix"] {
    transition: transform var(--gdp-fast) var(--gdp-ease),
        opacity var(--gdp-fast) var(--gdp-ease);
}
[aria-label="Next mix"]:hover,
[aria-label="Previous mix"]:hover {
    transform: scale(1.08);
}
[aria-label="Next mix"]:active,
[aria-label="Previous mix"]:active {
    transform: scale(0.96);
}

/* Every icon-only control the layer can reach gets a visible ring. */
[data-gdp-transport] button:focus-visible,
[aria-label^="Go to Mix"]:focus-visible,
[aria-label="Next mix"]:focus-visible,
[aria-label="Previous mix"]:focus-visible,
[aria-label="Leave party mode"]:focus-visible {
    outline: 2px solid #38d4ff;
    outline-offset: 3px;
    border-radius: 999px;
}

/* --------------------------------------------- D. archive as discovery
 *
 * The grid was a database: 74 equal cells, equal weight, equal silence. These
 * do not restructure it - they give it a hierarchy to read and something to
 * answer back when touched.
 */

/* The card lifts, and its art brightens a little under it. Two properties,
 * both composited. */
[data-gdp-card] {
    transition: transform var(--gdp-med) var(--gdp-ease),
        box-shadow var(--gdp-med) var(--gdp-ease);
}
[data-gdp-card]:hover {
    transform: translateY(-4px);
    box-shadow: 0 22px 44px -26px rgba(0, 0, 0, 0.9),
        0 0 0 1px rgba(56, 212, 255, 0.16);
}
[data-gdp-card] img {
    transition: filter var(--gdp-med) var(--gdp-ease);
}
[data-gdp-card]:hover img {
    filter: brightness(1.06) saturate(1.06);
}
[data-gdp-card]:focus-visible {
    outline: 2px solid #38d4ff;
    outline-offset: 4px;
}

/* The newest mix is the recommended one, and it should not have to shout to
 * say so. A hairline of brand light along its edge, permanently. */
[data-gdp-card][data-gdp-latest="1"] {
    box-shadow: 0 0 0 1px rgba(56, 212, 255, 0.34);
}
[data-gdp-card][data-gdp-latest="1"]:hover {
    box-shadow: 0 22px 44px -26px rgba(0, 0, 0, 0.9),
        0 0 0 1px rgba(56, 212, 255, 0.5);
}

/* Metadata rhythm: the mix number leads, the rest recedes. Tabular numerals so
 * counts and durations line up down the column instead of shimmering. */
[data-gdp-card] [data-gdp-meta] {
    font-variant-numeric: tabular-nums;
}

/* Genre chips: editorial rather than functional. The active one is filled;
 * the rest are outlines. Colour is never the only signal - the active chip is
 * also the only filled one, and carries aria-pressed. */
#gdp-filter .gdp-chip,
#gdp-coll .gdp-chip {
    transition: border-color var(--gdp-fast) var(--gdp-ease),
        color var(--gdp-fast) var(--gdp-ease),
        background-color var(--gdp-fast) var(--gdp-ease),
        transform var(--gdp-fast) var(--gdp-ease);
}
#gdp-filter .gdp-chip:active,
#gdp-coll .gdp-chip:active {
    transform: scale(0.97);
}

/* Surprise Me: playful, restrained. It brightens; it does not bounce. */
[data-gdp-surprise] {
    transition: transform var(--gdp-fast) var(--gdp-ease),
        box-shadow var(--gdp-med) var(--gdp-ease);
}
[data-gdp-surprise]:hover {
    transform: translateY(-1px);
    box-shadow: 0 0 22px -8px rgba(56, 212, 255, 0.7);
}
[data-gdp-surprise]:active {
    transform: translateY(0) scale(0.98);
}

/* ------------------------------------------------- E. legacy pacing
 *
 * Chapters, not a list. A hairline rule and a wider gap turn six paragraphs
 * into six milestones without touching a word of the copy.
 */
[data-framer-name="Era"] {
    position: relative;
}
[data-framer-name="Era"] + [data-framer-name="Era"] {
    margin-top: clamp(28px, 4.5vw, 56px);
    padding-top: clamp(24px, 4vw, 44px);
    border-top: 1px solid rgba(244, 241, 233, 0.08);
}
[data-gdp-attrib] {
    letter-spacing: 0.01em;
}

/* ------------------------------------------------ F. depth, not void
 *
 * Darkness reads as space when something sits in front of something else, and
 * as a void when everything is on one plane. One soft vignette, painted on the
 * body's own backdrop, gives the page a centre.
 */
body::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background: radial-gradient(
        120% 90% at 50% 0%,
        rgba(56, 212, 255, 0.05),
        transparent 58%
    );
}

/* Glow is reserved. Anything that is live, playing or a way in may carry it;
 * nothing else may. */
[data-gdp-live="1"],
[data-gdp-playing="1"] [data-gdp-transport] {
    /* declared above - listed here as the whitelist, deliberately */
}

/* ------------------------------------------------ H. responsive
 *
 * Decoration goes first on small screens; hierarchy is what carries the
 * premium feeling there, not density.
 */
@media (max-width: 760px) {
    /* Hover lift is meaningless on touch and leaves cards stuck in a hovered
       state after a tap. */
    [data-gdp-card]:hover {
        transform: none;
        box-shadow: none;
    }
    [data-gdp-card]:hover img {
        filter: none;
    }
    /* The vignette is a desktop nicety; on a phone it is a gradient over the
       whole screen for no legibility gain. */
    body::before {
        display: none;
    }
    /* Comfortable targets. */
    [data-gdp-surprise],
    #gdp-filter .gdp-chip,
    #gdp-coll .gdp-chip {
        min-height: 44px;
    }
}

/* Anything that animates forever stops when asked. */
@media (prefers-reduced-motion: reduce) {
    [data-gdp-spin],
    [data-gdp-player="loading"] [data-gdp-transport]::after,
    [data-gdp-loading="1"]::after {
        animation: none;
    }
    [data-gdp-card]:hover {
        transform: none;
    }
}

/* The way out of the room.
 *
 * Measured 99x42 on a phone. Two pixels under the minimum sounds pedantic
 * until you remember which control this is: the one a visitor reaches for
 * when they want the immersive fullscreen thing to stop. It already cost
 * one round of "there is no easy way out of party mode on mobile", and a
 * target that small is part of why. */
/* Second round of the same complaint, different cause: the pill was the
 * right size but wore the room's own colours - a near-black plate with a
 * faint ring, sitting on a near-black scene full of moving light. On a
 * static screenshot it reads; over beams and a video wall it camouflages.
 * The plate now blurs whatever the scene paints behind it (that
 * separation works against ANY backdrop, which a fixed colour cannot
 * promise) and carries a ring bright enough to read as a control. This
 * button is a leaf - the ancestor-effect rule (9g) is about containers
 * with composited subtrees, not about styling a childless control. */
[aria-label="Leave party mode"] {
    min-height: 44px;
    min-width: 44px;
    background: rgba(10, 10, 12, 0.55) !important;
    border: 1px solid rgba(244, 241, 233, 0.42) !important;
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    box-shadow:
        0 2px 16px rgba(0, 0, 0, 0.5),
        0 0 0 4px rgba(10, 10, 12, 0.25);
}
@media (max-width: 760px) {
    [aria-label="Leave party mode"] {
        top: calc(14px + env(safe-area-inset-top, 0px)) !important;
        right: 14px !important;
        min-height: 48px;
        min-width: 48px;
    }
}

/* The navbar resume chip and the floor bar are the same offer in two places,
 * which is the exact objection the design brief raised against a floor bar.
 * Answered by standing the chip down rather than giving up the bar: it is
 * hidden only while the bar is actually up and carrying something. */
html[data-gdp-bar="1"] [data-gdp-chip] {
    display: none !important;
}

/* Nothing should end underneath the bar. */
html[data-gdp-bar="1"] body {
    padding-bottom: 72px;
}
@media (max-width: 760px) {
    html[data-gdp-bar="1"] body {
        padding-bottom: 64px;
    }
}

/* ------------------------------------------------- the Floor and the Booth
 *
 * Two views of the same homepage, hide/show only - no node is ever removed,
 * so hydration and the audio shield are untouched. The floor keeps the
 * turntable (a record you tap is the whole tutorial); the crate, cue rail,
 * tracklist, selector and mixer are booth equipment.
 */
html[data-gdp-view="floor"] [data-gdp-booth="1"] {
    display: none !important;
}
/* Zero-flash versions of the same hides. The JS tags land a few hundred
 * milliseconds after first paint, which on a cold load let the crate flash
 * before collapsing. :has() needs no tags - the crate column is the child
 * containing crate items, the rail is the child containing the Right Rail -
 * so browsers that support it (all modern ones) never show the flash, and
 * the tagged rules above remain as the fallback for the rest. */
html[data-gdp-view="floor"] [data-framer-name="Booth Row"] > div:has(.gdp-crate-item) {
    display: none !important;
}
html[data-gdp-view="floor"]
    [data-framer-name="Booth Row"]
    > div:has([data-framer-name="Right Rail"]),
html[data-gdp-view="floor"] [data-framer-name="Right Rail"] {
    display: none !important;
}
/* The mix selector (previous / choose a mix / next) stays in BOTH views.
 * It was hidden on the desktop floor at first, but a player where you can
 * tap the record and not choose the record is a radio, not a player - the
 * operator called it: picking a mix is listener furniture, not booth gear.
 * No rule needed; kept as a comment so nobody re-hides it as "clutter". */
html[data-gdp-view="floor"] #gdp-deckbar {
    display: none !important;
}
/* The lone turntable centres in the row the crate used to share. */
html[data-gdp-view="floor"] [data-framer-name="Booth Row"] {
    justify-content: center !important;
}
/* The turntable is ghosted now, its "PLAY ME" caption with it - the row
 * holds one centred child, the club door. */
html[data-gdp-view="floor"] [data-framer-name="Booth Row"] {
    padding-bottom: 26px !important;
    align-items: center !important;
    gap: clamp(28px, 4vw, 60px) !important;
}

/* One door: once the Dance Floor doorway exists, the older "Party Mode"
 * button is the same action twice.
 *
 * Ghost-hidden, NOT display:none. The doorway works by relaying a click to
 * this button, and Framer's press handler does not fire on an element that
 * has no layout - verified directly: a click on the connected button under
 * display:none did nothing, the same click with the button merely clipped
 * opens the room. So it keeps its box and loses everything else: invisible,
 * unhittable, out of the reading order (gdp-view.js adds aria-hidden and
 * tabindex=-1, which CSS cannot). */
/* THE BUTTON, NEVER ITS CONTAINER.
 *
 * The first version of this rule ghosted [data-framer-name="Party Mode"] -
 * the container - and the Framer component mounts its fullscreen overlay
 * INLINE inside that container, not through a portal. Ancestor opacity
 * composites the whole subtree, position:fixed children included: the room
 * mounted, active() returned true, the exit button existed, every DOM check
 * passed - and not one pixel of the club was visible. The operator found it
 * the honest way, by pressing the button and seeing nothing.
 *
 * So the ghost styles land only on the trigger buttons themselves, tagged
 * data-gdp-ghost by gdp-view.js. The container stays untouched, because the
 * room lives in it. */
html[data-gdp-door="1"] [data-gdp-ghost],
html[data-gdp-door="1"] button[data-gdp-doorway] {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    overflow: hidden !important;
    clip-path: inset(50%) !important;
    opacity: 0 !important;
    pointer-events: none !important;
    margin: 0 !important;
    padding: 0 !important;
    border: 0 !important;
}


/* The compact CTA slot is a RELAY now, not a control. The scene door is
 * the homepage's one entry at every width; six files still open the
 * floor by calling #gdp-party-cta.click(), which works on a
 * display:none element (a programmatic click needs no layout - the
 * layout rule that DOES need it is the ghosted FRAMER button above,
 * which the cta presses via real.click()). The booth toggle that once
 * shared this slot is gone with the booth view. */
#gdp-ctaslot {
    display: none !important;
}

/* The compact door lives on inside the hidden slot only as a click
 * relay; its old prominence styling and nudge animation went with the
 * slot's visibility. The scene door below carries the prominence now. */

/* The explanation. Appears above the door on hover or keyboard focus, after
 * a beat so it never flashes on a pass-over; wired to aria-describedby by
 * gdp-view.js so a screen reader gets the same sentence without the hover. */
.gdp-tip {
    position: absolute;
    bottom: calc(100% + 13px);
    left: 50%;
    transform: translateX(-50%) translateY(5px);
    width: max-content;
    max-width: min(280px, 84vw);
    padding: 11px 14px;
    border-radius: 11px;
    background: rgba(13, 13, 19, 0.97);
    border: 1px solid rgba(244, 241, 233, 0.16);
    box-shadow: 0 18px 44px -18px rgba(0, 0, 0, 0.9);
    font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
    font-size: 12.5px;
    font-weight: 500;
    line-height: 1.5;
    letter-spacing: 0.01em;
    text-transform: none;
    text-align: left;
    color: rgba(244, 241, 233, 0.88);
    white-space: normal;
    opacity: 0;
    pointer-events: none;
    z-index: 6;
    transition: opacity var(--gdp-fast) var(--gdp-ease) 0.22s,
        transform var(--gdp-fast) var(--gdp-ease) 0.22s;
}
.gdp-tip::after {
    content: "";
    position: absolute;
    top: 100%;
    left: 50%;
    width: 9px;
    height: 9px;
    margin: -5px 0 0 -5px;
    transform: rotate(45deg);
    background: rgba(13, 13, 19, 0.97);
    border-right: 1px solid rgba(244, 241, 233, 0.16);
    border-bottom: 1px solid rgba(244, 241, 233, 0.16);
}
#gdp-party-cta:hover .gdp-tip,
#gdp-party-cta:focus-visible .gdp-tip {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}
@media (prefers-reduced-motion: reduce) {
    .gdp-tip {
        transition-duration: 1ms;
    }
}

/* ------------------------------------------------------- the club door
 *
 * ONE door, the homepage's single primary interaction, at every width -
 * sitting on the page's own video backdrop. The party is visible
 * through it: the reel on the back wall, silhouettes in front, the
 * neon over the glass, light spilling under the door. The turntable
 * that used to share this row is ghosted (its audio component lives
 * on); the booth door and booth view are gone.
 *
 * The ghost: everything invisible, nothing unmounted. Same clip-path
 * recipe as the party-mode button above - the component keeps its
 * layout life so React hydration and the audio bus never notice. */
html[data-gdp-view="floor"] [data-framer-name="Booth Row"] > [data-gdp-keep] {
    position: absolute !important;
    width: 1px !important;
    height: 1px !important;
    overflow: hidden !important;
    clip-path: inset(50%) !important;
    opacity: 0 !important;
    pointer-events: none !important;
    margin: 0 !important;
    /* visibility, not just opacity: the deck's platter is a tabbable
     * role="slider" - without this, keyboard users tab through an
     * invisible turntable and screen readers announce a player that is
     * not there. visibility removes the subtree from the tab order and
     * the accessibility tree while React and the audio element live on. */
    visibility: hidden !important;
}
html[data-gdp-view="floor"] .gdp-scene {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 15px;
    position: relative;
    isolation: isolate;
    width: clamp(260px, 34vw, 430px);
    max-width: calc(100vw - 48px);
    padding: 0;
    border: 0;
    background: transparent;
    cursor: pointer;
    font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
    color: #f4f1e9;
    transition: transform var(--gdp-med) var(--gdp-ease);
}
/* No hover hop: the leaves answer the hover - a door swings, it does
 * not bounce. */
.gdp-scene:focus-visible {
    outline: 2px solid #38d4ff;
    outline-offset: 5px;
    border-radius: 18px;
}
/* The frame height literal appears TWICE - here and in the .spill top
 * calc below. Edit them in lockstep or the light detaches from the
 * door.
 *
 * ARCHITECTURE, not a card: a steel lintel carrying the lit sign, and
 * below it the doorway - the same double door the club's 3D walk-in
 * owns (dark lacquered steel leaves, gold hairlines, the brand-cycling
 * light seam between them). The leaves stand ajar at rest with the
 * party visible through the gap; hover leans them; the click swings
 * them wide as the club fades in behind. */
.gdp-scene .frame {
    position: relative;
    width: min(100%, 386px);
    height: clamp(320px, 36vw, 470px);
    display: flex;
    flex-direction: column;
}
#gdp-scene-floor .lintel {
    position: relative;
    flex: none;
    height: 64px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 10px 10px 0 0;
    background: linear-gradient(180deg, #191921, #0d0d13);
    border: 1px solid rgba(244, 241, 233, 0.14);
    border-bottom: 1px solid rgba(217, 180, 92, 0.45);
    box-shadow: inset 0 1px 0 rgba(244, 241, 233, 0.07);
}
#gdp-scene-floor .doorway {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    border-radius: 0 0 10px 10px;
    padding: 2px;
    background: linear-gradient(
        165deg,
        rgba(56, 212, 255, 0.7),
        rgba(237, 47, 212, 0.55)
    );
    /* The stage the leaves swing on. */
    perspective: 950px;
}
.gdp-scene .peek {
    position: absolute;
    inset: 2px;
    border-radius: 0 0 8px 8px;
    overflow: hidden;
    background: #07070b;
    display: flex;
    align-items: center;
    justify-content: center;
}
/* The leaves. Hinged on the jambs, swung by rotateY - inner edges
 * recede into the room, so the ajar gap between them IS the view of
 * the party. Lacquered steel, two recessed panels, a gold push bar,
 * a porthole glowing with the light inside. */
#gdp-scene-floor .leaf {
    position: absolute;
    top: 2px;
    bottom: 2px;
    width: calc(50% - 2px);
    z-index: 2;
    background: linear-gradient(170deg, #191920 0%, #101016 55%, #0b0b10 100%);
    border: 1px solid rgba(244, 241, 233, 0.1);
    backface-visibility: hidden;
    transition: transform 480ms var(--gdp-ease);
    will-change: transform;
}
#gdp-scene-floor .leaf--l {
    left: 2px;
    width: calc(48% - 2px);
    border-radius: 0 0 0 8px;
    border-right: 1px solid rgba(217, 180, 92, 0.55);
    transform-origin: left center;
    /* Positive with a left origin = the free edge recedes INTO the
     * room (z = -x sin θ). Getting this sign wrong swings the doors
     * out at the visitor's face. */
    transform: rotateY(22deg);
}
#gdp-scene-floor .leaf--r {
    right: 2px;
    width: calc(48% - 2px);
    border-radius: 0 0 8px 0;
    border-left: 1px solid rgba(217, 180, 92, 0.55);
    transform-origin: right center;
    transform: rotateY(-22deg);
}
/* Stile-and-rail panels: recessed by shadow, proud by hairline. */
#gdp-scene-floor .leaf .lp {
    position: absolute;
    left: 14%;
    right: 14%;
    border-radius: 3px;
    background: linear-gradient(175deg, #0d0d12, #121218);
    box-shadow:
        inset 0 2px 6px rgba(0, 0, 0, 0.7),
        0 1px 0 rgba(244, 241, 233, 0.05);
}
#gdp-scene-floor .leaf .lp--a {
    top: 7%;
    height: 34%;
}
#gdp-scene-floor .leaf .lp--b {
    top: 58%;
    height: 34%;
}
#gdp-scene-floor .leaf .bar {
    position: absolute;
    top: 48.5%;
    left: 18%;
    right: 18%;
    height: 7px;
    border-radius: 4px;
    background: linear-gradient(180deg, #e3c47c, #8a6a30);
    box-shadow: 0 2px 5px rgba(0, 0, 0, 0.6);
}
#gdp-scene-floor .leaf .port {
    position: absolute;
    top: 16%;
    left: 50%;
    width: 34%;
    aspect-ratio: 1;
    margin-left: -17%;
    border-radius: 50%;
    border: 2px solid rgba(217, 180, 92, 0.55);
    background:
        radial-gradient(
            60% 60% at 50% 42%,
            rgba(237, 47, 212, 0.5),
            rgba(56, 212, 255, 0.28) 60%,
            rgba(7, 7, 11, 0.9) 100%
        )
        #0a0a10;
    box-shadow: inset 0 0 14px rgba(56, 212, 255, 0.35);
}
/* The light seam where the leaves meet - the exact signature the 3D
 * door carries, cycling the brand colours on the same slow clock. */
#gdp-scene-floor .seam {
    position: absolute;
    top: 2px;
    bottom: 2px;
    left: 50%;
    width: 3px;
    margin-left: -1.5px;
    z-index: 3;
    pointer-events: none;
    background: #38d4ff;
    filter: blur(2px);
    opacity: 0.85;
    animation: gdpSeamCycle 18s linear infinite;
    transition: opacity 480ms var(--gdp-ease);
}
@keyframes gdpSeamCycle {
    0%,
    100% {
        background-color: #38d4ff;
        box-shadow: 0 0 12px rgba(56, 212, 255, 0.9);
    }
    33% {
        background-color: #ed2fd4;
        box-shadow: 0 0 12px rgba(237, 47, 212, 0.9);
    }
    66% {
        background-color: #d9b45c;
        box-shadow: 0 0 12px rgba(217, 180, 92, 0.9);
    }
}
/* Hover leans the doors open; the click (gdp-club3d adds
 * .gdp-door-opening) swings them wide and the seam dissolves - open
 * doors have no seam. */
@media (hover: hover) {
    #gdp-scene-floor:hover .leaf--l,
    #gdp-scene-floor:focus-visible .leaf--l {
        transform: rotateY(42deg);
    }
    #gdp-scene-floor:hover .leaf--r,
    #gdp-scene-floor:focus-visible .leaf--r {
        transform: rotateY(-42deg);
    }
}
#gdp-scene-floor.gdp-door-opening .leaf--l {
    transform: rotateY(88deg) !important;
}
#gdp-scene-floor.gdp-door-opening .leaf--r {
    transform: rotateY(-88deg) !important;
}
#gdp-scene-floor.gdp-door-opening .seam {
    opacity: 0;
}
#gdp-scene-floor.gdp-door-opening .spill {
    opacity: 1;
    animation: none;
}
@media (prefers-reduced-motion: reduce) {
    #gdp-scene-floor .leaf {
        transition: none;
    }
    #gdp-scene-floor .seam {
        animation: none;
    }
}
/* Through the left door: the reel on the wall, silhouettes in front. The
   .wall's own glow is the no-video fallback on weak devices. */
#gdp-scene-floor .wall {
    position: absolute;
    inset: 0;
    background: radial-gradient(
            85% 60% at 50% 26%,
            rgba(237, 47, 212, 0.32),
            rgba(56, 212, 255, 0.14) 55%,
            transparent 80%
        )
        #07070b;
}
#gdp-scene-floor .wall video {
    width: 100%;
    height: 100%;
    object-fit: cover;
    /* No CSS filter on the video: a filtered <video> repaints through
     * the filter every frame, which is real cost on integrated GPUs.
     * The grade lives on the static overlay below instead. */
}
#gdp-scene-floor .wall::after {
    content: "";
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(
        180deg,
        rgba(7, 7, 11, 0.4),
        rgba(7, 7, 11, 0.08) 40%,
        rgba(7, 7, 11, 0.45) 100%
    );
}
#gdp-scene-floor .beamsL {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: linear-gradient(
            115deg,
            transparent 32%,
            rgba(56, 212, 255, 0.14) 45%,
            transparent 58%
        ),
        linear-gradient(
            245deg,
            transparent 30%,
            rgba(237, 47, 212, 0.12) 44%,
            transparent 58%
        );
    /* No blend mode: screen-blending over a playing video forces the
     * compositor off its fast path for every frame. */
}
#gdp-scene-floor .crowd {
    position: absolute;
    left: 0;
    right: 0;
    bottom: -2px;
    width: 100%;
    height: 38%;
}
/* Light under the door. Breathes, slowly - the slow tier of the motion
   system - and holds still under calm mode or reduced motion. */
#gdp-scene-floor .spill {
    position: absolute;
    left: 14%;
    right: 14%;
    top: calc(clamp(320px, 36vw, 470px) - 4px);
    height: 22px;
    border-radius: 50%;
    background: radial-gradient(
        50% 100% at 50% 0%,
        rgba(56, 212, 255, 0.55),
        rgba(237, 47, 212, 0.3) 55%,
        transparent 85%
    );
    filter: blur(6px);
    opacity: 0.55;
    pointer-events: none;
    animation: gdpSpillBreathe 4.5s ease-in-out infinite alternate;
}
@keyframes gdpSpillBreathe {
    from {
        opacity: 0.4;
    }
    to {
        opacity: 0.8;
    }
}
#gdp-scene-floor:hover .spill {
    opacity: 1;
    animation: none;
}
html[data-gdp-calm="1"] #gdp-scene-floor .spill {
    animation: none;
}
/* Captions */
.gdp-scene .cap {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 3px;
    line-height: 1.2;
}
.gdp-scene .cap .t {
    font-size: 15px;
    font-weight: 800;
    letter-spacing: 0.17em;
    text-transform: uppercase;
}
.gdp-scene .cap .s {
    font-size: 12.5px;
    font-weight: 600;
    letter-spacing: 0.02em;
    color: rgba(244, 241, 233, 0.62);
    transition: color var(--gdp-fast) var(--gdp-ease);
}
#gdp-scene-floor:hover .cap .s {
    color: #38d4ff;
}
.gdp-scene .arw {
    display: inline-block;
    transition: transform var(--gdp-fast) var(--gdp-ease);
}
.gdp-scene:hover .arw {
    transform: translateX(3px);
}
/* Scene tooltips ride the shared .gdp-tip; these show them. */
.gdp-scene:hover .gdp-tip,
.gdp-scene:focus-visible .gdp-tip {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
}
@media (prefers-reduced-motion: reduce) {
    .gdp-scene,
    .gdp-scene .arw,
    #gdp-scene-floor .wall video {
        transition: none;
    }
    #gdp-scene-floor .spill {
        animation: none;
    }
}

/* Party mode: the spinning logo disc is retired (operator call - it covered
 * the crowd and the video wall, and made the room read as a player skin).
 * Tagged at mount by gdp-party.js; the component's rotation loop keeps
 * writing to the hidden node harmlessly. */
html[data-gdp-party="1"] [data-gdp-disc] {
    display: none !important;
}

/* -------------------------------------------- the neon over the club door
 *
 * A tester could not find the dance floor. A lit sign over the door is
 * the one signal every human reads as "this is the entrance": cyan neon
 * on the glass, steady glow, the occasional real-neon stutter. */
#gdp-scene-floor .floor-neon {
    text-align: center;
    pointer-events: none;
    font-size: clamp(16px, 2vw, 22px);
    font-weight: 800;
    letter-spacing: 0.3em;
    line-height: 1.05;
    color: #b7ecff;
    text-shadow:
        0 0 6px rgba(56, 212, 255, 0.9),
        0 0 16px rgba(56, 212, 255, 0.65),
        0 0 34px rgba(56, 212, 255, 0.4);
    animation: gdpNeonHum 9s linear infinite;
}

#gdp-scene-floor .floor-neon i {
    display: block;
    font-style: normal;
    font-size: 0.52em;
    letter-spacing: 0.44em;
    margin-top: 2px;
    color: #d8f4ff;
}

/* Real neon is steady with the rare stutter - not a strobe. */
@keyframes gdpNeonHum {
    0%,
    100% {
        opacity: 1;
    }
    62% {
        opacity: 1;
    }
    63% {
        opacity: 0.55;
    }
    64% {
        opacity: 1;
    }
    64.8% {
        opacity: 0.7;
    }
    65.6% {
        opacity: 1;
    }
}

@media (prefers-reduced-motion: reduce) {
    #gdp-scene-floor .floor-neon {
        animation: none;
    }
}

/* ---------------------------------------- the door's broadcast badge
 *
 * The secondary countdown, ON the entrance (gdp-live.js mounts it into
 * the door's caption): before the show it counts down; when the /api/
 * live poll says Glenn is actually broadcasting it turns into the live
 * state. Never driven by the clock alone - a schedule is not a signal. */
.gdp-door-live {
    display: inline-flex;
    align-items: center;
    gap: 7px;
    margin-top: 2px;
    padding: 4px 12px;
    border-radius: 999px;
    border: 1px solid rgba(244, 241, 233, 0.18);
    background: rgba(11, 11, 16, 0.72);
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: rgba(244, 241, 233, 0.72);
}
.gdp-door-live b {
    font-weight: 800;
    color: #f4f1e9;
}
.gdp-door-live .dot {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(244, 241, 233, 0.45);
    flex: none;
}
.gdp-door-live[data-on="1"] {
    border-color: rgba(255, 77, 94, 0.65);
    background: rgba(56, 8, 14, 0.8);
    color: #ffd7db;
}
.gdp-door-live[data-on="1"] .dot {
    background: #ff4d5e;
    box-shadow: 0 0 8px rgba(255, 77, 94, 0.9);
    animation: gdpLiveDotPulse 1.6s ease-in-out infinite;
}
@keyframes gdpLiveDotPulse {
    0%,
    100% {
        opacity: 1;
    }
    50% {
        opacity: 0.45;
    }
}
@media (prefers-reduced-motion: reduce) {
    .gdp-door-live[data-on="1"] .dot {
        animation: none;
    }
}

/* While Glenn is live the whole door wears it: the neon warms from cyan
 * to the on-air red so the entrance itself announces the show. */
html[data-gdp-onair="1"] #gdp-scene-floor .floor-neon {
    color: #ffc9ce;
    text-shadow:
        0 0 6px rgba(255, 77, 94, 0.95),
        0 0 16px rgba(255, 77, 94, 0.7),
        0 0 34px rgba(255, 77, 94, 0.45);
}
/* The nav pill's compiled countdown yields to the live badge gdp-live.js
 * paints - two clocks on one pill disagree eventually. */
html[data-gdp-onair="1"] .gdp-count-in {
    display: none !important;
}
.gdp-pill-onair {
    display: none;
}
html[data-gdp-onair="1"] .gdp-pill-onair {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-weight: 800;
    letter-spacing: 0.14em;
    color: #ffd7db;
}
html[data-gdp-onair="1"] .gdp-pill-onair .dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    background: #ff4d5e;
    box-shadow: 0 0 8px rgba(255, 77, 94, 0.9);
    animation: gdpLiveDotPulse 1.6s ease-in-out infinite;
}


/* --------------------------------------- the page under the club
 *
 * While the 3D room is open the page beneath it is 100% occluded but
 * was still PAINTING: the ambient canvas, the ghosted player's
 * visualiser, the countdown ticking, the reel compositing. On
 * integrated GPUs that background work was the measured difference
 * between a smooth walk-in and a choppy one. Hidden surfaces cost
 * nothing; everything returns the instant the attribute clears. */
/* Keyed on data-gdp-pagehide, NOT data-gdp-club3d: the bridge sets
 * this one ~650ms after launch, once the overlay's crossfade is done -
 * hiding the page at t=0 blanked the door-swing the crossfade exists
 * to show. */
html[data-gdp-pagehide] #main,
html[data-gdp-pagehide] #gdp-floorbar,
html[data-gdp-pagehide] #gdp-req-fab,
html[data-gdp-pagehide] #gdp-calm-btn {
    visibility: hidden !important;
}
html[data-gdp-pagehide] #gdp-room {
    display: none !important;
}

/* Coming back from the club: the floorbar is now the page's ONE
 * player, and a visitor who just left a fullscreen room needs to be
 * SHOWN where the music lives. Four seconds of unmissable edge light
 * and a lift, then it settles back into furniture. */
html[data-gdp-returned="1"] #gdp-floorbar {
    box-shadow:
        0 -1px 0 rgba(56, 212, 255, 0.9),
        0 -10px 34px -8px rgba(56, 212, 255, 0.55);
    animation: gdpBarHello 0.9s var(--gdp-ease) 2;
}
@keyframes gdpBarHello {
    0%,
    100% {
        transform: translateY(0);
    }
    30% {
        transform: translateY(-7px);
    }
}
@media (prefers-reduced-motion: reduce) {
    html[data-gdp-returned="1"] #gdp-floorbar {
        animation: none;
    }
}

/* ------------------------------------------------- the marquee (Option A)
 *
 * The official logo - the art that lived on the retired disc - mounted
 * above the door as the club's own sign: warm backlight, the slowest
 * breathing on the page. The brand IS the venue; the sign is part of
 * the door button, so touching it walks you in. */
#gdp-scene-floor .marquee {
    position: relative;
    width: clamp(210px, 74%, 320px);
    aspect-ratio: 1;
    margin-bottom: -26px;
    flex: none;
    pointer-events: none;
}
#gdp-scene-floor .marquee::before {
    content: "";
    position: absolute;
    inset: -14%;
    border-radius: 50%;
    background: radial-gradient(
        50% 50% at 50% 54%,
        rgba(120, 80, 200, 0.28),
        rgba(237, 47, 212, 0.1) 55%,
        transparent 75%
    );
    filter: blur(12px);
    animation: gdpMarqueeBreathe 6.5s ease-in-out infinite alternate;
}
#gdp-scene-floor .marquee img {
    position: relative;
    width: 100%;
    height: 100%;
    object-fit: contain;
    filter: drop-shadow(0 10px 26px rgba(0, 0, 0, 0.65));
}
@keyframes gdpMarqueeBreathe {
    from {
        opacity: 0.55;
    }
    to {
        opacity: 1;
    }
}
/* While Glenn is live the sign catches the on-air warmth too. */
html[data-gdp-onair="1"] #gdp-scene-floor .marquee::before {
    background: radial-gradient(
        50% 50% at 50% 54%,
        rgba(255, 77, 94, 0.4),
        rgba(237, 47, 212, 0.12) 55%,
        transparent 75%
    );
}
@media (prefers-reduced-motion: reduce) {
    #gdp-scene-floor .marquee::before {
        animation: none;
        opacity: 0.8;
    }
}

/* The pill retires: the board beside the door is the schedule surface
 * now. Hidden, not removed - the anchor stays for React's hydration
 * and the live layer's guards stay harmless. */
html[data-gdp-view="floor"] [data-framer-name="Live Pill"] {
    display: none !important;
}

/* ------------------------------------------------------ the lineup board
 *
 * Bolted beside the door at eye height, the way a club posts tonight's
 * show at the entrance: ink plate, gold hairline frame, the countdown
 * ticking. When the /api/live verdict flips it turns into the red
 * ON AIR way in. */
.gdp-board {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 7px;
    align-self: center;
    width: clamp(168px, 15vw, 206px);
    padding: 20px 18px 16px;
    border-radius: 12px;
    border: 1px solid rgba(217, 180, 92, 0.5);
    background:
        linear-gradient(180deg, rgba(25, 25, 33, 0.96), rgba(11, 11, 16, 0.96));
    box-shadow:
        inset 0 1px 0 rgba(244, 241, 233, 0.07),
        0 18px 44px -18px rgba(0, 0, 0, 0.8);
    cursor: pointer;
    text-align: center;
    font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
    color: #f4f1e9;
    transition:
        transform var(--gdp-med) var(--gdp-ease),
        border-color var(--gdp-med) var(--gdp-ease);
}
.gdp-board:hover {
    transform: translateY(-3px);
    border-color: rgba(217, 180, 92, 0.9);
}
.gdp-board:focus-visible {
    outline: 2px solid #38d4ff;
    outline-offset: 4px;
}
.gdp-board .bk {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 10px;
    font-weight: 800;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: rgba(217, 180, 92, 0.9);
}
.gdp-board .bh {
    font-size: 34px;
    font-weight: 800;
    letter-spacing: -0.01em;
    line-height: 1.05;
}
.gdp-board .bh .bz {
    display: inline-block;
    margin-left: 6px;
    font-size: 13px;
    font-weight: 800;
    letter-spacing: 0.14em;
    color: rgba(244, 241, 233, 0.6);
}
.gdp-board .bc {
    font-size: 12.5px;
    line-height: 1.45;
    color: rgba(244, 241, 233, 0.72);
    font-variant-numeric: tabular-nums;
}
.gdp-board .bc b {
    color: #38d4ff;
    font-weight: 800;
}
.gdp-board .bf {
    margin-top: 4px;
    padding-top: 9px;
    width: 100%;
    border-top: 1px solid rgba(244, 241, 233, 0.1);
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: rgba(244, 241, 233, 0.45);
}
.gdp-board .dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: #ff4d5e;
    box-shadow: 0 0 8px rgba(255, 77, 94, 0.9);
    animation: gdpLiveDotPulse 1.6s ease-in-out infinite;
    flex: none;
}
/* ON AIR: the board goes red and becomes the second way in. */
.gdp-board[data-on="1"] {
    border-color: rgba(255, 77, 94, 0.75);
    background:
        linear-gradient(180deg, rgba(56, 10, 16, 0.96), rgba(20, 6, 9, 0.96));
}
.gdp-board[data-on="1"] .bk {
    color: #ffd7db;
}
.gdp-board[data-on="1"] .bc {
    color: rgba(255, 215, 219, 0.85);
}
.gdp-board[data-on="1"]:hover {
    border-color: #ff4d5e;
}
@media (prefers-reduced-motion: reduce) {
    .gdp-board .dot {
        animation: none;
    }
    .gdp-board,
    .gdp-board:hover {
        transform: none;
    }
}

/* Phones: no flanking space - the board slides beneath the door as a
 * slim strip, and the row stacks. */
@media (max-width: 760px) {
    html[data-gdp-view="floor"] [data-framer-name="Booth Row"] {
        flex-direction: column !important;
    }
    .gdp-board {
        flex-direction: row;
        flex-wrap: wrap;
        justify-content: center;
        align-items: center;
        gap: 6px 12px;
        width: min(100%, 340px);
        padding: 12px 16px;
    }
    .gdp-board .bh {
        font-size: 22px;
    }
    .gdp-board .bf {
        margin-top: 2px;
        padding-top: 6px;
    }
    /* Door first on a phone; the boards follow beneath. */
    #gdp-scene-floor {
        order: 0;
    }
    .gdp-board {
        order: 1;
    }
    /* No room for the rope line at phone width. */
    #gdp-scene-floor .rope {
        display: none;
    }
}

/* Links wear the board like the button does. */
a.gdp-board {
    text-decoration: none;
    color: #f4f1e9;
}
a.gdp-board .bf {
    color: rgba(217, 180, 92, 0.75);
}

/* ------------------------------------------------------------- the wall
 *
 * The frontage hangs on a building, not in a void: a faint panel-and-
 * seam wash behind the row, full-bleed, with the doorway's light
 * pooling low at the centre. Alphas stay low so the hero video keeps
 * breathing through it. */
html[data-gdp-view="floor"] [data-framer-name="Booth Row"] {
    position: relative !important;
    isolation: isolate !important;
}
html[data-gdp-view="floor"] [data-framer-name="Booth Row"]::before {
    content: "";
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    width: 100vw;
    top: -16px;
    bottom: -4px;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(
            44% 85% at 50% 92%,
            rgba(56, 212, 255, 0.09),
            rgba(237, 47, 212, 0.05) 45%,
            transparent 70%
        ),
        repeating-linear-gradient(
            90deg,
            rgba(244, 241, 233, 0.035) 0 1px,
            transparent 1px 150px
        ),
        linear-gradient(180deg, rgba(15, 15, 21, 0.5), rgba(9, 9, 13, 0.66));
    border-top: 1px solid rgba(244, 241, 233, 0.06);
    border-bottom: 1px solid rgba(244, 241, 233, 0.06);
}

/* --------------------------------------------------------- the rope line
 *
 * Gold stanchions and red velvet swags at the door's feet - the classic
 * club threshold, drawn inline (zero bytes), purely decorative. */
#gdp-scene-floor .rope {
    position: absolute;
    left: -27%;
    right: -27%;
    bottom: -12px;
    height: 118px;
    pointer-events: none;
    z-index: 3;
    filter: drop-shadow(0 7px 9px rgba(0, 0, 0, 0.55));
}
#gdp-scene-floor .rope svg {
    width: 100%;
    height: 100%;
}

/* ------------------------------------------------------------ pull-up
 *
 * The frontage IS the hero: with the orientation line and the ticker
 * retired, the section's 96px crown padding was the last thing holding
 * the door below the fold. The sign, the door and the boards now sit
 * directly under the nav. (:has keeps this to the page that carries
 * the Booth Row.) */
html[data-gdp-view="floor"]
    [data-framer-name="Opening"]:has([data-framer-name="Booth Row"]) {
    padding-top: 22px !important;
}
html[data-gdp-view="floor"]
    [data-framer-name="Hero Content"]:has([data-framer-name="Booth Row"]) {
    gap: 14px !important;
}

/* ------------------------------------------- the show board, prominent
 *
 * Operator call: the Saturday board is the show's front sign - it must
 * not read as a quiet plaque. Larger than its twin, a warm standing
 * glow that breathes until noticed, and its footer is an ACTION: play
 * the latest mix now (live: step into the show). */
#gdp-board {
    width: clamp(196px, 17.5vw, 244px);
    padding: 24px 20px 18px;
    border-color: rgba(217, 180, 92, 0.8);
    box-shadow:
        inset 0 1px 0 rgba(244, 241, 233, 0.08),
        0 18px 44px -18px rgba(0, 0, 0, 0.8),
        0 0 26px -6px rgba(217, 180, 92, 0.35);
    animation: gdpBoardGlow 5.2s ease-in-out infinite alternate;
}
#gdp-board .bh {
    font-size: 40px;
}
#gdp-board .bc b {
    font-size: 14px;
}
#gdp-board .bf--go {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    color: #0b0b10;
    background: linear-gradient(100deg, #38d4ff, #ed2fd4);
    border: 0;
    border-radius: 999px;
    padding: 9px 14px;
    font-size: 11px;
    letter-spacing: 0.14em;
}
#gdp-board .bf--go .tri {
    font-size: 9px;
}
#gdp-board:hover {
    border-color: #f2c465;
    box-shadow:
        inset 0 1px 0 rgba(244, 241, 233, 0.08),
        0 18px 44px -18px rgba(0, 0, 0, 0.8),
        0 0 34px -4px rgba(217, 180, 92, 0.55);
}
@keyframes gdpBoardGlow {
    from {
        box-shadow:
            inset 0 1px 0 rgba(244, 241, 233, 0.08),
            0 18px 44px -18px rgba(0, 0, 0, 0.8),
            0 0 20px -8px rgba(217, 180, 92, 0.25);
    }
    to {
        box-shadow:
            inset 0 1px 0 rgba(244, 241, 233, 0.08),
            0 18px 44px -18px rgba(0, 0, 0, 0.8),
            0 0 32px -4px rgba(217, 180, 92, 0.5);
    }
}
/* Live: the glow turns on-air red and stops being subtle. */
#gdp-board[data-on="1"] {
    animation: gdpBoardGlowLive 1.8s ease-in-out infinite alternate;
}
@keyframes gdpBoardGlowLive {
    from {
        box-shadow:
            inset 0 1px 0 rgba(244, 241, 233, 0.08),
            0 18px 44px -18px rgba(0, 0, 0, 0.8),
            0 0 22px -6px rgba(255, 77, 94, 0.4);
    }
    to {
        box-shadow:
            inset 0 1px 0 rgba(244, 241, 233, 0.08),
            0 18px 44px -18px rgba(0, 0, 0, 0.8),
            0 0 38px -2px rgba(255, 77, 94, 0.7);
    }
}
html[data-gdp-calm="1"] #gdp-board {
    animation: none;
}
@media (prefers-reduced-motion: reduce) {
    #gdp-board,
    #gdp-board[data-on="1"] {
        animation: none;
    }
}
