
/* -------------------- SLIDE CONTAINERS -------------------- */
.slides-container {
    position: relative;
    width: 100vw;
    max-width: 100vw;
}

.slide {
    width: 100vw;
    position: relative;
    max-width: 100vw;
    overflow: hidden;
    height: 100vh;
}

/* -------------------- SLIDE 1 GRIDS -------------------- */
.slide-1-grid-desktop {
    display: grid;
    grid-template-columns: 5% 10% 5% 60% 5% 10% 5%;
    grid-template-rows: max(5%, 36px) 10% 30% 10% 30% 10% max(5%, 36px);
    width: 100%;
    height: 100%;
    position: relative;
    z-index: 1;
}

.slide-1-grid-mobile {
    display: grid;
    grid-template-columns: 5% 10% 5% 60% 5% 10% 5%;
    /* Rows 3 and 5 trimmed 30% -> 27% so the auto marquee row (row 4) goes from 12% to
       18% of the slide — exactly 1.5x thicker, and proportional at every screen height.
       Rows 3-5 still total 72%, so .s1m-b1v-img (which spans 3/6) is unaffected. */
    grid-template-rows: max(2%, 10px) max(12%, 20px) 27% auto 27% max(12%, 20px) max(2%, 10px);
    width: 100%;
    height: 100%;
    position: relative;
    z-index: 1;
}

.slide-1-grid-mobile .grid-cell { position: relative; z-index: 1; }

.grid-cell {
    display: flex;
    align-items: center;
    justify-content: center;
    max-width: 100%;
    overflow: hidden;
}

.s1d-2-2-3 { grid-column: 2 / span 2; grid-row: 2; }
.s1d-2-5-6 { grid-column: 5 / span 2; grid-row: 2; }
.s1d-4-1-7 { grid-column: 1 / span 7; grid-row: 4; }

.s1m-2-4   { grid-column: 4; grid-row: 2; }
.s1m-4-1-7 { grid-column: 1 / span 7; grid-row: 4; }
.s1m-6-4   { grid-column: 4; grid-row: 6; }

/* -------------------- SLIDE 2 GRIDS -------------------- */
.big-screen-layout {
    display: grid;
    grid-template-columns: 5% 30% 5% 55% 5%;
    grid-template-rows: 15% 25% 25% 20% 15%;
    width: 100%;
    height: 100%;
    max-width: 100vw;
    box-sizing: border-box;
}

.small-screen-layout {
    display: grid;
    grid-template-columns: 5% 5% 80% 5% 5%;
    /* 1 gutter, 2 carousel, 3 gap, 4 headline, 5 body + arrows, 6 marketplace icons,
       7 gutter. Must total 100 — every track is a share of the slide height, so a set
       that does not add up silently rescales all of them.

       Portrait only by construction: .small-screen-layout is display:none in landscape,
       which runs .big-screen-layout and its own row set entirely separately. */
    grid-template-rows: 5% 50% 5% 12% 15% 8% 5%;
    width: 100vw;
    max-width: 100vw;
    height: 100%;
    box-sizing: border-box;
}

.bs-1-1 { grid-area: 1 / 1 / 2 / 2; }
.bs-1-2 { grid-area: 1 / 2 / 2 / 3; }
.bs-1-3 { grid-area: 1 / 3 / 2 / 4; }
.bs-1-4 { grid-area: 1 / 4 / 2 / 5; }
.bs-1-5 { grid-area: 1 / 5 / 2 / 6; }

.bs-2-1 { grid-area: 2 / 1 / 3 / 2; }
.bs-2-2 { grid-area: 2 / 2 / 3 / 3; }
.bs-2-3 { grid-area: 2 / 3 / 3 / 4; }
.bs-2-4 { grid-area: 2 / 4 / 3 / 5; }
.bs-2-5 { grid-area: 2 / 5 / 3 / 6; }

.bs-3-1 { grid-area: 3 / 1 / 4 / 2; }
.bs-3-2 { grid-area: 3 / 2 / 4 / 3; }
.bs-3-3 { grid-area: 3 / 3 / 4 / 4; }
.bs-3-4 { grid-area: 3 / 4 / 4 / 5; }
.bs-3-5 { grid-area: 3 / 5 / 4 / 6; }

.bs-4-1 { grid-area: 4 / 1 / 5 / 2; }
.bs-4-2 { grid-area: 4 / 2 / 5 / 3; }
.bs-4-3 { grid-area: 4 / 3 / 5 / 4; }
.bs-4-4 { grid-area: 4 / 4 / 5 / 5; }
.bs-4-5 { grid-area: 4 / 5 / 5 / 6; }

.bs-5-1 { grid-area: 5 / 1 / 6 / 2; }
.bs-5-2 { grid-area: 5 / 2 / 6 / 3; }
.bs-5-3 { grid-area: 5 / 3 / 6 / 4; }
.bs-5-4 { grid-area: 5 / 4 / 6 / 5; }
.bs-5-5 { grid-area: 5 / 5 / 6 / 6; }

.bs-carousel-combined { grid-area: 2 / 4 / 5 / 5; }
/* Column 2 only, with the row left-aligned inside it — see .bs-icons-aligned .icon-buttons
   below. Narrow landscape widths override both; that block is near the icon sizing tiers. */
.bs-icons-aligned     { grid-area: 4 / 2 / 5 / 3; overflow: visible; z-index: 1; }

.ss-1-1 { grid-area: 1 / 1 / 2 / 2; }
.ss-1-2 { grid-area: 1 / 2 / 2 / 3; }
.ss-1-3 { grid-area: 1 / 3 / 2 / 4; }
.ss-1-4 { grid-area: 1 / 4 / 2 / 5; }
.ss-1-5 { grid-area: 1 / 5 / 2 / 6; }

.ss-2-1 { grid-area: 2 / 1 / 3 / 2; }
.ss-2-2 { grid-area: 2 / 2 / 3 / 3; }
.ss-2-3 { grid-area: 2 / 3 / 3 / 4; }
.ss-2-4 { grid-area: 2 / 4 / 3 / 5; }
.ss-2-5 { grid-area: 2 / 5 / 3 / 6; }

.ss-3-1 { grid-area: 3 / 1 / 4 / 2; }
.ss-3-2 { grid-area: 3 / 2 / 3 / 3; }
.ss-3-3 { grid-area: 3 / 3 / 3 / 4; }
.ss-3-4 { grid-area: 3 / 4 / 3 / 5; }
.ss-3-5 { grid-area: 3 / 5 / 3 / 6; }

.ss-4-1 { grid-area: 4 / 1 / 5 / 2; }
.ss-4-2 { grid-area: 4 / 2 / 5 / 3; }
.ss-4-3 { grid-area: 4 / 3 / 5 / 4; }
.ss-4-4 { grid-area: 4 / 4 / 5 / 5; }
.ss-4-5 { grid-area: 4 / 5 / 5 / 6; }

.ss-5-1 { grid-area: 5 / 1 / 6 / 2; }
.ss-5-2 { grid-area: 5 / 2 / 6 / 3; }
.ss-5-3 { grid-area: 5 / 3 / 6 / 4; }
.ss-5-4 { grid-area: 5 / 4 / 6 / 5; }
.ss-5-5 { grid-area: 5 / 5 / 6 / 6; }

.ss-6-1 { grid-area: 6 / 1 / 7 / 2; }
.ss-6-2 { grid-area: 6 / 2 / 7 / 3; }
.ss-6-3 { grid-area: 6 / 3 / 7 / 4; }
.ss-6-4 { grid-area: 6 / 4 / 7 / 5; }
.ss-6-5 { grid-area: 6 / 5 / 7 / 6; }

.ss-7-1 { grid-area: 7 / 1 / 8 / 2; }
.ss-7-2 { grid-area: 7 / 2 / 8 / 3; }
.ss-7-3 { grid-area: 7 / 3 / 8 / 4; }
.ss-7-4 { grid-area: 7 / 4 / 8 / 5; }
.ss-7-5 { grid-area: 7 / 5 / 8 / 6; }

.ss-carousel      { grid-area: 2 / 3 / 3 / 4; }
.ss-icons-aligned { grid-area: 6 / 3 / 7 / 4; overflow: visible; z-index: 1; }
.ss-arrow-left    { grid-area: 5 / 2 / 6 / 3; }
.ss-arrow-right   { grid-area: 5 / 4 / 6 / 5; }

/* -------------------- SLIDE 3 GRID -------------------- */
#slide-3 {
    display: grid;
    grid-template-columns: 10% 80% 10%;
    grid-template-rows: 10% 20% 0% 45% 10% 10% 5%;
    width: 100vw;
    max-width: 100vw;
}

.s3-1-1 { grid-area: 1 / 1 / 2 / 2; }
.s3-2-2 { grid-area: 2 / 2 / 3 / 3; }
.s3-2-3 { grid-area: 6 / 2 / 7 / 3; }
.s3-3-1 { grid-area: 3 / 1 / 4 / 2; }
.s3-4-2 { grid-area: 4 / 2 / 5 / 3; }
.s3-5-1 { grid-area: 6 / 1 / 7 / 2; }
.s3-6-1 { grid-area: 7 / 1 / 8 / 2; }
.s3-1-3 { grid-area: 1 / 3 / 2 / 4; }
.s3-3-3 { grid-area: 3 / 3 / 4 / 4; }
.s3-5-3 { grid-area: 6 / 3 / 7 / 4; }
.s3-6-3 { grid-area: 7 / 3 / 8 / 4; }

/* Base: desktop-first */
.slide-1-grid-desktop { display: grid; }
.slide-1-grid-mobile  { display: none; }
.big-screen-layout    { display: grid; }
.small-screen-layout  { display: none; }

/* Slide 1 grids — orientation based */
@media screen and (orientation: portrait) {
    .slide-1-grid-desktop { display: none; }
    .slide-1-grid-mobile  { display: grid; }
}

@media screen and (orientation: landscape) {
    .slide-1-grid-desktop { display: grid; }
    .slide-1-grid-mobile  { display: none; }
}

@media screen and (orientation: portrait) {
    #slide-3 { grid-template-rows: 6% 10% max(4%, 10px) 60% max(4%, 10px) 10% 6%; }
}

@media screen and (orientation: landscape) {
    #slide-3 { grid-template-rows: 6% 9% max(2%, 10px) 66% max(2%, 10px) 9% 6%; }
}


/* Slide 2 layouts — orientation based */
@media screen and (orientation: portrait) {
    .big-screen-layout   { display: none; }
    .small-screen-layout { display: grid; }
}

@media screen and (orientation: landscape) {
    .big-screen-layout   { display: grid; }
    .small-screen-layout { display: none; }
}

/* -------------------- FONTS -------------------- */
@font-face {
    font-family: 'BebasNeue';
    src: url('BebasNeue-Regular.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'Manrope';
    src: url('Manrope-Regular.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
}

@font-face {
    font-family: 'EmbolismSpark';
    src: url('embolism-spark.regular.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
}

@font-face {
    font-family: 'InconsolataLGC';
    src: url('inconsolata-lgc.bold.otf') format('opentype');
    font-weight: bold;
    font-style: normal;
}

@font-face {
    font-family: 'Oswald';
    src: url('the-free-font.regular.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
}


/* -------------------- GLOBAL RESET -------------------- */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    -webkit-tap-highlight-color: transparent;
    -webkit-touch-callout: none;
}

button, a, input, label { outline: none; }
button:focus-visible, a:focus-visible { outline: none; }

/* -------------------- AMPERSAND -------------------- */
.ampersand {
    font-family: 'InconsolataLGC', monospace;
    font-weight: bold;
    font-size: 1.0em;
}


/* -------------------- PRELOADER -------------------- */
body.preloader-active {
    overflow: hidden;
    height: 100vh;
    position: fixed;
    width: 100%;
}

.preloader {
    position: fixed;
    top: 0; left: 0;
    width: 100%; height: 100%;
    /* The dark is a background, not the element's opacity: at the end only the dark
       lifts, while the mark stays visible and travels onto slide 1's logo. */
    background: #0b0a0a;
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 9999;
}

/* ---- Unveil + hand-off (preloader.js drives every step; 2.6s in all) ----
   1. The bird comes into focus out of a blur.
   2. It slides left; the name is drawn open beside it, from the bird outward.
   3. The whole mark shrinks and travels onto slide 1's own logo while the dark lifts,
      and slide 1's logo takes over in the same frame — no fade, no flash (Apple: a
      launch should be "nearly identical to the first screen").
      On phones slide 1 shows the bird alone, so there the name fades out on the way.
   The swap is seamless because both marks are built from the SAME --mark-* numbers
   below: slide 1's desktop logo is this mark at full size, scaled down by one
   transform. Same layout, same drawing, nothing to jump. */

.preloader-center {
    display: flex;
    flex-direction: column;
    align-items: center;
}

/* Anchor for .preloader-text, and the box that travels in the hand-off. Its size is the
   logo's alone (the text is absolute), so it scales about the logo's centre. */
.preloader-content {
    display: flex;
    align-items: center;
    justify-content: center;
    position: relative;
    --pl-gap: var(--mark-gap);
}

/* The slide lives on the logo's own column, so the name — placed at its final spot from
   the start — never moves; it is only drawn open. */
.preloader-logo-col {
    display: flex;
    align-items: center;
}

.preloader-logo {
    width: auto;
    height: var(--mark-logo);
    object-fit: contain;
    opacity: 0;               /* the unveil brings it in */
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
    image-rendering: -webkit-optimize-contrast;
}

.preloader-text {
    font-family: 'BebasNeue', sans-serif;
    font-size: var(--mark-text);
    letter-spacing: var(--mark-track);
    line-height: 1;
    color: #eae7df;
    text-transform: uppercase;
    font-weight: 400;
    white-space: nowrap;
    opacity: 0;
    /* Absolute against .preloader-content, just past the logo's right edge and centred
       on it. The margin subtracts --slide-offset so the name sits at its FINAL spot
       from the start: the logo slides left by the same offset, so once it lands the gap
       is exactly --pl-gap and the pair is centred. */
    position: absolute;
    left: 100%;
    margin-left: calc(var(--pl-gap, 12px) - var(--slide-offset, 0px));
    top: 50%;
    transform: translateY(-50%);
}

/* Slide 1's logo waits hidden under the preloader, and is shown in the exact frame the
   travelling mark reaches it. */
body.preloader-handoff .logo-target { visibility: hidden; }

/* ---- The mark: one set of numbers for the preloader AND slide 1's desktop logo ----
   --mark-home is how far slide 1 scales the mark down: its logo height / the preloader's
   (96/136, 88/120, 66/90), which keeps slide 1's logo exactly the size it always was. */
:root {
    --mark-logo: 136px; --mark-text: 45px; --mark-track: 3px;   --mark-gap: 15px; --mark-home: 0.70588;
}
@media screen and (min-width: 701px) and (max-width: 1024px) {
    :root { --mark-logo: 120px; --mark-text: 40px; --mark-track: 2px;   --mark-gap: 11px; --mark-home: 0.73333; }
}
@media screen and (max-width: 700px) {
    :root { --mark-logo: 90px;  --mark-text: 30px; --mark-track: 1.5px; --mark-gap: 9px;  --mark-home: 0.73333; }
}


/* -------------------- SLIDE 1 LOGO TARGET -------------------- */
.logo-target {
    width: 100%; height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}

.logo-target-content {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
}

.logo-target-logo { object-fit: contain; }

.logo-target-text {
    font-family: 'BebasNeue', sans-serif;
    color: #eae7df;
    text-transform: uppercase;
    font-weight: 400;
    white-space: nowrap;
}

/* Built at the preloader mark's full size and scaled down by one transform (about its
   centre, so it sits where it always did). This is what lets the preloader's travelling
   mark hand over to it without a single pixel of difference: rendered natively at the
   smaller size, text is laid out with rounded letter widths and the name came out a
   little narrower, which showed as a jump. The mobile logo overrides the height below. */
.logo-target-logo    { width: auto; height: var(--mark-logo); }
.logo-target-text    { font-size: var(--mark-text); letter-spacing: var(--mark-track); line-height: 1; }
.logo-target-content { gap: var(--mark-gap); }
#logo-target-desktop .logo-target-content { transform: scale(var(--mark-home)); }

/* The -6px nudge existed to optically centre the logo+text pair. The mobile logo has no
   text any more, so it centres on its own. Desktop keeps its text and its nudge. */
#logo-target-mobile .logo-target-content { transform: none; }

/* ---- Corner token (vertical only) ----
   One value drives BOTH the logo's inset and its size, so the mark and its margin scale
   as a single unit instead of drifting apart as independent numbers.

   max(5vw, 2vh) = the larger of the two corner margins (col 1 is 5% of width, row 1 is 2%
   of height), applied to both axes so the gap above and to the left is always equal.

   The clamp is what makes deriving the logo size from this safe: unbounded, the 5vw term
   reaches 41px+ on a tablet and would drag the logo to ~129px. The floor also holds the
   phone size exactly where it is today.

   Defined on .slide-1-grid-mobile, which is display:none in landscape — the desktop logo
   never sees any of this. */
.slide-1-grid-mobile {
    --corner: clamp(21px, max(5vw, 2vh), 27px);
}

#logo-target-mobile {
    position: absolute;
    top: var(--corner);
    left: var(--corner);
    width: auto;
    height: auto;
    z-index: 2;
}

/* The rule: the logo is 3.14x its corner margin. Height only — width stays auto so the
   file's own aspect ratio decides it. Higher specificity than the .logo-target-logo
   breakpoint tiers, so it replaces them here without touching the desktop logo. */
#logo-target-mobile .logo-target-logo {
    height: calc(var(--corner) * 2.50);
    width: auto;
}

.slide-1-grid-desktop .s1d-2-2-3,
.slide-1-grid-desktop .s1d-2-5-6,
.slide-1-grid-mobile .s1m-2-2,
.slide-1-grid-mobile .s1m-2-4,
.slide-1-grid-mobile .s1m-6-4 { overflow: visible; }

/* Vertical only (this cell exists only in .slide-1-grid-mobile).
   Optical centring for BUY NOW. The picture's visible bottom sits exactly on the row-5
   boundary, so the space under it is rows 6+7 — but the button is centred in row 6 alone,
   which reads ~9px high. The correction is the gap between those two centres, which works
   out to exactly half of row 7 at any screen size. Done as a transform rather than by
   spanning the cell into row 7, which would collide with the row-7 cells and reshuffle
   the grid. */
.slide-1-grid-mobile .s1m-6-4 .interested-container {
    transform: translateY(max(1vh, 5px));
}


/* -------------------- INTERESTED BUTTON -------------------- */
.interested-container {
    width: 100%; height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
}

.interested-btn {
    background: transparent;
    border: none;
    color: #eae7df;
    font-family: 'Oswald', sans-serif;
    cursor: pointer;
    white-space: nowrap;
    font-weight: normal;
    padding: 8px 16px;
}

@media screen and (min-width: 1025px) {
    .interested-btn { font-size: 29px; letter-spacing: 1.9px; }
}
@media screen and (min-width: 701px) and (max-width: 1024px) {
    .interested-btn { font-size: 26px; letter-spacing: 1.3px; }
}
@media screen and (max-width: 700px) {
    .interested-btn { font-size: 19px; letter-spacing: 1px; }
}


/* ==================== RIPPLE LAYER ====================
   Drawn over slide 2's flat field. The #272727 resting fill below is one half of a pair
   with the #171717 in .slide2-bg — see the note there, and move both or neither. */
.ripple-layer {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    pointer-events: none;
    z-index: 0;
    overflow: visible;
}

.ripple-layer[data-group="0"] { --ripple-color: #64a0ff; }
.ripple-layer[data-group="1"] { --ripple-color: #64dc96; }
.ripple-layer[data-group="2"] { --ripple-color: #ff6478; }

.ripple-speck { fill: #272727; }

/* Specks overlapping the carousel text / icon rows — kept in the DOM so the holes can
   follow the carousel as it flips, but not rendered. */
.rs-hidden { display: none; }

@keyframes speck-ripple {
    0%   { fill: #272727; animation-timing-function: ease-out; }
    35%  { fill: color-mix(in srgb, var(--ripple-color) calc(var(--ripple-intensity, 1) * 100%), #272727); animation-timing-function: ease-in; }
    100% { fill: #272727; }
}

.ripple-speck--animate {
    animation: speck-ripple;
}

.rs-colored, .rs-static {
    --rs-scale: 0.72;
    transform: scale(var(--rs-scale));
    transform-box: fill-box;
    transform-origin: center;
}

/* Portrait added alongside the existing size rules — must stay in step with isMobile()
   in ripple.js, which picks the tile spacing. If one includes portrait and the other
   doesn't, you get small shapes on wide spacing (or the reverse). */
@media (orientation: portrait), (max-width: 699px), (max-height: 499px) {
    .rs-colored, .rs-static { --rs-scale: 0.524; }
}

@keyframes speck-swell {
    0%   { transform: scale(var(--rs-scale)); animation-timing-function: ease-out; }
    35%  { transform: scale(calc(var(--rs-scale) * (1 + 0.18 * var(--ripple-intensity, 1)))); animation-timing-function: ease-in; }
    100% { transform: scale(var(--rs-scale)); }
}

.ripple-speck--animate .rs-colored,
.ripple-speck--animate .rs-static {
    animation-name: speck-swell;
    animation-duration: inherit;
    animation-delay: inherit;
    animation-fill-mode: inherit;
}

/* ==================== MARQUEE ==================== */
.s1d-4-1-7,
.s1m-4-1-7 {
    background-color: rgba(11, 10, 10, 0.85);
    color: #eae7df;
    font-family: 'BebasNeue', sans-serif;
    text-transform: uppercase;
    overflow: hidden;
    position: relative;
    display: flex;
    align-items: center;
    transition: opacity 0.6s;
}

.marquee-container {
    width: 100%;
    overflow: hidden;
    white-space: nowrap;
    position: relative;
}

.marquee-track {
    display: inline-flex;
    white-space: nowrap;
    animation: marquee linear infinite;
    will-change: transform;
    transform: translateZ(0);
    -webkit-transform: translateZ(0);
    -webkit-animation: marquee linear infinite;
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
}

.marquee-content { flex-shrink: 0; font-size: inherit; letter-spacing: inherit; }

/* Vertical only: marquee text 1.5x. Scaling with em rather than restating a size means
   this rides on top of all ~12 existing font-size breakpoints instead of duplicating
   them. letter-spacing is re-expressed in em (the original ratio is ~font/16 at every
   breakpoint) so it scales with the text instead of staying at its old pixel value.
   .s1m- prefix keeps this off the desktop marquee, which shares .marquee-content.
   Scroll speed self-corrects: marquee.js derives duration from track scrollWidth. */
.s1m-4-1-7 .marquee-content {
    font-size: 1.03em;
    letter-spacing: 0.0625em;
}

.s1d-4-1-7.fade-out,
.s1m-4-1-7.fade-out {
    opacity: 0;
    pointer-events: none;
}

.s1d-4-1-7::before, .s1d-4-1-7::after,
.s1m-4-1-7::before, .s1m-4-1-7::after {
    content: '';
    position: absolute;
    top: 0; height: 100%; width: 100px;
    z-index: 2; pointer-events: none;
}
.s1d-4-1-7::before, .s1m-4-1-7::before {
    left: 0;
    background: linear-gradient(to right, rgba(11,10,10,0.85) 0%, transparent 100%);
}
.s1d-4-1-7::after, .s1m-4-1-7::after {
    right: 0;
    background: linear-gradient(to left, rgba(11,10,10,0.85) 0%, transparent 100%);
}

@media screen and (min-width: 1025px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 57.6px; letter-spacing: 3.6px; }
}
@media screen and (min-width: 701px) and (max-width: 1024px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 41px; letter-spacing: 2.5px; }
}
@media screen and (max-width: 700px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 35px; letter-spacing: 2.2px; }
}

@keyframes marquee {
    0%   { transform: translateX(0); }
    100% { transform: translateX(-50%); }
}


/* ==================== CAROUSEL ==================== */

.carousel-container {
    position: relative;
    width: 100%; height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    perspective: 1200px;
    background: transparent;
}

.carousel-card {
    width: 100%; height: 100%;
    position: relative;
    transform-style: preserve-3d;
    transition: transform 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    background: transparent;
    transform: rotateY(0.01deg);
}

.carousel-card.flip-right { transform: rotateY(180deg); }
.carousel-card.flip-left  { transform: rotateY(-180deg); }

.carousel-face {
    position: absolute;
    width: 100%; height: 100%;
    backface-visibility: hidden;
    border-radius: 16px;
    overflow: hidden;
    background: transparent;
    transform-style: preserve-3d;
    -webkit-transform-style: preserve-3d;
}

.carousel-face.front { transform: rotateY(0deg); }
.carousel-face.back  { transform: rotateY(180deg); }

.carousel-image {
    width: 100%; height: 100%;
    object-fit: cover;
    border-radius: 16px;
    transform: translate3d(0, 0, 0.1px);
    -webkit-transform: translate3d(0, 0, 0.1px);
    backface-visibility: hidden;
    -webkit-backface-visibility: hidden;
}

.carousel-arrows {
    position: absolute;
    top: 50%; left: 0; right: 0;
    transform: translateY(-50%);
    display: flex;
    justify-content: space-between;
    padding: 0 20px;
    pointer-events: none;
    z-index: 10;
    max-width: 100%;
    box-sizing: border-box;
}

.carousel-arrow {
    pointer-events: auto;
    border: none;
    background: transparent !important;
    color: white;
    font-size: 32px;
    width: 50px; height: 50px;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    transition: transform 0.3s ease, opacity 0.3s ease;
    text-shadow: 0 2px 10px rgba(0, 0, 0, 0.8);
    user-select: none;
    border-radius: 50%;
}
.carousel-arrow:hover { transform: scale(1.1); }
.carousel-arrow:focus,
.carousel-arrow:focus-visible { outline: none; border: none; }

.big-arrows .carousel-arrow { background: none !important; font-size: 32px; padding: 10px; z-index: 20; }

@media screen and (orientation: landscape) {
    .big-arrows { padding-left: 20px !important; padding-right: 20px !important; }
    .big-arrows .carousel-arrow { font-size: 56px !important; padding: 15px !important; }
}

/* min-width: the arrow's column is only 5% wide (~19px on a phone), below Apple's 28px
   minimum tap size. The button now overflows its cell, centred on it, to a 44px target —
   the glyph stays where it was. The cells stay visible and above the body copy so the
   overhang is actually clickable. */
.small-arrow { border-radius: 0 !important; background: transparent !important; width: 100%; min-width: 44px; flex-shrink: 0; height: 100%; font-size: 28px; }
.small-arrow:hover              { background: transparent !important; border-radius: 0 !important; }
.ss-arrow-left, .ss-arrow-right { border-radius: 0 !important; background: transparent !important; overflow: visible; position: relative; z-index: 2; }

.icon-buttons {
    display: flex;
    justify-content: center;
    align-items: center;
    gap: 20px;
    width: 100%; height: 100%;
    opacity: 1; visibility: visible;
    transition: all 0.3s ease;
    max-width: 100%;
    box-sizing: border-box;
}
.icon-buttons.hidden { opacity: 0; visibility: hidden; }

/* Each icon sits in a transparent button. The icons are ~4.4:1, so on a phone they are
   only ~14px tall — far below Apple's 44px tap target. Vertical padding grows the tap
   area to 44px and the matching negative margin cancels it out, so the row lays out
   exactly as before and the <img> (which ripple.js measures) keeps its own size. */
.icon-hit {
    display: flex;
    align-items: center;
    background: none;
    border: none;
    padding: 15px 0;
    margin: -15px 0;
    cursor: pointer;
}
.icon-hit .icon-button { display: block; }

/* Was 22% / 22% of .icon-buttons. The <img> is inside a button now, so a percentage would
   resolve against the button instead — these are the same 22% written out against the
   cell: column 2 is 30vw wide and row 4 is 20% of the slide, less the row's 10px padding.
   Only the widest landscape screens see this; every other size has a px tier below. */
.icon-button {
    max-width: calc(0.22 * (30vw - 20px)); max-height: calc(0.22 * (20vh - 20px));
    width: auto; height: auto;
    object-fit: contain;
    transition: all 0.3s ease;
    border-radius: 4px;
    background: #ffffff;
    padding: 4px;
    box-sizing: border-box;
}
.icon-button:hover { transform: scale(1.1); cursor: pointer; }

.icon-link { cursor: pointer; }

#slide-2 .icon-button {
    box-sizing: border-box;
    transition: transform 0.3s ease;
}
#slide-2 .icon-button:hover,
#slide-2 .icon-button:focus,
#slide-2 .icon-button:focus-visible,
#slide-2 .icon-button:active {
    box-shadow: none !important;
}

/* ---- Icon row entrance / exit ----
   A plain fade, all three together — no stagger, no movement. Opacity stays on the
   container so they can't drift apart. visibility flips instantly on the way in and
   waits out the fade on the way out, so it never cuts the animation short while still
   blocking clicks once hidden. */
#slide-2 .icon-buttons {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.4s ease, visibility 0s;
}
#slide-2 .icon-buttons.hidden {
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.4s ease, visibility 0s 0.4s;
}

/* Left-aligned in the cell, not centred — this is what lines the icons up with the copy
   above them rather than with the middle of column 2.
   The !important that used to be on justify-content here has been dropped: this selector
   is (0,2,0) against the base .icon-buttons rule's (0,1,0), so it already wins on
   specificity alone. Removing it is what lets the narrow-landscape override below take
   over with plain source order instead of needing an !important of its own. */
.bs-icons-aligned .icon-buttons { justify-content: flex-start; padding: 10px; }

/* Below 1480 the icons stop hugging the left of column 2 and instead centre across
   columns 1 and 2 together. Column 1 is the empty 5% gutter, so the pair gives them 35%
   of the viewport to sit in rather than 30% — the row gets breathing room exactly where
   the layout is tightest, and re-centring keeps it from drifting into that gutter.
   Above 1480 there is enough width that the original left alignment still reads correctly
   against the copy, so it is left alone.
   Neither declaration needs !important: both match the base rules' specificity and simply
   come later in the file. */
@media screen and (orientation: landscape) and (max-width: 1479.98px) {
    .bs-icons-aligned              { grid-area: 4 / 1 / 5 / 3; }
    .bs-icons-aligned .icon-buttons { justify-content: center; }
    /* The cell spans columns 1-2 here (35vw), so the 22% is taken of that. */
    .bs-icons-aligned .icon-button  { max-width: calc(0.22 * (35vw - 20px)); }
}

@media screen and (orientation: landscape) and (max-width: 1400px) {
    .bs-icons-aligned .icon-button { max-width: min(100px, 12vw); max-height: min(100px, 12vh); }
}
@media screen and (orientation: landscape) and (max-width: 1200px) {
    .bs-icons-aligned .icon-button { max-width: min(80px, 10vw); max-height: min(80px, 10vh); }
}
/* One more step at the narrow end, continuing the 100 -> 80 ladder rather than shrinking
   the tiers above it. Must stay AFTER the 1200 rule: same specificity, so source order is
   what lets it win below 1000.
   The px value is the one that actually bites — the icons are 4.42:1, so max-width always
   binds before max-height, and 8vw only takes over below 750px wide. */
@media screen and (orientation: landscape) and (max-width: 1000px) {
    .bs-icons-aligned .icon-button { max-width: min(60px, 8vw); max-height: min(60px, 8vh); }
}
@media screen and (orientation: portrait) {
    .ss-icons-aligned .icon-button { max-width: 100px; max-height: 100px; }
}
@media screen and (max-width: 440px) {
    .ss-icons-aligned .icon-button { max-width: 60px; max-height: 60px; }
}

.text-content {
    position: absolute;
    width: 100%;
    opacity: 0;
    transform: translateX(-20px);
    transition: all 0.6s ease;
    pointer-events: none;
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    align-items: flex-start;
    color: #fff;
}
.text-content.active {
    opacity: 1;
    transform: translateX(0);
    pointer-events: auto;
}

@media screen and (orientation: landscape) {
    .bs-2-2 { position: relative; overflow: hidden; background: transparent !important; }
    .bs-3-2 { position: relative; overflow: visible; background: transparent !important; }
    .bs-2-2 .text-content { bottom: 0; left: 0; right: 0; height: auto; text-align: left; justify-content: flex-start; }
    /* right is the indent knob for the body copy — it pulls the wrap point in from the
       right edge, so the copy breaks earlier while its left edge stays flush with the
       headline in .bs-2-2 above.
       width:auto is load-bearing — the base .text-content rule sets width:100%, which
       over-constrains an element that also has left and right, so the browser drops
       `right` and the indent does nothing at all. Releasing the width lets left/right
       define the box. */
    .bs-3-2 .text-content { top: 0; left: 0; right: 60px; width: auto; height: auto; text-align: left; justify-content: flex-start; }
}
@media screen and (orientation: portrait) {
    .ss-4-3, .ss-5-3 { position: relative; align-items: left; overflow: hidden; background: transparent !important; }
    .ss-4-3 .text-content { top: 0; height: 100%; align-items: center; justify-content: flex-end; text-align: center; }
    .ss-5-3 .text-content { top: 0; height: 100%; align-items: center; justify-content: center; text-align: center; }
    /* No slide-in in portrait — text is centered so translateX causes it to bleed outside the bg box */
    .ss-4-3 .text-content,
    .ss-5-3 .text-content { transform: none; transition: opacity 0.6s ease; }
    .ss-4-3 .text-content.active,
    .ss-5-3 .text-content.active { transform: none; }
}

.carousel-headline {
    font-family: 'BebasNeue', sans-serif;
    font-size: 32px;
    margin: 0 0 25px 0;
    font-weight: normal;
    letter-spacing: 1px;
    line-height: 1.2;
    color: #eae7df;
    text-transform: uppercase;
}

.carousel-body {
    font-family: 'Manrope', sans-serif;
    font-size: 18px;
    line-height: 1.6;
    margin: 0;
    opacity: 0.9;
    max-width: 100%;
    color: rgba(255, 255, 255, 0.8);
    font-weight: 400;
}


.bs-carousel-combined,
.ss-carousel,
.carousel-container { overflow: visible !important; }

@media screen and (orientation: landscape) {
    .carousel-headline { font-size: 40px; }
    .carousel-body     { font-size: 22px; }
}
@media screen and (orientation: portrait) and (min-width: 701px) {
    .carousel-headline { font-size: 29px; margin-bottom: 0; }
    .carousel-body     { font-size: 19px; }
}
@media screen and (orientation: portrait) and (max-width: 700px) {
    .carousel-headline { font-size: 22px; margin-bottom: 0; }
    .carousel-body     { font-size: 15px; }
}

/* ---- Slide 2 only — small viewport height ---- */
/* Scoped to #slide-2 so slide 1 / marquee are never touched */
/* overflow: visible on text cells so headline is never clipped top/bottom */
@media screen and (max-height: 700px) and (orientation: landscape) {
    #slide-2 .bs-2-2, #slide-2 .bs-3-2 { overflow: visible; }
    #slide-2 .carousel-headline { font-size: 30px; margin-bottom: 0px; }
    #slide-2 .carousel-body     { font-size: 17px; }
}
@media screen and (max-height: 600px) and (orientation: landscape) {
    #slide-2 .carousel-headline { font-size: 26px; margin-bottom: 0px; }
    #slide-2 .carousel-body     { font-size: 15px; }
}
@media screen and (max-height: 500px) and (orientation: landscape) {
    #slide-2 .carousel-headline { font-size: 22px; margin-bottom: 0px; }
    #slide-2 .carousel-body     { font-size: 13px; }
}

/* ---- Slide 2 big-screen — shrink row 2, grow row 3 by same amount ---- */
@media screen and (orientation: landscape) and (max-height: 700px) {
    .big-screen-layout { grid-template-rows: 15% 18% 32% 20% 15%; }
}
@media screen and (orientation: landscape) and (max-height: 600px) {
    .big-screen-layout { grid-template-rows: 15% 14% 36% 20% 15%; }
}
@media screen and (orientation: landscape) and (max-height: 500px) {
    .big-screen-layout { grid-template-rows: 15% 10% 40% 20% 15%; }
}
@media screen and (max-height: 700px) and (orientation: portrait) {
    #slide-2 .ss-4-3, #slide-2 .ss-5-3 { overflow: visible; }
    #slide-2 .carousel-body     { font-size: 13px; }
}

/* ---- Marquee only — small viewport height ---- */
/* Uses marquee-specific classes only, no slide 2 classes touched */
@media screen and (max-height: 700px) and (min-width: 1025px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 38px; letter-spacing: 2.4px; }
}
@media screen and (max-height: 600px) and (min-width: 1025px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 32px; letter-spacing: 2.0px; }
}
@media screen and (max-height: 500px) and (min-width: 1025px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 27px; letter-spacing: 1.7px; }
}
@media screen and (max-height: 400px) and (min-width: 1025px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 21px; letter-spacing: 1.3px; }
}
@media screen and (max-height: 700px) and (min-width: 701px) and (max-width: 1024px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 30px; letter-spacing: 1.9px; }
}
@media screen and (max-height: 600px) and (min-width: 701px) and (max-width: 1024px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 25px; letter-spacing: 1.6px; }
}
@media screen and (max-height: 500px) and (min-width: 701px) and (max-width: 1024px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 21px; letter-spacing: 1.3px; }
}
@media screen and (max-height: 400px) and (min-width: 701px) and (max-width: 1024px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 16px; letter-spacing: 1.0px; }
}
@media screen and (max-height: 700px) and (max-width: 700px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 26px; letter-spacing: 1.6px; }
}
@media screen and (max-height: 600px) and (max-width: 700px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 21px; letter-spacing: 1.3px; }
}
@media screen and (max-height: 500px) and (max-width: 700px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 18px; letter-spacing: 1.1px; }
}
@media screen and (max-height: 400px) and (max-width: 700px) {
    .s1d-4-1-7, .s1m-4-1-7 { font-size: 14px; letter-spacing: 0.9px; }
}

:root {
    --shadow-blue:  rgba(100, 150, 255, 0.15);
    --shadow-green: rgba(100, 220, 150, 0.15);
    --shadow-red:   rgba(255, 100, 120, 0.15);
    --text-blue:    #64a0ff;
    --text-green:   #64dc96;
    --text-red:     #ff6478;
}

.highlight-blue  { color: var(--text-blue); }
.highlight-green { color: var(--text-green); }
.highlight-red   { color: var(--text-red); }

/* .carousel-face[data-index="0"] { box-shadow: 0 25px 60px var(--shadow-blue); } */
/* .carousel-face[data-index="1"] { box-shadow: 0 25px 60px var(--shadow-green); } */
/* .carousel-face[data-index="2"] { box-shadow: 0 25px 60px var(--shadow-red); } */


/* ==================== SLIDE 3 ==================== */

#slide-3 { background-color: #F3F3F3; position: relative; overflow: hidden; }

.slide3-video {
    grid-area: 3 / 2 / 6 / 3;
    width: 100%;
    height: 100%;
    z-index: 0;
    pointer-events: none;
    transition: opacity 0.6s ease;
}

/* Fullscreen typewriter mode — portrait < 400px */
#slide-3.typewriter-fullscreen .slide3-video {
    opacity: 0;
    pointer-events: none;
}
#slide-3.typewriter-fullscreen .s3-4-2 {
    overflow: visible;
}
#slide-3.typewriter-fullscreen .s3-4-2 .typewriter-wrapper {
    aspect-ratio: unset !important;
    padding: 0 32px;
    z-index: 10;
    background: transparent;
    transition: background 1s ease;
}
#slide-3.typewriter-fullscreen.typewriter-dim .s3-4-2 .typewriter-wrapper {
    background: #161616;
}

.slide3-video img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    object-position: center center;
    filter: blur(0px);
    transition: filter 2s ease;
}

#slide-3.typewriter-active .slide3-video img {
    filter: blur(5px) brightness(0.3);
}

@media screen and (orientation: portrait) {
    #slide-3.typewriter-active .slide3-video img {
        filter: blur(5px) brightness(0.5);
    }
}

@media screen and (orientation: landscape) {
    .slide3-video {
        grid-area: 4 / 2 / 5 / 3;
        width: auto;
        height: 100%;
        max-width: 100%;
        aspect-ratio: 2.35 / 1;
        align-self: center;
        justify-self: center;
    }
}

@media screen and (orientation: portrait) {
    /* Shrink-wrapping the card to the image was tried here and does NOT work: the image's
       max-height would resolve against a picture whose own height is auto, so it cannot
       constrain anything. At 760x1000 that gave a 608x938 image inside a 608x600 card —
       a 338px overflow. The card therefore keeps a definite box and object-fit: contain
       letterboxes inside it; typewriter.js measures that letterbox once and shares it. */
    .slide3-video {
        grid-area: 4 / 2 / 5 / 3;
        width: auto;
        height: 100%;
        max-width: 100%;
        aspect-ratio: 3 / 4;
        align-self: center;
        justify-self: center;
    }
}

/* Cells sitting on top of the video need z-index > 0 */
#slide-3 .s3-4-2,
#slide-3 .s3-3-1, #slide-3 .s3-3-3,
#slide-3 .s3-5-1, #slide-3 .s3-5-3 {
    position: relative;
    z-index: 1;
}

/* Button cells must sit above the fullscreen wrapper (z-index 10) */
#slide-3 .s3-2-2,
#slide-3 .s3-2-3 {
    position: relative;
    z-index: 20;
}

/* Fullscreen typewriter — buttons fade with opacity */
.typewriter-fs-element {
    transition: opacity 1s ease !important;
}

/* Remove default centering from all slide 3 cells */
#slide-3 .grid-cell {
    align-items: flex-start;
    justify-content: flex-start;
}
/* Re-apply centering only to the content cells */
#slide-3 .s3-4-2 {
    align-items: stretch;
    justify-content: center;
}
/* Approach the Light — top of cell, horizontally centered */
#slide-3 .s3-2-2 {
    align-items: flex-start;
    justify-content: center;
    overflow: visible;
}
/* Contact — bottom of cell, horizontally centered */
#slide-3 .s3-2-3 {
    align-items: flex-end;
    justify-content: center;
    overflow: visible;
}

/* Phones: both buttons belong to the picture, not to the screen edges (Apple: "group
   related items"). Each sits against the picture's side of its row, so the gap to the
   picture is the same above and below and only grows gently with the screen (the 4% row
   between them), while the spare height goes to the outer margins. It also brings the
   main button down, toward the thumb (Apple: controls are easiest to reach "in the middle
   or bottom area of the display"). */
@media screen and (orientation: portrait) {
    #slide-3 .s3-2-2 { align-items: flex-end; }
    #slide-3 .s3-2-3 { align-items: flex-start; }
}

/* "Approach the Light". The words are painted, not coloured: a #161616 gradient with one
   narrow silver band in it, clipped to the glyphs (background-clip: text). At rest the
   band is parked off the left edge, so the words read as plain #161616; .glint-pass
   slides it through the letters and off the right while the button lifts 3px and settles
   — the light passes through the words, never as a box around them.
   typewriter.js plays it: when the button becomes ready again after the text, on hover,
   and as an idle nudge (5s, then 10s, then every 20s without a press). */
.cta {
    border: none;
    color: transparent;
    background-image: linear-gradient(100deg, #161616 0 44%, #a4a4a4 50%, #161616 56% 100%);
    background-size: 250% 100%;
    background-position: 100% 0;
    -webkit-background-clip: text;
            background-clip: text;
    font-weight: normal;
    text-transform: uppercase;
    font-size: 16px;
    padding: 12px 24px;
    cursor: pointer;
    /* Only what actually changes. `all` would also have tweened the parked band. */
    transition: opacity 0.3s ease, scale 0.12s ease;
    font-family: 'BebasNeue', sans-serif;
    text-decoration: none;
    max-width: 100%;
    white-space: nowrap;
}

@media screen and (min-width: 1025px)                        { .cta { font-size: 32px; } }
@media screen and (min-width: 701px) and (max-width: 1024px) { .cta { font-size: 29px; } }
@media screen and (max-width: 700px)                         { .cta { font-size: 26px; } }

/* Press state (Apple: every custom button needs one). `scale`, not transform, so it
   never fights the lift animation for the same property. */
.cta:active:not(:disabled) { scale: 0.96; }

/* KEEP IN SYNC: NUDGE_MS in typewriter.js (1300). */
.cta.glint-pass {
    animation: ctaGlint 1.3s cubic-bezier(0.4, 0, 0.2, 1),
               ctaLift  1.3s linear;
}
@keyframes ctaGlint {
    from { background-position: 100% 0; }
    to   { background-position: 0% 0; }
}
/* Rises with the light, then settles with a hair of spring. */
@keyframes ctaLift {
    0%   { transform: translateY(0); }
    40%  { transform: translateY(-3px); animation-timing-function: cubic-bezier(0.3, 0, 0.3, 1); }
    75%  { transform: translateY(0.8px); }
    88%  { transform: translateY(-0.3px); }
    100% { transform: translateY(0); }
}

@media (prefers-reduced-motion: reduce) {
    .cta.glint-pass { animation: none; }
}

.s3-4-2 {
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent !important;
    position: relative;
    overflow: visible;
    max-width: 100%;
}

/* Wrapper is positioned by JS to exactly match the rendered picture */
.s3-4-2 .typewriter-wrapper {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    padding: 0 24px;
}

.s3-4-2 .typewriter-container {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    opacity: 1;
    visibility: visible;
    box-sizing: border-box;
    width: 100%;
}

.typewriter-line {
    font-size: 18px;
    line-height: 1.6;
    font-family: 'EmbolismSpark', cursive;
    color: #f5f0e0;
    font-weight: 500;
    text-shadow: 0 0 7px #161616, 0 0 7px #161616, 0 0 7px #161616, 0 0 7px #161616, 0 0 7px #161616, 0 0 5px #161616, 0 0 3px #161616;
    text-align: left;
    white-space: nowrap;
    margin: 0; padding: 0;
    height: 38.4px;
    display: block;
    opacity: 1;
    transform: translateY(0);
    transition: all 0.3s ease;
    overflow: visible;
    position: relative;
    width: 100%; max-width: 100%;
}
.typewriter-line.visible { opacity: 1; transform: translateY(0); }

.typewriter-cursor::after { content: ''; animation: blink 1s infinite; }
@keyframes blink {
    0%, 100% { opacity: 1; }
    50%       { opacity: 0; }
}

.typewriter-container.fade-out { animation: fadeOut 1.5s ease forwards; }
@keyframes fadeOut {
    0%   { opacity: 1; }
    100% { opacity: 0; }
}

/* typewriter-active no longer changes background — video plays continuously */

@media screen and (min-width: 1025px) { .typewriter-line { font-size: 40px; height: 46.4px; } }
@media screen and (max-width: 1024px) { .typewriter-line { font-size: 28px; height: 28px; } }


/* ==================== SLIDE 3 — PORTRAIT ONLY ====================
   Everything below is scoped to portrait on purpose. Landscape keeps its existing
   behaviour (dimmed background, light text, blurred photo, no card flip). Placed after
   the rules above so these overrides win without needing !important. */
@media screen and (orientation: portrait) {

    /* Approaching the light no longer darkens the slide — the fullscreen text wrapper
       stays clear and the text goes dark instead of light. */
    #slide-3.typewriter-fullscreen .s3-4-2 .typewriter-wrapper,
    #slide-3.typewriter-fullscreen.typewriter-dim .s3-4-2 .typewriter-wrapper {
        background: transparent;
    }

    .typewriter-line {
        color: #171717;
        text-shadow: none;
    }

    /* ---- Picture flip card ----
       No DOM change: the <picture> itself is the card, its <img> is the front face and
       ::after is the blank back. perspective() sits inside the transform so no ancestor
       has to become a 3D context (which would have re-based absolutely positioned
       descendants elsewhere on the slide). */
    .slide3-video {
        position: relative;
        transform-style: preserve-3d;
        -webkit-transform-style: preserve-3d;
        transition: opacity 0.6s ease;
        /* Identity transform held permanently. Without it the resting state is
           `transform: none`, so at the final frame of a flip the card stops being a
           transformed box and reverts to plain painting — the geometry is identical
           (measured: 0.000px), but the rasterisation changes, and that is the 1px twitch
           right as the animation goes static. Both ends are matrix3d now, so nothing
           switches. Must stay in the same function order as .pic-flipped below, or the
           two won't interpolate cleanly. */
        transform: perspective(1200px) rotateY(0deg) scale(1);
    }

    /* The flip transition is deliberately NOT on by default. If it were, rotating into
       portrait mid-session would animate a flip on its own: pic-flipped is already on the
       slide, so the instant this media query matches, the transform AND its transition
       both come alive together and the card rotates. No JS can suppress that — the resize
       handler is debounced and runs long after the animation has started. With the
       transition parked behind a class that JS only adds for a deliberate flip, an
       orientation change simply lands on the finished state. */
    /* Original easing, just slower — 0.8s -> 1.0s. Ease-in-out was tried here and read
       worse, not better: perspective already makes the card appear to crawl near 0°/180°
       and rush through 90°, so easing both ends on top of that exaggerated it.
       KEEP IN SYNC: the shadow animation below and FLIP_MS in typewriter.js. */
    #slide-3.flip-anim .slide3-video {
        /* box-shadow stays in the list: on the turn-back .flip-shadow is dropped while
           this class is still on, and without it here the shadow would snap off instead
           of fading out mid-flip. */
        transition: transform 1s cubic-bezier(0.25, 0.46, 0.45, 0.94), opacity 0.6s ease,
                    box-shadow 0.4s ease;
    }

    .slide3-video img {
        backface-visibility: hidden;
        -webkit-backface-visibility: hidden;
        /* The card flips to a blank face, so blurring the photo underneath buys nothing.
           During a turn the light falloff below animates brightness() on this img —
           checked, and safe: the img is a leaf, so being a grouping element flattens
           nothing, and the flip stays 3D with its back hidden.
           brightness(1) rather than none, deliberately: it changes nothing visually, but
           it keeps a filter on the element at ALL times. Letting the filter disappear at
           the end of a turn tears the layer down and the browser redraws the picture from
           scratch — a one-pixel twitch exactly as it lands. Same idea as the permanent
           matrix3d on the card above. */
        filter: brightness(1);
        transition: none;
    }

    #slide-3.typewriter-active .slide3-video img { filter: brightness(1); }

    .slide3-video::after {
        content: '';
        position: absolute;
        /* Photo-sized, same as the shadow plate. At inset: 0 this was the full card box —
           opaque, and painted after ::before — so it covered the shadow's spread wherever
           the box overhangs the picture. On a wide portrait the horizontal overhang is
           ~30px against a 24px blur, which swallowed the side shadows completely. */
        inset: var(--pic-inset-y, 0) var(--pic-inset-x, 0);
        background: #F3F3F3;
        transform: rotateY(180deg);
        backface-visibility: hidden;
        -webkit-backface-visibility: hidden;
        border-radius: 4px;
    }

    /* Shadow plate inset to the photo's true rectangle. --pic-inset-* comes from the
       SAME measurement that positions the typewriter wrapper (photoBox() in
       typewriter.js), so there is one letterbox calculation in the project, not two.
       It sits on ::before, not ::after: ::after is the blank face and carries
       backface-visibility: hidden, which made the shadow invisible for the first half of
       every turn. ::before has no such flag, so it is there for the whole rotation and
       foreshortens with the card.
       Two layers, because one uniform blur reads as a glow: a tight contact shadow that
       keeps the card anchored, plus a wide ambient one doing the "lifted off the page"
       work. No X offset — this rotates about Y, so a sideways offset would swing across
       as it turns. */
    .slide3-video::before {
        content: '';
        position: absolute;
        inset: var(--pic-inset-y, 0) var(--pic-inset-x, 0);
        border-radius: 4px;
        pointer-events: none;
        box-shadow:
            0 1px 2px rgba(22, 22, 22, 0),
            0 8px 24px rgba(22, 22, 22, 0);
        transition: box-shadow 0.4s ease;
    }

    /* Resting state once the card has landed. */
    #slide-3.flip-shadow .slide3-video::before {
        box-shadow:
            0 1px 2px rgba(22, 22, 22, 0.14),
            0 8px 24px rgba(22, 22, 22, 0.20);
    }

    /* The turn itself. As the card comes up on edge the contact shadow releases and the
       ambient one spreads and deepens — it reads as being picked up — then both settle
       as it lands. Ends exactly on the resting values above, so there is no jump when
       the animation hands back over.
       Gated on BOTH classes on purpose: dropping .flip-shadow cancels this animation,
       which is what lets the turn-back fade out mid-flip instead of being held here. */
    #slide-3.flip-anim.flip-shadow .slide3-video::before {
        /* Duration mirrors the flip above — the keyframes are percentages, so the
           halfway cut-off tracks the turn automatically. */
        animation: cardTurnShadow 1s cubic-bezier(0.25, 0.46, 0.45, 0.94);
    }

    /* The way back. Written as its own keyframes rather than `animation-direction:
       reverse`, because reverse also reverses the easing — the shadow would stop tracking
       the card, which plays this curve forwards. Same shapes, opposite order. */
    /* forwards is load-bearing. typewriter.js drops the classes 100ms AFTER the rotation
       ends; without it the animation stops controlling the shadow the moment it finishes
       and .flip-shadow — still on for those 100ms — puts the RESTING shadow back. With
       the 0.4s box-shadow transition below, that read as the shadow pulsing back in and
       leaving again just as the picture landed. Holding the last frame (transparent)
       means the class removal changes transparent to transparent: nothing to see. */
    #slide-3.flip-anim.flip-shadow.flip-back .slide3-video::before {
        animation: cardTurnShadowOut 1s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
    }

    @keyframes cardTurnShadowOut {
        0%   { box-shadow: 0 1px 2px rgba(22, 22, 22, 0.14), 0 8px 24px rgba(22, 22, 22, 0.20); }
        30%  { box-shadow: 0 1px 2px rgba(22, 22, 22, 0.10), 0 16px 36px rgba(22, 22, 22, 0.26); }
        /* Gone by the halfway point, before the photo comes round. */
        50%,
        100% { box-shadow: 0 1px 3px rgba(22, 22, 22, 0),    0 20px 44px rgba(22, 22, 22, 0); }
    }

    /* ---- Light falloff, spring seat, spring release -----------------------
       Three things make the flip read as a physical card under a light:

       1. FALLOFF. As a face turns away from the viewer it turns away from the light, so
          it darkens — to half brightness edge-on, following 1 - cos(angle). The photo
          does it through brightness() on the img; the blank back through its own colour
          (::after). The back arrives dim, and lands a touch BRIGHTER than the wall
          (#FAFAFA on #F3F3F3): lifted off the wall, it catches more light.

       2. SEAT. On landing the card is pressed into the wall on a spring: it recedes
          (scales to 0.972), goes a hair past flush and settles back. The shadow and the
          extra light run out at the exact moment of contact, so the card vanishes with a
          click rather than a fade — flush, it is #F3F3F3 on #F3F3F3 with no shadow.

       3. RELEASE. The same spring the other way: the card pops back out, a hair past its
          lifted height (shadow a touch deeper), and settles exactly on the resting shadow
          and the lifted #FAFAFA, so the turn back starts without a jump.

       The keyframes are sampled from the spring and the turn curve rather than written
       by hand — the maths lives in flip-click-preview.html, which is where this was
       tuned. KEEP IN SYNC: SHADOW_SINK_MS and SHADOW_RISE_MS in typewriter.js (750).

       Selectors are ordered, not just specific: the turn-down rules and the seat rules
       overlap for 100ms (flip-anim comes off at FLIP_MS + 100, the seat starts at
       FLIP_MS), so the seat rules share the turn rules' specificity and come after them
       — later wins, and the seat is never held back behind a finished turn. */

    /* Lifted, between the steps: the resting shadow is already on .flip-shadow (above);
       this is its light. Without it, the 250ms hold after the release would drop the
       back to the wall's grey and pop it again when the turn back starts. */
    #slide-3.flip-shadow .slide3-video::after { background: #FAFAFA; }

    /* 1. Falloff during the turns. */
    #slide-3.flip-anim.flip-shadow .slide3-video img      { animation: turnDownFront 1s linear; }
    #slide-3.flip-anim.flip-shadow .slide3-video::after   { animation: turnDownBack  1s linear; }
    /* Same reason: hold the landed state until the classes come off. */
    #slide-3.flip-anim.flip-shadow.flip-back .slide3-video img    { animation: turnUpFront 1s linear forwards; }
    #slide-3.flip-anim.flip-shadow.flip-back .slide3-video::after { animation: turnUpBack  1s linear forwards; }

    /* 2. Seat. */
    #slide-3.flip-sink.flip-shadow .slide3-video          { animation: cardSeat       750ms linear forwards; }
    #slide-3.flip-sink.flip-shadow .slide3-video::before  { animation: cardSeatShadow 750ms linear forwards; }
    #slide-3.flip-sink.flip-shadow .slide3-video::after   { animation: cardSeatBack   750ms linear forwards; }

    /* 3. Release. */
    #slide-3.flip-rise.flip-shadow .slide3-video          { animation: cardRelease       750ms linear forwards; }
    #slide-3.flip-rise.flip-shadow .slide3-video::before  { animation: cardReleaseShadow 750ms linear forwards; }
    #slide-3.flip-rise.flip-shadow .slide3-video::after   { animation: cardReleaseBack   750ms linear forwards; }

    @keyframes cardSeat {
            0% { transform: perspective(1200px) rotateY(180deg) scale(1.0000); }
            4% { transform: perspective(1200px) rotateY(180deg) scale(1.0000); }
            8% { transform: perspective(1200px) rotateY(180deg) scale(0.9999); }
           12% { transform: perspective(1200px) rotateY(180deg) scale(0.9997); }
           16% { transform: perspective(1200px) rotateY(180deg) scale(0.9990); }
           20% { transform: perspective(1200px) rotateY(180deg) scale(0.9978); }
           24% { transform: perspective(1200px) rotateY(180deg) scale(0.9957); }
           28% { transform: perspective(1200px) rotateY(180deg) scale(0.9929); }
           32% { transform: perspective(1200px) rotateY(180deg) scale(0.9893); }
           36% { transform: perspective(1200px) rotateY(180deg) scale(0.9853); }
           40% { transform: perspective(1200px) rotateY(180deg) scale(0.9811); }
           44% { transform: perspective(1200px) rotateY(180deg) scale(0.9771); }
           48% { transform: perspective(1200px) rotateY(180deg) scale(0.9738); }
           52% { transform: perspective(1200px) rotateY(180deg) scale(0.9715); }
           56% { transform: perspective(1200px) rotateY(180deg) scale(0.9701); }
           60% { transform: perspective(1200px) rotateY(180deg) scale(0.9697); }
           64% { transform: perspective(1200px) rotateY(180deg) scale(0.9699); }
           68% { transform: perspective(1200px) rotateY(180deg) scale(0.9705); }
           72% { transform: perspective(1200px) rotateY(180deg) scale(0.9712); }
           76% { transform: perspective(1200px) rotateY(180deg) scale(0.9717); }
           80% { transform: perspective(1200px) rotateY(180deg) scale(0.9721); }
           84% { transform: perspective(1200px) rotateY(180deg) scale(0.9722); }
           88% { transform: perspective(1200px) rotateY(180deg) scale(0.9722); }
           92% { transform: perspective(1200px) rotateY(180deg) scale(0.9721); }
           96% { transform: perspective(1200px) rotateY(180deg) scale(0.9720); }
          100% { transform: perspective(1200px) rotateY(180deg) scale(0.9720); }
    }

    @keyframes cardSeatShadow {
            0% { box-shadow: 0 1.00px 2.00px rgba(22, 22, 22, 0.140), 0 8.00px 24.00px rgba(22, 22, 22, 0.200); }
            4% { box-shadow: 0 1.00px 2.00px rgba(22, 22, 22, 0.140), 0 8.00px 24.00px rgba(22, 22, 22, 0.200); }
            8% { box-shadow: 0 1.00px 2.00px rgba(22, 22, 22, 0.140), 0 7.98px 23.94px rgba(22, 22, 22, 0.200); }
           12% { box-shadow: 0 0.99px 1.98px rgba(22, 22, 22, 0.139), 0 7.91px 23.72px rgba(22, 22, 22, 0.198); }
           16% { box-shadow: 0 0.96px 1.93px rgba(22, 22, 22, 0.137), 0 7.72px 23.16px rgba(22, 22, 22, 0.193); }
           20% { box-shadow: 0 0.92px 1.84px rgba(22, 22, 22, 0.133), 0 7.36px 22.08px rgba(22, 22, 22, 0.184); }
           24% { box-shadow: 0 0.85px 1.70px rgba(22, 22, 22, 0.127), 0 6.78px 20.35px rgba(22, 22, 22, 0.170); }
           28% { box-shadow: 0 0.75px 1.49px rgba(22, 22, 22, 0.117), 0 5.97px 17.92px rgba(22, 22, 22, 0.149); }
           32% { box-shadow: 0 0.62px 1.24px rgba(22, 22, 22, 0.105), 0 4.95px 14.86px rgba(22, 22, 22, 0.124); }
           36% { box-shadow: 0 0.47px 0.95px rgba(22, 22, 22, 0.089), 0 3.79px 11.38px rgba(22, 22, 22, 0.095); }
           40% { box-shadow: 0 0.32px 0.65px rgba(22, 22, 22, 0.071), 0 2.59px 7.78px rgba(22, 22, 22, 0.065); }
           44% { box-shadow: 0 0.18px 0.37px rgba(22, 22, 22, 0.051), 0 1.47px 4.40px rgba(22, 22, 22, 0.037); }
           48% { box-shadow: 0 0.07px 0.13px rgba(22, 22, 22, 0.027), 0 0.53px 1.58px rgba(22, 22, 22, 0.013); }
           52% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.000), 0 0.00px 0.00px rgba(22, 22, 22, 0.000); }
           56% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.000), 0 0.00px 0.00px rgba(22, 22, 22, 0.000); }
           60% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.000), 0 0.00px 0.00px rgba(22, 22, 22, 0.000); }
           64% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.000), 0 0.00px 0.00px rgba(22, 22, 22, 0.000); }
           68% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.000), 0 0.00px 0.00px rgba(22, 22, 22, 0.000); }
           72% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.000), 0 0.00px 0.00px rgba(22, 22, 22, 0.000); }
           76% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.000), 0 0.00px 0.00px rgba(22, 22, 22, 0.000); }
           80% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.004), 0 0.02px 0.06px rgba(22, 22, 22, 0.000); }
           84% { box-shadow: 0 0.01px 0.01px rgba(22, 22, 22, 0.007), 0 0.05px 0.16px rgba(22, 22, 22, 0.001); }
           88% { box-shadow: 0 0.01px 0.01px rgba(22, 22, 22, 0.007), 0 0.05px 0.15px rgba(22, 22, 22, 0.001); }
           92% { box-shadow: 0 0.00px 0.01px rgba(22, 22, 22, 0.005), 0 0.03px 0.09px rgba(22, 22, 22, 0.001); }
           96% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.003), 0 0.01px 0.03px rgba(22, 22, 22, 0.000); }
          100% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.000), 0 0.00px 0.00px rgba(22, 22, 22, 0.000); }
    }

    @keyframes cardSeatBack {
            0% { background: #FAFAFA; }
            4% { background: #FAFAFA; }
            8% { background: #FAFAFA; }
           12% { background: #FAFAFA; }
           16% { background: #F9F9F9; }
           20% { background: #F9F9F9; }
           24% { background: #F9F9F9; }
           28% { background: #F8F8F8; }
           32% { background: #F7F7F7; }
           36% { background: #F6F6F6; }
           40% { background: #F5F5F5; }
           44% { background: #F4F4F4; }
           48% { background: #F3F3F3; }
           52% { background: #F3F3F3; }
           56% { background: #F3F3F3; }
           60% { background: #F3F3F3; }
           64% { background: #F3F3F3; }
           68% { background: #F3F3F3; }
           72% { background: #F3F3F3; }
           76% { background: #F3F3F3; }
           80% { background: #F3F3F3; }
           84% { background: #F3F3F3; }
           88% { background: #F3F3F3; }
           92% { background: #F3F3F3; }
           96% { background: #F3F3F3; }
          100% { background: #F3F3F3; }
    }

    @keyframes cardRelease {
            0% { transform: perspective(1200px) rotateY(180deg) scale(0.9720); }
            4% { transform: perspective(1200px) rotateY(180deg) scale(0.9720); }
            8% { transform: perspective(1200px) rotateY(180deg) scale(0.9721); }
           12% { transform: perspective(1200px) rotateY(180deg) scale(0.9723); }
           16% { transform: perspective(1200px) rotateY(180deg) scale(0.9730); }
           20% { transform: perspective(1200px) rotateY(180deg) scale(0.9742); }
           24% { transform: perspective(1200px) rotateY(180deg) scale(0.9763); }
           28% { transform: perspective(1200px) rotateY(180deg) scale(0.9791); }
           32% { transform: perspective(1200px) rotateY(180deg) scale(0.9827); }
           36% { transform: perspective(1200px) rotateY(180deg) scale(0.9867); }
           40% { transform: perspective(1200px) rotateY(180deg) scale(0.9909); }
           44% { transform: perspective(1200px) rotateY(180deg) scale(0.9949); }
           48% { transform: perspective(1200px) rotateY(180deg) scale(0.9982); }
           52% { transform: perspective(1200px) rotateY(180deg) scale(1.0005); }
           56% { transform: perspective(1200px) rotateY(180deg) scale(1.0019); }
           60% { transform: perspective(1200px) rotateY(180deg) scale(1.0023); }
           64% { transform: perspective(1200px) rotateY(180deg) scale(1.0021); }
           68% { transform: perspective(1200px) rotateY(180deg) scale(1.0015); }
           72% { transform: perspective(1200px) rotateY(180deg) scale(1.0008); }
           76% { transform: perspective(1200px) rotateY(180deg) scale(1.0003); }
           80% { transform: perspective(1200px) rotateY(180deg) scale(0.9999); }
           84% { transform: perspective(1200px) rotateY(180deg) scale(0.9998); }
           88% { transform: perspective(1200px) rotateY(180deg) scale(0.9998); }
           92% { transform: perspective(1200px) rotateY(180deg) scale(0.9999); }
           96% { transform: perspective(1200px) rotateY(180deg) scale(1.0000); }
          100% { transform: perspective(1200px) rotateY(180deg) scale(1.0000); }
    }

    @keyframes cardReleaseShadow {
            0% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.000), 0 0.00px 0.00px rgba(22, 22, 22, 0.000); }
            4% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.001), 0 0.00px 0.00px rgba(22, 22, 22, 0.000); }
            8% { box-shadow: 0 0.00px 0.00px rgba(22, 22, 22, 0.004), 0 0.02px 0.06px rgba(22, 22, 22, 0.000); }
           12% { box-shadow: 0 0.01px 0.02px rgba(22, 22, 22, 0.010), 0 0.09px 0.28px rgba(22, 22, 22, 0.002); }
           16% { box-shadow: 0 0.04px 0.07px rgba(22, 22, 22, 0.019), 0 0.28px 0.84px rgba(22, 22, 22, 0.007); }
           20% { box-shadow: 0 0.08px 0.16px rgba(22, 22, 22, 0.031), 0 0.64px 1.92px rgba(22, 22, 22, 0.016); }
           24% { box-shadow: 0 0.15px 0.30px rgba(22, 22, 22, 0.045), 0 1.22px 3.65px rgba(22, 22, 22, 0.030); }
           28% { box-shadow: 0 0.25px 0.51px rgba(22, 22, 22, 0.061), 0 2.03px 6.08px rgba(22, 22, 22, 0.051); }
           32% { box-shadow: 0 0.38px 0.76px rgba(22, 22, 22, 0.078), 0 3.05px 9.14px rgba(22, 22, 22, 0.076); }
           36% { box-shadow: 0 0.53px 1.05px rgba(22, 22, 22, 0.095), 0 4.21px 12.62px rgba(22, 22, 22, 0.105); }
           40% { box-shadow: 0 0.68px 1.35px rgba(22, 22, 22, 0.111), 0 5.41px 16.22px rgba(22, 22, 22, 0.135); }
           44% { box-shadow: 0 0.82px 1.63px rgba(22, 22, 22, 0.124), 0 6.53px 19.60px rgba(22, 22, 22, 0.163); }
           48% { box-shadow: 0 0.93px 1.87px rgba(22, 22, 22, 0.134), 0 7.47px 22.42px rgba(22, 22, 22, 0.187); }
           52% { box-shadow: 0 1.02px 2.04px rgba(22, 22, 22, 0.142), 0 8.15px 24.45px rgba(22, 22, 22, 0.204); }
           56% { box-shadow: 0 1.07px 2.14px rgba(22, 22, 22, 0.146), 0 8.54px 25.62px rgba(22, 22, 22, 0.214); }
           60% { box-shadow: 0 1.08px 2.17px rgba(22, 22, 22, 0.147), 0 8.67px 26.00px rgba(22, 22, 22, 0.217); }
           64% { box-shadow: 0 1.08px 2.15px rgba(22, 22, 22, 0.146), 0 8.60px 25.80px rgba(22, 22, 22, 0.215); }
           68% { box-shadow: 0 1.05px 2.11px rgba(22, 22, 22, 0.144), 0 8.43px 25.28px rgba(22, 22, 22, 0.211); }
           72% { box-shadow: 0 1.03px 2.06px rgba(22, 22, 22, 0.142), 0 8.23px 24.70px rgba(22, 22, 22, 0.206); }
           76% { box-shadow: 0 1.01px 2.02px rgba(22, 22, 22, 0.141), 0 8.08px 24.23px rgba(22, 22, 22, 0.202); }
           80% { box-shadow: 0 1.00px 2.00px rgba(22, 22, 22, 0.140), 0 7.98px 23.94px rgba(22, 22, 22, 0.200); }
           84% { box-shadow: 0 0.99px 1.99px rgba(22, 22, 22, 0.139), 0 7.95px 23.84px rgba(22, 22, 22, 0.199); }
           88% { box-shadow: 0 0.99px 1.99px rgba(22, 22, 22, 0.139), 0 7.95px 23.85px rgba(22, 22, 22, 0.199); }
           92% { box-shadow: 0 1.00px 1.99px rgba(22, 22, 22, 0.140), 0 7.97px 23.91px rgba(22, 22, 22, 0.199); }
           96% { box-shadow: 0 1.00px 2.00px rgba(22, 22, 22, 0.140), 0 7.99px 23.97px rgba(22, 22, 22, 0.200); }
          100% { box-shadow: 0 1.00px 2.00px rgba(22, 22, 22, 0.140), 0 8.00px 24.00px rgba(22, 22, 22, 0.200); }
    }

    @keyframes cardReleaseBack {
            0% { background: #F3F3F3; }
            4% { background: #F3F3F3; }
            8% { background: #F3F3F3; }
           12% { background: #F3F3F3; }
           16% { background: #F3F3F3; }
           20% { background: #F4F4F4; }
           24% { background: #F4F4F4; }
           28% { background: #F5F5F5; }
           32% { background: #F6F6F6; }
           36% { background: #F6F6F6; }
           40% { background: #F7F7F7; }
           44% { background: #F8F8F8; }
           48% { background: #F9F9F9; }
           52% { background: #FAFAFA; }
           56% { background: #FAFAFA; }
           60% { background: #FAFAFA; }
           64% { background: #FAFAFA; }
           68% { background: #FAFAFA; }
           72% { background: #FAFAFA; }
           76% { background: #FAFAFA; }
           80% { background: #FAFAFA; }
           84% { background: #FAFAFA; }
           88% { background: #FAFAFA; }
           92% { background: #FAFAFA; }
           96% { background: #FAFAFA; }
          100% { background: #FAFAFA; }
    }

    @keyframes turnDownFront {
            0% { filter: brightness(1.000); }
            5% { filter: brightness(0.979); }
           10% { filter: brightness(0.916); }
           15% { filter: brightness(0.819); }
           20% { filter: brightness(0.700); }
           25% { filter: brightness(0.573); }
           30% { filter: brightness(0.500); }
           35% { filter: brightness(0.500); }
           40% { filter: brightness(0.500); }
           45% { filter: brightness(0.500); }
           50% { filter: brightness(0.500); }
           55% { filter: brightness(0.500); }
           60% { filter: brightness(0.500); }
           65% { filter: brightness(0.500); }
           70% { filter: brightness(0.500); }
           75% { filter: brightness(0.500); }
           80% { filter: brightness(0.500); }
           85% { filter: brightness(0.500); }
           90% { filter: brightness(0.500); }
           95% { filter: brightness(0.500); }
          100% { filter: brightness(0.500); }
    }

    @keyframes turnDownBack {
            0% { background: #848484; }
            5% { background: #848484; }
           10% { background: #848484; }
           15% { background: #848484; }
           20% { background: #848484; }
           25% { background: #848484; }
           30% { background: #8F8F8F; }
           35% { background: #A7A7A7; }
           40% { background: #BBBBBB; }
           45% { background: #CBCBCB; }
           50% { background: #D8D8D8; }
           55% { background: #E1E1E1; }
           60% { background: #E7E7E7; }
           65% { background: #ECECEC; }
           70% { background: #EFEFEF; }
           75% { background: #F2F2F2; }
           80% { background: #F4F4F4; }
           85% { background: #F6F6F6; }
           90% { background: #F7F7F7; }
           95% { background: #F8F8F8; }
          100% { background: #FAFAFA; }
    }

    @keyframes turnUpFront {
            0% { filter: brightness(0.500); }
            5% { filter: brightness(0.500); }
           10% { filter: brightness(0.500); }
           15% { filter: brightness(0.500); }
           20% { filter: brightness(0.500); }
           25% { filter: brightness(0.500); }
           30% { filter: brightness(0.549); }
           35% { filter: brightness(0.657); }
           40% { filter: brightness(0.748); }
           45% { filter: brightness(0.821); }
           50% { filter: brightness(0.876); }
           55% { filter: brightness(0.918); }
           60% { filter: brightness(0.947); }
           65% { filter: brightness(0.968); }
           70% { filter: brightness(0.981); }
           75% { filter: brightness(0.990); }
           80% { filter: brightness(0.995); }
           85% { filter: brightness(0.998); }
           90% { filter: brightness(0.999); }
           95% { filter: brightness(1.000); }
          100% { filter: brightness(1.000); }
    }

    @keyframes turnUpBack {
            0% { background: #FAFAFA; }
            5% { background: #F6F6F6; }
           10% { background: #ECECEC; }
           15% { background: #D9D9D9; }
           20% { background: #BFBFBF; }
           25% { background: #9E9E9E; }
           30% { background: #848484; }
           35% { background: #848484; }
           40% { background: #848484; }
           45% { background: #848484; }
           50% { background: #848484; }
           55% { background: #848484; }
           60% { background: #848484; }
           65% { background: #848484; }
           70% { background: #848484; }
           75% { background: #848484; }
           80% { background: #848484; }
           85% { background: #848484; }
           90% { background: #848484; }
           95% { background: #848484; }
          100% { background: #848484; }
    }


    @keyframes cardTurnShadow {
        /* Dead until the card passes 90°. While the photo is still facing us the shadow
           traces the card's full box — which is taller than the photo, because
           object-fit: contain letterboxes it — so it read as a white board turning along
           with the picture. Nothing is drawn until the blank face is the one showing. */
        0%,
        50%  { box-shadow: 0 1px 3px rgba(22, 22, 22, 0),    0 20px 44px rgba(22, 22, 22, 0); }
        /* Blank face has arrived and the card is still up on edge: spread and light.
           80%, not 70% — later in the turn, so the shadow has less of the rotation to sit
           through before it settles and reads as arriving with the card rather than ahead
           of it. */
        80%  { box-shadow: 0 1px 2px rgba(22, 22, 22, 0.10), 0 16px 36px rgba(22, 22, 22, 0.26); }
        /* Settled — matches the resting rule exactly so the handover is seamless. */
        100% { box-shadow: 0 1px 2px rgba(22, 22, 22, 0.14), 0 8px 24px rgba(22, 22, 22, 0.20); }
    }

    /* Face down for the whole typewriter session. */
    #slide-3.pic-flipped .slide3-video {
        transform: perspective(1200px) rotateY(180deg) scale(1);
    }

    /* Fullscreen text mode used to fade the picture out, which would hide the flip
       entirely — keep the card on screen so its blank face backs the text. */
    #slide-3.typewriter-fullscreen .slide3-video { opacity: 1; }
}

/* Reduced motion: no spring and no light play — the card simply is where it ends up. */
@media screen and (orientation: portrait) and (prefers-reduced-motion: reduce) {
    #slide-3 .slide3-video,
    #slide-3 .slide3-video img,
    #slide-3 .slide3-video::before,
    #slide-3 .slide3-video::after { animation-duration: 0.01ms !important; }
}


/* ==================== CONTACT ==================== */

.contact-container {
    position: relative;
    display: inline-block;
    width: 120px; height: 44px;   /* 44 = Apple's recommended tap height */
    cursor: pointer;
}

.contact-btn, .email-reveal {
    position: absolute;
    top: 0; left: 0;
    width: 100%; height: 100%;
    transition: all 0.4s cubic-bezier(0.65, 0, 0.35, 1), color 1s ease, -webkit-text-fill-color 1s ease;
    display: flex;
    align-items: center;
    justify-content: center;
    background: transparent;
    border: none;
    color: #161616;
    font-size: 18px;
    cursor: pointer;
}

/* A STATIC positioning box — it never animates. The reveal lives on .email-text below.
   It used to carry the scaleX itself, but a transform applies to the whole subtree, so
   the copy button got squeezed in along with the address. The button can't simply move
   out either: the offset that anchors it (`right: 100%` landscape, `top: 100%` portrait)
   is measured against an address whose width depends on the domain, and putting it in
   flow shoves the address off centre. Animating one level down solves both — the button
   stays anchored to a box that never moves. */
.email-reveal {
    font-size: 17px;
    pointer-events: none;
    /* Sized to the address instead of inheriting the button's 120px, so the box tracks
       the text rather than the contact button it replaces. */
    left: 50%;
    width: max-content;
    transform: translateX(-50%);
    transition: none;
    /* Finger, matching the contact button it replaces — it's still a click target that
       toggles back. Not selectable: copying is the copy button's job. */
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
}

/* The reveal itself. */
/* The reveal itself. */
.email-text {
    opacity: 0;
    transform: scaleX(0);
    transition: opacity 0.4s cubic-bezier(0.65, 0, 0.35, 1),
                transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}

/* Absolute, hung off the address — its left edge here, BELOW it in portrait (see the
   portrait block after this rule). Deliberately OUT of the flex flow either way.
   In flow it was a sibling of the text, so the row's width included it and the address
   sat off-centre by half the button. Now .email-reveal sizes to the address alone, stays
   centred by its own translateX(-50%), and the button hangs outside that box: adding,
   renaming, removing or REPOSITIONING it cannot move the address by a single pixel —
   which is what lets portrait move it without a second layout for the text. */
.email-copy {
    /* One knob for the whole button — text, padding, corner and gap all scale together,
       so "15% smaller" is a single number rather than four that can drift apart. Same
       pattern as --tile-w and --corner elsewhere. The 1px border stays 1px on purpose:
       scaling it would land on a sub-pixel and render fuzzy. */
    --copy-scale: 1;
    position: absolute;
    right: 100%;
    margin-right: calc(8px * var(--copy-scale));
    opacity: 0;
    pointer-events: none;
    top: 50%;
    /* Straight box centring — the two text runs land on the same centre axis on their
       own. Measured: the visible ink of "copy" and of the address share a centre to
       within 0.000px at 16px and 0.367px at 18px. No offset, no per-breakpoint numbers. */
    transform: translateY(-50%);
    padding: calc(2px * var(--copy-scale)) calc(6px * var(--copy-scale));
    /* currentColor, so the border is exactly the ink of its own label — a fixed rgba()
       sat under the element's opacity as well, which compounded into a washed-out grey
       that didn't match the text. */
    border: 1px solid currentColor;
    border-radius: calc(4px * var(--copy-scale));
    background: transparent;
    color: inherit;
    cursor: pointer;
    /* Same face as the address, a size down — set piecemeal because the `font`
       shorthand would drag font-size back to inherit along with the family. */
    font-family: inherit;
    font-weight: inherit;
    /* Floored at 13px — at 0.7em it came out ~10px, under Apple's minimum text size. */
    font-size: max(13px, calc(0.7em * var(--copy-scale)));
    line-height: 1.25;
    white-space: nowrap;
    /* Hidden, not just transparent: see the show state below for why. visibility flips
       only once the fade-out has finished, so leaving still fades. */
    visibility: hidden;
    /* Leaving is quicker than arriving — on the way back it should just get out of the
       way, not linger. The entrance is set on the show state below. */
    transition: opacity 0.3s ease, visibility 0s 0.3s, transform 0.15s ease, background-color 0.2s ease;
    /* NO will-change here, deliberately, and it is worth knowing why it was tried.
       Pinning this button to its own layer looked like the cure for the flicker on click.
       It was not — the toast's opacity transition was — and pinning it caused a second
       problem: this element is a CHILD of .email-reveal, whose centring is
       translateX(-50%) over a max-content box. That box is an odd width, so the translate
       lands on a half pixel. Text painted normally at a fractional offset is fine, but a
       permanent layer inside that subtree keeps the address composited, and composited
       text at a half-pixel offset renders soft — which is why the address came up blurry
       on first reveal and snapped sharp again on any resize.
       The toast carries the pin instead. It sits in .contact-container, outside this
       subtree, so it costs the address nothing. */
}

/* Invisible tap area. The button draws at ~22px tall; this stretches the clickable box to
   ~44px without changing what you see. Inherits pointer-events: none while hidden. */
.email-copy::after {
    content: '';
    position: absolute;
    inset: -11px -8px;
}

.email-copy:hover {
    background: rgba(22, 22, 22, 0.07);
}

.email-copy:active { transform: translateY(-50%) scale(0.94); }

/* Sits below the address so it never covers it — ABOVE it in portrait, see the single
   portrait block below. Absolute either way, so showing it can't nudge the address
   sideways. */
.copied-toast {
    position: absolute;
    top: calc(100% - 6px);
    left: 50%;
    width: max-content;
    font-family: 'Manrope', sans-serif;
    font-size: 13px;
    letter-spacing: 0.03em;
    color: #161616;
    opacity: 0;
    pointer-events: none;
    transform: translateX(-50%) translateY(4px);
    transition: opacity 0.25s ease, transform 0.25s ease;
    /* Same fix as on .email-copy, and needed for the same reason. Chrome promotes an
       element to its own compositing layer for the duration of a transition on a
       compositor-animatable property — transform is one, OPACITY IS THE OTHER. This
       toast sits 3px from the address, so every copy was promoting it, running 0.25s,
       then demoting it, and the address a few pixels away got re-rasterised at both
       ends. It never moved; it was redrawn with grayscale instead of subpixel
       antialiasing, which is what read as the address losing focus for an instant.
       Pinning it up front means there is no promote/demote cycle left to cause it.
       Both properties are listed because both are still transitioned in landscape. */
    will-change: opacity, transform;
}

.contact-container.copied .copied-toast {
    opacity: 0.75;
    transform: translateX(-50%) translateY(0);
}

/* -------------------- CONTACT, VERTICAL --------------------
   The copy button is 15% smaller and moves from the left of the address to underneath
   it; the toast moves out of that space to above the address. Only anchors change —
   the button stays absolute inside .email-reveal and the toast stays absolute inside
   .contact-container, so THE ADDRESS DOES NOT MOVE. Everything else (scale knob,
   border, padding, font, the slow drift-in) is shared with landscape and set once.

   This block is LAST in the contact section on purpose, and must stay last. A media
   query adds no specificity, so each rule here is an exact tie with its landscape
   counterpart above — source order is the only thing deciding it. Move this block up
   past .email-copy:active or .copied-toast and those overrides silently stop applying,
   with no warning and no visual clue except on a phone. */
@media screen and (orientation: portrait) {
    .email-copy {
        --copy-scale: 0.92;
        /* Centred WITHOUT a transform: left and right both pinned to 0, an intrinsic
           width, and auto side margins. The absolute box then centres itself inside
           .email-reveal, which is sized to the address — so the button's centre lands on
           the address's centre.
           It used to be left:50% + translateX(-50%). That works too, but it spends the
           transform property on layout, and transform is a single value: anything else
           that wants it — the press feedback below, or any future animation — has to
           remember to re-state the -50% or the button jumps to starting AT the centre
           line instead of straddling it. This form cannot be knocked out that way.
           The margin shorthand also clears the landscape margin-right in one go. */
        left: 0;
        right: 0;
        width: max-content;
        /* No top margin. The VISIBLE gap is not this number: .email-reveal is 44px tall
           and the address's line box is centred in it, so there is already ~9px of dead
           box below the text's ink before this margin adds anything. At 0 the gap reads
           as ~9px, which is the floor for this rule — going tighter means shortening
           .email-reveal, and that moves the address itself.
           A measured version was tried — JS centring the button between the address and
           the bottom of the slide — and it was exact to 0.01px but looked wrong: some 48
           of those 58px are slide 3's empty bottom gutter, so it centred against an edge
           nobody can see. */
        margin: 0 auto;
        top: 100%;
        transform: none;
    }

    /* Now that centring is not a transform, the press feedback is just the scale —
       nothing to re-state, nothing to keep in sync. */
    .email-copy:active { transform: scale(0.94); }

    /* The button now occupies the space under the address, where this used to sit, so
       the toast goes above. Mirrored exactly: `bottom` replaces `top`, and the same
       -6px still tightens the gap toward the address, just from the other side.
       Coupled to .email-copy's `top: 100%` above — if the button ever goes back to the
       side in portrait, this goes back under the address. */
    .copied-toast {
        top: auto;
        bottom: calc(100% - 6px);
        /* Pure fade here — no drift. The landscape version slides 4px on entry, but the
           toast sits only 7px above the address in this layout, and animating `transform`
           promotes it to its own compositing layer for the duration. That re-rasterises
           the text next to it, which Chrome typically redraws with grayscale instead of
           subpixel antialiasing — the address does not move, but it visibly changes
           weight for a moment and reads as a twitch.
           translateY(0) rather than dropping the Y entirely, so this matches the settled
           value in .contact-container.copied exactly: identical transforms at both ends
           means there is nothing for the browser to animate, and no layer gets promoted.
           opacity alone in the transition list for the same reason. */
        transform: translateX(-50%) translateY(0);
        transition: opacity 0.25s ease;
    }
}

.contact-container.show-email .contact-btn  { opacity: 0; transform: scaleX(0); pointer-events: none; user-select: none; }
.contact-container.show-email .email-reveal { pointer-events: auto; }
.contact-container.show-email .email-text   { opacity: 1; transform: scaleX(1); }

/* The button arrives well AFTER the address has settled, on its own slow fade — it no
   longer rides the reveal. Longer hold before it starts and a gentler curve than plain
   `ease`, which brightened too early and made it read as a pop. Coming back it leaves at
   once on the base rule, no delay.
   Only OPACITY is animated here on purpose: putting the entrance on `transform` would
   collide with the press feedback, since that shares the property and the class stays on
   for the whole session. */
.contact-container.show-email .email-copy {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    /* Slow and even. cubic-bezier(0.4, 0, 0.2, 1) front-loads the brightness — most of the
       opacity lands in the first third and the rest crawls, which is what read as sharp
       even at half a second. A near-linear curve spreads it evenly across the whole
       duration, so it genuinely drifts in.
       1.8s hold before it starts, so the address is read on its own first.
       visibility waits out the same hold: pointer-events alone went live at once, so for
       the whole hold the button was invisible but clickable — a tap there copied. */
    transition: opacity 1.2s cubic-bezier(0.45, 0.05, 0.55, 0.95) 1.8s,
                visibility 0s 1.8s,
                transform 0.15s ease,
                background-color 0.2s ease;
}

.contact-btn  { font-family: 'Oswald', sans-serif; font-weight: normal; }
.email-reveal { font-family: 'Manrope', sans-serif; font-weight: 500; }

@media screen and (min-width: 1025px) {
    .contact-btn  { font-size: 22px; }
    .email-reveal { font-size: 18px; }
}
@media screen and (min-width: 701px) and (max-width: 1024px) {
    .contact-btn  { font-size: 20px; }
    .email-reveal { font-size: 16px; }
}
@media screen and (max-width: 700px) {
    .contact-btn  { font-size: 17px; }
    .email-reveal { font-size: 16px; }
}


/* ==================== GLOBAL ==================== */

#slide-1, #slide-2 { position: relative; overflow: hidden; }
#slide-1 { background-color: #161616; }

.s1m-b1v-img {
    grid-column: 1 / span 7;
    /* Starts at row 2 (not 3) so the picture has a higher roof. Row 2 holds the logo,
       which stays on top: cells are z-index 1, this image is z-index 0. */
    grid-row: 2 / 6;
    position: absolute;
    /* 120% wide, pulled back 10%, so the outer 10% on each side hangs off screen and is
       clipped by #slide-1's overflow:hidden. The middle 80% is the safe zone. Still
       object-fit: contain, so the picture simply grows into that wider box instead of
       being capped at the screen width — taller picture, edges sacrificed. */
    width: 120%;
    /* Top edge capped at the MIDDLE of row 2 rather than its top. The box spans rows 2-5
       = 84% of the slide; half of row 2 is 6% of the slide, i.e. 6/84 = 1/14 of the box.
       So drop the top by 1/14 and take the same off the height. */
    top: calc(100% / 14);
    height: calc(100% - 100% / 14);
    object-fit: contain;
    /* Bottom-aligned, not centred. `contain` leaves empty space above and below the
       picture; centring split it in two, which put the picture's visible bottom ~45px
       above the box bottom — an edge CSS can't see, so anything aligned below it (the
       BUY NOW button) looked wrong. Pinning to the bottom makes the visible edge and the
       box edge the same line, and stacks all the slack at the top instead. */
    object-position: center bottom;
    z-index: 0;
    left: -10%;
    /* Matches .hero-bg so the portrait picture gets the same bottom-anchored parallax
       scale as landscape (driven from slide.js). */
    transform-origin: bottom center;
    will-change: transform;
    /* Soft fade on the bottom edge, the same idea as slide 2's veil but gentler: it only
       covers the last ~20%, so the statue's legs dissolve into the dark instead of ending
       on a hard line above BUY NOW. Eased so the fade has no visible starting seam. The
       box is bottom-aligned to the picture (see object-position above), so 100% here is
       the picture's real bottom edge. This element only exists in the portrait grid. */
    -webkit-mask-image: linear-gradient(to bottom, #000 80%, rgba(0,0,0,0.75) 86%, rgba(0,0,0,0.35) 93%, transparent 100%);
            mask-image: linear-gradient(to bottom, #000 80%, rgba(0,0,0,0.75) 86%, rgba(0,0,0,0.35) 93%, transparent 100%);
}

/* ==================== SLIDE 1 — SCROLL HINT ====================
   A whispered "pssst" in the typewriter font (slide 3's voice) and a hand-drawn arrow,
   beside the statue's legs at the foot of the page; the top of slide 2 nudges up along
   the bottom edge and drops back — Apple's progressive disclosure: show that there is
   more by letting a little of it through. scroll-hint.js types the word, draws the arrow
   and runs the nudge: once after the preloader, then after 5s, 10s and every 20s idle
   on slide 1. Everything is sized in em off the word, so the pair keeps its proportions
   at every screen size. Tuned in scroll-hint-preview-2.html (D1). */
.scroll-hint {
    position: absolute;
    left: 66%;
    bottom: 6.5%;
    z-index: 3;
    display: flex;
    align-items: flex-start;
    gap: 0.1em;
    color: #eae7df;
    pointer-events: none;
    font-family: 'EmbolismSpark', cursive;
    font-size: clamp(22px, 3.1vw, 40px);
    line-height: 1;
}
.scroll-hint-text { white-space: nowrap; letter-spacing: 0.02em; }
.scroll-hint-text span { opacity: 0; }
.scroll-hint-text span.on { opacity: 1; }

/* Beside the word, curling down past it — only a little taller than the word, so the word
   itself sits right down at the foot of the page. */
.scroll-hint-arrow {
    width: 1.43em;
    height: 2.17em;
    margin-top: 0.5em;
    overflow: visible;
}
.scroll-hint-arrow path {
    fill: none;
    stroke: currentColor;
    stroke-width: 1.6;
    stroke-linecap: round;
    stroke-linejoin: round;
    /* Scales with the arrow (about 1.1px on a phone, 2px on a big screen). Not
       non-scaling-stroke: that measures the dash in screen pixels while the draw-in
       animation measures the path in its own units, and the arrow would stop short. */
}

/* The top of slide 2, along the bottom edge. Height 0 at rest; the nudge lifts it. */
.scroll-peek {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 0;
    z-index: 2;
    overflow: hidden;
    pointer-events: none;
}

/* Landscape: a replica of slide 2 as it really is, anchored at the peek's top so only its
   top edge shows — the dark ground, the teal colour bleed, and the first picture on the
   right, dissolving in through the same fade. Same numbers as the VEIL section (stage from
   38vw, mask 4%→30%, bleed 38vw→58vw), and the same file the slide loads, so it's cached. */
.scroll-peek-inner {
    position: absolute;
    left: 0; top: 0;
    width: 100%;
    height: 100vh;
    height: 100dvh;
    background: linear-gradient(to right, transparent 38vw, rgba(20, 110, 118, 0.35) 58vw), #161616;
}
.scroll-peek-stage {
    position: absolute;
    top: 0; bottom: 0; right: 0;
    left: 38vw;
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to right, transparent 4%, rgba(0,0,0,0.12) 10%, rgba(0,0,0,0.45) 17%, rgba(0,0,0,0.85) 24%, #000 30%);
            mask-image: linear-gradient(to right, transparent 4%, rgba(0,0,0,0.12) 10%, rgba(0,0,0,0.45) 17%, rgba(0,0,0,0.85) 24%, #000 30%);
}
/* Framed like slide 2's first picture: 45% closer, on the woman. */
.scroll-peek-stage::before {
    content: '';
    position: absolute;
    inset: 0;
    background: url('c-1_1000.jpg') no-repeat center / cover;
    transform-origin: 50% 42%;
    transform: scale(1.45);
}
.scroll-peek::after {
    content: '';
    position: absolute;
    inset: 0;
    background: linear-gradient(to bottom, rgba(22, 22, 22, 0.6), transparent);
}

@media screen and (orientation: portrait) {
    /* Right of the legs, clear of BUY NOW (centred in the middle column). */
    .scroll-hint { left: 66%; bottom: 5%; font-size: clamp(17px, 5.4vw, 26px); }
    /* Phones: the replica of slide 2's phone layout — the picture full width across the
       top 62% of the slide, framed the same (45% closer, on the woman). No side fade or
       bleed here; only its top edge ever shows. Same file the phone layout loads. */
    .scroll-peek-inner { background: #161616; }
    .scroll-peek-stage {
        left: 0;
        bottom: auto;
        height: 62%;
        -webkit-mask-image: none;
                mask-image: none;
    }
    .scroll-peek-stage::before { background-image: url('c-1_700.jpg'); }
    .scroll-hint-arrow { width: 1.69em; height: 2.57em; }
}

/* Reduced motion: the word and arrow are simply there; no nudge. */
@media (prefers-reduced-motion: reduce) {
    .scroll-hint-text span { opacity: 1; }
}

.hero-bg {
    position: absolute;
    top: 0; left: 0;
    width: 100%; height: 100%;
    background-image: url('B1-H.jpg');
    background-repeat: no-repeat;
    background-position: center;
    background-size: auto 100%;
    will-change: transform;
    transform-origin: bottom center;
}

@media screen and (orientation: portrait) {
    .hero-bg { background-image: none; }
}

/* Slide 2's background: one flat field, with the ripple layer drawn on top of it.

   #171717 is deliberately NOT the #161616 used on slides 1 and 3. It is the ground the
   specks' resting #272727 was tuned against, and the two are read as one pair by the
   RIPPLE LAYER block further up — including inside its keyframes, which animate the fill
   away from #272727 and back. Change one without the other and the texture dies.

   #slide-2 carries no background of its own and no isolation:isolate. Both used to be
   here, and the isolation was load-bearing only because of the opaque colour beside it:
   without a stacking context the z-index:-1 layer resolved against .slides-wrapper and
   painted BEFORE #slide-2's own background, which then covered it. With nothing opaque
   painted on the parent there is nothing left to hide behind, so both go. If you ever
   give #slide-2 a background again, the isolation has to come back with it. */
.slide2-bg {
    z-index: -1;
    top: 0; left: 0;
    width: 100%; height: 100%;
    background: #171717;
}

.slides-container {
    position: relative;
    width: 100vw; max-width: 100vw;
    height: 100vh;
    overflow-y: scroll;
    scroll-behavior: smooth;
    scrollbar-width: none;
    -ms-overflow-style: none;
}
.slides-container::-webkit-scrollbar { display: none; }

body { overflow: hidden; }

.slide { height: 100dvh; }

.slides-wrapper {
    transition: transform 0.3s ease-out;
    will-change: transform;
}


/* ==================== SLIDE 2 — VEIL ====================
   The picture fills the right of the slide and dissolves into the dark on the left, where
   the copy sits on plain ground. Replaces the flip carousel and the ripple field; the old
   .big-screen-layout / .small-screen-layout rules above no longer match anything.

   Type is in rem so it follows the reader's browser text size (Apple: keep the hierarchy
   at every text size). 1rem = 16px at the default setting.

   The stage starts at 38vw and the copy ends at 40vw, so the mask stays transparent for
   its first 4% (2/62) — body text always sits on #161616 and its contrast never depends
   on which picture is showing. The fade is done by 30% (~57vw), well short of the
   picture's centre at 69vw, so the subject itself is never dimmed. */
#slide-2 { background: #161616; }

/* Colour bleed: where each picture dissolves into the dark, it dissolves into its own
   colour — a muted tone taken from the picture — which then runs out to the neutral
   #161616 before the copy. Only inside the veil's fade; nowhere else on the slide.
     0  Within Reach    teal    (the cyan clouds)
     1  A Good Name     sage    (the grass)
     2  Make Your Move  copper  (the hair and leather)
   One layer per picture, crossfading on the picture's 1.2s; caurusel.js sets data-mood.
   Behind the stage in the DOM, so it shows exactly through the fade's transparency. */
.veil-bleed {
    position: absolute;
    inset: 0;
    pointer-events: none;
    opacity: 0;
    transition: opacity 1.2s ease;
    /* The stage starts at 38vw and its mask is solid by ~57vw: the colour is full where
       the picture is solid and gone at the stage's (and so the copy's) edge. */
    background: linear-gradient(to right, transparent 38vw, var(--bleed) 58vw);
}
.veil-bleed.b0 { --bleed: rgba(20, 110, 118, 0.35); }
.veil-bleed.b1 { --bleed: rgba(96, 120, 64, 0.30); }
.veil-bleed.b2 { --bleed: rgba(150, 64, 34, 0.35); }
#slide-2[data-mood="0"] .veil-bleed.b0,
#slide-2[data-mood="1"] .veil-bleed.b1,
#slide-2[data-mood="2"] .veil-bleed.b2 { opacity: 1; }

@media screen and (orientation: portrait) {
    /* Picture on top: it dissolves downward, from ~45% to 62% of the slide, so the bleed
       runs the same way and is gone before the copy. */
    .veil-bleed { background: linear-gradient(to bottom, var(--bleed) 44%, transparent 63%); }
}

.veil-stage {
    position: absolute;
    inset: 0 0 0 38%;
    /* The frame holds the fade, and clips: the pictures zoom INSIDE it, so zooming one
       never moves the fade or pushes the photo over the copy.
       Eased rather than a straight ramp: a linear fade starts abruptly and leaves a
       visible vertical seam where it begins, most of all on the bright first picture. */
    overflow: hidden;
    -webkit-mask-image: linear-gradient(to right, transparent 4%, rgba(0,0,0,0.12) 10%, rgba(0,0,0,0.45) 17%, rgba(0,0,0,0.85) 24%, #000 30%);
            mask-image: linear-gradient(to right, transparent 4%, rgba(0,0,0,0.12) 10%, rgba(0,0,0,0.45) 17%, rgba(0,0,0,0.85) 24%, #000 30%);
}

.veil-img {
    position: absolute;
    inset: 0;
    width: 100%; height: 100%;
    object-fit: cover;
    opacity: 0;
    /* Each picture is framed at its own zoom (--zoom), centred on its subject (--focus).
       The incoming one settles from 4% past that to it over six seconds — slow enough to
       read as the image breathing in, not as a zoom. */
    transform-origin: var(--focus, 50% 50%);
    transform: scale(calc(var(--zoom, 1) * 1.04));
    transition: opacity 1.2s ease, transform 6s ease-out;
}
.veil-img.on { opacity: 1; transform: scale(var(--zoom, 1)); }

/* Framing. Within Reach's subject sat too far away: 45% closer, on the woman. A Good Name:
   15% closer, on the face. Make Your Move is already close and stays as shot. The same
   rules apply in both orientations — nothing below overrides them. */
.veil-img:nth-child(1) { --zoom: 1.45; --focus: 50% 42%; }
.veil-img:nth-child(2) { --zoom: 1.15; --focus: 45% 40%; }

/* Left edge, a little above the middle — the optical centre, where a gallery hangs its
   wall text. The texts share one grid cell, so the block's height, and therefore its
   centring, never changes between pictures. */
.veil-copy {
    position: absolute;
    left: 10vw;
    top: 46%;
    transform: translateY(-50%);
    width: 30vw;
    z-index: 2;
}

/* One grid cell, three texts: the cell sizes to the tallest. The outgoing text fades
   first; the incoming one waits 0.5s and then fades in, so the two never overlap.
   visibility keeps the hidden two out of screen readers and clicks. */
.veil-texts { display: grid; }
.veil-text {
    grid-area: 1 / 1;
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.5s ease, visibility 0s 0.5s;
}
.veil-text.on {
    opacity: 1;
    visibility: visible;
    transition: opacity 0.5s ease 0.5s, visibility 0s 0.5s;
}

/* (1,2,0) so these outrank the #slide-2 .carousel-headline / .carousel-body tiers
   further up, which were written for the old layout. */
#slide-2 .veil-copy .carousel-headline { font-size: 2.5rem;   margin: 0 0 1.125rem; }
#slide-2 .veil-copy .carousel-body     { font-size: 1.375rem; max-width: 34ch; }

/* Short landscape screens: same steps as before, now in rem. */
@media screen and (orientation: landscape) and (max-height: 700px) {
    #slide-2 .veil-copy .carousel-headline { font-size: 1.875rem; }
    #slide-2 .veil-copy .carousel-body     { font-size: 1.0625rem; }
}
@media screen and (orientation: landscape) and (max-height: 500px) {
    #slide-2 .veil-copy .carousel-headline { font-size: 1.375rem; margin-bottom: 0.625rem; }
    #slide-2 .veil-copy .carousel-body     { font-size: 0.875rem; }
}

/* Marketplace row. These are controls, not copy (Apple: "Differentiate controls from
   content"), so they leave the text block and join the dots on one bottom row: logos on
   the copy's left edge (10vw), dots under the picture. Same 44px height and 4vh inset
   as .veil-dots, so the two share a centre line. Shown on the first picture only. */
#slide-2 .veil-icons {
    position: absolute;
    left: 10vw;
    bottom: 4vh;
    z-index: 2;
    width: auto; height: 44px;
    justify-content: flex-start;
    gap: 16px;
    /* Load-bearing for the logo recolour below: this row is its own stacking context
       (positioned, z-index), so mix-blend-mode can only blend with what is painted
       inside it. Without a ground here the logos screen against nothing and their black
       box shows. Same colour as the slide, so the row itself is invisible. */
    background: #161616;
}
#slide-2 .veil-icons .icon-button { max-width: 112px; max-height: none; }

/* Same beat as .veil-text, so the logos leave and arrive with the copy: out over 0.5s,
   then in over 0.5s after a 0.5s hold. Overrides the old #slide-2 .icon-buttons timing
   (0.4s, no hold) — same specificity, later in the file. */
#slide-2 .veil-icons        { transition: opacity 0.5s ease 0.5s, visibility 0s 0.5s; }
#slide-2 .veil-icons.hidden { transition: opacity 0.5s ease,      visibility 0s 0.5s; }

/* Monochrome logos. The files are colour logos on white JPG, so they are recoloured
   rather than replaced: invert turns the white ground black and the logo a bright
   complement; grayscale + brightness push every logo colour, light teal and dark blue
   alike, to near-white; a touch of sepia warms that toward the site's cream. Screen
   blending then drops the black ground out entirely, leaving only the mark on #161616.
   No white plate, no padding. Hover and keyboard focus bring it to full strength. */
#slide-2 .veil-icons .icon-button {
    background: none;
    padding: 0;
    border-radius: 0;
    filter: invert(1) grayscale(1) brightness(2.6) sepia(0.18);
    mix-blend-mode: screen;
    opacity: 0.7;
    transition: opacity 0.3s ease;
}
#slide-2 .veil-icons .icon-hit:hover .icon-button,
#slide-2 .veil-icons .icon-hit:focus-visible .icon-button { opacity: 1; transform: none; }

/* The control row — Apple: "Center a page control at the bottom of the view." Centred
   under the picture rather than the whole slide: the stage runs from 38vw to 100vw, so
   its middle is 69vw. In landscape it holds an arrow, the dots and an arrow; on phones
   the arrows are hidden and the dots sit on their own exactly as before. */
.veil-controls {
    position: absolute;
    left: 69vw;
    bottom: 4vh;
    transform: translateX(-50%);
    display: flex;
    align-items: center;
    z-index: 2;
}

.veil-dots { display: flex; }

/* The arrows: small marks in a full 44x44 target, quiet until pointed at, with a press
   state (Apple: "Always include a press state for a custom button"). */
.veil-nav {
    width: 44px; height: 44px;
    display: grid; place-items: center;
    background: none; border: 0; padding: 0; cursor: pointer;
    color: #eae7df; opacity: 0.62;
    -webkit-tap-highlight-color: transparent;
    transition: opacity 0.3s ease;
}
.veil-nav:hover { opacity: 1; }
.veil-nav:active { scale: 0.92; }
.veil-nav svg { width: 12px; height: 21px; display: block; }
.veil-nav svg path {
    fill: none; stroke: currentColor;
    stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.veil-dots button {
    width: 44px; height: 44px;
    background: none; border: 0; cursor: pointer;
    display: grid; place-items: center;
}
.veil-dots button::before {
    content: '';
    width: 6px; height: 6px;
    border-radius: 50%;
    background: #eae7df;
    opacity: 0.5;
    transition: opacity 0.4s ease;
}
.veil-dots button[aria-current="true"]::before { opacity: 1; }
.veil-dots button:hover::before { opacity: 0.8; }
.veil-dots button[aria-current="true"]:hover::before { opacity: 1; }

/* The marks: the current one stretches into a short bar, so position reads at a glance.
   Both orientations. (No rule behind them — the marks carry it on their own.) */
.veil-dots button::before {
    border-radius: 3px;
    transition: opacity 0.4s ease, width 0.4s cubic-bezier(0.2, 0, 0, 1);
}
.veil-dots button[aria-current="true"]::before { width: 18px; }

/* Phones: a column — picture, copy, controls — everything centred on one axis.
   The picture takes 62% of the height and may shrink on short phones; the copy takes the
   band that is left and centres itself in it, so the text sits in the middle of the free
   space instead of being pushed up against the picture. Controls close the column. */
@media screen and (orientation: portrait) {
    #slide-2 { display: flex; flex-direction: column; }

    .veil-stage {
        position: relative;
        inset: auto;
        flex: 0 1 62%;
        min-height: 0;
    }
    /* The fade runs downward on phones — still on the frame, not the pictures. */
    .veil-stage {
        -webkit-mask-image: linear-gradient(to bottom, #000 72%, transparent 100%);
                mask-image: linear-gradient(to bottom, #000 72%, transparent 100%);
    }

    .veil-copy {
        position: relative;
        left: auto; top: auto;
        transform: none;
        width: auto;
        flex: 1 0 auto;
        display: flex;
        flex-direction: column;
        justify-content: center;
        padding: 1rem 7vw;
        text-align: center;
    }
    /* One step below Apple's iPhone defaults, to give the column room to breathe:
       24px sits between Title 2 (22pt) and Title 1 (28pt) — Bebas is tall and condensed,
       so it still reads as a strong heading — and 16px is Apple's Callout. Both well above
       the 11pt minimum. Balanced wrapping evens the lines and stops a lone last word. */
    #slide-2 .veil-copy .carousel-headline { font-size: 1.5rem; margin-bottom: 0.5rem; }
    #slide-2 .veil-copy .carousel-body     { font-size: 1rem; line-height: 1.5; margin-inline: auto; text-wrap: balance; }

    /* Directly above the dots, so the two controls read as one group at the foot.
       The row is only as tall as the logos (~16px) — the 46px tap targets hang over the
       space around it — so the hidden row on pictures 2 and 3 costs 24px, not 72. */
    #slide-2 .veil-icons {
        position: relative;
        left: auto; bottom: auto;
        height: 24px;
        justify-content: center;
    }
    #slide-2 .veil-icons .icon-button { max-width: 72px; }

    .veil-controls {
        position: static;
        transform: none;
        justify-content: center;
        margin: 0.5rem 0 max(2vh, 8px);
    }
    /* The arrows are here too — swiping still works, but nothing on a phone said the
       pictures could be changed.
       Everything is drawn smaller than in landscape (the slide itself is smaller, and the
       row sits close to the copy), and the arrows stand 10px clear of the three marks so
       the row reads as arrow — position — arrow. The 44x44 targets are untouched: only
       the marks inside them shrink. */
    .veil-nav svg { width: 10px; height: 17px; }
    .veil-nav.prev { margin-right: 10px; }
    .veil-nav.next { margin-left: 10px; }
    .veil-dots button::before { width: 5px; height: 5px; }
    .veil-dots button[aria-current="true"]::before { width: 14px; }
}

@media (prefers-reduced-motion: reduce) {
    .veil-img, .veil-text, .veil-text.on,
    #slide-2 .veil-icons, #slide-2 .veil-icons.hidden { transition-duration: 0.01ms; transition-delay: 0s; }
}
