/* ── INTRO CURTAIN ── */
#intro {
    position: fixed; inset: 0;
    z-index: 100;
    cursor: var(--cur-hand, pointer);
}
.curtain {
    position: absolute; top: 0; bottom: 0;
    width: 50.5%;
    background:
        radial-gradient(ellipse 120% 40% at 50% -5%, rgba(232,186,120,0.18), transparent 60%),
        repeating-linear-gradient(90deg,
            rgba(0,0,0,0.38) 0px, rgba(145,29,24,0.28) 22px, rgba(255,170,150,0.10) 38px,
            rgba(145,29,24,0.24) 52px, rgba(0,0,0,0.38) 76px),
        linear-gradient(180deg, #5a1310 0%, #3c0c0a 55%, #1c0605 100%);
    transition: transform 1.35s cubic-bezier(0.76, 0, 0.24, 1);
    box-shadow: 0 0 60px rgba(0,0,0,0.8);
}
.curtain.l { left: 0; transform-origin: left top; }
.curtain.r { right: 0; transform-origin: right top; }
#intro.open .curtain.l { transform: translateX(-115%) skewX(4deg); }
#intro.open .curtain.r { transform: translateX(115%) skewX(-4deg); }

.intro-mark {
    position: absolute; left: 50%; top: 50%;
    z-index: 2;                       /* above the veil: the subject is lit */
    transform: translate(-50%, -50%);
    display: flex; flex-direction: column; align-items: center; gap: 30px;
    transition: opacity 0.6s ease, transform 1s cubic-bezier(0.76, 0, 0.24, 1);
}
#intro.open .intro-mark { opacity: 0; transform: translate(-50%, -50%) scale(0.92); }
.intro-mark svg { width: 110px; height: 110px; overflow: visible; }

/* The mark is an outline, so its interior is transparent and the gold seam
   where the two curtain halves meet ran straight through the bishop. This
   is the shadow the mark stands in: it darkens the velvet just behind it,
   killing the seam locally and giving the lit metal something to read
   against. Static, so it costs nothing. */
.intro-mark::before {
    content: '';
    position: absolute;
    left: 50%; top: 55px;
    width: 420px; height: 420px;
    transform: translate(-50%, -50%);
    background: radial-gradient(circle closest-side,
        rgba(8,2,2,0.90) 0%, rgba(8,2,2,0.78) 20%, rgba(8,2,2,0.56) 34%,
        rgba(8,2,2,0.30) 50%, rgba(8,2,2,0.10) 68%, transparent 100%);
    z-index: 0;
    pointer-events: none;
}
.intro-sigil, .intro-name { position: relative; z-index: 1; }

/* ── The house going dark, then the lamp ──────────────────────────
   The curtain is not shown lit. The screen starts black, a warm pool
   is struck on the velvet, and the mark is found by that light rather
   than animated into place. Veil and lamp are both plain opacity, so
   the whole strike costs nothing to composite. */
.intro-veil {
    position: absolute; inset: 0;
    z-index: 1;
    background: #000;
    opacity: 1;
    animation: house-dim 2.1s cubic-bezier(0.4, 0, 0.3, 1) 0.25s forwards;
    pointer-events: none;
}
@keyframes house-dim { to { opacity: 0.34; } }
#intro.open .intro-veil { opacity: 0; transition: opacity 1s ease; animation: none; }

.intro-light {
    position: absolute; left: 50%; top: 50%;
    width: 150vmax; height: 150vmax;
    transform: translate(-50%, -54%);
    background:
        radial-gradient(circle at 50% 50%, rgba(255,214,160,0.30) 0%, rgba(255,214,160,0.13) 17%,
                        rgba(255,190,130,0.05) 30%, transparent 46%);
    opacity: 0;
    animation: lamp-up 2.3s cubic-bezier(0.32, 0, 0.26, 1) 0.2s forwards;
    pointer-events: none;
}
@keyframes lamp-up { to { opacity: 1; } }
#intro.open .intro-light { opacity: 0; transition: opacity 0.8s ease; animation: none; }

/* The mark does not travel. The lamp finds it — it surfaces with the
   light and settles a couple of percent, the way a lit object resolves
   out of the dark. */
/* The rock lives on the CONTAINER, not the svg, so the mark and the sheen
   that masks it tilt together. With the sheen as an untilted sibling its
   mask drifted off the silhouette by a couple of px at full swing, and the
   highlight spilled past the metal's edge. */
.intro-sigil {
    position: relative; line-height: 0;
    opacity: 0;
    transform: translateY(-7px) scale(0.94) rotate(0deg);
    /* A chess piece rocks on its BASE, not around its middle. Pivoting at
       the centre made the tilt read as a slight spin; down here it reads as
       a weighted piece tipping and righting itself. */
    transform-origin: 50% 93%;
    /* Ease-in-out applies to every segment between keyframes, which is what
       turns a list of angles into a pendulum rather than a sequence of
       linear slides. */
    animation: sigil-strike 2.8s cubic-bezier(0.45, 0, 0.55, 1) 0.5s forwards;
}
/* It settles in, comes to rest at 25% — 1.20s, which is exactly when the
   sweep is released — and only THEN begins to rock, leaning right first so
   it moves with the light rather than against it. Each swing is about
   two-thirds of the one before. Softer than it was: the first tip is 1.8deg
   rather than 2.6, because slow and small is what reads as weight, and past
   roughly 2deg at this size it starts reading as a wobble instead. */
@keyframes sigil-strike {
      0% { opacity: 0; transform: translateY(-7px) scale(0.94)  rotate(0deg);
           animation-timing-function: cubic-bezier(0.25, 0, 0.2, 1); }
     18% { opacity: 1; transform: translateY(0)    scale(1.01)  rotate(0deg); }
     25% { opacity: 1; transform: translateY(0)    scale(1)     rotate(0deg);
           animation-timing-function: cubic-bezier(0.36, 0, 0.32, 1); }
     36% { opacity: 1; transform: translateY(0)    scale(1)     rotate(1.8deg); }
     48% { opacity: 1; transform: translateY(0)    scale(1)     rotate(-1.18deg); }
     59% { opacity: 1; transform: translateY(0)    scale(1)     rotate(0.76deg); }
     69% { opacity: 1; transform: translateY(0)    scale(1)     rotate(-0.46deg); }
     78% { opacity: 1; transform: translateY(0)    scale(1)     rotate(0.27deg); }
     86% { opacity: 1; transform: translateY(0)    scale(1)     rotate(-0.15deg); }
     93% { opacity: 1; transform: translateY(0)    scale(1)     rotate(0.07deg); }
    100% { opacity: 1; transform: translateY(0)    scale(1)     rotate(0deg); }
}

/* ── The metal is silver once the light has passed ────────────────
   No transition: the mark is gold, a highlight crosses it, and it is
   silver afterwards. The silver copy sits on top of the gold one and is
   switched on in a 1ms keyframe — a step, not an animation. Both share
   the same ink box, so nothing shifts.

   2.25s is not arbitrary: it is when sheen-flick, the narrow second
   highlight, sits centred on the mark (measured by sampling the band
   every 50ms). Cutting inside that flash is what keeps the change from
   reading as a pop — the eye is on the light, not on the metal. Retime
   the flick and this has to move with it. */
.intro-sigil .turn {
    position: absolute; inset: 0;
    opacity: 0;
    animation: turn-silver 1ms linear 2.25s forwards;
}
.intro-sigil .turn svg { display: block; width: 100%; height: 100%; }
@keyframes turn-silver { to { opacity: 1; } }

/* The specular that travels across the metal. The mark's own silhouette is
   the mask and it never moves; the bright band underneath is what travels,
   so this is a transform animation, not a mask or filter one. */
.intro-sigil .sheen {
    position: absolute; inset: 0;
    overflow: hidden;
    pointer-events: none;
    -webkit-mask-image: url("../marks/sigil-outline-gold.9a6c5ab20f8c.webp");
            mask-image: url("../marks/sigil-outline-gold.9a6c5ab20f8c.webp");
    -webkit-mask-size: contain;   mask-size: contain;
    -webkit-mask-position: center; mask-position: center;
    -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
}
.intro-sigil .sheen::before,
.intro-sigil .sheen::after {
    content: '';
    position: absolute; top: -30%; bottom: -30%; left: 0;
    opacity: 0;
    will-change: transform;
}
/* The broad pass, as the mark lands. */
.intro-sigil .sheen::before {
    width: 58%;
    background: linear-gradient(100deg,
        rgba(255,250,228,0) 0%, rgba(255,250,228,0.20) 28%,
        rgba(255,253,242,1) 50%, rgba(255,250,228,0.20) 72%,
        rgba(255,250,228,0) 100%);
    animation: sheen-sweep 1.25s cubic-bezier(0.32, 0, 0.22, 1) 1.2s forwards;
}
/* A narrower, harder flick a beat later, timed to the second swing — one
   highlight is a gleam, two is polished metal catching the light twice as
   the piece rocks. */
.intro-sigil .sheen::after {
    width: 26%;
    background: linear-gradient(100deg,
        rgba(255,252,236,0) 0%, rgba(255,255,248,1) 50%, rgba(255,252,236,0) 100%);
    animation: sheen-flick 0.92s cubic-bezier(0.4, 0, 0.3, 1) 1.9s forwards;
}
/* Opacity is keyed so the band cannot show parked at either end; only the
   transform travels, which keeps this off the paint path. */
@keyframes sheen-sweep {
      0% { opacity: 0; transform: translateX(-190%); }
     14% { opacity: 1; }
     86% { opacity: 1; }
    100% { opacity: 0; transform: translateX(330%); }
}
@keyframes sheen-flick {
      0% { opacity: 0; transform: translateX(-160%); }
     18% { opacity: 0.9; }
     82% { opacity: 0.9; }
    100% { opacity: 0; transform: translateX(430%); }
}

/* ── The name, tracked in ─────────────────────────────────────────
   The letters start spread apart and draw together to their final
   spacing. Each one carries its own --dx (set in home.js from its
   distance to the centre) and animates translateX to zero, so the
   tracking closes without ever touching letter-spacing, which would
   relayout the row on every frame. */
.intro-name {
    font-family: var(--serif);
    font-weight: 500;
    letter-spacing: 0.34em;
    text-transform: uppercase;
    font-size: 1rem;
    display: flex; gap: 0.02em;
    animation: name-settle 2s cubic-bezier(0.24, 0.6, 0.2, 1) 1.5s both;

    /* The ore tiles at 520px by default, and the whole name is only ~180px
       wide — it would sit inside a third of one tile and come out a flat
       grey. Tightened so the word spans the ore's full run of blobs and
       actually reads as gold. */
    --ore-tile: 200px;
}
/* The name rings down with the mark, a beat later. */
/* Softer and squarer than the mark's. The rotation is gone: a rocking
   line of type reads as sloppy rather than settling, and nothing about a
   wordmark sitting off its baseline is formal. What is left is a breath of
   vertical give, under a pixel, so the name arrives with the mark instead
   of landing dead. */
@keyframes name-settle {
    0%, 52% { transform: translateY(0); }
        64% { transform: translateY(-0.9px); }
        77% { transform: translateY(0.42px); }
        88% { transform: translateY(-0.16px); }
       100% { transform: none; }
}

/* Each letter wears the headings' ore. The ore is one continuous tile
   across the whole name: home.js gives every letter a --bx of minus its
   own offsetLeft, so the slab lines up letter to letter instead of each
   one restarting the gradient. */
.intro-name span {
    position: relative;
    opacity: 0;
    transform: translateX(var(--dx, 0px));
    animation: track-in 1.9s cubic-bezier(0.22, 0.85, 0.24, 1) 1.5s forwards;
    background-position: var(--bx, 0px) 0;
}
@keyframes track-in { to { opacity: 1; transform: translateX(0); } }

/* The glint travelling through the type. A bright copy of each letter
   fades up and down, staggered left to right, which reads as one highlight
   crossing the word — and costs nothing but opacity. */
.intro-name span::after {
    content: attr(data-ch);
    position: absolute; left: 0; top: 0;
    padding: inherit;
    color: #fff6da;
    -webkit-text-fill-color: #fff6da;
    opacity: 0;
    /* Short flash, wide stagger. At 1.1s each against a 0.6s travel every
       letter was lit at once, which reads as the whole word brightening
       rather than a highlight crossing it. */
    animation: letter-glint 0.45s ease-out var(--gd, 0s) forwards;
    pointer-events: none;
}
@keyframes letter-glint {
      0% { opacity: 0; }
     40% { opacity: 1; }
    100% { opacity: 0; }
}

/* ── STAGE (rest of the first screen) ── */

/* ── The hero frame ─────────────────────────────────────────────
   One frame, three subjects. Each row is a grid cell that all three
   variants share, so the row is as tall as its TALLEST variant and the
   frame never moves or resizes when it turns over. The variants stay
   laid out and are hidden with visibility, not display — that is what
   keeps the height stable, and it still takes them out of the tab order
   and the accessibility tree.

   Only opacity animates. */
.hero-slot { display: grid; width: 100%; justify-items: center; }
.hero-slot > .hs {
    grid-area: 1 / 1;
    visibility: hidden;
    opacity: 0;
    transition: opacity 0.55s ease, visibility 0s linear 0.55s;
}
.hero-slot > .hs.on {
    visibility: visible;
    opacity: 1;
    transition: opacity 0.55s ease, visibility 0s;
}

/* The box row is pinned to the lineup's own height, so the shop and the
   film occupy exactly the piece grid's dimensions rather than their own. */
/* One height, declared once and used literally everywhere inside the box.
   Percentages do not resolve reliably through these stacked grid items --
   the lineup collapsed and the poster rendered at natural size -- so every
   slide is given the box height outright. */
/* Two measurements against the pre-carousel hero, same viewport, ink to
   ink: masthead to headline was 119px and had become 77px; headline to
   the top of the pieces was 55px and had become 50px.

   The first is the one that shows. .stage centres its column, and the
   carousel added a dots row, so the whole block grew by 64px and rose by
   half of that -- the headline came up to meet the nav. Fixing it by
   opening the box's own margin only made it worse: it pulls the headline
   up as it pushes the grid down, so the gap Juan was pointing at
   overshot to 71px while the headline climbed further.

   So: .hs-head carries the block back down (a first child's margin in a
   centred column moves everything down by half of it, which is why 56px
   buys the 28px that was missing), and .hs-box keeps close to the margin
   it always had. Measured back at 119px and 57px. Both in vh so a short
   screen gets proportionally less push and the dots stay above the fold;
   both capped so a tall one does not drift.

   The push-down is gated because the "half of it" only holds while
   .stage still has slack to centre in. Once the column is taller than its
   min-height, centring does nothing and the margin lands in full: it put
   the dots 5px under the fold at 1280x640 and 54px under at 414x736.
   Phones are the worst case -- the headline runs to three lines there, so
   the column is already the tallest it gets and the push-down buys
   nothing. Neither narrow nor short screens have the room to reproduce
   the old spacing, and the dots staying visible matters more. The width
   gate is the one .headline already uses. Measured clearance below the
   fold at the boundaries: 1280x660 +57, 1489x676 +54, 1280x700 +27. */
/* The three headlines share one grid cell, so the cell is as tall as the
   LONGEST of them. On a phone they run 2 / 1 / 2 lines, which left the
   short one ("Lleva tu nación.") sitting at the top of a two-line box
   with 31px of dead air under it — the heading drifted away from the
   images it belongs to, and only on that slide.

   Aligning to the END pins the LAST line of every headline the same
   distance above the box instead of pinning the first. The gap to the
   images is then identical on all three slides, and the slack moves up
   into the space under the nav, where there is room for it. The frame
   itself does not move or resize, which is the rule this deck is built
   on. No-op above 900px, where .headline drops its max-width and all
   three are one line anyway. */
.hs-head { margin-top: 0; align-items: end; }
@media (min-width: 900px) and (min-height: 672px) {
    .hs-head { margin-top: clamp(0px, 8.3vh, 64px); }
}
.hs-box {
    --u: clamp(52px, 14vh, 112px);
    --boxh: calc(var(--u) * 2.1);
    height: var(--boxh);
    margin-top: clamp(18px, 4.3vh, 42px);
    max-width: 900px;
}
.hs-box > .hs {
    min-width: 0;                 /* grid items default to min-content width */
    width: 100%; height: var(--boxh);
    display: grid; place-items: center;
}
/* min-width: auto again -- the lineup's seven slots have a 900px
   min-content width, which beat the 720px box on tablets. */
.hs-box .lineup { margin-top: 0; height: var(--boxh); min-width: 0; }

/* Shop: the products stand on the lineup's own floor, not in three lit
   tiles. .p-stage's border, ground and overflow: hidden come from
   mockups.css, where the shop grid wants them; in this frame they read as
   a different language from the pieces on the slide before.

   Selector is .hs-box > .box-shop because .hs-box > .hs already sets
   display and place-items at that weight.

   min-width: 0 stays: a flex item's min-width also defaults to
   min-content, and the mockup's natural width is over 1100px. */
.hs-box > .box-shop {
    display: flex;
    align-items: flex-end;
    justify-content: center;
    gap: clamp(8px, 3vw, 54px);
    position: relative;
}
/* One pool of light under the row -- the lineup's own, same numbers. */
.box-shop::before {
    content: '';
    position: absolute;
    left: 8%; right: 8%; bottom: -14px;
    height: 46px;
    background: radial-gradient(ellipse at center, rgba(255,214,160,0.20) 0%, rgba(0,0,0,0.35) 45%, transparent 70%);
    filter: blur(4px);
    z-index: 0;
    pointer-events: none;
}
.box-shop .p-stage {
    flex: 0 0 auto;
    min-width: 0; min-height: 0;
    height: auto;
    max-width: none;
    aspect-ratio: auto;
    border: 0;
    background: none;
    overflow: visible;
    display: block;
    position: relative;
    z-index: 1;
    perspective: 900px;
}
/* mockups.css grounds each product on its own contact shadow; sharing one
   floor, three of those read as three separate floors. */
.box-shop .p-stage::after { display: none; }
.box-shop .p-stage > .mock { width: 100%; }

/* Each mockup is sized off the box height and capped at a share of the
   frame's width: the box governs on a desktop, the frame governs on a
   phone. The shipped widths were clamp(150px, 16vw, 200px) -- driven by
   the viewport and blind to the box -- so at 390px three 150px mockups
   sat in ~106px columns and were cut off by that overflow: hidden.

   The multipliers are each mockup's natural height as a fraction of the
   box (the pin card is 3:4, the patch and sticker square, the tag 3:5);
   the percentages are those same ratios of 91% of the frame, which leaves
   the gaps their 9%. Keyed on the format rather than nth-child so
   changing the mix in home.js does not silently resize the wrong one. */
.box-shop [data-fmt="pin"]     { width: min(calc(var(--boxh) * 0.60), 25%); }
.box-shop [data-fmt="patch"]   { width: min(calc(var(--boxh) * 0.86), 36%); z-index: 2; }
.box-shop [data-fmt="sticker"] { width: min(calc(var(--boxh) * 0.72), 30%); }
.box-shop [data-fmt="tag"]     { width: min(calc(var(--boxh) * 0.50), 21%); }
/* The film never sets the box either: cap it to the box width on phones. */
.box-film a { max-width: 100%; }

/* Film: the clip fills the box height, never sets it. */
.box-film a {
    position: relative; display: block; height: var(--boxh);
    border-radius: 5px; overflow: hidden;
    border: 1px solid rgba(233,201,106,0.22);
    box-shadow: 0 14px 40px rgba(0,0,0,0.5);
    background: #0b0407;          /* behind the clip until its first frame */
}
/* aspect-ratio is doing real work here. A <video> with no source yet takes
   its width from the poster, and its intrinsic width once the clip loads;
   with the two at different ratios the box would visibly resize mid-slide,
   which this frame does not allow. Declaring 16/9 against a definite height
   fixes the width at 418px from first paint, poster or no poster.
   object-fit: cover then absorbs the phone case, where max-width crops the
   width below what the ratio asks for. */
.box-film video {
    display: block;
    height: var(--filmh, var(--boxh));
    aspect-ratio: 16 / 9;
    width: auto;
    max-width: 100%;
    object-fit: cover;
}
/* This slide carries no subtitle, so the clip takes the label row's height
   as well -- 38px of it, less 10px so it does not run into the buttons.
   Only the clip grows: .hs-box, .hs-label and the rest keep their own
   heights, so the frame still does not move when the deck turns. The clip
   is top-aligned rather than centred so the extra hangs into the empty
   row below instead of climbing toward the headline.

   Not below 760px, where max-width already caps the clip's width: adding
   height there does not make the picture bigger, it just crops more off
   the sides. */
@media (min-width: 760px) {
    .hs-box > .box-film {
        align-items: start;
        --filmh: calc(var(--boxh) + 28px);
    }
}
/* The badge now sits over a clip that is already running, where a standing
   play triangle reads as "press to start" and is simply wrong. It comes up
   on hover instead, where it says what it actually does: open Khora. */
.box-film .film-play {
    position: absolute; left: 50%; top: 50%;
    width: 54px; height: 54px; margin: -27px 0 0 -27px;
    border-radius: 50%;
    background: rgba(12,4,4,0.6);
    border: 1px solid var(--gold-hi);
    opacity: 0;
    transform: scale(0.85);
    transition: transform 0.3s cubic-bezier(0.34, 1.46, 0.64, 1), opacity 0.3s ease;
}
.box-film .film-play::after {
    content: '';
    position: absolute; left: 50%; top: 50%;
    transform: translate(-38%, -50%);
    border-left: 14px solid var(--gold-hi);
    border-top: 9px solid transparent;
    border-bottom: 9px solid transparent;
}
.box-film a:hover .film-play { opacity: 1; transform: scale(1); }

/* ── The dots ──────────────────────────────────────────────────────
   A lozenge apiece: the same gold diamond .mast-nav .sep sets between
   the nav links at the top of this page, so the deck is marked in the
   site's own punctuation rather than in generic circles.

   The button is 26px for the sake of a finger and both marks are drawn
   inside it, so the hit area is nowhere near the 11px you can see. */
.deck-dots {
    display: flex; justify-content: center; align-items: center;
    gap: 4px; margin-top: 18px;
}
.deck-dots button {
    position: relative;
    width: 26px; height: 26px; padding: 0;
    border: 0; background: none; cursor: var(--cur-hand, pointer);
}
/* The outline. Rotating a square beats a clip-path here: the border
   stays a hairline on every edge. */
.deck-dots button::after {
    content: '';
    position: absolute; inset: 7px;
    border: 1px solid var(--silver-dim);
    transform: rotate(45deg);
    transition: border-color 0.3s ease;
}
.deck-dots button:hover::after,
.deck-dots button.on::after { border-color: var(--gold-hi); }
/* The clock: a gold core growing from a point to fill the outline, so
   the turn is visible coming rather than a surprise. --dot-p is a plain
   number 0-1, written every frame by home.js; scale() takes a number,
   and the registration gives it a real 0 before the first frame lands
   instead of an invalid value.

   inherits: true is load-bearing. home.js writes --dot-p on the BUTTON
   and it is read here, inside ::before. A pseudo-element inherits from
   the element it belongs to, so a registered property declared
   inherits: false never reaches it -- it reads the initial value and
   nothing else. The sweep this replaces was registered that way and had
   been sitting at 0% since the day it was written. */
@property --dot-p { syntax: "<number>"; inherits: true; initial-value: 0; }
.deck-dots button.on::before {
    content: '';
    position: absolute; inset: 8px;
    background: linear-gradient(135deg, var(--gold), #f5e090);
    transform: rotate(45deg) scale(var(--dot-p));
}
/* Held: the clock has stopped, so the core stops claiming to run. */
.deck-dots.paused button.on::before { opacity: 0.3; }

.stage {
    min-height: calc(100vh - 120px);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    padding: 12px 24px 40px;
    text-align: center;
    position: relative;
}
.headline {
    font-family: var(--serif);
    font-weight: 500;
    font-size: clamp(1.7rem, 3.6vw, 3.1rem);
    line-height: 1.12;
    color: var(--white);
    letter-spacing: -0.005em;
    max-width: 18ch;
    text-wrap: balance;
}
@media (min-width: 900px) { .headline { max-width: none; } }
.headline em {
    font-style: italic;
    background: linear-gradient(100deg, #b98f35 0%, var(--gold-hi) 30%, #fff3c4 45%, var(--gold-hi) 60%, #a57c2a 100%);
    background-size: 250% 100%;
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent;
    animation: sheen 6s ease-in-out infinite;
    /* Clipped text is painted only inside the element box, so descenders
       hanging out of a tight line box go unpainted. This is inline, so the
       vertical padding buys paint area without touching the line at all. */
    padding: 0.10em 0.08em 0.14em 0;
}
@keyframes sheen { 0%,100% { background-position: 100% 0; } 50% { background-position: 0% 0; } }

/* Lineup: a set of real pieces, cycling through nations */
.lineup {
    --u: clamp(52px, 14vh, 112px);
    position: relative;
    display: flex; align-items: flex-end; justify-content: center;
    gap: clamp(0px, 0.6vw, 10px);
    margin-top: clamp(16px, 3.5vh, 36px);
    height: calc(var(--u) * 2.1);
    width: 100%;
    max-width: 900px;
}
.lineup::before {
    content: '';
    position: absolute;
    left: 10%; right: 10%; bottom: -14px;
    height: 46px;
    background: radial-gradient(ellipse at center, rgba(255,214,160,0.20) 0%, rgba(0,0,0,0.35) 45%, transparent 70%);
    filter: blur(4px);
    z-index: -1;
}
.lineup::after {
    content: '';
    position: absolute;
    left: 50%; top: -30%;
    width: 80%; height: 150%;
    transform: translateX(-50%);
    background: radial-gradient(ellipse 42% 44% at 50% 55%, rgba(255,226,180,0.12), transparent 100%);
    pointer-events: none;
    z-index: -1;
}
.slot {
    --s: 1;
    --d: 0;
    width: calc(var(--u) * var(--s));
    height: calc(var(--u) * var(--s));
    flex-shrink: 0;
    position: relative;
    transform: translate(calc(var(--px, 0) * var(--d) * 1px), calc(var(--py, 0) * var(--d) * 1px));
    transition: transform 0.25s ease-out;
    cursor: var(--cur-hand, pointer);
}
.slot img {
    width: 100%; height: 100%;
    object-fit: contain;
    display: block;
    filter: drop-shadow(0 0 1px rgba(255,236,200,0.5)) drop-shadow(0 10px 14px rgba(0,0,0,0.55));
    transform-origin: 50% 100%;
    transition: transform 0.45s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.3s ease, filter 0.3s ease;
    user-select: none;
    -webkit-user-drag: none;
}
.slot:hover img {
    transform: translateY(-10%) scale(1.12) rotate(-3deg);
    filter: drop-shadow(0 0 2px rgba(255,236,200,0.8)) drop-shadow(0 0 22px rgba(233,201,106,0.45)) drop-shadow(0 18px 18px rgba(0,0,0,0.6));
}
.slot.out img { opacity: 0; transform: translateY(18%) scale(0.9); transition-duration: 0.28s; }
.slot.pre img { opacity: 0; transform: translateY(-30%) scale(1.05); transition: none; }

.set-label {
    margin-top: 24px;
    display: flex; align-items: center; gap: 14px;
    font-family: var(--sans);
    font-size: 0.66rem; letter-spacing: 0.3em; text-transform: uppercase;
    color: var(--silver-mid);
    min-height: 1.2em;
}
.set-label b { color: var(--gold-hi); font-weight: 600; }
.set-label .line { width: 36px; height: 1px; background: linear-gradient(90deg, transparent, var(--silver-dim)); }
.set-label .line.r { background: linear-gradient(90deg, var(--silver-dim), transparent); }
#setName { display: inline-block; transition: opacity 0.25s, transform 0.25s; }
#setName.swap { opacity: 0; transform: translateY(6px); }

/* Entry choreography, triggered when the curtain opens */
.enter { opacity: 0; transform: translateY(16px); }
body.revealed .enter {
    opacity: 1; transform: none;
    transition: opacity 0.9s ease, transform 0.9s cubic-bezier(0.22, 1, 0.36, 1);
    transition-delay: var(--delay, 0s);
}

/* Collection: stats + marquee */
.collection { padding: 90px 0 110px; position: relative; }
.collection-head { text-align: center; padding: 0 24px; }
.collection-head .eyebrow { justify-content: center; }
.stats {
    display: flex; justify-content: center; flex-wrap: wrap;
    gap: 18px clamp(28px, 6vw, 80px);
    margin-top: 34px;
}
.stat b {
    display: block;
    font-family: var(--serif); font-weight: 500;
    font-size: clamp(2.2rem, 5vw, 3.6rem);
    line-height: 1;
    background: linear-gradient(180deg, var(--white), var(--silver-mid));
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent;
    font-variant-numeric: tabular-nums;
    /* Playfair's figures descend below the baseline (the 4 and 9 of "491",
       and every comma), and line-height 1 leaves them outside the paint
       box. The padding buys it back; the negative margin is swallowed by
       the label's own margin-top, so nothing moves. */
    padding-bottom: 0.14em;
    margin-bottom: -0.14em;
}
.stat span {
    display: block; margin-top: 8px;
    font-family: var(--sans);
    font-size: 0.62rem; letter-spacing: 0.28em; text-transform: uppercase;
    color: var(--silver-dim);
}
/* The edge-fade mask is a SECOND clip, one level above .m-row, and it was
   the one still slicing the hovered piece. A mask paints only inside the
   element's mask-clip area, which defaults to border-box — so anything a
   child draws outside .marquee's own box is silently dropped, no matter
   what the rows do with overflow. The top row sits flush with that edge
   and a hovered piece lifts 12px past it, which is exactly where it was
   being cut. The padding buys that headroom inside the box; the margins
   give it back so nothing on the page moves. The gradient is 90deg and
   its stops are percentages of the width, so extra height doesn't touch
   the fade. */
.marquee {
    margin-top: 44px;
    padding: 16px 0;
    margin-bottom: -16px;
    display: flex; flex-direction: column; gap: 10px;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
    mask-image: linear-gradient(90deg, transparent, #000 12%, #000 88%, transparent);
}
/* The row has to clip horizontally — the track is wider than the page and
   slides — but NOT vertically: on hover a piece scales to 1.25 from its
   bottom edge and lifts 14px, so it stands ~12px above the row, and its
   glow another 16px on top of that. `overflow: hidden` sliced both off
   into a hard-edged rectangle.
   `hidden` on one axis forces the other to `auto`, so it cannot be mixed
   with `visible` — `clip` can, which is the whole reason for this pair.
   The plain `hidden` stays as the fallback for anything without clip. */
.m-row { overflow: hidden; }
@supports (overflow: clip) {
    .m-row { overflow-x: clip; overflow-y: visible; }
}
.m-track {
    display: flex; width: max-content;
    animation: slide var(--dur, 70s) linear infinite;
}
.m-row.rev .m-track { animation-direction: reverse; }
.m-row:hover .m-track { animation-play-state: paused; }
@keyframes slide { to { transform: translateX(-50%); } }
.m-piece {
    width: 118px; height: 132px;
    flex-shrink: 0;
    position: relative;
    display: flex; align-items: flex-end; justify-content: center;
}
.m-piece img {
    width: 104px; height: 104px;
    object-fit: contain;
    filter: drop-shadow(0 0 1px rgba(255,236,200,0.4)) drop-shadow(0 8px 10px rgba(0,0,0,0.5));
    transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1), filter 0.3s;
    transform-origin: 50% 100%;
}
.m-piece:hover { z-index: 3; }   /* the lifted piece sits over its neighbours */
/* No gold bloom on hover (Juan's call) — the lift and the deeper cast
   shadow are the whole feedback. The 1px rim is carried over from the
   resting state unchanged, so nothing flickers on the way in or out. */
.m-piece:hover img { transform: translateY(-14px) scale(1.25); filter: drop-shadow(0 0 1px rgba(255,236,200,0.4)) drop-shadow(0 12px 12px rgba(0,0,0,0.6)); }
/* No hover label on the marquee pieces — the lift is the whole feedback.
   (The nation/year card was removed 2026-09-20.) */

/* Spanish labels run ~40% longer than the English, which pushed the three
   stats onto two rows on a phone. Tighten only that case. */
@media (max-width: 480px) {
    html[lang="es"] .stats { column-gap: 14px; }
    html[lang="es"] .stat span { letter-spacing: 0.18em; }
}

/* Feature rows */
.feature {
    display: grid;
    grid-template-columns: 1.05fr 1fr;
    gap: clamp(30px, 6vw, 90px);
    align-items: center;
}
.feature.flip { grid-template-columns: 1fr 1.05fr; }
.feature.flip .f-visual { order: 2; }
.f-list {
    list-style: none;
    margin-top: 26px;
    display: flex; flex-direction: column; gap: 12px;
}
.f-list li {
    font-family: var(--sans);
    font-size: 0.9rem;
    color: var(--silver-hi);
    display: flex; gap: 14px; align-items: baseline;
}
.f-list li i {
    text-transform: uppercase;
    font-style: normal;
    font-family: var(--serif);
    color: var(--gold);
    font-size: 0.8rem;
    min-width: 22px;
}
.f-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 32px; }

/* 3D board */
.board-scene {
    perspective: 1300px;
    height: clamp(320px, 42vw, 520px);
    display: flex; align-items: center; justify-content: center;
}
.board {
    --rx: 56deg;
    --rz: -32deg;
    width: clamp(240px, 30vw, 380px);
    aspect-ratio: 1;
    position: relative;
    transform-style: preserve-3d;
    transform: rotateX(var(--rx)) rotateZ(var(--rz));
    transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
    background: var(--board-img) center / cover;
    box-shadow: 0 0 0 6px #1a0d08, 0 0 0 8px rgba(201,168,76,0.55), 0 40px 80px rgba(0,0,0,0.7);
}
.board .bp {
    position: absolute;
    width: 12.5%; height: 12.5%;
    transform-style: preserve-3d;
}
.board .bp img {
    position: absolute;
    left: -35%; bottom: 30%;
    width: 170%;
    transform-origin: 50% 100%;
    transform: rotateZ(calc(-1 * var(--rz))) rotateX(calc(-1 * var(--rx)));
    transition: transform 0.6s cubic-bezier(0.22, 1, 0.36, 1);
    filter: drop-shadow(0 6px 4px rgba(0,0,0,0.45));
    pointer-events: none;
}
.board-caption {
    text-align: center;
    margin-top: 6px;
    font-family: var(--sans);
    font-size: 0.6rem; letter-spacing: 0.28em; text-transform: uppercase;
    color: var(--silver-dim);
}

/* Also here */
.more {
    margin-top: 40px;
    border-top: 1px solid rgba(228,228,228,0.1);
}
.more a {
    display: grid;
    grid-template-columns: 70px 1fr auto 40px;
    align-items: center;
    gap: 20px;
    padding: 30px 8px;
    border-bottom: 1px solid rgba(228,228,228,0.1);
    text-decoration: none;
    color: var(--silver-hi);
    position: relative;
    overflow: hidden;
    transition: padding-left 0.4s cubic-bezier(0.22, 1, 0.36, 1);
}
.more a::before {
    content: '';
    position: absolute; inset: 0;
    background: linear-gradient(90deg, rgba(233,201,106,0.09), transparent 70%);
    transform: translateX(-100%);
    transition: transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
}
.more a:hover { padding-left: 22px; }
.more a:hover::before { transform: none; }
.more .num { font-family: var(--serif); font-style: italic; color: var(--gold); font-size: 0.9rem; }
.more .title { font-family: var(--serif); font-size: clamp(1.4rem, 3vw, 2.2rem); font-weight: 500; }
.more .desc { font-family: var(--sans); font-size: 0.82rem; color: var(--silver-dim); margin-top: 4px; }
.more .go { font-size: 1.2rem; color: var(--silver-mid); transition: transform 0.3s, color 0.3s; }
.more a:hover .go { transform: translateX(6px); color: var(--gold-hi); }

@media (max-width: 860px) {
    .feature, .feature.flip { grid-template-columns: 1fr; }
    /* Same rule as the work exhibits: stacked, the copy comes before the
       visual it describes. .f-visual is first in the DOM for the desktop
       two-column layout, so it is pushed after the copy here. */
    .feature > :not(.f-visual) { order: -1; }
    .feature.flip .f-visual { order: 0; }
    .more a { grid-template-columns: 40px 1fr 30px; }
    .more a > span:empty { display: none; }
}
@media (max-width: 600px) {
    /* The box height comes from 14vh, which is right on a desktop where
       the box is what limits each slide. On a phone it is the WIDTH that
       limits all three — every mockup is capped at a share of the frame
       and the film at 100% of it — so a box sized off screen height
       reserves room nothing in it can reach, and the slack piles up above
       the art. Measured at 206x462: the box stood 136px while the shop's
       mockups came to 88, leaving 48px of air between the headline and
       the pictures it names; the lineup had 28px spare and only the film
       filled it.

       So on phones the box takes its height from the width instead, which
       is what actually governs there. It is still one fixed height shared
       by all three slides — the frame does not move when the deck turns. */
    .hs-box { --u: clamp(44px, 20vw, 96px); }
    /* Five pieces on a phone meant five small ones. Three, bigger, and
       the row still reads as a lineup: the .edge pair is already out,
       and dropping the second and sixth leaves 1.5 / 2.0 / 1.6 — the
       tall centre with a shoulder either side, which is the shape the
       seven had in the first place.

       --u rises from 11vw to 15.5vw to spend the width that frees up.
       Three slots come to 5.1u plus two gaps: at 390px that is 313px
       of the 342 available (.stage pads 24px a side, not 16), and the
       centre piece goes from 86px to 121. Width binds here, not the
       box. The margin left over is deliberate: .slot carries a pointer
       parallax of up to 4px each way, so a row fitted to the pixel would
       clip the outer two the moment the pointer moved. */
    .lineup .slot:nth-child(2),
    .lineup .slot:nth-child(6) { display: none; }
    .lineup { --u: clamp(46px, 15.5vw, 72px); }
    .slot.edge { display: none; }
    .m-piece { width: 84px; height: 100px; }
    .m-piece img { width: 76px; height: 76px; }
}
