/* ============================================================
   JD OVERVIEW
   Engine reproduced from a teardown of the reference build.
   No libraries. CSS custom properties driven by app.js.
   ============================================================ */

:root{
  color-scheme:light;

  /* Identity ------------------------------------------------ */
  --color-ground:#f4f2ef;  /* warm off-white */
  --color-ink:#111111;

  /* Swap this stack for a licensed condensed grotesque
     (Neue Rational Narrow, Söhne Schmal, GT America Condensed,
     Diatype Semi-Mono). Drop the @font-face above and change
     one line. Nothing else references the family. */
  --font-display:"Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif;
  --font-size-h1:clamp(30px, 5.5vw, 82px);
  --font-size-statement:clamp(18px, 4.4vw, 64px);
  --font-size-ui:clamp(11px, 1vw, 13px);

  /* Motion -------------------------------------------------- */
  --cubic:cubic-bezier(.77,0,.175,1);
  --cubic-2:cubic-bezier(.5,0,.5,1);

  /* Rhythm -------------------------------------------------- */
  --space-1:4px;
  --space-2:8px;
  --space-3:12px;
  --space-4:16px;
  --space-8:32px;
  --space-16:64px;
  --space-20:80px;

  --bar-lheight:14px;
  --bar-padding:28px;
  --bar:calc(var(--bar-lheight) + var(--bar-padding) * 2);
  --counter-row:26px;
}

*{box-sizing:border-box;margin:0;padding:0}

html{-webkit-text-size-adjust:100%}

body{
  background:var(--color-ground);
  color:var(--color-ink);
  font-family:var(--font-display);
  font-weight:700;
  font-size:var(--font-size-ui);
  letter-spacing:.06em;
  text-transform:uppercase;
  overflow-x:hidden;
}

img{display:block;border:0}
a{color:inherit;text-decoration:none}
button{font:inherit;color:inherit;background:none;border:0;cursor:pointer}

/* ------------------------------------------------------------
   1. SCROLL WRAPPER
   Fixed on desktop; app.js drives `top` from a lerped scroll
   position and mirrors this element's height onto <body>.
   ------------------------------------------------------------ */

#scroll{
  position:fixed;
  top:0; left:0;
  width:100%;
}

html.is--touch #scroll{position:relative}

/* The veil. Full-bleed blur + solid ground that dissolves on
   arrival and re-forms during a view change. */
#scroll::before{
  content:"";
  position:fixed;
  top:0; left:0;
  width:120%; height:150%;
  z-index:995;
  pointer-events:none;
  backdrop-filter:blur(12px);
  -webkit-backdrop-filter:blur(12px);
  background-color:var(--color-ground);
  transition:backdrop-filter .49s var(--cubic-2), background-color .49s var(--cubic-2);
}

body.has--loaded:not(.nav--isopen) #scroll::before{
  transition-delay:.1s;
  backdrop-filter:blur(0px);
  -webkit-backdrop-filter:blur(0px);
  background-color:color-mix(in srgb, var(--color-ground) 0%, transparent);
}

html.is--transitioning #scroll::before{
  transition:backdrop-filter .49s var(--cubic-2), background-color .49s var(--cubic-2) !important;
  transition-delay:0s !important;
  backdrop-filter:blur(12px) !important;
  -webkit-backdrop-filter:blur(12px) !important;
  background-color:var(--color-ground) !important;
}

/* ------------------------------------------------------------
   2. CHROME
   ------------------------------------------------------------ */

.header{
  position:fixed;
  top:0; left:0;
  width:100%;
  height:var(--bar);
  display:flex;
  align-items:center;
  justify-content:center;
  z-index:998;
  pointer-events:none;
}

.navopener{
  position:relative;
  width:22px; height:22px;
  pointer-events:auto;
}
.navopener__open,
.navopener__close{
  position:absolute;
  top:50%; left:50%;
  width:15px; height:15px;
  transform:translate(-50%,-50%);
  transition:opacity .3s var(--cubic), transform .49s var(--cubic);
}
.navopener__open::before,.navopener__open::after,
.navopener__close::before,.navopener__close::after{
  content:"";
  position:absolute;
  top:50%; left:50%;
  background:var(--color-ink);
}
.navopener__open::before,.navopener__close::before{
  width:15px; height:1.5px; transform:translate(-50%,-50%);
}
.navopener__open::after{
  width:1.5px; height:15px; transform:translate(-50%,-50%);
}
.navopener__close::after{
  width:1.5px; height:15px; transform:translate(-50%,-50%);
}
.navopener__close{opacity:0;transform:translate(-50%,-50%) rotate(0deg)}
body.nav--isopen .navopener__open{opacity:0;transform:translate(-50%,-50%) rotate(45deg)}
body.nav--isopen .navopener__close{opacity:1;transform:translate(-50%,-50%) rotate(45deg)}

.navscreen{
  position:fixed;
  inset:0;
  z-index:996;
  display:flex;
  align-items:center;
  justify-content:center;
  opacity:0;
  visibility:hidden;
  transition:opacity .49s var(--cubic-2), visibility 0s .49s;
}
body.nav--isopen .navscreen{opacity:1;visibility:visible;transition:opacity .49s var(--cubic-2)}

.navscreen__inner{text-align:center}
.navscreen__links{
  display:flex;
  flex-direction:column;
  gap:var(--space-2);
  font-size:var(--font-size-h1);
  letter-spacing:.01em;
  line-height:1.05;
}
.navscreen__links > *{
  display:block;
  transform:translateY(40%) rotateX(-40deg);
  opacity:0;
  filter:blur(10px);
  transition:transform .79s var(--cubic),
             opacity .79s var(--cubic),
             filter .79s var(--cubic);
}
/* Waiting on a destination. Identical to a link, does nothing. */
.navscreen__links .is--pending{cursor:default}
body.nav--isopen .navscreen__links > *{transform:none;opacity:1;filter:blur(0)}
body.nav--isopen .navscreen__links > *:nth-child(2){transition-delay:.06s}
body.nav--isopen .navscreen__links > *:nth-child(3){transition-delay:.12s}
body.nav--isopen .navscreen__links > *:nth-child(4){transition-delay:.18s}
.navscreen__meta{
  margin-top:var(--space-8);
  font-size:var(--font-size-ui);
  opacity:0;
  transition:opacity .49s var(--cubic) .3s;
}
body.nav--isopen .navscreen__meta{opacity:.5}

.footer{
  position:fixed;
  bottom:0; left:0;
  width:100%;
  height:var(--bar);
  display:flex;
  align-items:center;
  justify-content:center;
  z-index:997;
  pointer-events:none;
}
.footer__back,
.footer__counter{
  pointer-events:auto;
  opacity:0;
  transform:translateY(6px);
  transition:opacity .49s var(--cubic), transform .49s var(--cubic);
}
/* --frame-right is written per frame: the distance from the right of
   the screen to where the photograph actually ends. Most of the set is
   portrait, so the frame box is wider than the picture inside it and
   aligning to the box would leave the counter floating in space. */
.footer__counter{
  position:absolute;
  right:var(--frame-right, var(--space-4));
  opacity:0;
  font-variant-numeric:tabular-nums;
  pointer-events:none;
}
body.is--gallery.has--finished .footer__back{opacity:1;transform:none;transition-delay:.3s}
body.is--gallery.has--finished .footer__counter{opacity:.6;transform:none;transition-delay:.3s}
body.nav--isopen .footer__back,
body.nav--isopen .footer__counter{opacity:0}

/* ------------------------------------------------------------
   3. WORDMARK
   Fixed at viewport centre, painted beneath the field.
   ------------------------------------------------------------ */

/* Width and size are written by app.js, which grows the type until the
   block just fills the screen. The clamp below is only what shows if
   scripting never runs. */
.wordmark{
  position:fixed;
  top:50%; left:50%;
  transform:translate(-50%,-50%);
  font-size:var(--font-size-statement);
  line-height:1.06;
  letter-spacing:-.005em;
  text-align:center;
  perspective:1000px;
  pointer-events:none;
  width:92vw;
  opacity:0;
  z-index:0;
}
.wordmark__sig{
  display:block;
  margin-top:.9em;
}
.wordmark__inner{
  display:block;
  transform-style:preserve-3d;
  transform-origin:center center;
  transform:translateY(50%) rotateX(-40deg);
  backface-visibility:hidden;
  opacity:0;
  filter:blur(12px);
}
body.has--finished:not(.is--gallery) .wordmark{opacity:1}
/* Never `all`: that includes font-size, which app.js measures during
   the fit. Any transitioned property reads back mid-flight and the
   measurement is garbage. */
body.has--finished:not(.is--gallery) .wordmark__inner{
  transform:none;
  opacity:1;
  filter:blur(0);
  transition:transform .79s var(--cubic) .6s,
             opacity .79s var(--cubic) .6s,
             filter .79s var(--cubic) .6s;
}
html.has--landed body:not(.is--gallery) .wordmark__inner{transition-delay:0s}

/* ------------------------------------------------------------
   4. VIEWS
   ------------------------------------------------------------ */

.view{display:none}
body:not(.is--gallery) .view--overview{display:block}
body.is--gallery .view--gallery{display:block}

/* ------------------------------------------------------------
   5. OVERVIEW GRID
   Five columns, deep row gutters, 110% wide pulled left so the
   outer columns bleed off both edges. Columns 2 and 4 drop half
   a row, which is what produces the stagger.
   ------------------------------------------------------------ */

.grid{
  --offset-gap:12vh;
  --col-gap:6vw;
  --row-gap:22vh;
  position:relative;
  left:-5%;
  top:calc(var(--offset-gap) * -1);
  width:110%;
  overflow:hidden;
  display:grid;
  grid-template-columns:repeat(5,1fr);
  column-gap:var(--col-gap);
  row-gap:var(--row-gap);
}

.tile{
  position:relative;
  z-index:1; /* every tile paints above the wordmark, clones included */
  height:calc((100svh + var(--offset-gap) * 2) / 3 - var(--row-gap) * 2 / 3);
  opacity:0;
}
.tile:nth-child(5n+2),
.tile:nth-child(5n+4){
  transform:translateY(calc(50% + var(--row-gap) / 2));
}
.tile.is--visible{z-index:var(--z,1)}

.tile__media{
  position:absolute;
  top:50%; left:50%;
  width:110%; height:120%;
  transform:translate(calc(var(--tx,0px) - 50%), calc(var(--ty,0px) - 50%));
}
.tile__media img{
  position:absolute;
  top:50%; left:50%;
  transform:translate(-50%,-50%);
  max-width:100%;
  max-height:100%;
  width:auto; height:auto;
  cursor:pointer;
}

/* Arrival: every tile starts stacked at viewport centre and flies
   to its slot on its own randomised clock. */
body.has--loaded .tile{opacity:1}
body.has--finished .tile.is--visible .tile__media{
  transform:translate(-50%,-50%);
  transition:transform var(--duration,1s) var(--cubic);
  transition-delay:calc(var(--delay,1) * .01s);
}
html.has--landed .tile.is--visible .tile__media{transition-delay:0s}

/* ------------------------------------------------------------
   6. GALLERY
   One picture pinned in the middle; the scroll cycles which one.
   ------------------------------------------------------------ */

.gallery{position:relative}

.gallery__focus{
  position:fixed;
  z-index:9;
  top:var(--bar);
  left:50%;
  transform:translateX(-50%);
  height:calc(100vh - var(--bar) * 2);
  width:calc(100vw - (var(--space-20) + var(--space-4) * 2 + var(--space-16)) * 2);
  pointer-events:none;
}
/* No fade. The reference cuts hard between frames: the .79s
   transition there sits on the container, for page-level fades, not
   on the individual picture. A fade here reads as lag. */
.focus__item{
  position:absolute;
  inset:0;
  opacity:0;
}
.focus__item.is--active{opacity:1}

/* Scroll length. Nothing is drawn; it only gives the page something to
   travel through while the picture stays pinned. */
.gallery::after{
  content:"";
  display:block;
  height:var(--gallery-scroll,100vh);
}
/* Box-driven, not intrinsic-driven. With srcset + sizes and width:auto
   the browser sizes the element from the `sizes` value, which shrank
   the frame. Filling the box and containing inside it renders
   identically and leaves `sizes` to do its only real job: picking
   which file to fetch. */
.focus__item img{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  object-fit:contain;
}

/* ------------------------------------------------------------
   6b. TOUCH GALLERY
   Same model, tighter margins for a phone.
   ------------------------------------------------------------ */

html.is--touch .gallery__focus{
  /* the counter gets its own line above BACK, so the frame gives it up */
  height:calc(100svh - var(--bar) * 2 - var(--counter-row));
  width:calc(100vw - var(--space-4) * 2);
}

html.is--touch .footer__counter{
  left:50%;
  right:auto;
  bottom:calc(var(--bar) + 2px);
  transform:translate(-50%, 6px);
}
html.is--touch body.is--gallery.has--finished .footer__counter{
  transform:translate(-50%, 0);
}

/* Narrow screens: the field loosens rather than compressing. */
@media (max-width:768px){
  .grid{--col-gap:var(--space-4);--offset-gap:8vh;width:100%;left:0}
  .tile__media{width:160%;height:70%}
}

/* ------------------------------------------------------------
   7. REDUCED MOTION
   Keep the composition, drop the physics.
   ------------------------------------------------------------ */

@media (prefers-reduced-motion:reduce){
  #scroll{position:relative !important;top:0 !important}
  #scroll::before{display:none}
  .tile{opacity:1 !important}
  .tile__media{transform:translate(-50%,-50%) !important;transition:none !important}
  .wordmark{opacity:1 !important}
  .wordmark__inner{transform:none !important;opacity:1 !important;filter:none !important}
  .focus__item{transition:none}
}
