/* ==========================================================================
   Bunn Design Co. — About page (page-specific system)

   Everything in this file is scoped to about.html only — it is not linked
   from any other page. It sits on top of assets/site.css and reuses that
   file's tokens, type scale, spacing scale, buttons, nav and footer exactly
   as-is. Nothing here redefines or overrides a shared component; anything
   that needed a different look on this page got its own about-* class
   instead, so index.html / services.html / work.html / start-a-project.html
   render exactly as before.

   Recurring motifs reused across every section below, per the brief:
     1. thin lines drawing / connecting sections
     2. cube fragments separating and assembling
     3. muted-green highlights responding to interaction
   ========================================================================== */

: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: five roles, three page-scoped sizes ----
     site.css's own tokens (--t-body, --t-lead, --t-xs, --eyebrow styling)
     already cover "body copy" and "small label/metadata" consistently, so
     they're reused as-is below rather than duplicated. These three are the
     only NEW sizes this page needed: one for the hero title, one shared by
     every section heading (previously three different sources — --t-h2 on
     three sections and the much larger --t-display on the CTA — which is
     exactly the "inconsistent, oversized" pattern being fixed here), and one
     for the two "highlighted statement" moments (the pull quote and the
     comparison's featured card name). Fluid clamp()s throughout — no
     breakpoint swaps a reader would feel as a jump. */
  --about-t-hero:    clamp(2.75rem, 1.7rem + 5vw, 6.1rem);
  /* the second hero line ("No unnecessary complexity.") is a full clause,
     not a two-word phrase like the first line — at the hero's own size it
     doesn't fit on one line at phone widths, so it still needs its own,
     smaller scale to stay on a single line at every width. Sized as large
     as that constraint allows though (re-verified down to 320px), rather
     than shrinking it further than necessary — the gap to line one reads
     as a deliberate weight change, not a mismatched afterthought. */
  --about-t-hero-sub: clamp(1.4rem, 0.8rem + 3.2vw, 4rem);
  --about-t-heading: clamp(2.15rem, 1.55rem + 2.4vw, 3.55rem);
  --about-t-quote:   clamp(1.45rem, 1.15rem + 1.5vw, 2rem);

  /* hero art block palette — the exact same warm neutrals used by the cube
     canvases elsewhere on the site (about.js's own SMOKE constants), so the
     hero's blocks read as the same material family, not an unrelated set
     of colours. */
  --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 page-specific below additionally checks the
   same media query directly wherever a keyframe/loop needs to be skipped
   outright rather than just shortened to nothing. */

/* ---------- page-wide texture: one fixed grain layer ----------
   A single fixed, full-viewport element instead of a per-section background
   image — the texture is then genuinely continuous under every section
   boundary at zero extra paint cost while scrolling (it never moves).
   Fully decorative: aria-hidden, pointer-events:none, skipped visually under
   reduced-motion isn't necessary since it's static, but stays regardless. */
.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 — this never intercepts a click or a text
   selection, it only follows on top of them. Built (and enabled from
   about.js) only for real mice: fine pointer + hover-capable + not reduced
   motion. Touch devices, trackpads-as-touch, 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 when hovering 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;
}

/* ---------- shared section scaffolding ---------- */
.about-sec { position: relative; z-index: 2; }
.about-sec__wash {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
}

/* every section heading on this page shares one size — see --about-t-heading
   in :root — instead of the three different sources (two of them the same
   value by coincidence, one of them the hero's own much larger scale) this
   page had before. */
.about-compare-sec .sec-head__title,
.about-process-sec .sec-head__title,
.about-why__statement,
.about-designer__title,
.about-cta__title {
  font-size: var(--about-t-heading);
  text-wrap: balance;
}

/* masked text reveal: a line of text sits inside an overflow-hidden mask and
   slides/blurs up into place, instead of the site-wide fade+rise .reveal.
   Used sparingly — hero headline only — per "don't animate every paragraph
   the same way, but keep a shared small set of behaviours." */
.about-mask {
  display: block; overflow: hidden;
  /* padding+matching negative margin expands the clip box just enough for
     descenders (g, y) to clear it without shifting where the line sits */
  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.js already writes --d per mask (its index in the heading) and the
     hero cascade below sets a base offset — this is what makes either of
     them actually reach the transition. */
  transition-delay: var(--d, 0ms);
}
.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 used through the rest of the page —
   deliberately similar to the site-wide .reveal (same easing, same rough
   distance) but adds the blur-to-sharp step the brief asks for, so it reads
   as "the same family, slightly richer" rather than 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 on the page the identical motion:
   --heading slides up further and settles slightly slower, so section
   titles read as arriving with real weight ("slide up like the hero");
   --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 visual noise at a scale where it 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;
}
/* same specificity trap as the compare card and side-card hover fixes
   elsewhere on this page: .about-reveal.is-in and these two modifiers are
   both 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; }
}

/* Every eyebrow on the page carries a 26px leading dash (site.css). Where
   the eyebrow is a revealing element, that dash now draws itself out from
   the left instead of arriving at full length — the smallest instance of
   the "thin lines connecting things" motif, and at this type size the only
   movement a label of five words actually needs. It inherits the label's
   own --d, so it stays welded to it wherever the delay comes from. */
.eyebrow.about-reveal::before {
  transform: scaleX(0); transform-origin: left;
  transition: transform .6s var(--about-ease);
  transition-delay: var(--d, 0ms);
}
.eyebrow.about-reveal.is-in::before { transform: scaleX(1); }
.no-js .eyebrow.about-reveal::before { transform: none; }

/* a hairline that draws left-to-right on reveal — the "connecting line" motif */
.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); }

/* ==========================================================================
   1 — ABOUT HERO
   ========================================================================== */
.about-hero {
  position: relative; isolation: isolate; overflow: hidden;
  padding-top: var(--s-6); padding-bottom: var(--s-7);
  background: var(--paper);
}
/* The hero's surface is the shared mouse-reactive weave (hero-texture.css /
   hero-texture.js), the same one the homepage and the case studies use —
   it replaced a hand-placed cluster of cube-language blocks that lived here.
   The canvas needs nothing from this file beyond .about-hero's own
   position/isolation/overflow above; data-hero-quiet on the section names
   which copy blocks the weave thins out around. */

.about-hero__eyebrow { margin-bottom: var(--s-3); }
.about-hero__title {
  font-size: var(--about-t-hero); line-height: .98; letter-spacing: -.045em;
  max-width: 15ch;
  text-wrap: balance;
}
/* both lines are the bold sans by default; the one word that should read as
   the editorial serif accent ("unnecessary") carries its own .serif class
   inline, rather than the whole second line being italic. Both lines now
   live inside a single .about-mask so the whole headline comes up as one
   motion (matching services.html's single-mask hero) instead of two
   separately-staggered reveals — .about-hero__line just forces each onto
   its own line and carries the per-line size, the mask/motion is shared. */
.about-hero__title .about-mask__inner {
  font-family: var(--display); font-weight: 700; font-style: normal; letter-spacing: -.045em;
}
.about-hero__line { display: block; }
.about-hero__line--sub {
  font-size: var(--about-t-hero-sub); white-space: nowrap;
}
.about-hero__title .serif { letter-spacing: -.01em; }

.about-hero__base {
  margin-top: var(--s-6);
  display: grid; grid-template-columns: minmax(0, .82fr) minmax(0, 1fr);
  gap: var(--s-5) var(--s-6);
  align-items: start;
}
.about-hero__lead { max-width: 40ch; text-wrap: pretty; }

/* the "thread" — a short vertical line that visually ties the headline block
   to the info strip beneath it, instead of leaving them as two independent
   floating blocks. Draws downward on reveal (same mechanism as .about-rule,
   just the vertical axis). */
.about-hero__thread {
  width: 1px; height: clamp(28px, 4vw, 44px); background: var(--ink-14);
  margin-bottom: var(--s-3);
  transform: scaleY(0); transform-origin: top;
  transition: transform .8s var(--about-ease);
  transition-delay: var(--d, 0ms);
}
.about-hero__thread.is-in,
.no-js .about-hero__thread { transform: scaleY(1); }

.about-hero__index {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid var(--ink-14);
}
.about-hero__stat {
  position: relative;
  padding: var(--s-3) var(--s-3) 0 0;
  border-left: 1px solid var(--ink-14);
  padding-left: var(--s-3);
}
.about-hero__stat:first-child { border-left: 0; padding-left: 0; }
.about-hero__stat-label {
  display: block; font-family: var(--display); font-size: var(--t-xs); font-weight: 600;
  letter-spacing: .13em; text-transform: uppercase; color: var(--ink-55);
  margin-bottom: .5rem;
}
.about-hero__stat-value {
  display: block; font-family: var(--display); font-weight: 600;
  font-size: clamp(1rem, .92rem + .4vw, 1.15rem); letter-spacing: -.015em;
}

/* ---------- hero entrance choreography ----------
   The whole hero is above the fold, so every observer in about.js fires in
   the same frame — without this the eyebrow, headline, thread, lead and
   stats all land together as one flat slab, which is the difference between
   a page that loaded and a page that introduces itself.

   The order is reading order, played out in time: the label, then the
   headline, then the thread physically drawing down INTO the strip it
   exists to connect, then the lead beside it, then the three stats left to
   right. The thread's delay is deliberately tied to the headline finishing
   rather than picked to look nice on its own — it has to read as the
   headline handing off to what's below it.

   Written here as plain delays rather than in JS: nothing about this
   sequence depends on runtime state, and site.css's reduced-motion block
   zeroes transition-delay outright, so the opted-out path is one rule
   rather than a second code path. */
.about-hero__eyebrow.about-reveal { --d: 40ms; }
.about-hero__title .about-mask__inner { --d: 200ms; }
.about-hero__thread { --d: 640ms; }
.about-hero__lead.about-reveal { --d: 720ms; }
/* --d on each stat is the per-child stagger about.js writes from
   data-stagger-about; this shifts the whole group past the lead. */
.about-hero__stat.about-reveal { transition-delay: calc(830ms + var(--d, 0ms)); }

@media (max-width: 900px) {
  .about-hero__base { grid-template-columns: 1fr; gap: var(--s-4); }
}
@media (max-width: 560px) {
  .about-hero__index { grid-template-columns: 1fr; }
  .about-hero__stat { border-left: 0; border-top: 1px solid var(--ink-14); padding: var(--s-3) 0 0; }
  .about-hero__stat:first-child { border-top: 0; }
}

/* ==========================================================================
   2 — MEET THE DESIGNER
   ========================================================================== */
.about-designer {
  position: relative; isolation: isolate; overflow: hidden;
  padding-block: var(--s-7);
  background: var(--paper-2);
}
.about-designer::before {
  /* soft green wash bleeding up into the hero above and down into the
     comparison section below, so the paper -> paper-2 -> paper handoff never
     reads as a hard rectangle edge. --wash-y is a scroll-linked parallax
     offset (about.js) — the wash drifts a little independently of the
     content in front of it instead of scrolling in lockstep, the same
     depth cue as the hero's cube layer above. */
  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));
}
.about-designer__grid {
  display: grid; grid-template-columns: minmax(0, .62fr) minmax(0, 1fr);
  gap: var(--s-6);
  align-items: start;
}
/* reserved photo space — deliberately empty (no stock image, no fake
   placeholder graphic), just a bordered shape ready for a real portrait
   later. The hover-wander and entry reveal reuse the exact same mechanisms
   as the CTA buttons ([data-magnetic] in about.js, .about-reveal here) so it
   doesn't introduce a new animation language for one element. */
.about-designer__photo {
  position: relative;
  aspect-ratio: 3 / 4; max-width: 100%; width: min(420px, 100%); overflow: hidden;
  border: 1.5px solid var(--about-green-35); border-radius: 20px;
  background: var(--paper-2);
  transition: border-color .3s ease, box-shadow .35s ease;
}
.about-designer__photo:hover {
  border-color: var(--blue);
  box-shadow: 0 16px 32px var(--about-green-16);
}
/* a green rule drawing across the foot of the frame on hover — the same
   "thin line connecting things" motif as .about-rule and the hero thread.
   Deliberately NOT a scale on the image: this element is a [data-magnetic]
   target and its <img> carries the scroll parallax, so both transforms are
   already spoken for by JS and a third would fight them. */
.about-designer__photo::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--blue);
  transform: scaleX(0); transform-origin: left;
  transition: transform .55s var(--about-ease);
  pointer-events: none; z-index: 1;
}
.about-designer__photo:hover::after { transform: scaleX(1); }
/* the frame stays put; the photo drifts very slightly within it on scroll
   (--photo-y, set by about.js) — scaled up first so the drift never
   uncovers an edge. A quiet, premium touch rather than the whole box
   moving. */
.about-designer__photo img {
  width: 100%; height: 100%; object-fit: cover;
  transform: scale(1.1) translateY(var(--photo-y, 0px));
}
@media (prefers-reduced-motion: reduce) {
  .about-designer__photo img { transform: none; }
}

.about-designer__body { padding-top: clamp(0px, 1vw, 8px); }
.about-designer__label { margin-bottom: var(--s-2); }
.about-designer__title { max-width: 20ch; }
.about-designer__copy { margin-top: var(--s-4); display: grid; gap: var(--s-3); max-width: 56ch; }
.about-designer__copy .lead { max-width: none; }
.about-designer__copy p { text-wrap: pretty; }
.about-designer__name { color: var(--ink); font-weight: 600; }

@media (max-width: 900px) {
  .about-designer__grid { grid-template-columns: 1fr; gap: var(--s-5); }
  /* stacked (not beside the copy) — sized as a percentage of viewport
     rather than a small fixed cap, so it reads as a real photo and not a
     shrunken leftover next to a lot of empty space around it */
  .about-designer__photo { max-width: min(380px, 78vw); margin-inline: auto; }
}

/* ==========================================================================
   3 — THE RIGHT-SIZED STUDIO (editorial comparison)
   ========================================================================== */
.about-compare-sec { padding-block: var(--s-7); background: var(--paper); }
/* gap is load-bearing here: with zero gap the side boxes' own borders sit
   flush against the feature card's edge and visually slice into it. Bunn
   Design Co. stays the visual centre column in source order too (template,
   feature, agency) — nothing reorders it on any layout, row or stacked.
   No border-top on the grid itself — every card already carries its own
   full border, so a container-level top rule was redundant and, in the
   side-by-side layout, read as one line running across all three cards. */
.studio-compare {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.32fr) minmax(0, 1fr);
  gap: var(--s-4);
  align-items: start;
}
.studio-compare__side {
  padding: var(--s-4);
  border: 1px solid var(--ink-14);
  background: var(--paper-2);
  color: var(--ink-55);
  transition: transform .4s var(--about-ease-out), border-color .3s ease, box-shadow .35s ease;
}
/* same hover language as the photo frame (border brightens, soft shadow
   lifts it) — restrained compared to the feature card's own hover-wander,
   so the two side options still read as clearly secondary. */
/* higher specificity than .about-reveal.is-in (which resets transform to
   none once settled) so the hover lift isn't silently cancelled once the
   card has finished revealing — the exact bug hit earlier with the
   featured card's scale, fixed the same way here pre-emptively. */
.studio-compare__side:hover,
.studio-compare__side.about-reveal.is-in:hover {
  transform: translateY(-4px);
  border-color: var(--about-green-35);
  box-shadow: 0 14px 28px rgba(17, 17, 17, .08);
}
/* the card "wakes up" as well as lifting: its name and points come out of
   the muted grey they rest in and its icons come up to full strength. The
   resting state is the point of these two cards (they are the options you
   are NOT being sold), so this only ever restores them to normal — it never
   pushes them past the featured card in the middle. */
.studio-compare__side .studio-compare__name {
  font-size: var(--t-h3); color: var(--ink-70);
  transition: color .3s ease;
}
.studio-compare__side:hover .studio-compare__name { color: var(--ink); }
.studio-compare__side:hover .studio-compare__list li { color: var(--ink-70); }
.studio-compare__tag { margin-top: .4rem; font-size: var(--t-sm); }
.studio-compare__list { display: grid; gap: .6rem; margin-top: var(--s-4); }
.studio-compare__list li {
  display: flex; align-items: flex-start; gap: .55rem; font-size: var(--t-sm);
  transition: color .3s ease;
}
.studio-compare__icon {
  flex: none; width: 16px; height: 16px; margin-top: .2em; opacity: .6;
  transition: opacity .3s ease;
}
.studio-compare__side:hover .studio-compare__icon { opacity: .9; }

/* no negative margin-top here on purpose — an earlier version raised this
   card above the grid's own border-top divider, which visually drew that
   line straight through "Bunn Design Co." at the top of the card. */
.studio-compare__feature {
  position: relative; z-index: 1;
  padding: var(--s-5) var(--s-5) var(--s-6);
  background: var(--about-green-08);
  border: 1px solid var(--about-green-35);
  box-shadow: 0 24px 56px rgba(75, 93, 52, .14), 0 1px 0 rgba(17,17,17,.03);
}
.studio-compare__flag {
  display: inline-block;
  font-family: var(--display); font-size: var(--t-xs); font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase; color: #fff;
  background: var(--blue); padding: .35rem .7rem; margin-bottom: var(--s-3);
}
/* the featured card's name is the section's one sanctioned "bold + green +
   larger" combination (it's the deliberate focal point of the comparison),
   but it reuses the pull-quote tier rather than the full section-heading
   size — big enough to lead the card, never competing with the heading
   above it. nowrap + clamp(min...) keeps "Bunn Design Co." on one line at
   every width the 3-up grid actually renders at, rather than letting the
   fluid quote-tier size push it onto two lines on the narrower tablet
   columns. */
.studio-compare__feature .studio-compare__name {
  font-size: clamp(1.25rem, 1.05rem + 1vw, var(--about-t-quote));
  color: var(--blue-ink); white-space: nowrap;
}
.studio-compare__feature .studio-compare__tag { color: var(--ink-70); margin-top: .5rem; }
.studio-compare__feature .studio-compare__list { margin-top: var(--s-4); gap: .75rem; }
.studio-compare__feature .studio-compare__list li { font-size: var(--t-sm); color: var(--ink); }
.studio-compare__feature .studio-compare__icon { opacity: 1; color: var(--blue-ink); }

/* pop-in entrance: each of the three cards grows from a smaller scale up to
   full size as one whole unit (box, border, icons, copy — everything moves
   together, nothing fragments into separate per-line reveals), rather than
   using the page's generic fade+rise. A gentler starting scale and a
   longer, slower settle than the first pass — that version resolved in
   .6s from 82%, which read as an abrupt snap rather than a smooth arrival;
   this eases in from 90% over .95s with the same no-overshoot ease-out as
   the rest of the page — a soft pop, not a snap. */
.studio-compare__side.about-reveal,
.studio-compare__feature.about-reveal {
  transform: scale(.9) translateY(14px);
  filter: blur(2px);
  transition: opacity .95s var(--about-ease-out), transform .95s var(--about-ease-out), filter .7s ease;
}
/* three classes to unconditionally beat the plain two-class .is-in rule —
   the same specificity fix already needed twice elsewhere on this page. */
.studio-compare__side.about-reveal.is-in,
.studio-compare__feature.about-reveal.is-in {
  transform: none; filter: blur(0);
}
@media (prefers-reduced-motion: reduce) {
  .studio-compare__side.about-reveal,
  .studio-compare__feature.about-reveal { transform: none; filter: none; }
}

@media (max-width: 900px) {
  .studio-compare { grid-template-columns: 1fr; }
  /* same borderless, on-background look as desktop — no per-item pill
     behind each point, just the icon and text sitting on the card */
  .studio-compare__side .studio-compare__list { display: flex; flex-wrap: wrap; gap: .5rem .7rem; }
  .studio-compare__side .studio-compare__list li { font-size: var(--t-xs); gap: .35rem; }
  .studio-compare__side .studio-compare__icon { width: 12px; height: 12px; }
}

/* ==========================================================================
   4 — WHY IT STARTED (scroll-linked fragment alignment — the emotional
   centre of the page, so this is the one section allowed a bespoke,
   scroll-tied effect rather than a one-shot reveal)
   ========================================================================== */
.about-why {
  position: relative; isolation: isolate; overflow: hidden;
  padding-block: var(--s-7);
  background: var(--paper-2);
}
.about-why::before {
  content: ""; position: absolute; inset: -18% 0; z-index: -1;
  background: radial-gradient(110% 90% at 82% 30%, var(--about-green-08), transparent 62%);
  pointer-events: none;
  transform: translateY(var(--wash-y, 0px));
}
/* single column (default, and mobile/tablet): label, heading, lead, then
   the logo assembly sitting between the two paragraphs, closing paragraph
   last — plain document flow, no grid/order trickery. The two-column
   desktop layout below reuses this exact DOM order, so nothing here needs
   duplicating for the wide viewport. */
.about-why__statement {
  max-width: 20ch; line-height: 1.04; letter-spacing: -.03em;
  margin-top: var(--s-5);
}
.about-why__lead { margin-top: var(--s-4); text-wrap: pretty; }

/* ---------- square assembly ----------
   Section 3's visual: a square that arrives in scattered beige shards and
   pulls itself into one solid green square as the block scrolls past —
   the literal form of "good businesses shouldn't look pieced together."

   Geometry: 13 shards radiating from one off-centre origin, four of the
   wedges also split across an inner radius so the break pattern reads as
   irregular rather than as a pie chart. Neighbouring shards are generated
   from the same boundary angles and therefore share vertices exactly, so
   once assembled the square is seamless — important, because the whole
   payoff is it reading as ONE shape at the end, with no hairlines left
   over from the break-up. */
.about-why__layout { position: relative; }
.about-square-assemble {
  width: min(64%, 260px);
  aspect-ratio: 1 / 1;
  margin: var(--s-6) auto;
  position: relative;
  /* Scatter reach. Seeds top out at 38, so the widest travel is roughly
     2.4 * 38 = 91px against a 340px desktop square — far enough that it is
     genuinely in pieces when it first scrolls into view rather than looking
     nearly whole already. The vw term scales this right down on a phone
     (~27px at 390px wide), where the square is smaller and the paragraphs
     sit directly above and below it. */
  --assemble-scatter-unit: clamp(.6px, .18vw, 2.4px);
}
.about-square-assemble__glow {
  position: absolute; inset: -18%; z-index: -1; border-radius: 50%;
  background: radial-gradient(circle, var(--about-green-16), transparent 68%);
  opacity: 0;
  transition: opacity .9s var(--about-ease);
  pointer-events: none;
}
.about-square-assemble.is-assembled .about-square-assemble__glow { opacity: 1; }

/* ---------- arrival effect ----------
   Fires once the last shard lands: a shockwave ring pushing outward and a
   single soft rebound on the square itself. */
.about-square-assemble__ring {
  position: absolute; inset: -6%; z-index: -1;
  border-radius: 50%;
  border: 1.5px solid var(--about-green-35);
  opacity: 0; transform: scale(.74);
  pointer-events: none;
}
.about-square-assemble.is-assembled .about-square-assemble__ring {
  animation: aboutSquareRing 1.5s var(--about-ease-out) both;
}
@keyframes aboutSquareRing {
  0%   { opacity: 0;   transform: scale(.74); }
  20%  { opacity: .85; }
  100% { opacity: 0;   transform: scale(1.3); }
}

/* deliberately NOT overflow:hidden — each shard's polygon is already clipped
   to the square's bounds, so the silhouette is correct without a clip here,
   and the shards stay visible while they're scattered outside it. Clipping
   the wrapper instead would shear the pieces off flat against the square's
   edges on the way in, which read as pieces vanishing rather than travelling. */
.about-square-assemble__pieces { position: absolute; inset: 0; }
/* the rebound is on the wrapper, not the shards — the shards' own transforms
   are already driven by --p, and this has to stay independent of that */
.about-square-assemble.is-assembled .about-square-assemble__pieces {
  animation: aboutSquareSettle .85s var(--about-ease-out) both;
}
@keyframes aboutSquareSettle {
  0%   { transform: scale(1); }
  32%  { transform: scale(1.035); }
  100% { transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
  .about-square-assemble.is-assembled .about-square-assemble__ring,
  .about-square-assemble.is-assembled .about-square-assemble__pieces { animation: none; }
}
.about-square-assemble__piece {
  position: absolute; inset: 0;
  /* Scattered state is beige, assembled state is the page green. Each shard
     carries a slightly different beige via --ti so the broken-up state reads
     as separate pieces of material rather than one shape someone cracked;
     they all converge on the identical green, which is what lets the
     finished square look solid and seamless. */
  background: color-mix(in srgb, var(--about-block-beige) calc(100% - var(--ti) * 4%), var(--about-block-cream));
  transition: background-color .7s var(--about-ease), background .7s var(--about-ease);
  /* base scattered position/rotation, interpolated toward 0 via --p (0 =
     fully scattered, 1 = fully assembled) as the section scrolls through the
     viewport — a real scroll-linked transform, not a single fade. --sx/--sy
     are unitless; --assemble-scatter-unit scales them on narrow screens. */
  --sx: 0; --sy: 0; --sr: 0deg; --ti: 0;
  transform:
    translate(calc(var(--sx) * var(--assemble-scatter-unit) * (1 - var(--p, 0))), calc(var(--sy) * var(--assemble-scatter-unit) * (1 - var(--p, 0))))
    rotate(calc(var(--sr) * (1 - var(--p, 0))));
}
.about-square-assemble.is-assembled .about-square-assemble__piece {
  background: rgb(75, 93, 52);
}
@media (prefers-reduced-motion: reduce) {
  .about-square-assemble__piece { transform: none !important; }
}
@media (min-width: 901px) {
  /* text in a narrower left column, the assembly held in a fixed-width
     column to its right, vertically centred against the full text block —
     "on the right of the text," matching the hero/designer sections'
     established two-column pattern (see .about-hero__base) at the same
     900px breakpoint. */
  .about-why__layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(220px, 340px);
    column-gap: var(--s-6);
  }
  .about-why__layout > .eyebrow { grid-column: 1; grid-row: 1; }
  .about-why__statement          { grid-column: 1; grid-row: 2; }
  .about-why__lead                { grid-column: 1; grid-row: 3; }
  /* in single-column the assembly itself separates the two paragraphs; here
     it has moved to the right column, so the gap has to be restored */
  .about-why__body                { grid-column: 1; grid-row: 4; margin-top: var(--s-4); }
  /* rows 2-4 only, deliberately NOT row 1: centring across the eyebrow as
     well pulled the mark visibly high. Spanning just the heading and the
     two paragraphs sits it in the middle of the text people actually read. */
  .about-square-assemble {
    grid-column: 2; grid-row: 2 / 5; align-self: center;
    width: 100%; margin: 0;
  }
}
/* same size as the lead paragraph above the assembly, not the smaller
   default body copy — the two are meant to read as one continuous voice */
.about-why__body { color: var(--ink-70); max-width: 60ch; font-size: var(--t-lead); text-wrap: pretty; }

/* ==========================================================================
   5 — PROCESS (connected sequence)
   ========================================================================== */
.about-process-sec { padding-block: var(--s-7); background: var(--paper); }
/* label, heading, then the supporting line stacked directly underneath it —
   the same label->heading->body rhythm ("s-2" then "s-4") already used in
   the designer and why-it-started sections, instead of the two-column
   sec-head layout (heading left, aside right) this used previously. */
/* centred as a block on larger screens — the number, heading and paragraph
   all sit on the section's centre line, not anchored left; phone keeps the
   original left-aligned stack, which already reads fine at that width. */
.about-process-head { margin-bottom: var(--s-5); text-align: center; }
.about-process-head .eyebrow { margin-inline: auto; }
.about-process-head .sec-head__title { margin-top: var(--s-2); max-width: 22ch; margin-inline: auto; }
.about-process-head__aside { margin-top: var(--s-4); max-width: 44ch; margin-inline: auto; }
@media (max-width: 700px) {
  .about-process-head { text-align: left; }
  .about-process-head .eyebrow,
  .about-process-head .sec-head__title,
  .about-process-head__aside { margin-inline: 0; }
}
.about-process {
  position: relative; border-top: 1px solid var(--ink-14);
  padding-left: clamp(3px, .6vw, 5px); /* room for the progress line to sit clear of the numerals */
}
/* thicker green line that grows down the left edge as the block scrolls
   through view — there's real space for it now that the numerals sit
   inset from the edge (see .about-process-row's own padding-left below). */
.about-process::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: var(--blue);
  transform: scaleY(var(--progress, 0)); transform-origin: top;
  transition: transform .1s linear;
}
.about-process-row {
  position: relative;
  display: grid; grid-template-columns: minmax(0, 7rem) minmax(0, 15ch) minmax(0, 1fr);
  gap: var(--s-3) var(--s-3); align-items: center;
  padding-block: var(--s-4);
  padding-left: var(--s-4); /* keeps the numeral off the section's own edge */
  border-bottom: 1px solid var(--ink-14);
  overflow: hidden;
  transition: background .35s ease, border-color .4s ease;
}
.about-process-row::after {
  /* the "small line moving through the row" on activation */
  content: ""; position: absolute; left: 0; right: 100%; bottom: -1px; height: 1px;
  background: var(--blue);
  transition: right 1.1s var(--about-ease);
}
.about-process-row.is-active::after { right: 0; }
.about-process-row.is-active { background: rgba(75, 93, 52, .045); border-bottom-color: var(--blue); }
/* Resting: a hollow, black-outlined numeral (transparent fill + stroke) so it
   reads as "not there yet." Active: the outline fills solid green, the
   plain-color fallback below keeps every browser without text-stroke support
   readable — it just skips the hollow state and transitions ink -> green. */
.about-process-row .num {
  /* larger than the sitewide default so there's visibly more green fill to
     read on hover/active — scoped to this page's process rows only, the
     shared .num used on the homepage is untouched */
  font-size: clamp(2.6rem, 2rem + 2.6vw, 4.4rem);
  color: var(--ink-55);
  transition: color .4s ease, text-shadow .35s ease;
}
@supports ((-webkit-text-stroke: 1px black) or (text-stroke: 1px black)) {
  .about-process-row .num {
    color: transparent;
    -webkit-text-stroke: 2px var(--ink-55);
    text-stroke: 2px var(--ink-55);
    transition: color .4s ease, -webkit-text-stroke-color .4s ease, text-shadow .35s ease;
  }
}
.about-process-row.is-active .num { color: var(--blue); text-shadow: 0 0 18px var(--about-green-35); }
@supports ((-webkit-text-stroke: 1px black) or (text-stroke: 1px black)) {
  .about-process-row.is-active .num { -webkit-text-stroke-color: var(--blue); text-stroke-color: var(--blue); }
}
.about-process-row__name {
  font-family: var(--display); font-weight: 700; font-size: var(--t-h3); letter-spacing: -.02em;
  display: inline-block;
  transition: transform .35s var(--about-ease);
}
.about-process-row.is-active .about-process-row__name { transform: translateX(8px); }
.about-process-row__body {
  color: var(--ink-70); font-size: var(--t-sm); max-width: 52ch;
  opacity: .62; text-wrap: pretty;
  transition: opacity .4s ease;
}
.about-process-row.is-active .about-process-row__body { opacity: 1; }

/* ---------- hover on a row the scroll position hasn't reached ----------
   Same vocabulary as .is-active, at about half strength: the numeral fills
   toward ink rather than green, the name takes half the nudge, the body
   copy comes most of the way up. A row you're pointing at should answer,
   but the green + full 8px shift stays reserved for the row the page
   itself has decided you're on.

   Scoped with :not(.is-active) rather than relying on source order — plain
   :hover and .is-active are the same specificity, so without it whichever
   came last in the file would win, and the pointer would quietly downgrade
   the active row while passing over it. */
.about-process-row:not(.is-active):hover { background: rgba(75, 93, 52, .028); }
.about-process-row:not(.is-active):hover .about-process-row__name { transform: translateX(4px); }
.about-process-row:not(.is-active):hover .about-process-row__body { opacity: .88; }
.about-process-row:not(.is-active):hover .num { color: var(--ink-70); }
@supports ((-webkit-text-stroke: 1px black) or (text-stroke: 1px black)) {
  .about-process-row:not(.is-active):hover .num {
    color: transparent;
    -webkit-text-stroke-color: var(--ink-70);
    text-stroke-color: var(--ink-70);
  }
}

@media (max-width: 700px) {
  .about-process-row { grid-template-columns: 4.5rem 1fr; padding-left: var(--s-3); }
  .about-process-row__body { grid-column: 1 / -1; }
}

/* ==========================================================================
   6 — FINAL CALL TO ACTION
   ========================================================================== */
/* straight top-to-bottom fade, resolving to --night — the exact colour the
   footer below already uses (untouched, its own default from site.css) —
   so the two read as one continuous fade with no seam at the handoff,
   rather than the CTA's own separate diagonal fade cutting off early. */
.about-cta {
  position: relative; isolation: isolate; overflow: hidden;
  min-height: 88vh; min-height: 88svh;
  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__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);
  max-width: 15ch; color: #fff;
}
.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); }

/* magnetic attraction target — about.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 about.js); elements
   without it pull at full strength. */
[data-magnetic] { transition: transform .4s var(--about-ease-out); }

