/* =========================================================
   SUFYAN SHEIKH — CINEMATIC LAYER
   Loaded AFTER css/style.css on every public page.

   This file only ADDS a cinematic/film-inspired layer on top of
   the existing design system — it deliberately does not restyle
   the palette, type or layout defined in style.css. Everything
   here is either:
     · a new decorative asset (film-strip divider, timecode
       read-out, viewfinder brackets, light leak, vignette),
     · a motion primitive driven by js/cinematic.js, or
     · an enhancement to an existing component's hover state.

   MOTION CONTRACT
   Tilt/parallax transforms are composed in CSS from custom
   properties (--rx, --ry, --par-y ...) that JS writes. JS never
   sets `transform` directly on a component that already has a
   CSS :hover transform, so the two can never fight each other.

   Every animation here is disabled under
   `prefers-reduced-motion: reduce` (see the block at the end).
   ========================================================= */

:root{
  /* monospace stack for timecode / camera read-outs — deliberately
     a system stack so it costs no extra font request */
  --font-mono: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, 'Liberation Mono', monospace;

  /* cinematic easings */
  --ease-cine: cubic-bezier(.16,.84,.24,1);   /* long, settled — reveals */
  --ease-out-soft: cubic-bezier(.33,1,.68,1); /* quick out — hovers */

  --film-edge: rgba(255,255,255,.07);
}

/* Anchor jumps (#work, #portfolio ...) used to land underneath the
   fixed header. Native smooth scrolling is already on in style.css;
   this makes it stop in the right place. */
html{ scroll-padding-top:104px; }

/* =========================================================
   1. FILM TEXTURE — light leak + vignette
   Injected into the existing .ambient fixed layer by
   js/cinematic.js, so they cost nothing in layout.
   ========================================================= */

/* RESTRAINT NOTE — read before adding anything here.
   The base design in style.css is deep black (#060607) with a soft
   neon glow. That near-black is the whole reason the site reads as
   premium. A first pass at this file added full-viewport scanlines
   (mix-blend-mode:soft-light) and a moving VHS tracking band; side
   by side with the original they lifted the blacks into a flat green
   wash and made the page look cheaper, not more cinematic. They were
   removed rather than tuned.
   What survives is only what darkens or frames: a gentle vignette and
   one warm light leak. Anything added here that LIFTS the blacks will
   cost more than it gains. */

/* Warm film-burn leak in one corner — the only thing breaking up the
   cold neon/black palette, the way a light leak does on real film.
   Deliberately weak: it should be noticed on the second look, not
   the first. */
.film-leak{
  position:absolute;
  top:-12%; right:-10%;
  width:42vw; height:42vw;
  max-width:560px; max-height:560px;
  background:radial-gradient(circle at 65% 35%,
    rgba(255,176,92,.045),
    rgba(255,120,60,.02) 42%,
    transparent 68%);
  filter:blur(50px);
  pointer-events:none;
  animation:leakBreathe 22s ease-in-out infinite;
}
@keyframes leakBreathe{
  0%,100%{ opacity:.5; transform:scale(1); }
  50%    { opacity:.85; transform:scale(1.06); }
}

/* Vignette — darkened edges, the classic cinema frame. Subtractive
   only, so the blacks stay black. No blur, so it costs nothing. */
.film-vignette{
  position:absolute;
  inset:0;
  background:radial-gradient(ellipse at 50% 42%,
    transparent 55%,
    rgba(0,0,0,.22) 85%,
    rgba(0,0,0,.38) 100%);
  pointer-events:none;
}

/* =========================================================
   2. FILM STRIP DIVIDER (decorative asset)
   Inline-SVG background: crisp at any DPI, zero extra requests.
   ========================================================= */

/* Horizontal film-strip rule — a section divider that reads as a
   frame of film rather than a plain <hr>. Faint on purpose; at full
   strength a row of bright sprockets under the header looked like a
   rendering glitch. */
.film-divider{
  position:relative;
  height:14px;
  max-width:var(--container);
  margin:0 auto;
  padding:0 32px;
  opacity:.45;
}
.film-divider::before,
.film-divider::after{
  content:'';
  position:absolute;
  left:32px; right:32px;
  height:1px;
  background:linear-gradient(to right,
    transparent,
    rgba(255,255,255,.10) 18%,
    rgba(255,255,255,.10) 82%,
    transparent);
}
.film-divider::before{ top:0; }
.film-divider::after{ bottom:0; }
.film-divider span{
  position:absolute;
  inset:1px 32px;
  display:block;
  background:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='12'%3E%3Crect x='15' y='4' width='10' height='4' rx='1.5' fill='rgba(255,255,255,0.05)'/%3E%3C/svg%3E") repeat-x center;
  background-size:40px 12px;
  /* fade the sprocket run out at both ends so it doesn't butt up
     against the container edge */
  mask-image:linear-gradient(to right, transparent, #000 18%, #000 82%, transparent);
  -webkit-mask-image:linear-gradient(to right, transparent, #000 18%, #000 82%, transparent);
}

/* =========================================================
   3. VIEWFINDER FRAMING — corner brackets + camera read-out
   ========================================================= */

/* A camera's focus frame: all FOUR corners, drawn as eight background
   gradients on a single element.
   Two reasons it is built this way. It is a child element rather than
   pseudo-elements on the component, because both places it is used
   (the hero photo frame and project thumbnails) set `overflow:hidden`
   — brackets drawn on the component itself with a negative inset would
   just be clipped away. And it uses backgrounds rather than ::before
   /::after because two pseudo-elements only give two corners, and two
   opposite corners read as a broken box, not a focus frame. */
.viewfinder-corners{
  --vf: rgba(var(--neon-rgb),.85);
  --vf-len: 18px;
  --vf-w: 1.5px;
  position:absolute;
  inset:12px;
  z-index:3;
  pointer-events:none;
  background-image:
    linear-gradient(var(--vf),var(--vf)), linear-gradient(var(--vf),var(--vf)),
    linear-gradient(var(--vf),var(--vf)), linear-gradient(var(--vf),var(--vf)),
    linear-gradient(var(--vf),var(--vf)), linear-gradient(var(--vf),var(--vf)),
    linear-gradient(var(--vf),var(--vf)), linear-gradient(var(--vf),var(--vf));
  background-repeat:no-repeat;
  background-size:
    var(--vf-len) var(--vf-w), var(--vf-w) var(--vf-len),   /* top-left    */
    var(--vf-len) var(--vf-w), var(--vf-w) var(--vf-len),   /* top-right   */
    var(--vf-len) var(--vf-w), var(--vf-w) var(--vf-len),   /* bottom-left */
    var(--vf-len) var(--vf-w), var(--vf-w) var(--vf-len);   /* bottom-right*/
  background-position:
    left top, left top,
    right top, right top,
    left bottom, left bottom,
    right bottom, right bottom;
  transition:opacity .4s var(--ease-out-soft), inset .5s var(--ease-out-soft);
}
/* Hero photo keeps its brackets on permanently, at lower intensity —
   it reads as a framed shot rather than a hover affordance. */
.viewfinder-corners.is-static{
  --vf: rgba(var(--neon-rgb),.55);
  --vf-len: 22px;
  --vf-w: 2px;
  inset:14px;
}

/* Camera read-out: REC dot + live timecode. Small, muted, monospace —
   the kind of chrome an editor sees all day. */
.cine-readout{
  display:inline-flex;
  align-items:center;
  gap:9px;
  font-family:var(--font-mono);
  font-size:.68rem;
  letter-spacing:.13em;
  color:var(--gray-dim);
  text-transform:uppercase;
  white-space:nowrap;
}
.cine-readout .rec-dot{
  width:7px; height:7px;
  border-radius:50%;
  background:#ff4d4d;
  box-shadow:0 0 9px 1px rgba(255,77,77,.65);
  animation:recBlink 1.6s steps(1,end) infinite;
  flex:none;
}
@keyframes recBlink{ 0%,49%{ opacity:1; } 50%,100%{ opacity:.18; } }
.cine-readout .tc{
  color:var(--gray);
  font-variant-numeric:tabular-nums;
}
.cine-readout .divider{ color:rgba(255,255,255,.16); }

/* =========================================================
   4. HERO — cinematic entrance + framing
   ========================================================= */

.hero{ overflow:hidden; }

/* Thin neon rule + label above the hero name, like a slate marker. */
.hero-slate{
  display:flex;
  align-items:center;
  gap:14px;
  margin-bottom:22px;
}
.hero-slate::after{
  content:'';
  flex:1;
  height:1px;
  max-width:120px;
  background:linear-gradient(to right, rgba(var(--neon-rgb),.55), transparent);
}
/* the eyebrow's own bottom margin is the slate's job now */
.hero-slate .eyebrow{ margin-bottom:0; }
@media (max-width:900px){
  .hero-slate{ justify-content:center; }
  .hero-slate::after{ display:none; }
}

/* Word-by-word mask reveal for the hero name. JS wraps each word in
   .word > span; the inner span slides up out of the clipped word box,
   which is what gives it the film-title feel rather than a plain fade. */
.hero-name .word{
  display:inline-block;
  overflow:hidden;
  vertical-align:top;
  padding-bottom:.08em; /* room for descenders inside the clip box */
}
.hero-name .word > span{
  display:inline-block;
  transform:translateY(105%);
  will-change:transform;
}
/* No-JS / reduced-motion safety: if JS never runs, the name must still
   be visible. .cine-ready is set on <html> by js/cinematic.js only once
   it has taken over the reveal. */
html:not(.cine-ready) .hero-name .word > span{ transform:none; }

/* style.css gives every .reveal-item (including the h1) a `heroIn`
   fade-up on load. Once the word-mask reveal above is running, that
   outer fade would hide the words for the first 300ms and then pop
   them in already-revealed. So when JS is in charge, the h1 itself
   is left static and the words alone carry the entrance. */
html.cine-ready .hero-name.reveal-item{
  animation:none;
  opacity:1;
  transform:none;
}

/* Cinema framing for the hero: a hair-thin rule inset from the
   viewport edge with a tick at each corner — the geometry of a
   camera frame, at an opacity where it registers as structure rather
   than decoration. This replaced a floating sprocket strip beside
   the photo, which read as a stray artifact rather than a frame. */
.hero-frame{
  position:absolute;
  /* bottom inset clears the .scroll-cue (bottom:36px in style.css) —
     at a smaller inset the frame's bottom rule runs straight through
     the word "Scroll" */
  inset:96px 34px 92px;
  z-index:0;
  pointer-events:none;
  border:1px solid rgba(255,255,255,.045);
  border-radius:6px;
}
.hero-frame::before,
.hero-frame::after{
  content:'';
  position:absolute;
  width:9px; height:9px;
  border:1px solid rgba(var(--neon-rgb),.3);
}
.hero-frame::before{ top:-1px; left:-1px; border-right:0; border-bottom:0; }
.hero-frame::after{ bottom:-1px; right:-1px; border-left:0; border-top:0; }
@media (max-width:900px){
  .hero-frame{ inset:88px 16px 84px; }
}

/* Hero photo gets viewfinder brackets + a 3D tilt driven by pointer.
   The transform is composed here from JS-written custom properties so
   it can never collide with another rule's transform. */
.hero-photo-frame{
  --rx:0deg; --ry:0deg;
  transform:perspective(1100px) rotateX(var(--rx)) rotateY(var(--ry));
  transform-style:preserve-3d;
  transition:transform .5s var(--ease-out-soft);
}
.hero-photo-frame.is-tilting{ transition-duration:.14s; }

/* Sheen that rakes across the photo as it tilts — reads as light
   catching glass. Driven by --sheen (0-100) from JS. */
.hero-photo-frame::after{
  content:'';
  position:absolute;
  inset:0;
  z-index:3;
  pointer-events:none;
  background:linear-gradient(
    105deg,
    transparent 35%,
    rgba(255,255,255,.10) 48%,
    rgba(var(--neon-rgb),.07) 52%,
    transparent 65%);
  background-size:280% 100%;
  background-position:calc(var(--sheen, 50) * 1%) 0;
  opacity:0;
  transition:opacity .45s var(--ease-out-soft);
}
.hero-photo-frame.is-tilting::after{ opacity:1; }

/* Timecode strip under the hero photo */
.hero-photo-meta{
  margin-top:14px;
  display:flex;
  justify-content:center;
}

/* =========================================================
   5. SECTION REVEALS (Motion-driven, opt-in)
   These use data-cine attributes rather than the existing
   .reveal-on-scroll class, so the IntersectionObserver system in
   js/main.js keeps working untouched and nothing double-animates.
   ========================================================= */

/* Clip-path mask reveal for section titles — the text is wiped in
   from below rather than faded, which feels like a title card. */
[data-cine="mask"]{
  clip-path:inset(0 0 100% 0);
  opacity:0;
}
html:not(.cine-ready) [data-cine="mask"]{
  clip-path:none;
  opacity:1;
}

/* Parallax targets. JS writes --par-y; CSS owns the transform. */
[data-cine-parallax]{
  transform:translate3d(0, var(--par-y, 0px), 0);
  will-change:transform;
}

/* =========================================================
   6. PROJECT / WORK CARD HOVER — 3D tilt + film framing
   ========================================================= */

/* Shared tilt composition. EVERY transform component these cards use
   is a custom property composed here, and only here:
     --rx/--ry  pointer tilt        (js/cinematic.js)
     --ty       hover lift          (CSS :hover, below)
     --sc       arrival settle      (js/cinematic.js, staggered)
   Nothing may set `transform` on these elements directly — an inline
   transform replaces this whole expression and silently kills the
   tilt. This is the one invariant to preserve when editing. */
/* SPECIFICITY NOTE — why these selectors are two classes deep.
   These cards also carry .reveal-on-scroll, and style.css declares
   `.reveal-on-scroll.is-visible { transform: translateY(0) }`. That is
   specificity (0,2,0); a plain `.work-item` rule is (0,1,0) and loses,
   so the reveal's transform silently flattened the tilt to identity.
   Rather than fight the reveal, its offset is folded in here as --rv
   and the whole thing is composed in one place at (0,2,0)/(0,3,0).

   --rx/--ry  pointer tilt     (js/cinematic.js)
   --ty       hover lift       (CSS :hover, below)
   --sc       filter shrink    (.filtering-out, below)
   --rv       reveal offset    (mirrors .reveal-on-scroll in style.css)

   Nothing may set `transform` on these elements directly — an inline
   transform replaces this whole expression and kills the tilt. */
.work-grid .work-item,
.portfolio-grid .project-card{
  --rx:0deg; --ry:0deg; --ty:0px; --sc:1; --rv:36px;
  transform:perspective(1000px) rotateX(var(--rx)) rotateY(var(--ry)) translateY(calc(var(--ty) + var(--rv))) scale(var(--sc));
}
.work-grid .work-item.is-visible,
.portfolio-grid .project-card.is-visible{ --rv:0px; }

/* Same reason: style.css's `.project-card.filtering-out` sets its own
   transform for the category-filter fade-out. The composition above
   now outranks it, so that offset is re-expressed here as custom
   properties — otherwise filtered-out cards would fade but stop
   sliding/shrinking. (The opacity half still comes from style.css.) */
.portfolio-grid .project-card.filtering-out{ --ty:14px; --sc:.97; }

.work-item.is-tilting,
.project-card.is-tilting{ transition-duration:.14s; }

/* The original translateY(-8px) hover, re-expressed as a custom
   property so the lift and the tilt can coexist. */
.project-card:hover{ --ty:-8px; }
.work-item:hover{ --ty:-6px; }

/* On cards the brackets are a hover affordance: they snap in as the
   pointer arrives, like a camera acquiring focus. */
.work-item .viewfinder-corners,
.project-thumb .viewfinder-corners{
  inset:10px;
  opacity:0;
}
.work-item:hover .viewfinder-corners,
.project-card:hover .viewfinder-corners{
  opacity:1;
  inset:14px;
}

/* Colour grade shift on hover: footage "comes up" out of a flat log
   look into full contrast/saturation. This is the single most
   editor-native hover I can give these cards. */
.work-item img,
.project-thumb img{
  filter:saturate(.72) contrast(1.02) brightness(.92);
  transition:transform .7s var(--ease-cine), filter .55s var(--ease-out-soft);
}
.work-item:hover img,
.project-card:hover .project-thumb img{
  filter:saturate(1.08) contrast(1.06) brightness(1);
}

/* Sweeping light bar across the thumbnail on hover — a "playhead"
   passing over the frame. */
.work-item::after,
.project-thumb::before{
  content:'';
  position:absolute;
  top:0; bottom:0;
  width:38%;
  left:-45%;
  z-index:2;
  pointer-events:none;
  background:linear-gradient(100deg,
    transparent,
    rgba(255,255,255,.07) 45%,
    rgba(var(--neon-rgb),.10) 55%,
    transparent);
  transition:left .75s var(--ease-cine);
}
.work-item:hover::after,
.project-card:hover .project-thumb::before{ left:110%; }

/* Featured-work overlay: slide the label up instead of a plain fade */
.work-overlay span{
  transform:translateY(10px);
  transition:transform .5s var(--ease-cine);
}
.work-item:hover .work-overlay span{ transform:translateY(0); }

/* Project card: neon edge light that traces the top of the card */
.project-card{ position:relative; }
.project-card::after{
  content:'';
  position:absolute;
  top:0; left:0; right:0;
  height:1px;
  background:linear-gradient(to right,
    transparent,
    rgba(var(--neon-rgb),.85),
    transparent);
  transform:scaleX(0);
  transform-origin:center;
  transition:transform .6s var(--ease-cine);
  z-index:4;
}
.project-card:hover::after{ transform:scaleX(1); }

/* Frame counter on the thumbnail — pure decoration, monospace, the
   kind of overlay a scrubbing UI shows. Content comes from a
   data-attribute written by JS so it differs card to card. */
.project-thumb .frame-tc,
.work-item .frame-tc{
  position:absolute;
  bottom:10px; left:12px;
  z-index:3;
  font-family:var(--font-mono);
  font-size:.62rem;
  letter-spacing:.1em;
  color:rgba(255,255,255,.72);
  background:rgba(6,6,7,.55);
  border:1px solid rgba(255,255,255,.1);
  border-radius:4px;
  padding:3px 7px;
  opacity:0;
  transform:translateY(6px);
  transition:opacity .4s var(--ease-out-soft), transform .4s var(--ease-out-soft);
  pointer-events:none;
}
.work-item .frame-tc{ bottom:auto; top:10px; left:10px; }
.project-card:hover .frame-tc,
.work-item:hover .frame-tc{ opacity:1; transform:translateY(0); }

/* Play badge: iris-open feel on hover */
.play-badge{
  backdrop-filter:blur(3px);
  transition:transform .4s var(--ease-cine), background .3s var(--ease-out-soft), box-shadow .4s var(--ease-out-soft);
}
.work-item:hover .play-badge,
.project-card:hover .play-badge{
  transform:translate(-50%,-50%) scale(1.14);
  box-shadow:0 0 0 6px rgba(var(--neon-rgb),.10), 0 0 26px -4px rgba(var(--neon-rgb),.55);
}

/* =========================================================
   7. MICRO-INTERACTIONS
   ========================================================= */

/* Button press. Uses :active only — no JS, so it can never fight the
   existing :hover transform in style.css. */
.btn{ position:relative; overflow:hidden; }
.btn:active{ transform:translateY(-1px) scale(.985); }

/* Light sweep across primary buttons on hover */
.btn-primary::after,
.btn-lg::after{
  content:'';
  position:absolute;
  top:0; bottom:0; left:-60%;
  width:45%;
  background:linear-gradient(100deg, transparent, rgba(255,255,255,.35), transparent);
  transform:skewX(-18deg);
  transition:left .6s var(--ease-cine);
  pointer-events:none;
}
.btn-primary:hover::after,
.btn-lg:hover::after{ left:120%; }

/* Nav links: the underline already wipes in style.css — add a soft
   neon glow on the active item so the current page reads instantly. */
.main-nav a.active{ text-shadow:0 0 18px rgba(var(--neon-rgb),.45); }

/* Service / award / price cards: lift + edge light */
.service-card,
.award-card,
.price-card,
.stat-card{
  transition:transform .45s var(--ease-cine), border-color .45s var(--ease-out-soft),
             background .45s var(--ease-out-soft), box-shadow .45s var(--ease-out-soft);
}
.service-card:hover,
.award-card:hover{
  box-shadow:0 24px 50px -26px rgba(0,0,0,.75), 0 0 34px -18px rgba(var(--neon-rgb),.35);
}
.service-card:hover .service-icon{
  transform:translateY(-2px) scale(1.06);
  filter:drop-shadow(0 0 16px rgba(var(--neon-rgb),.6));
}
.service-icon{ transition:transform .45s var(--ease-cine), filter .45s var(--ease-out-soft); }

/* Stat cards: neon top edge traces in on reveal */
.stat-card{ position:relative; overflow:hidden; }
.stat-card::after{
  content:'';
  position:absolute;
  top:0; left:0; right:0; height:1px;
  background:linear-gradient(to right, transparent, rgba(var(--neon-rgb),.7), transparent);
  transform:scaleX(0);
  transition:transform .9s var(--ease-cine) .2s;
}
.stat-card.is-visible::after{ transform:scaleX(1); }

/* Filter tabs — press feedback */
.filter-tab{ transition:transform .3s var(--ease-out-soft), background .3s var(--ease-out-soft), border-color .3s var(--ease-out-soft), color .3s var(--ease-out-soft); }
.filter-tab:active{ transform:scale(.96); }

/* =========================================================
   8. CINEMATIC PAGE TRANSITION
   A hard cut to black between pages, the way a scene change works.
   The overlay lives on every page and is driven by js/cinematic.js.
   ========================================================= */
.cine-curtain{
  position:fixed;
  inset:0;
  z-index:400;
  background:var(--black);
  pointer-events:none;
  opacity:0;
  visibility:hidden;
  transition:opacity .34s var(--ease-cine), visibility 0s linear .34s;
}
.cine-curtain.is-out{           /* leaving the page: fade up to black */
  opacity:1;
  visibility:visible;
  pointer-events:all;
  transition:opacity .34s var(--ease-cine), visibility 0s linear 0s;
}
/* the incoming page fades the curtain away from full black */
.cine-curtain.is-in{
  opacity:1;
  visibility:visible;
}
.cine-curtain .curtain-mark{
  position:absolute;
  top:50%; left:50%;
  transform:translate(-50%,-50%);
  display:flex;
  align-items:center;
  gap:10px;
  opacity:0;
  transition:opacity .2s linear;
}
.cine-curtain.is-out .curtain-mark{ opacity:.85; }

/* Whole-page entrance: a short fade-up from black on every load, so
   arriving on a page feels like a cut rather than a snap. */
@keyframes cineFadeIn{ from{ opacity:0; } to{ opacity:1; } }
html.cine-ready main{ animation:cineFadeIn .5s var(--ease-cine) both; }

/* =========================================================
   9. MOBILE / TOUCH
   Tilt, parallax and sheen are pointer affordances — they are not
   applied on touch devices at all (see js/cinematic.js), and the
   heavier decorative layers are dialled back on small screens.
   ========================================================= */
@media (max-width:700px){
  .film-vignette{ background:radial-gradient(ellipse at 50% 45%, transparent 62%, rgba(0,0,0,.3) 100%); }
  .film-leak{ width:70vw; height:70vw; }
  .viewfinder-corners{ --vf-len:14px; }
  .cine-readout{ font-size:.62rem; letter-spacing:.1em; }
  .film-divider{ padding:0 20px; }
  .film-divider::before,
  .film-divider::after{ left:20px; right:20px; }
  .film-divider span{ inset:1px 20px; }
}

/* Coarse pointers get no hover-only chrome that they can never trigger */
@media (hover:none){
  .work-item .viewfinder-corners,
  .project-thumb .viewfinder-corners{ display:none; }
  .work-item .frame-tc,
  .project-thumb .frame-tc{ opacity:1; transform:none; }
  .work-overlay{ opacity:1; background:linear-gradient(180deg, transparent 55%, rgba(0,0,0,.8) 100%); }
  .work-overlay span{ transform:none; }
  .work-item img,
  .project-thumb img{ filter:none; }
}

/* =========================================================
   10. REDUCED MOTION
   One block, everything off. Content must be fully visible and
   fully usable with no animation at all.
   ========================================================= */
@media (prefers-reduced-motion: reduce){
  html{ scroll-behavior:auto; }

  .film-leak,
  .cine-readout .rec-dot{ animation:none !important; }

  .hero-name .word > span{ transform:none !important; }
  [data-cine="mask"]{ clip-path:none !important; opacity:1 !important; }
  [data-cine-parallax]{ transform:none !important; }

  .hero-photo-frame,
  .work-item,
  .project-card{ transform:none !important; }
  .hero-photo-frame::after{ display:none; }

  .work-item::after,
  .project-thumb::before,
  .btn-primary::after,
  .btn-lg::after{ display:none; }

  html.cine-ready main{ animation:none; }
  .cine-curtain{ display:none !important; }

  *,*::before,*::after{
    transition-duration:.01ms !important;
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
  }
}

/* =========================================================
   11. 3D COVERFLOW — Featured Projects carousel
   Activated by js/coverflow.js, which adds .is-coverflow to
   #workGrid and writes the --cf-* custom properties per card.

   The base state of this section is still the plain responsive
   grid from style.css. Everything below only applies once the
   .is-coverflow class exists, so no-JS and reduced-motion
   visitors keep the grid and lose nothing.
   ========================================================= */

/* The viewport owns the perspective AND the clipping.
   Both have to be here rather than on the grid: `overflow:hidden` on the
   element carrying `transform-style:preserve-3d` flattens the 3D context,
   and without clipping the fanned side cards widen the page. */
.cf-viewport{
  position:relative;
  height:clamp(300px, 36vw, 470px);
  margin-bottom:2px;
  perspective:1600px;
  perspective-origin:50% 44%;
  overflow:hidden;
  /* Reserve horizontal gestures for the carousel while leaving vertical
     ones to the page. Without this the browser claims the whole drag for
     scrolling and fires pointercancel the moment a touch swipe starts, so
     swiping the carousel on a phone did nothing at all. */
  touch-action:pan-y;
  /* soften the hard clip so cards dissolve at the edges instead of being
     sliced off mid-card */
  mask-image:linear-gradient(to right, transparent, #000 11%, #000 89%, transparent);
  -webkit-mask-image:linear-gradient(to right, transparent, #000 11%, #000 89%, transparent);
}

.work-grid.is-coverflow{
  display:block;
  position:relative;
  height:100%;
  gap:0;
  margin-bottom:0;
  transform-style:preserve-3d;
}

/* Every transform component is a custom property written by JS; the
   composition lives here. Three classes deep on purpose — the cards also
   carry .reveal-on-scroll, and `.reveal-on-scroll.is-visible` (0,2,0) sets
   its own transform, which would otherwise flatten the whole carousel. */
.work-grid.is-coverflow .work-item{
  --cf-x:0px; --cf-z:0px; --cf-ry:0deg; --cf-s:1; --cf-o:1;
  position:absolute;
  top:50%;
  left:50%;
  width:clamp(240px, 35vw, 470px);
  margin-left:calc(clamp(240px, 35vw, 470px) / -2);
  opacity:var(--cf-o);
  transform:translateY(-50%) translateX(var(--cf-x)) translateZ(var(--cf-z))
            rotateY(var(--cf-ry)) scale(var(--cf-s));
  transition:transform .62s var(--ease-cine), opacity .45s var(--ease-out-soft);
  will-change:transform;
  cursor:pointer;
  box-shadow:0 30px 60px -28px rgba(0,0,0,.95);
}

/* Off-centre cards sit back in flat, desaturated "log" footage; the centre
   one comes up to full grade. Same colour-grade idea as the project cards,
   used here to say which frame is live. */
.work-grid.is-coverflow .work-item img{
  filter:saturate(.6) brightness(.74) contrast(.97);
  transition:filter .55s var(--ease-out-soft);
}
.work-grid.is-coverflow .work-item.is-active img{
  filter:saturate(1.06) contrast(1.05) brightness(1);
}
.work-grid.is-coverflow .work-item.is-active{
  border-color:rgba(var(--neon-rgb),.4);
  box-shadow:
    0 40px 80px -30px rgba(0,0,0,1),
    0 0 46px -14px rgba(var(--neon-rgb),.4);
  cursor:default;
}

/* the label belongs to whichever frame is centred, no hover needed.
   The gradient is heavier than the grid's: at carousel size the title sits
   over the busiest part of the frame, and it has to stay legible on any
   thumbnail the owner uploads. */
.work-grid.is-coverflow .work-overlay{
  opacity:0;
  background:linear-gradient(180deg, transparent 38%, rgba(0,0,0,.7) 72%, rgba(0,0,0,.95) 100%);
}
.work-grid.is-coverflow .work-overlay span{ font-size:1.12rem; }
.work-grid.is-coverflow .work-item.is-active .work-overlay{ opacity:1; }
.work-grid.is-coverflow .work-item.is-active .work-overlay span{ transform:translateY(0); }

.work-grid.is-coverflow .play-badge{ opacity:.35; transform:translate(-50%,-50%) scale(.8); }
.work-grid.is-coverflow .work-item.is-active .play-badge{
  opacity:1;
  transform:translate(-50%,-50%) scale(1);
}

/* the pointer sweep and the focus brackets are hover chrome for the grid —
   on a carousel they fight the 3D, so they are off here */
.work-grid.is-coverflow .work-item::after,
.work-grid.is-coverflow .viewfinder-corners{ display:none; }
.work-grid.is-coverflow .work-item .frame-tc{ opacity:0; }
.work-grid.is-coverflow .work-item.is-active .frame-tc{ opacity:1; transform:translateY(0); }

/* floor light under the centre frame — grounds the fan without needing a
   mirrored DOM copy the way a real reflection would */
.cf-glow{
  position:absolute;
  left:50%; bottom:2%;
  width:min(58%, 460px);
  height:16%;
  transform:translateX(-50%);
  background:radial-gradient(ellipse at 50% 50%,
    rgba(var(--neon-rgb),.16), transparent 70%);
  filter:blur(16px);
  pointer-events:none;
  z-index:0;
}

/* ---- controls ---- */
.cf-controls{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:22px;
  margin:24px 0 52px;
}
.cf-arrow{
  width:46px; height:46px;
  border-radius:50%;
  border:1px solid rgba(255,255,255,.16);
  background:rgba(255,255,255,.02);
  color:var(--white);
  display:flex; align-items:center; justify-content:center;
  transition:border-color .35s var(--ease-out-soft), color .35s var(--ease-out-soft),
             background .35s var(--ease-out-soft), transform .35s var(--ease-cine);
}
.cf-arrow svg{ width:18px; height:18px; }
.cf-arrow:hover{
  border-color:var(--neon);
  color:var(--neon);
  background:rgba(var(--neon-rgb),.06);
}
.cf-arrow:active{ transform:scale(.93); }
.cf-arrow:focus-visible{ outline:2px solid var(--neon); outline-offset:3px; }

.cf-counter{
  display:inline-flex;
  align-items:baseline;
  gap:7px;
  font-family:var(--font-mono);
  font-size:.78rem;
  letter-spacing:.14em;
  color:var(--gray-dim);
  font-variant-numeric:tabular-nums;
  min-width:74px;
  justify-content:center;
}
.cf-counter .cf-now{ color:var(--neon); font-weight:600; }
.cf-counter .cf-sep{ color:rgba(255,255,255,.18); }

@media (max-width:640px){
  .cf-viewport{
    height:clamp(230px, 62vw, 320px);
    mask-image:linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
    -webkit-mask-image:linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
  }
  .work-grid.is-coverflow .work-item{
    width:min(78vw, 330px);
    margin-left:calc(min(78vw, 330px) / -2);
  }
  .cf-controls{ gap:16px; margin:18px 0 40px; }
  .cf-arrow{ width:42px; height:42px; }
}

/* Coarse pointers get no hover-only chrome, but the carousel's own states
   are class-driven so they keep working on touch. */
@media (hover:none){
  .work-grid.is-coverflow .work-overlay{ opacity:0; }
  .work-grid.is-coverflow .work-item.is-active .work-overlay{ opacity:1; }
  .work-grid.is-coverflow .work-item img{ filter:saturate(.6) brightness(.74) contrast(.97); }
  .work-grid.is-coverflow .work-item.is-active img{ filter:saturate(1.06) contrast(1.05) brightness(1); }
  .work-grid.is-coverflow .work-item .frame-tc{ opacity:0; }
  .work-grid.is-coverflow .work-item.is-active .frame-tc{ opacity:1; }
}

/* =========================================================
   12. VIDEO FALLBACK
   Shown in place of the YouTube player when the page was opened
   straight off disk (file://). The player cannot start there —
   no Referer/origin is sent, so YouTube answers with
   "Video player configuration error (Error 153)" — and no
   amount of markup changes that. This says so plainly instead.
   ========================================================= */
.video-fallback-slot{
  position:absolute;
  inset:0;
  display:flex;
  z-index:2;
}
.lightbox-media{ position:relative; }

.video-fallback{
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:14px;
  width:100%;
  height:100%;
  padding:34px 30px;
  text-align:center;
  background:
    radial-gradient(ellipse at 50% 40%, rgba(var(--neon-rgb),.05), transparent 68%),
    var(--charcoal);
  border-bottom:1px solid rgba(255,255,255,.06);
}
.video-fallback-title{
  font-family:var(--font-head);
  font-size:1.06rem;
  font-weight:600;
  color:var(--white);
}
.video-fallback-body{
  max-width:46ch;
  font-size:.9rem;
  line-height:1.6;
  color:var(--gray);
}
.video-fallback .btn{ margin-top:4px; }

/* the show reel uses the same notice, inside its 16:9 frame */
.showreel-frame .video-fallback{ border-bottom:0; }

@media (max-width:600px){
  .video-fallback{ padding:22px 18px; gap:11px; }
  .video-fallback-title{ font-size:.96rem; }
  .video-fallback-body{ font-size:.84rem; }
}
