/* ── Reset & base ── */
* { margin: 0; padding: 0; box-sizing: border-box; }

:root {
  --paper: #f4f1ea;
  --ink: #14120f;
  --ink-soft: #57534a;
  --cream: #f4f1ea;
  --cream-muted: rgba(244, 241, 234, 0.72);
  --font-head: "Yuji Syuku", serif;
  --font-body: "Zen Kaku Gothic New", sans-serif;
  /* One 12-column grid, shared by every section, so content lines up
     on the same left/right edges as it scrolls from the hero into the
     sections below — override --page-pad at the mobile breakpoint and
     every consumer follows without touching them individually. */
  --grid-cols: 12;
  --grid-gap: clamp(0.75rem, 1.5vw, 1.75rem);
  --page-pad: 7vw;
}

html {
  cursor: none;
  scroll-behavior: smooth;
}

body {
  font-family: var(--font-body);
  background: var(--paper);
  color: var(--ink);
  overflow-x: hidden;
  -webkit-font-smoothing: antialiased;
}

a, button {
  cursor: none;
}

/* ── Custom cursor ── */
.cursor-dot {
  position: fixed;
  top: 0;
  left: 0;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  border-radius: 50%;
  background: var(--ink);
  mix-blend-mode: multiply;
  pointer-events: none;
  z-index: 9999;
  transition: background 0.25s ease, opacity 0.2s ease;
  will-change: transform;
}

.cursor-dot.is-hidden { opacity: 0; }

#ink-canvas {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 9998;
}

/* ── Hero ── */
/* Pinned to the top of the viewport for the duration of its own
   height, so the next section (which scrolls up normally, opaque)
   slides over it like a sheet being laid on top rather than the two
   simply scrolling past each other. */
.hero {
  position: sticky;
  top: 0;
  z-index: 1;
  width: 100%;
  height: 100vh;
  min-height: 640px;
  background: #F8F3ED;
  overflow: hidden;
}

/* The video occupies a band at the bottom of the hero. Because it's
   `cover` + top-anchored, shrinking the band crops the BOTTOM of the
   footage — which is where all the solid black is. The frame is pure
   white for its top 30% and only turns solid black past 78%, so
   trimming the tail is what lightens the hero.

   56.25vw is the clip's own height when drawn at full width (9/16), so
   multiplying by --hero-crop shows exactly that fraction of it from the
   top, independent of viewport height. The % floor stops the band
   collapsing on narrow screens, where `cover` crops width instead. */
/* The poster frame (shown before playback catches up) is a late,
   heavily-inked frame — at full opacity it hits as a hard black slab
   the instant the page loads. Fading it in avoids that jolt; once
   actual playback starts a frame or two later, the fade is over and
   this has no effect on the footage itself. */
.hero-video {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
  z-index: 0;
  width: 100%;
  height: max(40%, calc(56.25vw * var(--hero-crop, 0.75)));
  object-fit: cover;
  object-position: center top;
  mix-blend-mode: multiply;
  opacity: 0;
  animation: fadeIn 0.6s ease forwards;
}

/* 12-column grid, top-aligned: name+tagline sit in the left columns,
   the description in the right columns, both starting on the same row
   so they read as one deliberate two-up block rather than a stack.
   The video is confined to the bottom band, so nothing here needs the
   difference-blend invert trick — it's always over plain paper. */
.hero-content {
  position: relative;
  height: 100%;
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), 1fr);
  column-gap: var(--grid-gap);
  align-content: start;
  padding: clamp(7rem, 18vh, 10.5rem) var(--page-pad) 0;
}

.hero-intro {
  grid-column: 1 / 6;
  grid-row: 1;
}

.hero-name {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: clamp(1.6rem, 2.8vw, 2.4rem);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink);
  opacity: 0;
  transform: translateY(14px);
  animation: rise 0.9s cubic-bezier(0.22, 1, 0.36, 1) forwards 0.75s;
}

.hero-tagline {
  margin-top: 0.35rem;
  font-family: var(--font-body);
  font-weight: 500;
  font-size: clamp(0.95rem, 1.25vw, 1.15rem);
  line-height: 1.55;
  color: var(--ink-soft);
  opacity: 0;
  transform: translateY(14px);
  animation: rise 0.9s cubic-bezier(0.22, 1, 0.36, 1) forwards 0.95s;
}

/* Slower, genuine ease-in (not the ease-out "rise" used above) — it
   builds speed into place instead of decelerating into it, so it reads
   as a distinct, more deliberate entrance following the name/tagline. */
.hero-subtext {
  grid-column: 7 / -1;
  grid-row: 1;
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(0.95rem, 1.3vw, 1.08rem);
  line-height: 1.65;
  color: var(--ink-soft);
  max-width: 46ch;
  opacity: 0;
  transform: translateY(20px);
  animation: rise 1.1s cubic-bezier(0.55, 0.055, 0.675, 0.19) forwards 0.85s;
}

/* Anchored to the bottom edge, over the ink band. White + difference
   inverts automatically depending on what's beneath each name at that
   moment — dark ink or plain paper — so it stays legible either way
   without needing to track the footage's shape. No z-index, same
   reason as .hero-content: it would isolate the list into its own
   stacking context and cut it off from the video, killing the blend. */
.hero-clients {
  position: absolute;
  left: 0;
  right: 0;
  bottom: clamp(2rem, 5vh, 3.5rem);
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 0.6rem 1.5rem;
  padding: 0 var(--page-pad);
  list-style: none;
}

.hero-clients li {
  font-family: var(--font-body);
  font-weight: 500;
  font-size: clamp(0.72rem, 0.85vw, 0.85rem);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #fff;
  mix-blend-mode: difference;
  opacity: 0;
  animation: fadeIn 1s ease forwards 2.1s;
}

@keyframes rise {
  to { opacity: 1; transform: translateY(0); }
}

.scroll-cue {
  position: absolute;
  bottom: 2.4rem;
  right: var(--page-pad);
  z-index: 1;
  width: 1px;
  height: 44px;
  background: linear-gradient(to bottom, rgba(244, 241, 234, 0), rgba(244, 241, 234, 0.55));
  opacity: 0;
  animation: fadeIn 1s ease forwards 2.4s;
}

@keyframes fadeIn {
  to { opacity: 1; }
}

/* ── Next section ── */
.section-next {
  /* Paper, not ink — matching a warm near-black to the video's true
     black (0,0,0) never actually reads as continuous, it just trades
     one hard seam for a slightly softer one. Matching the hero's own
     background color exactly (not --paper, which is a slightly
     different cream) removes the seam altogether. */
  /* Watercolor paper (an SVG filter, so no extra image request): a
     low-frequency "blotch" layer for the soft, cloudy mottling where
     pigment pools unevenly, blended with a high-frequency "fiber"
     layer for fine grain, combined via feBlend. Composited onto the
     flat cream with background-blend-mode — that blends the two
     background layers with each other, not the whole element against
     the page, so it can't end up on top of the text or interact with
     any other mix-blend-mode on the page. Kept subtle so it adds
     tooth without touching legibility. */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='500' height='500'%3E%3Cfilter id='w' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.02' numOctaves='3' seed='7' stitchTiles='stitch' result='blotch'/%3E%3CfeColorMatrix in='blotch' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.035 0' result='blotchA'/%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='3' stitchTiles='stitch' result='fiber'/%3E%3CfeColorMatrix in='fiber' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0' result='fiberA'/%3E%3CfeBlend in='blotchA' in2='fiberA' mode='multiply'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23w)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-color: #F8F3ED;
  background-blend-mode: multiply;
  color: var(--ink);
  /* Also sticky, not just relative: a sticky element's "stuck" range
     is bounded by its containing block (body), which is much taller
     than one section — so hero, being the only sticky element, would
     otherwise stay pinned under EVERYTHING once this section scrolls
     past it, bleeding through under Work. Making each section in the
     chain sticky in turn (and higher z-index than the last) means it
     locks in place the moment it fully covers the previous one,
     instead of continuing to scroll away and exposing what's stuck
     underneath. Work, being the last section, doesn't need this —
     nothing has to cover it in turn. */
  position: sticky;
  top: 0;
  z-index: 2;
  /* One viewport, like the hero — copy was trimmed to fit rather than
     scrolling the section past 100vh. */
  height: 100vh;
  min-height: 640px;
  /* Same shared grid as the hero (--grid-cols/--grid-gap/--page-pad)
     so the body copy lands on the same left/right edges as it does
     above. */
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), 1fr);
  column-gap: var(--grid-gap);
  align-content: center;
  padding: clamp(3rem, 8vh, 6rem) var(--page-pad);
}

/* Also reused as a link (the footer's label), so it needs its own
   text-decoration reset — harmless for the plain <p> uses above. */
.about-label {
  grid-column: 1 / 3;
  font-family: var(--font-head);
  font-size: 0.85rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  color: rgba(20, 18, 15, 0.55);
  white-space: nowrap;
}

.about-body {
  grid-column: 7 / -1;
  max-width: 62ch;
}

.about-body p {
  font-size: clamp(1rem, 1.15vw, 1.15rem);
  line-height: 1.7;
  color: var(--ink-soft);
}

.about-body p + p { margin-top: 1.3rem; }

/* ── Work ── */
/* Same grid frame as the hero/about (--grid-cols/--grid-gap/--page-pad)
   so the label lands on the same left edge as "[ Story ]" above it;
   the item row itself is a flex strip spanning the full width, not
   locked to the 12-col tracks, since 4 equal panels read better as
   their own system. */
.work {
  /* Shared backdrop for every card's thumb/shot slots — defined once
     here (used via var(), not nested inside url()) so every rule
     that needs it stays in sync with a single file reference. */
  --work-texture: url("../assets/work/background.png");
  background: #F8F3ED;
  color: var(--ink);
  /* Sticky now too, same reason as About: the footer after it needs
     something locked in place to slide over, not a section that just
     scrolls away and exposes whatever's stuck underneath it. */
  position: sticky;
  top: 0;
  z-index: 3;
  /* Strict, not min: a hard 100vh cap (with the content sized to fit
     comfortably inside it) is what actually guarantees no scroll,
     rather than just centering within a box that can still grow taller. */
  height: 100vh;
  min-height: 640px;
  overflow: hidden;
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), 1fr);
  align-content: center;
  column-gap: var(--grid-gap);
  padding: clamp(0.75rem, 2vh, 1.5rem) var(--page-pad);
}

.work-grid {
  grid-column: 1 / -1;
  margin-top: 1rem;
  display: flex;
  align-items: stretch;
  border-top: 1px solid rgba(20, 18, 15, 0.15);
  border-bottom: 1px solid rgba(20, 18, 15, 0.15);
}

.work-item {
  flex: 1 1 0%;
  min-width: 0;
  border-right: 1px solid rgba(20, 18, 15, 0.15);
  /* The grow/shrink is what makes the clicked column take over —
     transitioning flex-grow (not width/grid-template-columns) keeps
     it smooth and framework-free. Same duration + easing on both
     properties on purpose — width and dimming reading as one unified
     motion, not two animations that happen to overlap. */
  transition: flex-grow 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}

.work-item:first-child { border-left: 1px solid rgba(20, 18, 15, 0.15); }

/* Once something is expanded, the rest compress and dim rather than
   just staying full-size and empty — makes the expansion read as a
   deliberate focus shift, not just one panel randomly growing. */
.work-grid.has-expanded .work-item { flex-grow: 0.35; opacity: 0.45; }
.work-grid.has-expanded .work-item.is-expanded { flex-grow: 4; opacity: 1; }

/* No height:100% here — that would force this to fill .work-item's
   full (stretched) box rather than its own content, which caused
   expanded content to overflow past the border. Sized to its own
   content instead; the collapsed row still lines up evenly since
   align-items:stretch on .work-grid handles that. */
.work-item-toggle {
  all: unset;
  box-sizing: border-box;
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: 1rem 1.25rem 1rem;
}

.work-item-toggle:focus-visible {
  outline: 1px solid var(--ink);
  outline-offset: -4px;
}

/* Header row: number+title on the left, blurb top-right — both share
   this row instead of the blurb being pushed into a separate block
   further down. */
.work-item-head-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
}

.work-item-head-main {
  display: flex;
  flex-direction: column;
  /* Fills the row (not just its own content width) so title/role have
     a definite width to truncate against once compressed, and so the
     centering below has the column's full width to center within. */
  flex: 1 1 auto;
  min-width: 0;
}

/* Once compressed by a sibling's expansion, narrow columns read
   better centered than left-clinging. Left-aligned by default (the
   wide, unexpanded grid) is unaffected. */
.work-grid.has-expanded .work-item:not(.is-expanded) .work-item-number,
.work-grid.has-expanded .work-item:not(.is-expanded) .work-item-title,
.work-grid.has-expanded .work-item:not(.is-expanded) .work-item-role {
  text-align: center;
}

.work-item-number {
  font-family: var(--font-head);
  font-size: clamp(1.9rem, 3vw, 2.6rem);
  color: var(--ink);
}

.work-item-heading {
  display: flex;
  flex-direction: column;
  margin-top: 1.25rem;
  min-width: 0;
}

/* Truncated, not wrapped — a compressed column is narrow enough that
   a wrapped title/role would run to a different number of lines item
   to item, pushing the media row below to a different height each
   time. One line always, everywhere, keeps that row aligned. */
.work-item-title {
  font-weight: 500;
  font-size: clamp(0.95rem, 1.1vw, 1.05rem);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.work-item-role {
  margin-top: 0.2rem;
  font-size: 0.8rem;
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Label/value table (Role / Context / Outcome) instead of a single blurb
   paragraph, so each project reads as scannable facts rather than prose.
   Always present in the layout (not display:none) with a fixed reserved
   height — collapsed cards need that same vertical space held open even
   though the text is invisible, otherwise their media row starts higher
   than the expanded card's and the thumbnails/shots go out of alignment
   across the row. Width is zeroed while collapsed though (unlike height)
   — it must NOT claim horizontal space in the head row, or it squeezes
   work-item-head-main and truncates the title/role even when the column
   itself has plenty of room. */
.work-item-details {
  flex: 0 0 0;
  width: 0;
  max-width: 46rem;
  height: 9.5rem;
  overflow: hidden;
  text-align: left;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.3s ease;
}

.work-item.is-expanded .work-item-details {
  flex: 0 1 auto;
  width: auto;
  opacity: 1;
  pointer-events: auto;
  transition: opacity 0.4s ease 0.2s;
}

.work-item-detail-row {
  display: flex;
  gap: 1.5rem;
  padding: 0.85rem 0;
  border-top: 1px solid rgba(20, 18, 15, 0.15);
}

.work-item-detail-row:first-child {
  border-top: none;
}

.work-item-detail-label {
  flex: 0 0 6rem;
  font-weight: 600;
  font-size: 0.8rem;
  color: var(--ink);
}

.work-item-detail-value {
  flex: 1 1 auto;
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--ink-soft);
}

/* One constant height always — collapsed and expanded look the same
   size, so nothing "grows." What changes on expand is which images
   are visible (the shots fade in) and whether the row scrolls. */
/* No `gap` here — it'd apply between all 5 children unconditionally,
   including the shots when they're sitting at zero width collapsed,
   quietly eating into the thumb's width. Spacing comes from each
   shot's own margin-left instead, which collapses to 0 right along
   with everything else. */
.work-item-media {
  display: flex;
  margin-top: 0.75rem;
  height: 320px;
  overflow-x: hidden;
  /* Scrollable but scrollbar-free — the row itself (drag/wheel/touch)
     and the nudge animation are the only scroll affordances needed. */
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.work-item-media::-webkit-scrollbar { display: none; }

.work-item.is-expanded .work-item-media {
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* One-shot hint that the row scrolls: a quick shimmy once the
     shots have finished growing in (last one starts at 0.24s, takes
     0.6s), not before — otherwise the shimmy and the growth read as
     two competing motions instead of one settling, then a nudge. */
  animation: mediaNudge 0.8s ease 0.95s;
}

@keyframes mediaNudge {
  0%, 100% { transform: translateX(0); }
  35% { transform: translateX(-14px); }
  65% { transform: translateX(3px); }
}

/* Cropped tight when collapsed (background-size: cover — the narrow
   box forces a zoomed-in detail crop), full and uncropped once
   expanded (background-size: contain — see the .is-expanded override
   below). Box dimensions never change for this, only how the image
   fills them — the width the row grows to via the existing expand
   mechanic is what actually reveals more of it. Doesn't affect the
   current solid-color placeholders at all; only matters once a real
   background-image is set per project. */
.work-item-thumb {
  position: relative;
  flex: 1 1 0%;
  min-width: 60%;
  height: 100%;
  overflow: hidden;
  scroll-snap-align: start;
  background-image: var(--work-texture);
  background-repeat: no-repeat;
  background-position: top center;
  background-size: cover;
  /* Without this, the hover scale below snapped instantly instead of
     easing in. */
  transition: transform 0.3s ease;
}

.work-item.is-expanded .work-item-thumb,
.work-item.is-expanded .work-item-shot {
  background-size: contain;
  background-position: center;
}

.work-item:nth-child(1).is-expanded .work-item-thumb {
  /* Centered now that the whole image is visible (contain), rather
     than anchored top-left — top/bottom inset so it doesn't sit flush
     against the card's edges. */
  padding: 24px 0;
  background-size: contain, cover;
  background-position: center, center;
}

/* Project 1's real thumbnail — a transparent-margin UI screenshot, so
   it needs something behind it rather than sitting on flat color. Two
   background-image layers: the real PNG on top, the shared texture
   underneath, composited over the site's own paper cream. */
.work-item:nth-child(1) .work-item-thumb {
  /* Inset for the real image only — background-origin/-clip:
     content-box on that layer means `cover`/`contain` size against
     the padded-in area, and the image is painted only within it, so
     the 24px gap at top/left reveals the texture layer (border-box)
     underneath instead of being clipped away or showing flat color. */
  padding: 24px 0 0 24px;
  background-image:
    url("../assets/work/project-01/thumb.png"),
    var(--work-texture);
  background-repeat: no-repeat, no-repeat;
  /* Real image anchored top-left (keeps the sidebar + header in
     frame when cropped), texture layer stays centered and full-bleed. */
  background-position: top left, center;
  background-size: cover, cover;
  background-origin: content-box, border-box;
  background-clip: content-box, border-box;
  background-color: #F8F3ED;
}

/* Project 1's real shots — each a self-contained, full-bleed slide
   (headline + screenshot, already composited on the site's own paper
   texture), unlike the thumbnail's raw transparent-margin screenshot.
   So no inset/backdrop layering needed here, just the image itself;
   contain, shown in full, once expanded (inherited from the
   .is-expanded rule above). Five shots (01-05.png), one child ahead
   of where thumb.png ends — see the extra .work-item-shot in the
   markup for this card. */
.work-item:nth-child(1) .work-item-shot:nth-child(2) {
  background-image: url("../assets/work/project-01/01.png");
}

.work-item:nth-child(1) .work-item-shot:nth-child(3) {
  background-image: url("../assets/work/project-01/02.png");
}

.work-item:nth-child(1) .work-item-shot:nth-child(4) {
  background-image: url("../assets/work/project-01/03.png");
}

.work-item:nth-child(1) .work-item-shot:nth-child(5) {
  background-image: url("../assets/work/project-01/04.png");
}

.work-item:nth-child(1) .work-item-shot:nth-child(6) {
  background-image: url("../assets/work/project-01/05.png");
}

/* Hover is the only cue that a panel is interactive (no cursor change
   — the site runs its own custom cursor dot everywhere), so the scale
   + hint both need to read clearly on their own. */
/* Only while collapsed — once expanded the thumb is just the first
   frame in a scrollable row, not a distinct "click to expand" target,
   so it shouldn't keep scaling on hover while you're scrolling past it. */
.work-item:not(.is-expanded) .work-item-toggle:hover .work-item-thumb { transform: scale(1.035); }

/* Viewfinder brackets, not text — four L-shaped corner marks that
   fade in and draw inward toward the edges on hover, like a camera
   focusing on the frame. Reads as a considered cue rather than an
   instruction, and being pure geometry it never has anything to wrap
   or clip regardless of how narrow the column gets. */
.work-item-corner {
  position: absolute;
  width: 14px;
  height: 14px;
  border: 0 solid var(--ink);
  opacity: 0;
  transition: opacity 0.3s ease, top 0.3s ease, right 0.3s ease, bottom 0.3s ease, left 0.3s ease;
}

.work-item-corner-tl { top: 14px; left: 14px; border-width: 1.5px 0 0 1.5px; }
.work-item-corner-tr { top: 14px; right: 14px; border-width: 1.5px 1.5px 0 0; }
.work-item-corner-bl { bottom: 14px; left: 14px; border-width: 0 0 1.5px 1.5px; }
.work-item-corner-br { bottom: 14px; right: 14px; border-width: 0 1.5px 1.5px 0; }

.work-item:not(.is-expanded) .work-item-toggle:hover .work-item-corner { opacity: 0.55; }
.work-item:not(.is-expanded) .work-item-toggle:hover .work-item-corner-tl { top: 8px; left: 8px; }
.work-item:not(.is-expanded) .work-item-toggle:hover .work-item-corner-tr { top: 8px; right: 8px; }
.work-item:not(.is-expanded) .work-item-toggle:hover .work-item-corner-bl { bottom: 8px; left: 8px; }
.work-item:not(.is-expanded) .work-item-toggle:hover .work-item-corner-br { bottom: 8px; right: 8px; }

.work-item.is-expanded .work-item-corner { display: none; }

/* Hidden collapsed (not just shrunk to zero-width) so the row's
   scrollable content doesn't exist until there's something to scroll
   to. Same 60% width as the thumb's own min-width once expanded (see
   .work-item-thumb) — both resolve against the same containing block,
   so they land on the same pixel width and every slide in the row
   reads as one consistent size. Still leaves the next slide cut off
   at the edge as a "there's more" cue rather than fitting neatly.

   No display:none — that toggle can't be animated, so the shot used
   to pop into the layout at full width the instant a card expanded,
   independent of its own fade-in. Zero flex-basis instead: it's
   already in the flex row (taking no space) and grows to its target
   width on the same easing curve as everything else, so revealing it
   reads as one continuous motion instead of a layout jump plus a
   separately-timed fade. */
/* Normal images, not crop-teasers like the thumb — these only ever
   appear in the expanded gallery (never a separate "collapsed" form
   to reconcile with), so there's no state to switch between and
   nothing at risk of jumping. contain, shown in full. */
/* Shared texture placeholder for projects 2-4 (no real shots yet) —
   cover, since a solo texture fill should read as full-bleed paper,
   not a letterboxed image. Project 1's real shots (see the
   nth-child(1) overrides above) just swap this background-image;
   they're already full-bleed slides so no other layering is needed. */
.work-item-shot {
  flex: 0 0 0px;
  margin-left: 0;
  overflow: hidden;
  height: 100%;
  scroll-snap-align: start;
  opacity: 0;
  background-image: var(--work-texture);
  background-repeat: no-repeat;
  background-position: center;
  background-size: cover;
  transition: flex-basis 0.6s cubic-bezier(0.22, 1, 0.36, 1), margin-left 0.6s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.5s ease;
}

.work-item.is-expanded .work-item-shot {
  flex-basis: 60%;
  margin-left: 0.5rem;
  opacity: 1;
}

/* Staggered start, same growth — each shot begins a beat after the
   last rather than all snapping in together. Project 1's card has a
   5th shot (see its extra .work-item-shot in the markup), so this
   goes one step further than the other three cards need. */
.work-item.is-expanded .work-item-shot:nth-child(3) { transition-delay: 0.08s; }
.work-item.is-expanded .work-item-shot:nth-child(4) { transition-delay: 0.16s; }
.work-item.is-expanded .work-item-shot:nth-child(5) { transition-delay: 0.24s; }
.work-item.is-expanded .work-item-shot:nth-child(6) { transition-delay: 0.32s; }

/* Once expanded, an image is no longer "click to expand the card" — it's
   "click to open it large" instead. Custom cursor is a dot everywhere
   else on the site, so this is the one spot that needs an actual system
   cursor back to read as clickable. */
.work-item.is-expanded .work-item-thumb,
.work-item.is-expanded .work-item-shot {
  cursor: zoom-in;
}

/* ── Lightbox ── */
/* Single shared overlay (only one work item can be expanded at a time,
   so only one gallery can ever be open at once) — its track is rebuilt
   from whichever item's thumb/shots were clicked rather than existing
   per-item, so the markup isn't repeated four times over. Dark scrim on
   purpose: it's the one place on the page that departs from the cream
   paper, because the photos need to read against something neutral
   instead of competing with the site's own texture. */
html.lightbox-open {
  overflow: hidden;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 500;
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.35s ease, visibility 0s linear 0.35s;
}

.lightbox.is-open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.35s ease;
}

.lightbox-scrim {
  position: absolute;
  inset: 0;
  background: rgba(20, 18, 15, 0.94);
}

.lightbox-stage {
  position: relative;
  z-index: 1;
  width: min(88vw, 1100px);
  height: min(78vh, 720px);
  overflow: hidden;
}

.lightbox-track {
  display: flex;
  height: 100%;
  transition: transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

.lightbox-slide {
  flex: 0 0 100%;
  height: 100%;
  background-repeat: no-repeat;
  background-position: center;
  background-size: contain;
}

/* Cream backdrop only behind the thumbnail slide (see
   .work-item:nth-child(1) .work-item-thumb) — its transparent-margin
   screenshot needs something behind it besides the dark scrim. Shot
   slides fill their frame with their own image/texture layer, so no
   fallback color is needed there. Matches :first-child (the real
   thumbnail slide) and :last-child (the wrap-around clone of it,
   appended after the shots — see openLightbox in main.js). */
.lightbox-slide:first-child,
.lightbox-slide:last-child {
  background-color: var(--cream);
}

/* Viewfinder-brackets language reused here (same border-only geometry
   as .work-item-corner) instead of an icon font — chevrons made of a
   single rotated corner rather than a separate visual system. */
.lightbox-arrow {
  all: unset;
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  z-index: 2;
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.lightbox-arrow-prev { left: clamp(0.5rem, 3vw, 2.5rem); }
.lightbox-arrow-next { right: clamp(0.5rem, 3vw, 2.5rem); }

.lightbox-arrow-chevron {
  width: 11px;
  height: 11px;
  border: 0 solid var(--cream);
  border-width: 1.5px 1.5px 0 0;
  opacity: 0.65;
  transition: opacity 0.2s ease;
}

.lightbox-arrow-prev .lightbox-arrow-chevron { transform: rotate(-135deg); }
.lightbox-arrow-next .lightbox-arrow-chevron { transform: rotate(45deg); }

.lightbox-arrow:hover .lightbox-arrow-chevron { opacity: 1; }

/* Hidden on the first slide (no wrapping backward) — set via the
   `hidden` attribute in JS rather than a class, since there's no other
   state it needs to carry. */
.lightbox-arrow[hidden] { display: none; }

.lightbox-close {
  all: unset;
  position: absolute;
  top: clamp(1rem, 3vw, 2rem);
  right: clamp(1rem, 3vw, 2rem);
  z-index: 2;
  width: 32px;
  height: 32px;
  cursor: pointer;
}

.lightbox-close-line {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 18px;
  height: 1.5px;
  background: var(--cream);
  opacity: 0.65;
  transition: opacity 0.2s ease;
}

.lightbox-close-line:first-child { transform: translate(-50%, -50%) rotate(45deg); }
.lightbox-close-line:last-child { transform: translate(-50%, -50%) rotate(-45deg); }

.lightbox-close:hover .lightbox-close-line { opacity: 1; }

@media (max-width: 720px) {
  .lightbox-stage {
    width: 92vw;
    height: 60vh;
  }
  .lightbox-arrow-prev { left: 0.5rem; }
  .lightbox-arrow-next { right: 0.5rem; }
}

/* ── Footer ── */
/* Last in the pin/cover chain — plain relative + higher z-index is
   enough here, nothing has to cover it in turn. Same grid, same
   cream, same 100vh discipline as the sections above it. Restraint
   is the "personality" rather than added ornament: one label, one
   line, one link — no logo row, no copyright bar, no sitemap. */
.footer {
  /* Same watercolor-paper texture as the Story section, not just the
     same flat color — see .section-next for how the grain itself is
     built (an SVG feTurbulence filter, composited via
     background-blend-mode so it can't interfere with the text). */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='500' height='500'%3E%3Cfilter id='w' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.02' numOctaves='3' seed='7' stitchTiles='stitch' result='blotch'/%3E%3CfeColorMatrix in='blotch' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.035 0' result='blotchA'/%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' seed='3' stitchTiles='stitch' result='fiber'/%3E%3CfeColorMatrix in='fiber' type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0' result='fiberA'/%3E%3CfeBlend in='blotchA' in2='fiberA' mode='multiply'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23w)'/%3E%3C/svg%3E");
  background-repeat: repeat;
  background-color: #F8F3ED;
  background-blend-mode: multiply;
  color: var(--ink);
  position: relative;
  z-index: 4;
  height: 100vh;
  min-height: 480px;
  display: grid;
  grid-template-columns: repeat(var(--grid-cols), 1fr);
  align-content: center;
  column-gap: var(--grid-gap);
  /* Same top/bottom padding as the Story section. */
  padding: clamp(3rem, 8vh, 6rem) var(--page-pad);
}

.footer-body {
  grid-column: 7 / -1;
}

.footer-line {
  font-family: var(--font-head);
  font-weight: 400;
  font-size: clamp(1.05rem, 2vw, 1.5rem);
  line-height: 1.4;
  max-width: 32ch;
  color: var(--ink);
}

.footer-link {
  display: inline-block;
  margin-top: 3.5rem;
  font-size: 0.9rem;
  letter-spacing: 0.03em;
  color: var(--ink-soft);
  text-decoration: none;
  border-bottom: 1px solid rgba(20, 18, 15, 0.3);
  padding-bottom: 0.2rem;
  transition: color 0.2s ease, border-color 0.2s ease;
}

.footer-link:hover {
  color: var(--ink);
  border-color: var(--ink);
}

/* A quiet signature, not a copyright bar — pinned to the section's
   own bottom edge rather than part of the centered quote/link block,
   so it reads as a small mark at the foot of the page instead of
   another line of content competing with the closing line. */
.footer-credit {
  position: absolute;
  left: var(--page-pad);
  right: var(--page-pad);
  bottom: clamp(1.25rem, 3vh, 2rem);
  font-size: 0.7rem;
  letter-spacing: 0.02em;
  text-align: center;
  color: rgba(20, 18, 15, 0.4);
}

/* ── Responsive ── */
/* Shared by both the 2-up tablet range and the stacked phone range
   below — anything about surviving a narrow column, regardless of
   whether that column is one of two side by side or the only one. */
@media (max-width: 980px) {
  /* Work stops assuming its content fits one viewport once cards
     stack (or even just double up) — un-pin it the same way Footer
     already opts out of the chain (see the "last in the pin/cover
     chain" comment on .footer): plain relative + its existing
     z-index is enough to still paint over About as it scrolls past,
     it just no longer forces its own height to 100vh, so extra rows
     of cards grow the section instead of clipping. */
  .work {
    position: relative;
    height: auto;
    min-height: 100vh;
    overflow: visible;
  }
  .work-item-heading { margin-top: 1.5rem; }
  .work-item-detail-label { flex-basis: 4.5rem; }
}

/* Two cards per row instead of four — four-across is unreadably thin
   well before there's enough room to stack usefully, so this fills
   the gap between the wide desktop grid and the phone breakpoint's
   single column below. Fixed 50% basis (not the desktop's grow-driven
   0%) since there's no spare width for flex-grow to distribute once
   two items already sum to a full row — expand still reveals a
   card's details/gallery, it just no longer widens the column too
   (see the flex-grow override below, same idea as the phone
   breakpoint canceling the same effect for its single column). */
@media (max-width: 980px) and (min-width: 721px) {
  .work-grid { flex-wrap: wrap; }
  .work-item {
    flex: 0 0 50%;
    max-width: 50%;
  }
  .work-item:nth-child(odd) { border-left: 1px solid rgba(20, 18, 15, 0.15); }
  .work-item:nth-child(-n+2) { border-bottom: 1px solid rgba(20, 18, 15, 0.15); }
  .work-grid.has-expanded .work-item { flex-grow: 0; opacity: 1; }
}

@media (max-width: 720px) {
  /* Every section's side padding narrows together — they all read
     from this one token instead of each needing its own override. */
  :root {
    --page-pad: 6vw;
  }
  .hero-content {
    padding-top: clamp(6rem, 16vh, 8rem);
  }
  /* No room for a side-by-side split at this width — stack the intro
     above the description instead. */
  .hero-intro,
  .hero-subtext {
    grid-column: 1 / -1;
  }
  .hero-intro { grid-row: 1; }
  .hero-subtext {
    grid-row: 2;
    margin-top: 1.6rem;
  }
  /* Same split as Hero — label and body sat in separate column
     ranges (1/3 and 7/-1) with a gap between, which only works with
     room to spare. Stack instead, and drop the strict 100vh pin (same
     reason as Work — see that section's mobile override) since two
     stacked blocks can run taller than one viewport where the
     side-by-side version never did. */
  .about {
    position: relative;
    height: auto;
    min-height: 100vh;
  }
  .about-label,
  .about-body {
    grid-column: 1 / -1;
  }
  .about-body { margin-top: 1.5rem; }
  .hero-clients {
    display: none;
  }
  .scroll-cue {
    display: none;
  }
  /* No room to shrink siblings sideways on a phone — stack instead,
     and let the expanded item just grow taller in place. */
  .work-grid {
    flex-direction: column;
  }
  .work-item {
    border-right: none;
    border-bottom: 1px solid rgba(20, 18, 15, 0.15);
  }
  .work-item:first-child { border-left: none; }
  /* Grid's own border-bottom already closes off the stack — the last
     card doesn't need its own, or the line doubles up. */
  .work-item:last-child { border-bottom: none; }
  .work-grid.has-expanded .work-item { flex-grow: 1; opacity: 1; }
  /* Same gap as the Story section's stacked label/body (see
     .about-body above) — footer-body already lands on its own row
     here (it can't fit beside the label's narrow 1/3 column), it just
     never had the matching margin-top to space it the same way. */
  .footer-body {
    grid-column: 1 / -1;
    margin-top: 1.5rem;
  }
}

@media (hover: none) {
  html { cursor: auto; }
  .cursor-dot, #ink-canvas { display: none; }
  a, button { cursor: pointer; }
}
