/* ════════════════════════════════════════════════════════════
   CINEMATIC SCROLL GALLERY — resources/views/gallery.blade.php
   Animation logic lives in cinematic-gallery.js, data in
   config/gallery.php. Plain CSS (no @apply) — this file is a
   linked stylesheet, not scanned by the Tailwind Play CDN.
   ════════════════════════════════════════════════════════════ */

#cine-gallery {
    position: relative;
    background: #0B0F17;
    overflow: hidden;
}

/* ─── Ambient galaxy atmosphere — a persistent, low-contrast background
   layer behind the intro title and the pinned gallery. Fixed to the
   viewport (not scoped to #cine-gallery's own — very tall — content box)
   so the drift reads as slow ambient space rather than something
   scrolling past at ordinary page speed during the ~990vh pin track.
   z-index:0 keeps it below every other layer on this page (opening
   overlay 200, progress rail 30, nav 50). Every animation here is a
   continuous, non-scroll-tied drift (same convention as .hero-orb/
   .hero-particle/.hero-ray elsewhere) rather than scroll-scrubbed —
   ambient background motion reading as "always gently alive" regardless
   of how fast/slow the visitor happens to be scrolling, matching how
   every other ambient effect already on this site behaves. ─── */
#cine-atmosphere {
    position: fixed;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    overflow: hidden;
    /* Consumed by every animation-duration: calc(...) below. JS
       (initCineAtmosphere) drops this toward ~0.35 while the visitor is
       actively scrolling and eases it back to 1 shortly after they stop —
       changing animation-duration on an already-running CSS animation
       re-times it smoothly from its current point rather than restarting
       it, so speeding up mid-cycle never causes a visible jump. */
    --cine-atmo-speed: 1;
}

/* Nebula wash — big, soft, gold/teal color fields. Their real strength is
   set directly on the gradient's own alpha below (not doubled up with a
   separate container opacity — that was the original bug: a .5 container
   opacity stacked on top of an already-faint .10 gradient alpha multiplied
   down to ~.05 effective, then .cine-stage-vignette's dark corners (where
   these sit) cut it roughly in half again — net result was invisible in
   practice, not just "subtle." Positioned closer in from the very corner
   for the same reason: less of the vignette's darkest zone to fight. */
.cine-atmo-nebula {
    position: absolute;
    border-radius: 50%;
    filter: blur(75px);
    will-change: transform;
}
.cine-atmo-nebula-1 {
    width: 70vw; height: 70vw; top: -8%; left: -5%;
    background: radial-gradient(circle, rgba(201,168,76,.26) 0%, transparent 70%);
    animation: cine-atmo-drift-a calc(26s * var(--cine-atmo-speed)) ease-in-out infinite;
}
.cine-atmo-nebula-2 {
    width: 60vw; height: 60vw; bottom: -5%; right: 0%;
    background: radial-gradient(circle, rgba(44,166,164,.24) 0%, transparent 70%);
    animation: cine-atmo-drift-b calc(32s * var(--cine-atmo-speed)) ease-in-out infinite reverse;
}
/* Duration cut from 75s/90s to 26s/32s and travel distance roughly
   doubled — the original cycle was technically animating but far too
   slow/small to read as motion within a normal viewing session. Added a
   slow rotation on top of the translate/scale for a gentle swirl rather
   than a straight back-and-forth drift. */
@keyframes cine-atmo-drift-a {
    0%, 100% { transform: translate(0, 0) scale(1) rotate(0deg); }
    50%       { transform: translate(9%, 7%) scale(1.16) rotate(6deg); }
}
@keyframes cine-atmo-drift-b {
    0%, 100% { transform: translate(0, 0) scale(1) rotate(0deg); }
    50%       { transform: translate(-8%, -9%) scale(1.13) rotate(-5deg); }
}

/* Distant stars — genuinely randomly-positioned elements, populated by
   initCineAtmosphere() in cinematic-gallery.js (see #cine-atmo-starfield
   in the Blade markup). Previously a tiled radial-gradient background
   (background-size repeating every 90–160px) — cheap, but a perfectly
   even lattice is the one thing that reads as obviously NOT a real
   starfield once you look at it for more than a second. Individual
   elements cost more DOM nodes but genuinely scatter, and each one can
   vary in size/brightness/twinkle independently (see .cine-atmo-star). */
#cine-atmo-starfield {
    position: absolute;
    inset: -5%;
    will-change: transform;
    animation: cine-atmo-starfield-drift calc(70s * var(--cine-atmo-speed)) linear infinite alternate;
}
@keyframes cine-atmo-starfield-drift {
    from { transform: translate(0, 0); }
    to   { transform: translate(-4%, -5%); }
}
.cine-atmo-star {
    position: absolute;
    border-radius: 50%;
    background: #fff;
    will-change: opacity;
}

/* Ambient light rays — desktop only, same reasoning as the Hero's own
   .hero-ray dropping these on small screens for paint cost. */
.cine-atmo-rays { position: absolute; inset: 0; }
.cine-atmo-ray {
    position: absolute;
    top: -30%; left: -20%;
    width: 150%; height: 200%;
    background: linear-gradient(100deg, transparent 44%, rgba(255,255,255,.09) 50%, transparent 56%);
    filter: blur(26px);
    will-change: transform;
}
.cine-atmo-ray-1 { animation: cine-atmo-ray-sway calc(18s * var(--cine-atmo-speed)) ease-in-out infinite; }
.cine-atmo-ray-2 { opacity: .6; animation: cine-atmo-ray-sway calc(23s * var(--cine-atmo-speed)) ease-in-out infinite reverse; animation-delay: -6s; }
@keyframes cine-atmo-ray-sway {
    0%, 100% { transform: rotate(-14deg) translateX(0); }
    50%       { transform: rotate(-6deg) translateX(50px); }
}
@media (max-width: 767px) {
    .cine-atmo-rays { display: none; }
}

/* Floating dust — populated by initCineAtmosphere() in
   cinematic-gallery.js, reusing the .hero-particle look already defined
   sitewide. */
#cine-atmo-dust { position: absolute; inset: 0; }

/* Depth fog — softens the starfield near the very top/bottom of the
   viewport rather than letting it cut off with a hard edge. Shortened
   from the original 22vh, which was eating a large share of the visible
   starfield near every edge on top of everything else being too faint. */
.cine-atmo-fog { position: absolute; left: 0; right: 0; height: 12vh; pointer-events: none; }
.cine-atmo-fog-top    { top: 0;    background: linear-gradient(180deg, #0B0F17 0%, transparent 100%); }
.cine-atmo-fog-bottom { bottom: 0; background: linear-gradient(0deg,   #0B0F17 0%, transparent 100%); }

@media (prefers-reduced-motion: reduce) {
    .cine-atmo-nebula, #cine-atmo-starfield, .cine-atmo-ray { animation: none !important; }
}

/* Mobile: trim density/cost — smaller blur radius (cheaper to paint on
   lower-end GPUs); star count itself is trimmed in JS (initCineAtmosphere)
   since these are individual generated elements, not a CSS layer to hide;
   rays already dropped above. */
@media (max-width: 767px) {
    .cine-atmo-nebula { filter: blur(55px); }
}

/* ─── Opening sequence — a fixed overlay that plays once on load, sitting
   on top of the real page (which renders normally underneath the whole
   time). Scrolling is fully locked for its duration (no early skip) via
   JS — see initCineOpening() in cinematic-gallery.js for the body
   position:fixed lock and its hard safety-net release. pointer-events is
   still none here so clicks on the real nav underneath (e.g. leaving the
   page entirely) aren't blocked, only scrolling is.
   The failsafe keyframe (bottom of this block) guarantees the overlay
   itself can never get stuck covering the page forever if JS fails to
   load at all — the exact class of bug documented in FEATURES.md
   §29/§30 for the old video intro and the film-grain overlay. Because the
   scroll lock is only ever applied by that same JS, "JS never loads"
   also means scrolling was never locked in the first place. ─── */
#cine-opening {
    position: fixed;
    inset: 0;
    z-index: 200;
    background: #0B0F17;
    overflow: hidden;
    pointer-events: none;
}
.cine-opening-bg {
    position: absolute;
    inset: 0;
    opacity: 0;
    background:
        radial-gradient(ellipse 60% 55% at 50% 45%, rgba(201,168,76,.10), transparent 70%),
        radial-gradient(circle at 20% 25%, rgba(44,166,164,.10), transparent 46%),
        #0B0F17;
}
.cine-opening-stars {
    position: absolute;
    inset: 0;
    opacity: 0;
    background-image: radial-gradient(circle, rgba(255,255,255,.10) 1px, transparent 1px);
    background-size: 46px 46px;
}
.cine-opening-field {
    position: absolute;
    inset: 0;
}
.cine-opening-frame {
    position: absolute;
    border-radius: 18px;
    overflow: hidden;
    box-shadow: 0 30px 90px rgba(0,0,0,.55), 0 0 0 1px rgba(201,168,76,.14);
    aspect-ratio: 4 / 3;
    opacity: 0;
    transform: scale(.6);
    filter: blur(14px);
    will-change: transform, opacity, filter;
}
.cine-opening-frame img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    display: block;
}
.cine-opening-frame-1 { top: 50%; left: 50%; width: min(40vw, 480px); z-index: 5; }
.cine-opening-frame-2 { top: 24%; left: 18%; width: min(22vw, 260px); z-index: 4; }
.cine-opening-frame-3 { top: 20%; left: 80%; width: min(20vw, 230px); z-index: 4; }
.cine-opening-frame-4 { top: 76%; left: 22%; width: min(19vw, 220px); z-index: 3; }
.cine-opening-frame-5 { top: 80%; left: 78%; width: min(21vw, 250px); z-index: 3; }
.cine-opening-frame-6 { top: 50%; left: 50%; width: min(30vw, 340px); z-index: 2; }

@media (max-width: 767px) {
    .cine-opening-frame-1 { width: min(64vw, 320px); }
    .cine-opening-frame-2 { width: min(38vw, 190px); top: 20%; left: 24%; }
    .cine-opening-frame-3 { width: min(34vw, 170px); top: 18%; left: 76%; }
    .cine-opening-frame-6 { width: min(50vw, 260px); }
    .cine-opening-frame-4, .cine-opening-frame-5 { display: none; }
}

/* Pure-CSS failsafe: if JS never runs (blocked script, slow CDN, etc.) this
   fades the overlay out unconditionally ~4.6s in regardless — it's already
   pointer-events:none so it was never blocking clicks, but without this it
   would sit there covering the page indefinitely. In the normal path the
   JS timeline finishes and removes the element well before this fires, so
   this rule has no visible effect most of the time. */
@keyframes cine-opening-failsafe {
    to { opacity: 0; visibility: hidden; }
}
#cine-opening {
    animation: cine-opening-failsafe .6s ease forwards;
    animation-delay: 4.6s;
}
@media (prefers-reduced-motion: reduce) {
    #cine-opening {
        animation-delay: 0s;
        animation-duration: .01s;
    }
}

/* ─── Intro ─── */
.cine-intro {
    position: relative;
    min-height: 76vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 10vh 6vw 6vh;
}
/* Softens the handoff into .cine-stage-vignette right below — that
   vignette starts darkening abruptly at .cine-stage's own top edge (a
   hard DOM boundary), while .cine-intro above it has no darkening at
   all, which read as a visible seam right where the two sections meet.
   This fades .cine-intro's own bottom edge down to roughly the same
   darkness the vignette starts at, so the transition is continuous. */
.cine-intro::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 20vh;
    background: linear-gradient(180deg, transparent, rgba(0,0,0,.4));
    pointer-events: none;
}
.cine-kicker {
    font-size: .75rem;
    letter-spacing: .32em;
    text-transform: uppercase;
    font-weight: 700;
    color: #3FBDBB;
    margin-bottom: 1.1rem;
}
.cine-intro-title {
    font-family: 'Playfair Display', serif;
    font-weight: 800;
    color: #fff;
    font-size: clamp(2.4rem, 6.6vw, 5rem);
    line-height: 1.06;
    letter-spacing: -.01em;
}
.cine-intro-sub {
    color: rgba(255,255,255,.60);
    max-width: 36rem;
    margin: 1.5rem auto 0;
    font-size: clamp(1rem, 1.6vw, 1.15rem);
    line-height: 1.75;
}
.cine-scroll-cue {
    margin-top: 3rem;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 8px;
    color: rgba(255,255,255,.5);
    font-size: .68rem;
    letter-spacing: .24em;
    text-transform: uppercase;
}
.cine-scroll-cue-track {
    width: 20px;
    height: 32px;
    border-radius: 12px;
    border: 1.5px solid rgba(255,255,255,.32);
    display: flex;
    align-items: flex-start;
    justify-content: center;
    padding-top: 6px;
}
.cine-scroll-cue-track span {
    width: 4px;
    height: 8px;
    border-radius: 2px;
    background: rgba(201,168,76,.9);
    animation: cine-scroll-dot 1.9s ease-in-out infinite;
}
@keyframes cine-scroll-dot {
    0%,100% { transform: translateY(0);   opacity: 1; }
    60%      { transform: translateY(9px); opacity: .25; }
}

/* ─── Progress rail — a constellation ─── */
.cine-progress {
    position: fixed;
    right: 24px;
    top: 50%;
    transform: translateY(-50%);
    z-index: 30;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 16px;
    pointer-events: none;
}
/* Connecting light-line behind the stars — static faint track, plus a
   glowing fill that grows to the active star's position (height set by
   JS from the same scroll progress that already drives the dots/counter
   below). Same track+fill technique as the homepage's #section-rail. */
.cine-progress-track {
    position: absolute;
    left: 50%;
    top: 3px;
    bottom: 3px;
    width: 1.5px;
    transform: translateX(-50%);
    background: rgba(255,255,255,.10);
    border-radius: 2px;
}
.cine-progress-fill {
    position: absolute;
    left: 50%;
    top: 3px;
    width: 1.5px;
    height: 0;
    transform: translateX(-50%);
    background: linear-gradient(180deg, #FFE9B0, rgba(201,168,76,.25));
    box-shadow: 0 0 8px rgba(201,168,76,.65);
    border-radius: 2px;
}
.cine-dot {
    position: relative;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: rgba(255,255,255,.4);
    box-shadow: 0 0 4px rgba(255,255,255,.35);
    transition: width .4s ease, height .4s ease, background .4s ease, box-shadow .4s ease;
    animation: cine-star-twinkle 4.5s ease-in-out infinite;
}
/* Staggered twinkle so the stars don't all pulse in sync — nth-child
   offsets (not random delays) so it's deterministic across reloads. */
.cine-dot:nth-child(4n+2) { animation-delay: -1.1s; }
.cine-dot:nth-child(4n+3) { animation-delay: -2.2s; }
.cine-dot:nth-child(4n+4) { animation-delay: -3.3s; }
@keyframes cine-star-twinkle {
    0%, 100% { opacity: .55; }
    50%       { opacity: 1; }
}
.cine-dot.is-active {
    width: 11px;
    height: 11px;
    background: #FFE9B0;
    box-shadow: 0 0 14px rgba(201,168,76,.9), 0 0 30px rgba(201,168,76,.5);
    animation: cine-star-pulse 2.2s ease-in-out infinite;
}
@keyframes cine-star-pulse {
    0%, 100% { transform: scale(1); }
    50%       { transform: scale(1.22); }
}
.cine-progress-count {
    margin-top: 6px;
    font-size: .64rem;
    letter-spacing: .18em;
    color: rgba(255,255,255,.4);
    writing-mode: vertical-rl;
}
@media (prefers-reduced-motion: reduce) {
    .cine-dot, .cine-dot.is-active { animation: none; }
}
@media (max-width: 1023px) {
    .cine-progress { display: none; }
}

/* ─── Pinned stage ─── */
.cine-pin-track { position: relative; }
.cine-stage {
    position: relative;
    height: 100vh;
    overflow: hidden;
    /* No opaque background here (was #0B0F17) — left transparent on
       purpose so #cine-atmosphere, fixed behind everything on this page,
       shows through during the pin. #cine-gallery's own #0B0F17 is still
       the ultimate fallback beneath the atmosphere layer itself. */
    perspective: 1600px;
    transform-style: preserve-3d;
}
/* Shooting-star trails — one per transition between projects. Drawn in
   (stroke-dashoffset) and faded out by JS, scrubbed to the same scroll
   position as that transition's crossfade — see the Blade comment above
   #cine-trails for why this is a brief connecting streak rather than a
   literal line tracking each image's exact on-screen position. Kept thin
   and modestly opaque (max ~.6) so it reads as a delicate accent, never
   competing with the project photo itself.
   stroke-dasharray/dashoffset are NOT set here — each <line> carries its
   own exact geometric length inline (see the Blade comment above
   #cine-trails for why pathLength="100" wasn't reliable on <line>), so
   setting a generic value here would just be dead, overridden CSS. */
.cine-trail {
    stroke: rgba(255,233,176,.6);
    stroke-width: 1.3px;
    stroke-linecap: round;
    opacity: 0;
    filter: drop-shadow(0 0 3px rgba(201,168,76,.7));
}
/* Small bright "spark" sharing the trail's own path — a short dash that
   loops its dashoffset continuously (see addTrail() in
   cinematic-gallery.js) while the trail is visible, so light appears to
   travel along the connecting line instead of it just sitting drawn and
   static. Same note as .cine-trail above — dasharray/dashoffset are set
   inline per-line, not here. */
.cine-trail-spark {
    stroke: #FFF9E8;
    stroke-width: 2.2px;
    stroke-linecap: round;
    opacity: 0;
    filter: drop-shadow(0 0 5px rgba(255,246,220,.95)) drop-shadow(0 0 10px rgba(201,168,76,.6));
}

.cine-stage-bg {
    position: absolute;
    inset: 0;
    pointer-events: none;
    /* Local color wash on top of the now-visible atmosphere layer — the
       opaque #0B0F17 this used to end with is dropped for the same
       reason as .cine-stage's own background above. */
    background:
        radial-gradient(ellipse 60% 50% at 50% 40%, rgba(201,168,76,.08), transparent 70%),
        radial-gradient(circle at 18% 22%, rgba(44,166,164,.09), transparent 46%);
}
.cine-stage-vignette {
    position: absolute;
    inset: 0;
    pointer-events: none;
    background: radial-gradient(ellipse at 50% 50%, transparent 32%, rgba(0,0,0,.62) 100%);
}

.cine-scene {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 5vw;
    padding: 6vh 7vw;
    transform-origin: center center;
    will-change: transform, opacity, filter;
    /* Only the currently-active scene should receive hover/click — the other
       10 are stacked underneath it (position:absolute;inset:0) and would
       otherwise intercept input meant for the one actually on screen. JS
       toggles .is-active on whichever scene is current (see onSceneActivate
       in cinematic-gallery.js). */
    pointer-events: none;
}
.cine-scene.is-active { pointer-events: auto; }

/* ─── Image frame ─── */
.cine-frame-wrap {
    position: relative;
    flex: 0 0 auto;
}
/* Ambient glow — lives outside .cine-frame's own overflow:hidden so its
   blur can bleed past the frame's edges instead of being clipped flush
   against them. Color varies per scene via the --cine-glow-color custom
   property set on the .cine-variant-* classes further down. */
.cine-frame-glow {
    position: absolute;
    inset: -14%;
    border-radius: 30px;
    pointer-events: none;
    opacity: 0;
    filter: blur(38px);
    background: radial-gradient(ellipse at center, var(--cine-glow-color, rgba(201,168,76,.35)), transparent 70%);
}
.cine-glow-pulse { animation: cine-glow-pulse 6s ease-in-out infinite; }
@keyframes cine-glow-pulse {
    0%, 100% { opacity: .35; }
    50%      { opacity: .6; }
}

/* Ghost cards — Preset E only ("stacked cards separating into depth"). Sit
   at opacity:0/no offset by default (inert) for every other preset, same
   convention as .cine-frame-sweep/.cine-frame-border below. Plain glass
   silhouettes rather than a second copy of the photo — cheaper (no extra
   image decode) and reads as an abstract "stack" without looking like a
   literal duplicate underneath the real one. */
.cine-frame-ghost {
    position: absolute;
    inset: 0;
    border-radius: 22px;
    background: rgba(255,255,255,.03);
    border: 1px solid rgba(201,168,76,.20);
    opacity: 0;
    pointer-events: none;
}

.cine-frame {
    position: relative;
    width: min(48vw, 620px);
    aspect-ratio: 4 / 3;
    border-radius: 22px;
    overflow: hidden;
    --mx: 50%;
    --my: 50%;
    --shadow-x: 0px;
    --shadow-y: 40px;
    box-shadow: var(--shadow-x) var(--shadow-y) 120px rgba(0,0,0,.55), 0 0 0 1px rgba(201,168,76,.16);
    transform-style: preserve-3d;
}
.cine-frame img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
    transform: scale(1.08);
    will-change: transform;
}
/* Cursor-tracking highlight — topmost layer so it's never dimmed by the
   sweep/border layers beneath it. */
.cine-frame::after {
    content: '';
    position: absolute;
    inset: 0;
    z-index: 4;
    background: radial-gradient(260px circle at var(--mx) var(--my), rgba(255,255,255,.10), transparent 68%);
    opacity: 0;
    transition: opacity .3s ease;
    pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
    .cine-frame:hover::after { opacity: 1; }
}
@keyframes cine-kenburns {
    0%   { transform: scale(1.08); }
    100% { transform: scale(1.17); }
}
.cine-kenburns-run {
    animation: cine-kenburns 16s ease-in-out infinite alternate;
}

/* Light sweep — a single one-shot pass played by JS when the scene becomes
   active, not a repeating loop (kept subtle rather than flashy). */
.cine-frame-sweep {
    position: absolute;
    inset: 0;
    z-index: 2;
    overflow: hidden;
    pointer-events: none;
}
.cine-frame-sweep::before {
    content: '';
    position: absolute;
    top: -30%;
    left: 0;
    width: 34%;
    height: 160%;
    background: linear-gradient(105deg, transparent, rgba(255,255,255,.30), transparent);
    transform: translateX(-160%) skewX(-16deg);
}
/* One-shot pass — JS toggles .is-sweeping (see playSweep() in
   cinematic-gallery.js) and removes it again on animationend, so this
   never loops on its own. */
.cine-frame-sweep.is-sweeping::before {
    animation: cine-sweep-pass 0.9s ease-in-out forwards;
}
@keyframes cine-sweep-pass {
    to { transform: translateX(320%) skewX(-16deg); }
}

/* Animated border — an SVG rect whose stroke draws in, scrubbed in lockstep
   with the scene's own scroll-driven crossfade (see the JS timeline) rather
   than a separate autoplay, so it never looks out of sync with the image
   fading in/out as the user scrolls back and forth. pathLength="100" on the
   <rect> (set in the Blade markup) normalizes dash values to a plain 0–100
   range regardless of the frame's real rendered size. */
.cine-frame-border {
    position: absolute;
    inset: 0;
    z-index: 3;
    width: 100%;
    height: 100%;
    pointer-events: none;
}
.cine-frame-border rect {
    fill: none;
    stroke: rgba(201,168,76,.55);
    stroke-width: 1.5;
    stroke-dasharray: 100;
    stroke-dashoffset: 100;
}

/* Per-scene variation (index % 3) — alternates the ambient glow's color
   between gold and teal so the gallery doesn't read as one effect repeated
   identically 11 times. */
.cine-variant-0 .cine-frame-glow { --cine-glow-color: rgba(201,168,76,.38); }
.cine-variant-1 .cine-frame-glow { --cine-glow-color: rgba(63,189,187,.34); }
.cine-variant-2 .cine-frame-glow { --cine-glow-color: rgba(201,168,76,.24); }

/* ─── Text panel — glassmorphism ─── */
.cine-info {
    position: relative;
    flex: 0 0 auto;
    max-width: 26rem;
    text-align: left;
    padding: 1.8rem 2rem;
    border-radius: 20px;
    background: rgba(255,255,255,.045);
    border: 1px solid rgba(255,255,255,.10);
    backdrop-filter: blur(14px);
    -webkit-backdrop-filter: blur(14px);
    box-shadow: 0 20px 60px rgba(0,0,0,.30);
    --mx: 50%;
    --my: 50%;
}
/* Cursor-tracking highlight, same technique as .cine-frame::after and
   .about-card::after (layouts/app.blade.php) elsewhere on the site. */
.cine-info::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: radial-gradient(220px circle at var(--mx) var(--my), rgba(201,168,76,.12), transparent 68%);
    opacity: 0;
    transition: opacity .3s ease;
    pointer-events: none;
}
@media (hover: hover) and (pointer: fine) {
    .cine-info:hover::after { opacity: 1; }
}
.cine-index {
    display: block;
    font-family: 'Playfair Display', serif;
    font-size: .95rem;
    color: rgba(201,168,76,.55);
    letter-spacing: .2em;
    margin-bottom: 1rem;
}
.cine-category {
    display: inline-block;
    font-size: .7rem;
    letter-spacing: .24em;
    text-transform: uppercase;
    font-weight: 700;
    color: #3FBDBB;
    margin-bottom: .9rem;
}
.cine-title {
    font-family: 'Playfair Display', serif;
    font-weight: 800;
    color: #fff;
    font-size: clamp(1.7rem, 3.1vw, 2.8rem);
    line-height: 1.14;
    letter-spacing: -.01em;
}
.cine-rule {
    width: 64px;
    height: 2px;
    margin: 1.1rem 0;
    background: linear-gradient(90deg, #C9A84C, transparent);
}
.cine-desc {
    color: rgba(255,255,255,.62);
    font-size: 1rem;
    line-height: 1.75;
}

/* ─── Per-project CTA — small pill, same fill-sweep hover mechanic as the
   site's .hero-btn-* buttons (layouts/app.blade.php) but sized to fit
   comfortably inside a pinned scene's tighter vertical budget. ─── */
.cine-cta {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin-top: 1.4rem;
    padding: 11px 20px;
    border-radius: 9999px;
    border: 1.5px solid rgba(201,168,76,.40);
    color: #FFE9B0;
    font-size: .85rem;
    font-weight: 700;
    overflow: hidden;
    transition: border-color .25s ease, color .25s ease, transform .25s ease;
}
.cine-cta-fill {
    position: absolute;
    inset: 0;
    z-index: 0;
    background: linear-gradient(135deg, #C9A84C 0%, #E6C878 50%, #C9A84C 100%);
    transform: scaleX(0);
    transform-origin: left;
    transition: transform .4s cubic-bezier(.65,0,.35,1);
}
.cine-cta-content {
    position: relative;
    z-index: 1;
    display: inline-flex;
    align-items: center;
    gap: 8px;
}
.cine-cta svg { transition: transform .3s ease; }
@media (hover: hover) and (pointer: fine) {
    .cine-cta:hover { border-color: transparent; color: #15202C; transform: translateY(-2px); }
    .cine-cta:hover .cine-cta-fill { transform: scaleX(1); }
    .cine-cta:hover svg { transform: translateX(3px); }
}

/* ─── Reduced-motion / no-JS fallback: static stacked layout ─── */
.cine-reduced .cine-pin-track { height: auto !important; }
.cine-reduced .cine-stage { position: relative; height: auto; overflow: visible; }
.cine-reduced .cine-scene {
    position: relative !important;
    inset: auto;
    flex-direction: column;
    text-align: center;
    opacity: 1 !important;
    visibility: visible !important;
    transform: none !important;
    filter: none !important;
    padding: 9vh 6vw;
    /* Every scene is in normal document flow here, not stacked
       position:absolute — nothing to accidentally intercept, so all of
       them (not just one "active" one) stay interactive. */
    pointer-events: auto;
}
.cine-reduced .cine-info { text-align: center; }
.cine-reduced .cine-frame { width: min(80vw, 560px); }
.cine-reduced .cine-kenburns-run { animation: none; }
.cine-reduced .cine-progress { display: none; }
.cine-reduced .cine-frame-glow { opacity: .3; }
.cine-reduced .cine-frame-sweep { display: none; }
.cine-reduced .cine-frame-border rect { stroke-dashoffset: 0; }
.cine-reduced .cine-frame-ghost { display: none; }

/* ─── Mobile: stack image above text, even inside the live pin ─── */
@media (max-width: 767px) {
    .cine-scene { flex-direction: column; gap: 3.2vh; padding: 7vh 7vw; }
    .cine-frame { width: 80vw; }
    .cine-info { text-align: center; padding: 1.4rem 1.6rem; }
    .cine-desc {
        display: -webkit-box;
        -webkit-line-clamp: 3;
        -webkit-box-orient: vertical;
        overflow: hidden;
    }
}

/* ─── Finale ─── */
.cine-finale {
    position: relative;
    padding: 14vh 6vw 16vh;
    text-align: center;
    background: linear-gradient(180deg, #0B0F17 0%, #0F1520 100%);
}
.cine-finale-img {
    width: min(56vw, 380px);
    margin: 0 auto 2.2rem;
    display: block;
}
.cine-finale-title {
    font-family: 'Playfair Display', serif;
    font-weight: 800;
    color: #fff;
    font-size: clamp(2rem, 4.4vw, 3.2rem);
    line-height: 1.1;
}
.cine-finale-sub {
    color: rgba(255,255,255,.6);
    max-width: 34rem;
    margin: 1.1rem auto 0;
    font-size: 1.05rem;
    line-height: 1.7;
}
.cine-finale-ctas {
    margin-top: 2.4rem;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
    gap: 1rem;
}

@media (prefers-reduced-motion: reduce) {
    .cine-kenburns-run { animation: none !important; }
}
