:root{ --ink:#111; --muted:#555; --pheader-h:96px; }
*{box-sizing:border-box;}
html,body{height:100%;margin:0;overflow:hidden;}
body{font-family:'Plus Jakarta Sans',-apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;color:var(--ink);background:#fff;}
a{color:inherit;text-decoration:none;}

.pheader{position:fixed;top:0;left:0;right:0;z-index:6;padding:24px 20px 0;text-align:center;background:transparent;transition:color .3s ease;}
.pheader .logo-img{width:38vw;max-width:260px;height:auto;transition:filter .3s ease;filter:drop-shadow(0 1px 4px rgba(0,0,0,.55)) drop-shadow(0 0 10px rgba(0,0,0,.35));}
.pheader .header-social{display:none;}
.pheader nav.mainnav{margin-top:10px;display:flex;justify-content:center;gap:28px;}
.pheader nav.mainnav a{font-size:13px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:#fff;opacity:.6;text-shadow:0 1px 4px rgba(0,0,0,.55), 0 0 10px rgba(0,0,0,.35);transition:color .3s ease,opacity .2s ease;}
.pheader nav.mainnav a.active{opacity:1;}
@media(min-width:768px){
  .pheader{display:flex;align-items:center;justify-content:space-between;padding:30px 40px;text-align:left;}
  .pheader .logo-img{width:320px;max-width:none;}
  .pheader .header-social{display:flex;gap:14px;position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);}
  .pheader .header-social a{display:flex;align-items:center;justify-content:center;color:#fff;text-shadow:0 1px 4px rgba(0,0,0,.55), 0 0 10px rgba(0,0,0,.35);transition:color .3s ease;}
  .pheader nav.mainnav{margin-top:0;}
}

/* mix-blend-mode was tried here so the logo/nav/icons would stay legible
   against any video background, but .pheader and .viewport are separate
   fixed-position siblings (each creates its own stacking context) --
   verified live in Chrome that the blend never actually composites across
   that boundary: the white logo stayed flat white even over a bright sky,
   and over the white content view it disappeared outright instead of
   inverting to black. A drop-shadow/text-shadow halo (above) solves the
   same legibility problem without needing that cross-context blending: it
   keeps the logo/nav/icons readable over both dark and bright video
   frames, layered on top of the existing light/dark color toggle below for
   the content view. */
body.view-content .pheader .logo-img{filter:brightness(0);}
body.view-content .pheader nav.mainnav a,
body.view-content .pheader .header-social a{color:var(--ink);text-shadow:none;}

.viewport{position:fixed;inset:0;overflow:hidden;}
.track{position:relative;width:100%;height:calc(var(--vh100, 1vh) * 200);transition:transform .6s cubic-bezier(.65,0,.35,1);will-change:transform;}
.slide{position:relative;width:100%;height:calc(var(--vh100, 1vh) * 100);}

.slide-video{background-color:#000;background-size:cover;background-position:center;overflow:hidden;}
.slide-video iframe{position:absolute;inset:0;width:100%;height:100%;border:0;}
.slide-video button.play{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:none;border:0;padding:0;cursor:pointer;width:100%;z-index:4;}
.slide-video .scroll-capture{position:absolute;inset:0;z-index:3;background:transparent;}
.play span{width:70px;height:70px;border:2px solid #fff;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:22px;}
.slide-video.launching button.play span{animation:playLaunch .42s ease forwards;}
@keyframes playLaunch{0%{transform:scale(1);opacity:1;}55%{transform:scale(1.3);opacity:.55;}100%{transform:scale(1);opacity:1;}}

.scroll-hint{position:absolute;left:50%;bottom:26px;z-index:5;color:#fff;opacity:.85;animation:scrollBounce 1.8s ease-in-out infinite;pointer-events:auto;cursor:pointer;padding:4px 16px;}
.scroll-hint svg{display:block;}
.scroll-hint-up{bottom:auto;top:26px;animation:scrollBounceUp 1.8s ease-in-out infinite;}
@keyframes scrollBounce{0%,100%{transform:translate(-50%,0);}50%{transform:translate(-50%,10px);}}
@keyframes scrollBounceUp{0%,100%{transform:translate(-50%,0);}50%{transform:translate(-50%,-10px);}}
.meta .scroll-hint,.extra-videos-page .scroll-hint,.stills-page .scroll-hint,.social-page .scroll-hint{color:var(--ink);opacity:.35;bottom:18px;}
.meta .scroll-hint-up,.extra-videos-page .scroll-hint-up,.stills-page .scroll-hint-up,.social-page .scroll-hint-up{bottom:auto;top:calc(var(--pheader-h) + 8px);}

.slide-content{background:#fff;overflow-y:auto;-webkit-overflow-scrolling:touch;}

.page-body{min-height:100%;box-sizing:border-box;padding-top:var(--pheader-h);display:flex;flex-direction:column;}

/* Fixed height so the gallery always begins at the identical vertical
   position on every project page, no matter how much title/credit/
   description text a given project has. The height itself is computed by
   JS (--meta-h, see project.js syncMetaHeight): header + gallery + footer
   are measured first, and .meta gets exactly what's left of the screen,
   so the content view keeps fitting in one screen instead of a guessed
   constant either overflowing or leaving a big empty gap. The value here
   is only the pre-JS fallback. Excess content (rare) becomes an internal
   scroll rather than ever shifting the gallery's start position. */
.meta{position:relative;max-width:none;margin:0;padding:0 20px;box-sizing:border-box;
  height:var(--meta-h, 50vh);overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;}
.meta-head{position:relative;z-index:1;}
.kicker{font-size:12px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--muted);margin:0 0 6px;}
.meta h1{font-size:clamp(48px,15vw,180px);line-height:0.92;margin:0;font-weight:800;max-width:95%;}
/* On phones the limiting factor is often vertical, not horizontal --
   long titles at a pure vw size can push the content view past one
   screen even with only 4 stills. Below 768px, also cap the title by
   available height (--vh100, falling back to vh) so it shrinks on
   short screens instead of forcing a scroll, while taller phones keep
   the normal vw-based size. */
@media(max-width:767.98px){
  .meta h1{font-size:clamp(32px, min(15vw, calc(var(--vh100, 1vh) * 7)), 96px);}
}
/* Desktop titles used a pure vw clamp up to 180px -- fine for a short
   title, but a longer one then wraps to several lines at that huge size
   and blows past any fixed .meta height. Capping the desktop max (and
   mixing in a height-based term, same idea as the mobile rule above)
   keeps title height in a comparable range across projects. */
@media(min-width:768px){
  .meta h1{font-size:calc(clamp(40px, min(8vw, calc(var(--vh100, 1vh) * 14)), 110px) * var(--fit, 1));}
}
.meta-top{display:flex;flex-wrap:wrap;gap:40px;}
.meta-left{flex:1 1 320px;min-width:0;}
.meta-body{position:relative;z-index:2;display:flex;flex-direction:column;align-items:flex-start;text-align:left;gap:10px;margin:36px 0 0;}
.meta-body p{font-size:14px;line-height:1.7;color:var(--muted);max-width:600px;margin:0;}
.credits{display:flex;flex-wrap:wrap;justify-content:flex-start;gap:48px;font-size:14px;color:var(--muted);}
/* Room on the right for one or more secondary video embeds, next to the
   left-aligned title/credits column -- opt-in per project via .meta-top /
   .meta-left / .extra-videos (see project-mastercard.html for the first
   use). Flex-wrap means it naturally drops to a single stacked column on
   narrow screens without a separate media query. */
.extra-videos{flex:1 1 280px;max-width:380px;display:flex;flex-direction:column;gap:20px;}
.xvideo{position:relative;aspect-ratio:16/9;background:#111;cursor:pointer;overflow:hidden;}
.xvideo img{width:100%;height:100%;object-fit:cover;display:block;}
.xvideo iframe{position:absolute;inset:0;width:100%;height:100%;border:0;}
.xplay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.25);}
.xplay span{width:44px;height:44px;border:2px solid #fff;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:16px;}
.xtitle{margin-top:8px;font-size:12px;line-height:1.5;color:var(--muted);}

/* Social media posts (Instagram / TikTok). Markup: a .social-posts wrapper
   holding one .spost[data-url="<post link>"] per post. project.js turns each
   data-url into the platform's /embed iframe, and on mobile gives every post
   its own swipe page. */
/* Desktop: the posts sit in the right-hand column of .meta-top, next to the
   text -- same place Mastercard puts its extra videos. Three across, two rows.
   Each tile is cropped to the post's video area (project.js positions the
   iframe); the tile itself opens the post on Instagram, so nothing has to be
   playable at this size and the content slide never needs to scroll. */
.social-posts{flex:0 0 clamp(360px, 38vw, 540px);max-width:100%;align-self:start;display:grid;grid-template-columns:repeat(3,1fr);gap:16px;align-content:start;}
/* Two rows have to fit the height the content slide has left over for .meta,
   so the tile height comes straight from --meta-h (set by syncMetaHeight in
   project.js) rather than from an aspect ratio. */
.spost{position:relative;height:calc((var(--meta-h, 360px) - 20px) / 2);overflow:hidden;background:#111;cursor:pointer;}
.spost iframe{position:absolute;left:0;top:0;width:100%;height:100%;border:0;display:block;pointer-events:none;}
.spost .sbadge{position:absolute;right:6px;bottom:6px;z-index:2;width:20px;height:20px;opacity:.9;color:#fff;filter:drop-shadow(0 1px 3px rgba(0,0,0,.6));pointer-events:none;}
.credits .group div{line-height:1.7;}
@media(min-width:768px){
  .meta{padding:0 40px;}

  /* Content pages are split down the middle: everything textual lives in the
     left half, extra videos or social posts fill the right half. The split
     only kicks in when there actually is something for the right side --
     text-only projects keep the full width. */
  .meta-top{display:grid;grid-template-columns:1fr;gap:40px;align-items:start;}
  .meta-top:has(.extra-videos),.meta-top:has(.social-posts){grid-template-columns:1fr 1fr;}
  .meta-left{min-width:0;}
  .extra-videos,.social-posts{min-width:0;max-width:none;}

  /* In the split layout the title only has half the width, so a headline set
     for the full width would eat the whole half on its own and force fitMeta()
     to shrink the credits along with it. A smaller cap here keeps the credits
     readable. */
  .meta-top:has(.extra-videos) h1,.meta-top:has(.social-posts) h1{font-size:calc(clamp(34px, min(5.5vw, calc(var(--vh100, 1vh) * 10)), 76px) * var(--fit, 1));}
  .meta-top:has(.extra-videos) .credits,.meta-top:has(.social-posts) .credits{gap:calc(30px * var(--fit, 1));}

  /* Extra videos lay themselves out across the half instead of stacking in a
     narrow column, so two of them fit side by side and stay inside the height
     the content slide has to give. */
  .extra-videos{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px;align-content:start;}

  /* --fit is set by fitMeta() in project.js: if the text still does not fit the
     height left over for .meta, everything textual is scaled down a notch at a
     time until it does. That keeps the promise that a content page is exactly
     one screen, without an inner scrollbar. */
  .meta-body{margin-top:calc(40px * var(--fit, 1));}
  .meta-body p{font-size:calc(14px * var(--fit, 1));}
  .credits{gap:calc(48px * var(--fit, 1));font-size:calc(14px * var(--fit, 1));}
}

/* Gallery: horizontal-scroll carousel instead of a static grid, so a
   project can show more than 4 stills -- swipe/scroll or use the arrow
   buttons to reveal the rest. Images stay clickable to open the lightbox. */
.gallery-wrap{position:relative;margin-top:28px;}
.gallery{display:flex;gap:2px;overflow-x:auto;scroll-snap-type:x mandatory;-webkit-overflow-scrolling:touch;scrollbar-width:none;}
.gallery::-webkit-scrollbar{display:none;}
.gallery img{flex:0 0 auto;width:82%;aspect-ratio:16/9;object-fit:cover;display:block;scroll-snap-align:start;cursor:pointer;}
.gal-arrow{position:absolute;top:50%;transform:translateY(-50%);width:40px;height:40px;border-radius:50%;border:0;background:rgba(17,17,17,.55);color:#fff;font-size:20px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;z-index:2;}
.gal-arrow:hover{background:rgba(17,17,17,.8);}
.gal-prev{left:8px;}
.gal-next{right:8px;}
@media(min-width:720px){
  .gallery img{width:calc(25% - 1.5px);}
}

footer{padding:40px 20px;text-align:center;font-size:12px;color:var(--muted);margin-top:auto;}
.foot-social{display:flex;justify-content:center;gap:14px;margin-bottom:14px;}
.foot-social a{display:flex;align-items:center;justify-content:center;color:var(--ink);}
.foot-logo{width:120px;height:auto;filter:brightness(0);}

/* Lightbox: fullscreen still viewer with close + prev/next, cycling
   through every still in the project's gallery. */
.lightbox{position:fixed;inset:0;background:rgba(0,0,0,.95);z-index:50;display:none;align-items:center;justify-content:center;}
.lightbox.open{display:flex;}
.lightbox .lb-img{max-width:90vw;max-height:80vh;object-fit:contain;display:block;}
.lightbox .lb-close,.lightbox .lb-prev,.lightbox .lb-next{position:absolute;background:none;border:0;color:#fff;cursor:pointer;line-height:1;}
.lightbox .lb-close{top:18px;right:20px;font-size:34px;}
.lightbox .lb-prev,.lightbox .lb-next{top:50%;transform:translateY(-50%);font-size:48px;padding:10px 16px;}
.lightbox .lb-prev{left:8px;}
.lightbox .lb-next{right:8px;}
@media(min-width:768px){ .lightbox .lb-close{font-size:40px;} }


/* Mobile only: replace the hijacked wheel/touch 2-view state machine
   (video <-> content) with plain full-height pages the browser
   scroll-snaps between natively: video (with play button), then
   title+credits, then (if the project has them) extra videos, then
   (if the project has them) stills -- with the footer always attached
   to whichever of those content pages is last, mirroring the Work
   overview's mobile paging. Collapsing .viewport/.track/.slide-content/
   .page-body with display:contents removes their boxes (and the
   fixed/transformed positioning that drove the desktop hijack) while
   keeping their children -- .slide-video, .meta, .extra-videos-page,
   .stills-page (the latter two built by project.js, setupMobilePaging)
   -- as if they were direct children of <body>, which is what CSS
   scroll-snap requires for them to align. Desktop/tablet (>=720px) are
   completely untouched by this block. */
@media(max-width:719.98px){
  html{height:100%;overflow:hidden;}
  body{height:100vh;height:100dvh;overflow-y:auto;overflow-x:hidden;scroll-snap-type:y mandatory;-webkit-overflow-scrolling:touch;}
  .viewport,.track,.slide-content,.page-body{display:contents;}
  .slide-video{scroll-snap-align:start;scroll-snap-stop:always;height:100vh;height:100dvh;}

  /* Every page after the video is a fixed one-screen-tall snap section
     with its own internal scroll as a safety net, instead of growing
     taller than one screen and letting the next section's content peek
     into view at the bottom of the swipe (the previous bug). */
  .meta,.extra-videos-page,.stills-page,.social-page{scroll-snap-align:start;scroll-snap-stop:always;height:100vh;height:100dvh;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;overscroll-behavior-y:contain;box-sizing:border-box;position:relative;display:flex;flex-direction:column;justify-content:flex-start;}
  .meta{padding-top:calc(var(--pheader-h) + 46px);}
  .extra-videos-page{padding:calc(var(--pheader-h) + 46px) 20px 20px;}
  .stills-page{padding-top:calc(var(--pheader-h) + 20px);padding-bottom:20px;}
  .social-page{padding:calc(var(--pheader-h) + 46px) 14px 20px;align-items:center;}
  .social-page .spost{flex:1 1 auto;width:100%;max-width:420px;height:auto;min-height:0;aspect-ratio:auto;background:transparent;cursor:default;}
  .social-page .spost iframe{position:static;width:100%;height:100%;pointer-events:auto;}
  .social-page .spost .sbadge{display:none;}
  .meta footer,.extra-videos-page footer,.social-page footer{margin-top:auto;}
  .stills-page footer{margin-top:24px;}

  /* Mobile keeps the same left-aligned layout as desktop, just stacked
     into a single column (title, then credits) instead of the desktop's
     two-column layout. Extra videos (if any) move to their own swipe
     page (see project.js setupMobilePaging), so this page only ever
     shows the title and credits. */
  .meta-top{flex-direction:column;align-items:flex-start;}
  .extra-videos{width:100%;max-width:380px;margin:0;}

  /* Gallery on mobile: instead of one still per swipe, lay images out in
     a 2-column grid and page through groups of them -- as many as fit
     the screen -- via the same horizontal swipe/scroll-snap the carousel
     already used. Grouping into .gallery-page happens in project.js
     (setupMobileGalleryGrid), based on the actual available height. */
  .gallery-page{flex:0 0 100%;width:100%;scroll-snap-align:start;display:grid;grid-template-columns:repeat(2,1fr);gap:2px;align-content:start;}
  .gallery-page img{width:100%;}
  .gal-arrow{display:none;}
}
