/* ══════════════════════════════════════════════════════════════════════
   NEKOFREK — BAU (FEATURED) PAGE STYLING
   ══════════════════════════════════════════════════════════════════════

   Shared by all nine BAU case-study pages. Edit here once and every page
   changes together.

   The layout is the long-scroll case-study structure — hero → breakdown
   lists → pinned breakout gallery → scrubbed body copy → media → credits
   → next project — restyled onto the site's own palette so a BAU page
   reads as part of Nekofrek_Guild.html rather than a different website.

   Two things live here that work.css has no equivalent for, and both are
   load-bearing for the motion in bau.js:

     · .bau-gallery              the pinned breakout. Its sizing rules and
                                 the GSAP timeline are one design; changing
                                 a dimension here without reading the
                                 GALLERY note below will break the pin.
     · .anim-in / .split-line    the resting (hidden) states that GSAP
                                 animates OUT of. They look like bugs —
                                 opacity:0 on visible content — and they
                                 are not. bau.js reveals them, and turns
                                 them off entirely under reduced motion.

   You should not need to touch this file to change copy or images —
   that all lives in projects.js.
   ══════════════════════════════════════════════════════════════════════ */


/* ── Palette ───────────────────────────────────────────────────────────
   Lifted from work.css, which lifted it from the landing page, so all
   three match exactly. ── */
:root {
  --paper:  #0D0D0D;                      /* the site ground */
  --sheet:  #100F11;                      /* media wells, a step up */
  --ink:    #D2D2D2;                      /* headings */
  --ink-2:  #D2D2D2;                      /* body copy */
  --ink-3:  rgba(210, 210, 210, 0.55);    /* labels, list items, captions */

  /* the frosted-glass recipe, reused from the menu / flip dots */
  --glass:        rgba(211, 174, 184, 0.16);
  --glass-line:   rgba(211, 174, 184, 0.28);
  --glass-blur:   blur(16.4px);

  --rule:   rgba(211, 174, 184, 0.55);    /* hairlines between sections */
  --pad:    5%;                           /* page side gutter */

  /* Corner radius. The reference rounds every well at rest and squares
     them off at the end of the breakout — 0 is the tween target, so this
     is only ever the RESTING value. */
  --round:  16px;
}


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

/* No scroll-behavior:smooth. Lenis owns the scroll on this page, and the
   two fight: a native smooth jump and a Lenis lerp run at once and land
   somewhere neither intended. bau.js falls back to native smooth scroll
   for anchors only when Lenis didn't load. */
html { }

body {
  /* Gilroy is declared in bau.html's @font-face block, loaded from
     ../../fonts/ — two levels up, because this page sits in MERGER/bau/
     while the fonts live in VSCODE/fonts/. */
  font-family: 'Gilroy', sans-serif;
  font-weight: 400;
  background-color: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  /* The breakout gallery grows tiles to 100vw. Mid-tween they are wider
     than the column that holds them, and without this the page grows a
     horizontal scrollbar for the duration of the pin. */
  overflow-x: hidden;
}

img, video { display: block; max-width: 100%; }

/* <picture> is only a wrapper for choosing a format — it should not sit
   between a media well and the <img> it is sizing. */
picture { display: contents; }

::selection { background: rgba(255, 135, 9, 0.28); }


/* ══════════════════════════════════════════════════════════════════════
   NAV — the cat pill, ported from the landing page
   ══════════════════════════════════════════════════════════════════════
   Identical to work.css. What's below is only the RESTING (closed)
   state: a bare cat button, with the pill collapsed to a circle at zero
   opacity behind it. GSAP animates outward from here, so don't "fix" the
   opacity:0 — that's the thing being animated from. ── */
#cat-menu {
  --menu-size: 5vh;
  --circle:    var(--menu-size);
  --pill-w:    min(calc(var(--menu-size) * 7.5), 92vw);
  --pill-h:    calc(var(--menu-size) * 0.44);

  position: fixed;
  top: 1.25rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 50;
  width:  var(--pill-w);
  height: var(--circle);
  pointer-events: none;
}

#cat-menu .pill {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width:  var(--circle);          /* collapsed; GSAP widens it on open */
  height: var(--pill-h);
  background: var(--glass);
  border: 1px solid var(--glass-line);
  border-radius: 9999px;
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  opacity: 0;                     /* ← animated to 1; see bau.js */
  overflow: hidden;
  pointer-events: auto;
}

#cat-menu .links {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: 1fr var(--circle) 1fr;   /* gap in the middle for the cat */
  align-items: center;
  height: 100%;
}

#cat-menu .links .half {
  display: flex;
  justify-content: space-evenly;
  align-items: center;
}

#cat-menu .menu-link {
  color: var(--ink);
  font-family: 'Gilroy', sans-serif;
  font-size: calc(var(--menu-size) * 0.29);
  font-weight: 400;
  line-height: 1;
  text-decoration: none;
  opacity: 0;                     /* ← staggered up to 1 as the pill opens */
  transition: opacity .15s;
}
/* !important because GSAP writes opacity inline while animating, and the
   hover feedback has to beat it */
#cat-menu .menu-link:hover { opacity: .55 !important; }
#cat-menu .menu-link:focus-visible {
  outline: 2px solid var(--ink); outline-offset: 4px; border-radius: 4px;
}

#cat-menu .cat-btn {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  width:  var(--circle);
  height: var(--circle);
  padding: 0;
  border-radius: 9999px;
  background: var(--glass);
  border: 1px solid var(--glass-line);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  display: grid;
  place-items: center;
  overflow: hidden;
  pointer-events: auto;
  cursor: pointer;
}
/* logo_mark.gif frames its head far smaller inside its own canvas than
   the old cat-head.gif did — 70%x47% of a 1280 square against 77%x65% of
   a 200 one — so it needs to be drawn BIGGER than the button to land at
   the same optical size, and `img { max-width: 100% }` up top was quietly
   holding the old 1.44 down to the button's own 100%. max-width: none
   lets the multiplier through; 1.17 is what puts the mark's white-pixel
   bbox back at the size the old head rendered. Re-measure that bbox if
   the GIF is ever re-exported — neither number is arbitrary. */
#cat-menu .cat-gif {
  width: calc(var(--menu-size) * 1.17);
  max-width: none;
  height: auto;
  pointer-events: none;
}
/* The WORK pages only. They carry `menu mark.gif` instead — the same head,
   but drawn inside its own wobbling ring, near full-bleed in its 1280
   canvas (97%x78% of it, against logo_mark's 70%x47%) — at 1.5x the size.
   Scoped on #work-root rather than written into one of the two sheets
   because the layouts are SWAPPED (see the head of each HTML): work.html
   loads bau.css and bau.html loads work.css, so a sheet is not a page.
   The selector is, and it survives the swap being undone.

   The 1.5x lands on --circle, not on the image alone, and that is the
   whole trick: this mark draws its OWN ring, so growing the picture inside
   a fixed button just pushes that ring past the glass edge and crops the
   head down to one ear. Growing the button with it keeps the mark whole —
   the GIF's ring sits just inside the glass circle and reads as its inner
   line. Everything else in the bar is already sized off --circle (the
   reserved gap between the links, the collapsed pill, the nav's height),
   so they follow along on their own. */
body:has(#work-root) #cat-menu {
  --circle: calc(var(--menu-size) * 1.5);
}
body:has(#work-root) #cat-menu .cat-gif {
  width: var(--circle);
}

/* ── If GSAP didn't load ───────────────────────────────────────────
   No CDN — offline, blocked, or opened from a stick in a field. The
   resting state above is invisible by design, so without the timeline
   the menu would simply not be there. bau.js sets .no-gsap on <html>
   when it can't find gsap, and these rules park the menu open instead.

   The same class also has to un-hide everything else the motion was
   going to reveal — see the .no-gsap rules further down. ── */
.no-gsap #cat-menu .pill      { width: var(--pill-w); opacity: 1; }
.no-gsap #cat-menu .menu-link { opacity: 1; }


/* ══════════════════════════════════════════════════════════════════════
   CLOSE — top left
   ══════════════════════════════════════════════════════════════════════
   The Featured panel's close control, reused, and pointing back at
   #Featured_section — the folder stack this page was opened from.

   Geometry copied exactly from .neko-close in neko-hero/neko-scene.js
   (26×2px bars, 5px gap, 6px padding, .3s ease) so the two read as the
   same control. The ±7px is the bar height plus the gap — exactly the
   distance from an outer bar to the centre line. ── */
.bau-close {
  position: fixed;
  top: 1.25rem;
  left: clamp(16px, var(--pad), 60px);
  z-index: 51;                    /* over the nav */
  display: flex;
  flex-direction: column;
  justify-content: center;        /* centres the bars against the pill's 5vh */
  gap: 5px;
  height: 5vh;
  padding: 6px;
  background: none;
  border: 0;
  cursor: pointer;
}
.bau-close span {
  display: block;
  width: 26px;
  height: 2px;
  background: var(--ink-2);
  transition: transform .3s ease, opacity .3s ease;
}
.bau-close:hover span:nth-child(1) { transform: translateY(7px)  rotate(45deg); }
.bau-close:hover span:nth-child(2) { opacity: 0; }
.bau-close:hover span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

.bau-close:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
  border-radius: 2px;
}
/* Keyboard focus should show the X too, not just the bars */
.bau-close:focus-visible span:nth-child(1) { transform: translateY(7px)  rotate(45deg); }
.bau-close:focus-visible span:nth-child(2) { opacity: 0; }
.bau-close:focus-visible span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }


/* ══════════════════════════════════════════════════════════════════════
   SCROLL PROGRESS — the hairline down the right edge
   ══════════════════════════════════════════════════════════════════════
   A thin track with a thumb whose height is the fraction of the document
   in view and whose position is the scroll progress. bau.js writes both
   as a transform, never as top/height, so it never lays out mid-scroll.

   It fades out when the page is idle, which is why it can afford to sit
   over the content. ── */
.bau-progress {
  position: fixed;
  top: 0; right: 0; bottom: 0;
  width: 10px;
  z-index: 49;
  pointer-events: none;
}
.bau-progress__thumb {
  position: absolute;
  top: 0; right: 3px;
  width: 4px;
  height: 100px;                  /* placeholder; bau.js scales it */
  transform-origin: top center;
  border-radius: 9999px;
  background: var(--glass-line);
  opacity: 0;
  transition: opacity .35s ease;
  will-change: transform;
}
/* bau.js adds .is-scrolling for 700ms after the last scroll event */
html.is-scrolling .bau-progress__thumb { opacity: 1; }


/* ══════════════════════════════════════════════════════════════════════
   HERO
   ══════════════════════════════════════════════════════════════════════
   A full-screen media well with the headline over it and the services
   strip fused to its bottom edge.

   The well is drawn TWICE. .bau-hero__plate is the real one; behind it
   .bau-hero__glow is the same picture blown out, heavily blurred and
   breathing on a 5s loop, so the hero has a soft halo that moves even
   when the footage doesn't. Straight from the reference. ── */
.bau-hero {
  position: relative;
  width: 100%;
  height: 100svh;
  padding: clamp(84px, 9vh, 140px) clamp(16px, var(--pad), 60px) clamp(16px, 3vh, 44px);
}

.bau-hero__frame {
  position: relative;
  width: 100%;
  height: 100%;
}

/* the blurred double, behind everything */
.bau-hero__glow {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: var(--round);
  overflow: hidden;
  /* blur() on a large element is the single most expensive thing on this
     page. It is hidden below 760px, where it is also nearly invisible. */
  filter: blur(28px);
  opacity: .55;
  transform: translateZ(0);       /* its own layer, so the loop never repaints the hero */
}
/* The breathing loop rides on an INNER element, not on .bau-hero__glow
   itself. The outer one carries .anim-in, and GSAP writes the entrance
   scale to its inline transform — which would silently win over a CSS
   animation on the same property and stop the loop dead. Two elements,
   one transform each, and neither can clobber the other. */
.bau-hero__glow-inner { width: 100%; height: 100%; }
.bau-hero__glow img,
.bau-hero__glow video { width: 100%; height: 100%; object-fit: cover; }

.bau-hero__plate {
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: var(--round);
  overflow: hidden;
  background: var(--sheet);
}
.bau-hero__plate > img,
.bau-hero__plate > video,
.bau-hero__plate > picture > img {
  width: 100%; height: 100%; object-fit: cover;
}

/* a floor under the headline so white type survives a pale frame */
.bau-hero__scrim {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 66%;
  z-index: 2;
  border-radius: 0 0 var(--round) var(--round);
  background: linear-gradient(to top, rgba(13,13,13,.62), transparent);
  pointer-events: none;
}

.bau-hero__copy {
  position: absolute;
  left: clamp(18px, 3vw, 46px);
  bottom: clamp(18px, 4vh, 54px);
  right: clamp(18px, 3vw, 46px);
  z-index: 3;
}
/* When a services strip is present it occupies the bottom edge, so the
   headline has to clear it. :has() so the offset only applies when the
   strip is actually there. */
.bau-hero__frame:has(.bau-services) .bau-hero__copy { bottom: clamp(96px, 15vh, 190px); }

.bau-hero__title {
  font-size: clamp(2.6rem, 8.5vw, 8rem);
  font-weight: 900;
  line-height: .92;
  letter-spacing: -0.035em;
  text-transform: uppercase;
  white-space: pre-line;          /* \n in projects.js becomes a line break */
  text-wrap: balance;
}
.bau-hero__subtitle {
  margin-top: clamp(10px, 1.6vh, 20px);
  font-size: clamp(.82rem, 1.1vw, 1.05rem);
  font-weight: 600;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--ink-3);
}


/* ── SERVICES STRIP ────────────────────────────────────────────────────
   The row fused to the bottom of the hero. At rest each cell shows only
   its title; hovering one squares off its top corners, grows the title,
   and opens the "We worked on the" line and the tag pills that were
   sitting at height:0.

   The open/close is a GSAP tween in bau.js, not a CSS transition — the
   height it animates is auto, which CSS cannot interpolate. What's here
   is the resting state and the geometry. ── */
.bau-services {
  position: absolute;
  left: 50%;
  bottom: 0;
  transform: translateX(-50%);
  /* a hair over 100% so the cells' own borders can't leave a seam of
     hero showing down either side — the reference does the same */
  width: 100.5%;
  z-index: 4;
  display: grid;
  align-items: end;
  overflow: hidden;
  border-radius: 0 0 var(--round) var(--round);
  color: var(--ink);
}
/* hidden on small screens: three cells at phone width are unreadable,
   and the tags wrap into a wall */
@media (max-width: 900px) { .bau-services { display: none; } }

.bau-service {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 1rem 2rem;
  background: #100F11;            /* the strip's own dark, not --paper */
  cursor: default;
}
/* three shades so adjacent cells read as separate panels; bau.js cycles
   them by index, and these are the values it uses */
.bau-service:nth-child(3n+2) { background: #100F11; }
.bau-service:nth-child(3n+3) { background: #100F11; }

.bau-service__sub {
  display: block;
  height: 0;                      /* ← opened by GSAP on hover */
  overflow: hidden;
  font-size: .74rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-3);
  pointer-events: none;
}
.bau-service__title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  font-size: 1rem;                /* ← grown to 1.25rem on hover */
  font-weight: 500;
  letter-spacing: .04em;
}
.bau-service__arrow { width: .8rem; margin-top: -2px; flex: none; }
.bau-service__tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  height: 0;                      /* ← opened by GSAP on hover */
  overflow: hidden;
  user-select: none;
}
.bau-service__tags span {
  padding: .5rem 1.25rem;
  border-radius: 9999px;
  background: rgba(210, 210, 210, .1);
  color: var(--ink-3);
  font-size: .82rem;
  white-space: nowrap;
  pointer-events: none;
}


/* ══════════════════════════════════════════════════════════════════════
   BODY
   ══════════════════════════════════════════════════════════════════════ */
.bau-body {
  display: flex;
  flex-direction: column;
  gap: clamp(72px, 12vh, 168px);
  padding: clamp(72px, 12vh, 160px) 0;
}

/* Most blocks sit in the gutter. The gallery deliberately does NOT —
   it needs the full viewport width to break out into. */
.bau-block { padding: 0 clamp(16px, var(--pad), 60px); }


/* ── STATEMENT — a small label over a very large line ──────────────── */
.bau-statement { display: flex; flex-direction: column; gap: clamp(14px, 2.5vh, 40px); }
.bau-statement__label {
  font-size: clamp(.85rem, 1.15vw, 1.4rem);
  font-weight: 600;
  color: var(--ink-3);
}
.bau-statement__text {
  font-size: clamp(1.7rem, 5vw, 6rem);
  font-weight: 600;
  line-height: 1.02;
  letter-spacing: -0.03em;
  width: 100%;
  text-wrap: pretty;
}
@media (min-width: 1024px) { .bau-statement__text { width: 80%; } }


/* ── BODY COPY ─────────────────────────────────────────────────────── */
.bau-text { display: flex; flex-direction: column; gap: clamp(14px, 2.5vh, 32px); }
.bau-text p {
  font-size: clamp(1.05rem, 2vw, 2rem);
  font-weight: 500;
  line-height: 1.45;
  letter-spacing: -0.01em;
  color: var(--ink-3);
  max-width: 62ch;
}


/* ── BREAKDOWN — what we did, in columns ───────────────────────────── */
.bau-breakdown {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: clamp(28px, 5vw, 90px);
}
.bau-breakdown__col h3 {
  font-size: clamp(.78rem, 1vw, .95rem);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding-bottom: 14px;
  margin-bottom: 6px;
  border-bottom: 1px solid var(--glass-line);
}
.bau-breakdown__col ul { list-style: none; }

/* ── the prose variant of a breakdown column ───────────────────────────
   The work data's two-column text block lands on this grid (see the
   `text2` case in bau.js). Its columns hold paragraphs, not a list of
   deliverables, so the list's display size would shout — this is the
   same column at reading size. The <h3> rule above still applies and is
   what keeps the two variants looking like the same component. */
.bau-breakdown__col--prose p {
  font-size: clamp(1rem, 1.4vw, 1.25rem);
  font-weight: 400;
  line-height: 1.62;
  color: var(--ink-3);
  max-width: 46ch;
}
.bau-breakdown__col--prose p + p { margin-top: 1.1em; }

/* The optional heading on a one-column text block (the work data's
   `text1`). Same eyebrow treatment as a breakdown column's, so a page
   that mixes the two reads as one system. */
.bau-text__heading {
  font-size: clamp(.78rem, 1vw, .95rem);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-3);
  padding-bottom: 14px;
  border-bottom: 1px solid var(--glass-line);
}

/* ── the drawn ↗ in a headline ─────────────────────────────────────────
   The work copy this layout now renders puts ↗ mid-headline and bau.js
   swaps it for a drawn glyph. Lifted from work.css verbatim, including
   the vertical-align: the SVG centres on the x-height, which sits
   visibly low against the full caps of .bau-hero__title, and 0.26em
   re-centres it on the cap-height instead. */
.arrow-container {
  display: inline-block;
  width: clamp(60px, 8vw, 100px);
  vertical-align: 0.26em;
  margin: 0 0.22em;
}
.arrow-svg { width: 100%; height: auto; overflow: visible; }
.bau-breakdown__col li {
  font-size: clamp(1.1rem, 2.2vw, 2rem);
  font-weight: 500;
  line-height: 1.6;
  letter-spacing: -0.01em;
}


/* ══════════════════════════════════════════════════════════════════════
   ★ THE BREAKOUT GALLERY
   ══════════════════════════════════════════════════════════════════════
   The page's signature move, and the one piece of layout here that is
   inseparable from its JavaScript. Read this before changing a number.

   At rest: a row of tiles, centred, sitting in the middle of a viewport-
   tall wrapper. On scroll bau.js pins that wrapper and, over one further
   screen of scroll, tweens every tile to width:100vw / height:100vh /
   border-radius:0 while the row's gap opens to 8vw. The tiles unfold
   into a strip of full-screen frames.

   Three rules make it work, and each one is easy to undo by accident:

     .bau-gallery-wrap { height: 100vh }
        The pinned element. It must be exactly one viewport tall or the
        pin starts and ends in the wrong place.

     .bau-gallery { width: min-content }
        The row must be as wide as its contents and no wider. A stretched
        row would centre its tiles by distributing slack, and the tween
        would then fight the layout instead of driving it.

     .bau-gallery__item is sized in vw/vh
        The tween's END values are 100vw/100vh, so the START has to be in
        the same units or the interpolation goes through a unit
        conversion on every frame.

   The section's padding-bottom is the pin's scroll budget. pinSpacing is
   off in the timeline — meaning the pin adds no space of its own — so
   this padding IS the distance you travel while pinned, plus a little
   over. Shorten it and the gallery will still be mid-unfold when the
   next block arrives. ── */
.bau-gallery-section {
  width: 100%;
  padding-top: clamp(32px, 6vh, 90px);
  padding-bottom: 105vh;          /* ← the pin's scroll budget. See above. */
}
.bau-gallery-wrap {
  height: 100vh;                  /* ← must stay exactly one viewport */
  /* FLEX centring, not grid. This is the one that centres a child WIDER
     than its container, spilling equally off both edges — which is what
     the row does for the whole second half of the tween.

     display:grid + place-items:center looks equivalent and is not: an
     auto-sized grid track simply GROWS to fit an oversized item, so the
     row would be pinned to the left edge and run off to the right, and
     the move would end looking at tile one's right-hand half. */
  display: flex;
  align-items: center;
  justify-content: center;
}
.bau-gallery {
  /* How many tiles this particular gallery has. bau.js writes it inline
     from the length of the images list, because the tile width has to
     divide the screen by it — a row sized without knowing the count
     overflows at four tiles and looks lost at two. */
  --n:   3;
  --gap: clamp(12px, 2vw, 28px);

  display: grid;
  grid-auto-flow: column;         /* one row, however many tiles */
  gap: var(--gap);                /* ← tweened out to 8vw */
  width: min-content;             /* ← must stay min-content */
  place-items: center;
}
.bau-gallery__item {
  position: relative;
  display: grid;
  place-items: center;
  overflow: hidden;
  /* Two limits, whichever bites first:
       · the share of 92vw left over once the gaps are taken out, so the
         whole row fits on screen at rest whatever --n is
       · 26vw, so a two-tile gallery doesn't turn into two half-screens
     The tween's end value is 100vw, i.e. the same unit either way. */
  width: min(26vw, calc((92vw - (var(--n) - 1) * var(--gap)) / var(--n)));
  height: 34vh;                   /* ← tweened to 100vh */
  border-radius: var(--round);    /* ← tweened to 0 */
  background: var(--sheet);
}
.bau-gallery__inner { position: relative; width: 100%; height: 100%; }
.bau-gallery__item img,
.bau-gallery__item video {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}

/* Phones don't pin: the tween needs a pointer-driven scroll it can be
   scrubbed against, and a 100vw tile on a 390px screen is the same
   picture either way. bau.js checks this breakpoint too and skips the
   timeline, so the two must agree. */
@media (max-width: 760px) {
  .bau-gallery-section { padding-bottom: clamp(48px, 8vh, 90px); }
  .bau-gallery-wrap    { height: auto; }
  .bau-gallery {
    grid-auto-flow: row;
    width: 100%;
    padding: 0 clamp(16px, var(--pad), 60px);
  }
  .bau-gallery__item { width: 100%; height: auto; aspect-ratio: 16/10; }
}


/* ── MEDIA — one image or video, full width ────────────────────────── */
.bau-media { position: relative; }
.bau-media__well {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: var(--round);
  background: var(--sheet);
  aspect-ratio: 16/9;             /* overridden inline by the block's ratio */
}
.bau-media__well > img,
.bau-media__well > video,
.bau-media__well > picture > img {
  width: 100%; height: 100%; object-fit: cover;
}
.bau-caption {
  margin-top: 14px;
  font-size: .82rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-3);
}


/* ── DUO — two side by side ────────────────────────────────────────── */
.bau-duo {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: clamp(16px, 2vw, 28px);
}
.bau-duo__well {
  position: relative;
  aspect-ratio: 4/5;
  overflow: hidden;
  border-radius: var(--round);
  background: var(--sheet);
}
.bau-duo__well > img,
.bau-duo__well > video,
.bau-duo__well > picture > img {
  width: 100%; height: 100%; object-fit: cover;
}


/* ── STACK — full-width boards, one under the other ────────────────── */
.bau-stack { display: flex; flex-direction: column; gap: clamp(24px, 5vh, 64px); }
.bau-stack__heading {
  font-size: clamp(.78rem, 1vw, .95rem);
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.bau-stack__well {
  position: relative;
  width: 100%;
  overflow: hidden;
  border-radius: var(--round);
  background: var(--sheet);
  aspect-ratio: 16/9;
}
.bau-stack__well > img,
.bau-stack__well > video,
.bau-stack__well > picture > img {
  width: 100%; height: 100%; object-fit: cover;
}


/* ── QUOTE ─────────────────────────────────────────────────────────── */
.bau-quote { text-align: center; }
.bau-quote blockquote {
  font-size: clamp(1.6rem, 4vw, 4rem);
  font-weight: 600;
  line-height: 1.06;
  letter-spacing: -0.03em;

  /* The measure is in em — relative to THIS element's own display size,
     so it scales with the clamp above.

     NOT ch. ch is the advance width of the digit "0", which in Gilroy is
     0.536em: nothing like the average glyph in a line of Semibold prose.
     A "22ch" measure here rendered one word per line. 14em is about
     twenty-five characters at this weight, which is two or three lines
     for a sentence-length quote. */
  max-width: min(14em, 92%);
  margin: 0 auto;
}
.bau-quote cite {
  display: block;
  margin-top: clamp(16px, 3vh, 34px);
  font-size: .8rem;
  font-style: normal;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-3);
}


/* ══════════════════════════════════════════════════════════════════════
   VIEW WEBSITE — the pill that slides up from the bottom left
   ══════════════════════════════════════════════════════════════════════
   Parked off the bottom of its own box at rest. bau.js slides it in once
   you are a screen into the page and slides it back out before the
   credits, so it is only ever offering the link while you are actually
   in the work. ── */
.bau-visit {
  position: fixed;
  left: clamp(16px, var(--pad), 60px);
  bottom: clamp(20px, 4vh, 44px);
  z-index: 48;
  transform: translateY(200%);    /* ← the resting state; GSAP brings it in */
}
.bau-visit a {
  display: inline-flex;
  align-items: center;
  gap: .7rem;
  padding: .85rem 1.5rem;
  border-radius: 9999px;
  background: var(--glass);
  border: 1px solid var(--glass-line);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  color: var(--ink);
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .14em;
  text-transform: uppercase;
  text-decoration: none;
  transition: background .25s ease, transform .25s ease;
}
.bau-visit a:hover { background: rgba(211, 174, 184, .28); transform: scale(1.04); }
.bau-visit svg { width: 12px; height: 12px; flex: none; }
@media (max-width: 620px) { .bau-visit { display: none; } }


/* ══════════════════════════════════════════════════════════════════════
   CREDITS
   ══════════════════════════════════════════════════════════════════════ */
.bau-credits {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: clamp(24px, 3vw, 56px);
  padding-top: clamp(40px, 7vh, 90px);
  border-top: 1px solid var(--glass-line);
}
.bau-credits h4 {
  font-size: .72rem;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 12px;
}
.bau-credits p { font-size: 1rem; line-height: 1.7; color: var(--ink-2); }


/* ══════════════════════════════════════════════════════════════════════
   NEXT PROJECT — the card that closes the page
   ══════════════════════════════════════════════════════════════════════ */
.bau-next {
  display: block;
  position: relative;
  margin: 0 clamp(16px, var(--pad), 60px);
  padding: clamp(40px, 8vh, 110px) clamp(24px, 4vw, 64px);
  border-radius: var(--round);
  border: 1px solid var(--glass-line);
  background: var(--sheet);
  overflow: hidden;
  color: var(--ink);
  text-decoration: none;
}
.bau-next__label {
  display: block;
  font-size: .74rem;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 14px;
}
.bau-next__title {
  font-size: clamp(2rem, 6vw, 5.5rem);
  font-weight: 900;
  line-height: .95;
  letter-spacing: -0.035em;
  text-transform: uppercase;
}
/* the media sits behind the type, dimmed, and lifts on hover */
.bau-next__media {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: .28;
  transition: opacity .5s ease, transform .7s cubic-bezier(.22,.68,0,1);
}
.bau-next__media > img,
.bau-next__media > video,
.bau-next__media > picture > img {
  width: 100%; height: 100%; object-fit: cover;
}
.bau-next:hover .bau-next__media { opacity: .45; transform: scale(1.04); }
.bau-next:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; }


/* ══════════════════════════════════════════════════════════════════════
   RESTING STATES FOR THE MOTION
   ══════════════════════════════════════════════════════════════════════
   These look wrong on their own — content set to opacity:0 — and they
   are correct. GSAP animates OUT of them. Every one of them is cancelled
   by .no-gsap and by prefers-reduced-motion, both of which are handled
   at the bottom of this file, so nothing here can leave content hidden
   on a machine that never runs the animation. ── */

/* the hero's pieces, brought in by the elastic entrance timeline */
.anim-in { opacity: 0; transform: scale(.9); will-change: transform, opacity; }

/* a line produced by SplitText, before its scrubbed reveal */
.split-line { will-change: transform, opacity; }

/* a media well or a column, before its rise-in */
.rise-in { opacity: 0; transform: translateY(3rem); will-change: transform, opacity; }


/* ══════════════════════════════════════════════════════════════════════
   AMBIENT LOOPS
   ══════════════════════════════════════════════════════════════════════
   Two slow, never-ending transforms, lifted from the reference. They run
   on their own layers and touch nothing but transform, so they cost a
   composite and no layout.

   `linear` rather than an ease is deliberate: an eased loop has a
   detectable beat, and at this duration a beat reads as a stutter. ── */
@keyframes bau-float     { 0% { transform: translateY(0); }  50% { transform: translateY(-.5rem); } 100% { transform: translateY(0); } }
@keyframes bau-breathe   { 0% { transform: scale(.95); }     50% { transform: scale(1); }           100% { transform: scale(.95); } }

.float-anim   { animation: bau-float 6s linear infinite; will-change: transform; }
.breathe-anim { animation: bau-breathe 5s ease-in-out infinite; will-change: transform; }


/* ══════════════════════════════════════════════════════════════════════
   MISSING ASSET PLACEHOLDER
   ══════════════════════════════════════════════════════════════════════
   When an image named in projects.js isn't on disk yet, bau.js swaps it
   for one of these — a frosted panel printing the exact path it wanted.
   So the page never looks broken, and you can read straight off it what
   to name the file and where to drop it. Once the file exists the real
   image takes over and this disappears on its own. ── */
.asset-missing {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 20px;
  text-align: center;
  background: var(--glass);
  border: 1px dashed var(--glass-line);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
}
.asset-missing__label {
  font-size: 0.72rem;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--ink-2);
  opacity: .45;
}
.asset-missing__path {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: clamp(11px, 1.05vw, 14px);
  line-height: 1.5;
  color: var(--ink);
  opacity: .78;
  word-break: break-all;
}


/* ══════════════════════════════════════════════════════════════════════
   NOT FOUND — shown when ?p= names a project that isn't in projects.js
   ══════════════════════════════════════════════════════════════════════ */
.bau-404 {
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0 clamp(16px, var(--pad), 60px);
}
.bau-404 h1 {
  font-size: clamp(2rem, 5vw, 4rem);
  font-weight: 900;
  letter-spacing: -0.03em;
  margin-bottom: 12px;
}
.bau-404 p { opacity: .6; margin-bottom: 32px; }
.bau-404 ul { list-style: none; display: grid; gap: 10px; }
.bau-404 a {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 5px;
  font-size: clamp(1rem, 1.6vw, 1.3rem);
}


/* ── Small screens ─────────────────────────────────────────────────── */
@media (max-width: 760px) {
  .bau-hero__glow { display: none; }   /* the blur costs more than it shows */
}
@media (max-width: 620px) {
  /* the pill and the back link would collide at the top — stack them by
     pushing the pill down to its own line */
  #cat-menu { top: auto; bottom: 1.25rem; }
}


/* ══════════════════════════════════════════════════════════════════════
   NO GSAP / REDUCED MOTION
   ══════════════════════════════════════════════════════════════════════
   The two ways this page has to work with none of the motion running.
   Both do the same job: cancel every resting state above, so content
   that was waiting to be revealed is simply there.

   These are the last rules in the file so they win on order, and they
   carry !important because GSAP writes its values inline — under reduced
   motion bau.js never starts a timeline, but a page that changes the
   setting mid-session could still have inline transforms parked on it. ── */
.no-gsap .anim-in,
.no-gsap .rise-in,
.no-gsap .split-line {
  opacity: 1 !important;
  transform: none !important;
}
.no-gsap .bau-visit { transform: none; }

@media (prefers-reduced-motion: reduce) {
  .anim-in, .rise-in, .split-line {
    opacity: 1 !important;
    transform: none !important;
  }
  .bau-visit { transform: none; }
  /* the ambient loops are the whole point of the setting */
  .float-anim, .breathe-anim { animation: none !important; }
  * { transition-duration: 0.01ms !important; }
}
