/* ==========================================================================
   Bunn Design Co. — studio layer

   The visual/motion system introduced on the About page, extracted so every
   page shares one copy of it instead of each carrying its own near-identical
   duplicate. Sits between site.css (tokens, layout, buttons, nav, footer)
   and any page-specific stylesheet.

   Load order on every page:
     site.css  ->  studio.css  ->  [page].css (optional)

   What lives here: the motion tokens, the page grain, the green cursor, the
   masked heading reveal, the fade/rise/blur reveal tiers, the drawing
   hairline, the magnetic-target transition, the unified heading scale and
   the dark gradient call-to-action. Nothing here is page-specific — anything
   that only one page needs stays in that page's own stylesheet.
   ========================================================================== */

:root {
  --about-ease:      cubic-bezier(.2, .7, .2, 1);   /* same curve as .reveal, everywhere */
  --about-ease-out:  cubic-bezier(.16, 1, .3, 1);   /* same curve as the header CTA settle */
  --about-green-04:  rgba(75, 93, 52, .045);
  --about-green-08:  rgba(75, 93, 52, .09);
  --about-green-16:  rgba(75, 93, 52, .16);
  --about-green-35:  rgba(75, 93, 52, .35);
  --about-green-55:  rgba(75, 93, 52, .55);

  /* ---- type system ----
     site.css's own tokens (--t-body, --t-lead, --t-xs, --eyebrow styling)
     already cover body copy and small labels consistently, so they're reused
     as-is rather than duplicated. These are the only sizes the studio layer
     adds: one for page-opening titles, one shared by every section heading
     (replacing the old mix of --t-h2 on some sections and the much larger
     --t-display on others), and one for "highlighted statement" moments.
     Fluid clamp()s throughout — no breakpoint swaps a reader would feel. */
  --about-t-hero:    clamp(2.75rem, 1.7rem + 5vw, 6.1rem);
  --about-t-heading: clamp(2.15rem, 1.55rem + 2.4vw, 3.55rem);
  --about-t-quote:   clamp(1.45rem, 1.15rem + 1.5vw, 2rem);

  /* block palette — the same warm neutrals the cube canvases use, so any
     hand-placed block reads as the same material family */
  --about-block-cream: #FAF7F0;
  --about-block-beige: #D6C7AF;
  --about-block-brown: #846A4E;
  --about-block-dark:  #2E2720;
}

/* Reduced-motion is handled globally in site.css (forces .001ms durations and
   resets .reveal). Everything below additionally checks the same media query
   directly wherever an effect needs skipping outright rather than shortening. */

/* ---------- page-wide texture: one fixed grain layer ----------
   A single fixed, full-viewport element instead of a per-section background
   image — the texture is genuinely continuous under every section boundary
   at zero extra paint cost while scrolling (it never moves). */
.about-grain {
  position: fixed; inset: 0; z-index: 1; pointer-events: none;
  opacity: .05; mix-blend-mode: multiply;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- custom cursor ----------
   pointer-events:none throughout — never intercepts a click or a text
   selection, it only follows on top of them. Enabled from studio.js only for
   real mice: fine pointer + hover-capable + not reduced motion. Touch
   devices and reduced-motion keep the plain system cursor and this markup
   simply never receives .is-ready. */
.about-cursor {
  position: fixed; left: 0; top: 0; z-index: 300;
  width: 7px; height: 7px; margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: var(--blue);
  box-shadow: 0 0 10px 1px var(--about-green-55);
  opacity: 0; pointer-events: none;
  transition: opacity .25s ease, width .25s var(--about-ease), height .25s var(--about-ease),
              margin .25s var(--about-ease), border-radius .3s var(--about-ease),
              background .25s ease, box-shadow .25s ease;
  will-change: transform;
}
.about-cursor.is-ready { opacity: 1; }
/* ring: expands over anything clickable */
.about-cursor.is-ring {
  width: 34px; height: 34px; margin: -17px 0 0 -17px;
  background: transparent; box-shadow: none;
  border: 1.5px solid var(--about-green-55);
}
/* square: a brief nod toward the cube language over a cube-bearing area */
.about-cursor.is-square {
  border-radius: 4px; transform: translate(-50%, -50%) rotate(45deg);
}
.about-cursor.is-ring.is-square { border-radius: 6px; }
body.about-cursor-active, body.about-cursor-active a, body.about-cursor-active button,
body.about-cursor-active .btn {
  cursor: none;
}

/* ---------- masked text reveal ----------
   A line of text sits inside an overflow-hidden mask and slides/blurs up
   into place. Used sparingly — page-opening headlines only. */
.about-mask {
  display: block; overflow: hidden;
  /* padding+matching negative margin expands the clip box just enough for
     descenders (g, j, p, q, y) to clear it without shifting the line */
  padding-bottom: .16em; margin-bottom: -.16em;
}
.about-mask__inner {
  display: block;
  transform: translateY(115%);
  filter: blur(6px);
  transition: transform .9s var(--about-ease), filter .7s ease;
}
.about-mask.is-in .about-mask__inner { transform: translateY(0); filter: blur(0); }
@media (prefers-reduced-motion: reduce) {
  .about-mask__inner { transform: none; filter: none; transition: none; }
}
/* no-JS (or JS that hasn't run yet): never ship headline text translated out
   of its own clipping mask with nothing left to bring it back */
.no-js .about-mask__inner { transform: none; filter: none; }

/* ---------- generic fade+rise+sharpen reveal ----------
   Deliberately similar to site.css's .reveal (same easing, same rough
   distance) but adds a blur-to-sharp step, so it reads as the same family,
   slightly richer — not a second unrelated system. */
.about-reveal {
  opacity: 0; transform: translateY(18px) scale(.985); filter: blur(3px);
  transition: opacity .8s var(--about-ease), transform .8s var(--about-ease), filter .6s ease;
  transition-delay: var(--d, 0ms);
}
.about-reveal.is-in { opacity: 1; transform: none; filter: blur(0); }
@media (prefers-reduced-motion: reduce) {
  .about-reveal { opacity: 1; transform: none; filter: none; transition: none; }
}
.no-js .about-reveal { opacity: 1; transform: none; filter: none; }

/* two variants on the same base reveal, matched to text role rather than
   giving every element the identical motion:
   --heading slides further and settles slightly slower, so section titles
   arrive with real weight; --fade drops the movement almost entirely for
   small uppercase labels — text that size doesn't need to travel to be
   noticed, and sliding it just adds noise at a scale that isn't legible
   mid-motion anyway. */
.about-reveal.about-reveal--heading {
  transform: translateY(34px);
  transition-duration: .9s, .9s, .7s;
}
.about-reveal.about-reveal--fade {
  transform: translateY(6px) scale(1);
  filter: blur(1.5px);
  transition-duration: .6s, .6s, .5s;
}
/* .about-reveal.is-in and these two modifiers are all two-class selectors,
   so without this, source order alone would decide the winner and the
   settled state could silently revert to the resting transform. Three
   classes here beats both unconditionally. */
.about-reveal.about-reveal--heading.is-in,
.about-reveal.about-reveal--fade.is-in {
  transform: none; filter: blur(0);
}
@media (prefers-reduced-motion: reduce) {
  .about-reveal.about-reveal--heading,
  .about-reveal.about-reveal--fade { transform: none; filter: none; }
}

/* ---------- a hairline that draws left-to-right on reveal ---------- */
.about-rule {
  height: 1px; background: var(--ink-14); border: 0; margin: 0;
  transform: scaleX(0); transform-origin: left;
  transition: transform .9s var(--about-ease); transition-delay: var(--d, 0ms);
}
.about-rule.is-in { transform: scaleX(1); }
.about-rule--bright.is-in { background: var(--blue); }
.no-js .about-rule { transform: scaleX(1); }

/* ---------- magnetic attraction target ----------
   studio.js nudges transform toward the pointer within a small radius while
   hovered; this just declares the transition so the release back to
   translate(0,0) is smooth rather than snapping. data-magnetic-strength
   scales the pull (read by studio.js); elements without it pull fully. */
[data-magnetic] { transition: transform .4s var(--about-ease-out); }

/* ---------- unified heading scale ----------
   One size for every section heading, replacing the previous mix of --t-h2
   on some sections and the much larger --t-display on others. Pages opt in
   per element with .studio-title (section headings) or .studio-hero-title
   (the one page-opening headline). */
.studio-title {
  font-size: var(--about-t-heading);
  letter-spacing: -.03em;
  text-wrap: balance;
}
.studio-hero-title {
  font-size: var(--about-t-hero);
  line-height: .98; letter-spacing: -.045em;
  text-wrap: balance;
}
.studio-hero-title .about-mask__inner {
  font-family: var(--display); font-weight: 700; font-style: normal; letter-spacing: -.045em;
}
.studio-hero-title .serif { letter-spacing: -.01em; }

/* ---------- page header (inner pages), studio treatment ----------
   Same slot as site.css's .phead, but with the green wash, the unified hero
   scale and room for the masked headline. --wash-y is a scroll-linked
   parallax offset set by studio.js, so the wash drifts a little
   independently of the content in front of it. */
.studio-phead {
  position: relative; isolation: isolate; overflow: hidden;
  padding-top: var(--s-6); padding-bottom: var(--s-6);
  background: var(--paper);
}
.studio-phead::before {
  content: ""; position: absolute; inset: -18% 0; z-index: -1;
  background: radial-gradient(120% 90% at 82% 15%, var(--about-green-08), transparent 60%);
  pointer-events: none;
  transform: translateY(var(--wash-y, 0px));
}
.studio-phead__eyebrow { margin-bottom: var(--s-3); }
.studio-phead__title { max-width: 17ch; }
.studio-phead__lead { margin-top: var(--s-5); max-width: 46ch; text-wrap: pretty; }
@media (max-width: 900px) {
  .studio-phead__title { max-width: 14ch; }
}

/* ---------- soft green section wash ----------
   Bleeds up and down past the section's own edges so a paper -> paper-2 ->
   paper handoff never reads as a hard rectangle edge. */
.studio-wash {
  position: relative; isolation: isolate; overflow: hidden;
}
.studio-wash::before {
  content: ""; position: absolute; inset: -18% 0; z-index: -1;
  background: radial-gradient(120% 90% at 18% 20%, var(--about-green-08), transparent 60%);
  pointer-events: none;
  transform: translateY(var(--wash-y, 0px));
}
.studio-wash--right::before {
  background: radial-gradient(110% 90% at 82% 30%, var(--about-green-08), transparent 62%);
}

/* ==========================================================================
   PORTFOLIO GALLERY (work.html)

   A captionless wall of finished pieces — every print piece, identity,
   mockup, website and photograph, mixed together rather than sorted into
   project blocks. The point is the body of work seen at once, so nothing
   here carries a title or a description; the filter bar above it is the
   only sorting on offer.

   WHY MULTICOL AND NOT GRID
   Every piece has to appear at its own proportions — a 21:9 advertisement,
   a square logo, a 1:2 phone screenshot and a 4:5 poster are all in here,
   and forcing them into one aspect-ratio box (which is what a uniform grid
   with object-fit: cover does) crops the work to fit the layout. CSS
   columns is the one layout that lets each item keep its natural height,
   so the varied sizes are the real proportions of the pieces rather than a
   decorative pattern imposed on top of them. It also needs no JS, no
   measuring pass and no reflow after the images decode — width/height on
   every <img> reserves the right space on first paint.
   ========================================================================== */
.pf-gallery {
  columns: 4;
  column-gap: var(--s-3);
}
@media (max-width: 1300px) { .pf-gallery { columns: 3; } }
@media (max-width: 1000px) { .pf-gallery { columns: 2; } }
@media (max-width: 640px)  { .pf-gallery { columns: 1; } }

.pf-item {
  display: block; position: relative;
  /* a column may not split a piece across its own break */
  break-inside: avoid;
  margin-bottom: var(--s-3);
  overflow: hidden;
  background: var(--paper-2);
  /* the border is present at rest, just transparent, so gaining it on hover
     doesn't shift the image by 2px inside its own box */
  border: 2px solid transparent;
  transition: border-color .35s var(--about-ease), box-shadow .35s ease;
}
/* One transform declaration, composed from two variables: --tilt-s is the
   hover zoom, --tilt-x/--tilt-y the cursor-position drift site.js writes on
   a hovered [data-tilt] host. Composing them here rather than in two
   competing rules is what lets the piece drift AND zoom at once — the
   photograph moves a few pixels inside a border that never moves, so the wall
   gains depth without a single pixel of layout shift. */
.pf-item img {
  display: block; width: 100%; height: auto;
  transform: translate3d(var(--tilt-x, 0px), var(--tilt-y, 0px), 0) scale(var(--tilt-s, 1));
  transition: transform var(--dur-1) var(--ease-entry);
}
.pf-item:hover,
.pf-item:focus-visible {
  border-color: var(--blue);
  box-shadow: 0 14px 30px var(--about-green-16);
}
.pf-item:hover img,
.pf-item:focus-visible img { --tilt-s: 1.05; }

/* The gallery's own reveal is deliberately lighter than the page's default
   .about-reveal: 79 elements each carrying a blur() filter is a real paint
   cost for an effect nobody can see at this scale, so these fade and lift
   only. Three classes to beat .about-reveal.is-in's own two. */
.pf-item.about-reveal {
  transform: translateY(14px) scale(.99);
  filter: none;
  transition: opacity .7s var(--about-ease), transform .7s var(--about-ease);
}
.pf-item.about-reveal.is-in { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .pf-item.about-reveal { transform: none; }
  .pf-item img { transition: none; }
}

/* the filter bar sets [hidden]; multicol re-balances around the survivors */
.pf-item[hidden] { display: none; }

/* ==========================================================================
   FINAL CALL TO ACTION
   Straight top-to-bottom fade resolving to --night — the exact colour the
   footer below already uses — so the two read as one continuous fade with no
   seam at the handoff, rather than a flat green panel butting into black.
   ========================================================================== */
.about-cta {
  position: relative; isolation: isolate; overflow: hidden;
  min-height: 62vh; min-height: 62svh;
  display: flex; align-items: center;
  background: linear-gradient(180deg, var(--blue-dark) 0%, var(--night) 94%);
  color: var(--paper);
  padding-block: var(--s-6);
}
.about-cta--tall { min-height: 88vh; min-height: 88svh; }
.about-cta__canvas { position: absolute; inset: 0; width: 100%; height: 100%; opacity: .5; }
.about-cta__inner { position: relative; z-index: 1; width: 100%; }
.about-cta__eyebrow { color: rgba(245,243,237,.85); }
.about-cta__eyebrow::before { background: rgba(245,243,237,.7); }
.about-cta__title {
  margin-top: var(--s-3);
  font-size: var(--about-t-heading);
  max-width: 16ch; color: #fff;
  text-wrap: balance;
}
.about-cta__title .serif { color: var(--paper); }
.about-cta__body {
  margin-top: var(--s-4); max-width: 46ch;
  color: rgba(245,243,237,.82); font-size: var(--t-lead); text-wrap: pretty;
}
.about-cta__actions { margin-top: var(--s-5); }
