/* about.html only. Adds no tokens and touches nothing else.

   The page shell (header, footer, lead modal) is index.html's compiled Tailwind
   reused verbatim, so it needs no CSS here. The About panel below is written by
   hand on purpose: vendor.css is a FINISHED Tailwind build with no build step,
   so a class that is not already compiled into it does not exist.

   Every value resolves a token already recorded in DESIGN.md: paper #FCFAF8 as
   the canvas, forest #1E3E39 ink, forest-muted #364946 secondary copy, amber as
   the single accent, 2.5rem section radius, and the card lift shadow. */

.ab {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  background: #FCFAF8;                 /* hsl(40 33% 98%) = --background, the page canvas */
  color: #1E3E39;
  /* top pad clears the fixed header (logo h-10 inside py-6) at every width; the
     bottom pad leaves room for the wave, which now caps the band below and
     overlaps up into this panel */
  padding: clamp(8rem, 14vw, 10.5rem) 0 clamp(5rem, 9vw, 8rem);
}

/* ---------------- the curves ---------------- */
/* The site already owns its background curves: the cream ribbon artwork behind
   community.html's sections (river-01..03.png, .gb-river in site-paper.css).
   Reusing that artwork instead of drawing new arcs is what keeps this page in
   the same world. The class names are page scoped on purpose: two live
   definitions of .gb-river in two stylesheets is a footgun the moment some page
   loads both, and this panel wants its own placement anyway, since ribbons read
   louder on the pale canvas than they do on cream. */
.ab__river {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-repeat: no-repeat;
  background-size: 118% auto;
}
.ab__river--upper {
  background-image: url(assets/img/river-02.png);
  background-position: right -2% top 16%;
  opacity: .9;
}
.ab__river--lower {
  background-image: url(assets/img/river-01.png);
  background-position: right -4% bottom -18%;
  transform: scaleX(-1);
  opacity: .7;
}
@media (max-width: 900px) {
  .ab__river { background-size: 210% auto; opacity: .65; }
}

/* ONE FADE FOR EVERY RIBBON LAYER ON THIS PAGE (Sarah, 22 Aug 2026).
   site-paper.css has always given .gb-river a vertical mask that takes the
   artwork to nothing before it reaches the top and bottom of its section, which
   is what lets the ribbons run down the home page without a seam at every band
   edge. The two page scoped copies on this page, .ab__river and .adv__river, were
   written without it, so both cut off dead straight at their section edges and
   the artwork ended in a hanging edge instead of running out of view. Same mask,
   same numbers, so all three bands hand off the way index.html's do. */
.ab__river,
.adv__river {
  -webkit-mask-image: linear-gradient(180deg, transparent 0, #000 12%, #000 78%, transparent 97%);
  mask-image: linear-gradient(180deg, transparent 0, #000 12%, #000 78%, transparent 97%);
}

/* The curve at the hand off to the band is NOT drawn here. index.html is built on
   the paper system now, so the band arrives already capped by .gb-pwave filled
   with this panel's ivory (site-paper.css, and pwave("#FCFAF8") in
   build_site_from_finclarity.py). That is what makes the curve and the band share
   one green: everything under the curve is the band's own ground, amber glows and
   all. A wave drawn at the bottom of this panel instead painted a second flat
   green above a gradient lit band, and the join between the two showed. */

.ab__wrap {
  position: relative;
  z-index: 1;
  width: min(1200px, 100% - 2 * clamp(20px, 4vw, 48px));
  margin-inline: auto;
}

/* Photograph left, copy right, as the reference sets it. Stacked, the photograph
   still comes first, so narrow screens read the same order rather than a new one. */
.ab__grid { display: grid; gap: clamp(2.25rem, 5vw, 3.5rem); align-items: center; }
@media (min-width: 900px) {
  .ab__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: clamp(3rem, 5.5vw, 5rem); }
}

/* ---------------- the film ---------------- */
/* Same card the photograph had: 2.5rem, DESIGN.md's card lift. It is square, so
   it stands taller than the photograph did and the column reads closer to the
   reference. Silent and looping, so no control furniture. */
.ab__film {
  border-radius: 2.5rem;
  overflow: hidden;
  background: #1E3E39;                 /* the film's own ground, so no flash of
                                          canvas before the first frame paints */
  box-shadow: 0 20px 50px -12px rgba(0, 0, 0, .3);
}
.ab__film { position: relative; }
.ab__film video {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1 / 1;
  transition: filter .45s var(--ab-ease, cubic-bezier(.16, 1, .3, 1)), transform .45s cubic-bezier(.16, 1, .3, 1);
}
/* The poster is SHOWN, not blurred (Sarah, 19 Aug 2026). It used to be blurred and
   darkened, because every frame of this film is typographic and a centred prompt
   stacked its words on the frame's own: "Watch why this club exists" landed across
   "of Canadians have no", and the play circle sat on the 91%.

   That collision was real, but blurring the whole picture is not the only way out
   of it, and it hid the thing the poster is there to show. The prompt moves instead:
   down into the empty band under the statistic, where nothing is set. So the figure,
   its claim and its source are all legible at rest, and the prompt sits below them
   rather than on top of them. */

/* The watch prompt is JUST THE CIRCLE (Sarah, 19 Aug 2026). No wash, no words over
   the poster: the frame is shown exactly as it is and one amber play button sits on
   it. The WHOLE card is still the button, so the target is the size of the picture
   and the circle is only what marks it.

   The two labels are still in the DOM and still read out: they are the button's
   accessible name, and about.js writes to the first of them. They are hidden the
   way .ab__film-says is, not removed, because a button with no name is a button a
   screen reader announces as "button". */
.ab__filmstart {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: grid;
  /* Centred left to right, but sitting in the BAND UNDER the statistic, not in the
     middle of the card. Dead centre puts the amber circle on the amber meter and
     clips the figure: on this poster the type runs from the top to about 79% and
     only the bottom is clear. */
  align-content: end;
  justify-items: center;
  padding: 0 0 5%;
  color: #FCFAF8;
  background: transparent;
  transition: background .3s;
}
/* the only wash is on hover, and it is a hint rather than a scrim */
.ab__filmstart:hover { background: rgba(17, 44, 40, .18); }

.ab__filmstart__say,
.ab__filmstart__meta {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.ab__filmstart:focus-visible { outline: 3px solid #EDAE1D; outline-offset: -6px; }
.ab__filmstart[hidden] { display: none; }

.ab__filmstart__ring {
  /* alone on the card now, so it carries the whole invitation and is sized for it */
  width: clamp(58px, 6vw, 78px);
  height: clamp(58px, 6vw, 78px);
  display: grid;
  place-items: center;
  border-radius: 999px;
  color: #112C28;
  background: #EDAE1D;
  box-shadow: 0 12px 30px -10px rgba(0, 0, 0, .55);
  transition: transform .35s cubic-bezier(.16, 1, .3, 1);
}
.ab__filmstart__ring svg { fill: currentColor; margin-left: 3px;
  width: 60%; height: 60%; }
.ab__filmstart:hover .ab__filmstart__ring { transform: scale(1.07); }

/* Sound and pause, shown only while the film is running. */
.ab__filmbar[hidden] { display: none; }
.ab__filmbar {
  position: absolute;
  right: 18px;
  bottom: 18px;
  display: flex;
  gap: 10px;
}
.ab__filmbtn {
  width: 44px;                         /* a real touch target, not an 18px icon */
  height: 44px;
  display: grid;
  place-items: center;
  border-radius: 999px;
  color: #FCFAF8;
  background: rgba(17, 44, 40, .55);
  backdrop-filter: blur(6px);
  box-shadow: inset 0 0 0 1px rgba(252, 250, 248, .22);
  transition: background .25s, transform .25s;
}
.ab__filmbtn:hover { background: rgba(17, 44, 40, .78); transform: translateY(-1px); }
.ab__filmbtn:focus-visible { outline: 2px solid #EDAE1D; outline-offset: 3px; }
.ab__ico { fill: currentColor; }
.ab__filmbtn .ab__ico--play,
.ab__filmbtn .ab__ico--on { display: none; }
.ab__filmbtn[data-paused] .ab__ico--play { display: block; }
.ab__filmbtn[data-paused] .ab__ico--pause { display: none; }
.ab__filmbtn[data-loud] .ab__ico--on { display: block; }
.ab__filmbtn[data-loud] .ab__ico--off { display: none; }

/* Sound is on from the first frame, so amber is this control's resting state and
   the grey is the muted exception. */
.ab__filmbtn[data-loud] { background: #EDAE1D; color: #112C28; }
.ab__filmbtn[data-loud]:hover { background: #F2B72E; }

/* The figures the film shows, as text. Present for screen readers and crawlers,
   and it is what the film is described by; not shown twice to a reader who can
   already watch it. */
.ab__film-says {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------------- copy ---------------- */
.ab__eyebrow {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin: 0 0 1.5rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: #364946;                      /* --muted-foreground, a warm ink, not a grey */
}
.ab__eyebrow::after {
  content: "";
  flex: 1;
  height: 1px;
  background: #E0DBD1;                 /* --border */
}

.ab__h1 {
  font-family: Inter, sans-serif;      /* DESIGN.md: the Inter Default Rule */
  font-weight: 700;                    /*            and the Heavy-Head Rule */
  font-size: clamp(1.9rem, 3.6vw, 2.85rem);
  line-height: 1.12;
  letter-spacing: -.025em;
  text-wrap: balance;
  margin: 0;
}
/* ONE yellow on this page (Sarah, 21 Aug 2026). Everything else amber here is
   the brand amber #EDAE1D: the play button on the film, the sound control, and
   the film itself. The mission line was the only thing set in the darkened
   display ink #B8820A, which read as a second, muddier yellow beside them, so it
   is the brand amber now and matches index.html accent headline as well.
   The trade, stated rather than hidden: #EDAE1D on the #FCFAF8 canvas measures
   about 1.8:1, under the 3:1 WCAG asks of large text. Taken knowingly, on a
   phrase inside a heading whose other words carry the forest ink and the whole
   sentence, so no information is lost by not reading the amber as text.
   #B8820A is the fix if that call is ever reversed. */
.ab__h1 em { font-style: normal; color: #EDAE1D; }

.ab__lede {
  margin: 1.5rem 0 0;
  max-width: 46ch;
  font-size: 1rem;
  line-height: 1.6;
  color: #364946;
}

.ab__actions { margin-top: 2.25rem; }

/* The band's own top padding comes from site-paper.css (section.gb-pt, with
   !important), which is sized to clear the wave cap. Nothing to add here. */

/* ---------------------------------------------------------------------------
   The founder band (Sarah, 22 Aug 2026)
   ---------------------------------------------------------------------------
   Portrait left in a tall arch, the story right, per the reference
   (Screenshot 2026-08-22 004516). Cream ground, so it reads as its own chapter
   between the paper panel above and the forest community band below; the wave
   cap on that band is refilled with this cream in build_about_page.py, because
   the cap is a picture of the ground above spilling over the green. */
/* CREAM AGAIN, AND THIS BAND IS NOW THE GAP CALCULATOR (Sarah, 22 Aug 2026).
   It went ivory earlier the same day so the whole page read as one field. She has
   reversed that for this band only: it takes the ground, the curve and the ink
   collage of index.html's gap section, id "check", and the ribbons that were
   here are gone. The panels either side stay ivory, so the cream is once more
   what marks the founder story as its own chapter.
   The curve at the top is a real .gb-pwave now rather than the drawn line that
   was briefly here. That line only existed because a filled wave cannot work
   between two bands of the same colour, and the grounds differ again. */
.who {
  position: relative;
  overflow: hidden;                    /* the collage sits in the margins and the
                                          wave laps the top edge */
  background: #FDF8F0;                 /* Cream Band, DESIGN.md, as #check is */
  color: #1E3E39;
  padding: clamp(3.5rem, 7vw, 5.5rem) 0 clamp(4.5rem, 9vw, 7rem);
}
/* The collage sits at z-index 0 and the wave at 6, both from site-paper.css, so
   the copy needs raising over the drawings. index.html does the same thing with a
   `relative z-10` on the gap section's own container. */
.who__wrap {
  position: relative;
  z-index: 1;
  width: min(1200px, 100% - 2 * clamp(20px, 4vw, 48px));
  margin-inline: auto;
}
/* Photograph left, copy right. The copy column is the wider of the two, as the
   reference sets it (426 to 570 measured off the screenshot). Stacked, the
   photograph still comes first, so narrow screens read the same order. */
.who__grid { display: grid; gap: clamp(2.5rem, 5vw, 3.5rem); align-items: center; }
@media (min-width: 900px) {
  .who__grid {
    grid-template-columns: minmax(0, .82fr) minmax(0, 1fr);
    gap: clamp(3rem, 6vw, 5.5rem);
  }
}

/* ---------------- the portrait ---------------- */
/* The arch. Horizontal radius is half the width, so the top and bottom close as
   full arcs; the vertical radius is a quarter of the height, which leaves the
   sides straight through the middle. That is the reference's shape, and it is
   why a plain border-radius:50% (a true ellipse) is wrong here.
   The image is built to 0.62 wide-to-tall by scripts/build_sarah_photo.py, so
   the box carries the same ratio and nothing is cropped a second time. */
.who__photo {
  position: relative;
  margin: 0;
  justify-self: center;
  width: min(100%, 460px);
}
.who__photo img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 1040 / 1678;
  object-fit: cover;
  border-radius: 50% / 25%;
  box-shadow: 0 20px 50px -12px rgba(0, 0, 0, .3);   /* DESIGN.md card lift */
}

/* The reference's circular badge, bottom left, half off the picture. It makes
   the claim the footer, the JSON-LD and every blog byline already make. */
.who__seal {
  position: absolute;
  left: -6%;
  bottom: 4%;
  width: clamp(96px, 11vw, 130px);
  filter: drop-shadow(0 10px 24px rgba(0, 0, 0, .18));
}
.who__seal svg { display: block; width: 100%; height: auto; }
.who__sealtext {
  font-family: Inter, sans-serif;
  font-size: 9.4px;
  font-weight: 600;
  letter-spacing: .04em;
  fill: #1E3E39;
}
@media (max-width: 620px) {
  .who__seal { left: auto; right: 2%; bottom: 2%; }
}

/* ---------------- the copy ---------------- */
/* .ab__eyebrow above it is shared with the panel; nothing to add. */

/* DESIGN.md's Display Script beat: Dancing Script 600 in amber, oversized, once
   per page as the emotional line. It is what carries the reference's serif
   flourish here, because the Inter Default Rule keeps the headline in Inter. */
/* The amber is the same #EDAE1D the panel above uses, ONE yellow on this page
   (Sarah, 21 Aug 2026). It measures about 1.8:1 on cream, under the 3:1 WCAG
   asks of large text, and that is taken knowingly here for the same reason the
   .ab__h1 accent takes it: the name is also the page title, the JSON-LD Person
   and the byline on every guide, so nothing is only knowable by reading it in
   gold. #B8820A is the fix if that call is ever reversed. */
.who__script {
  margin: .35rem 0 0;
  font-weight: 600;                    /* .font-script sets the family */
  font-size: clamp(2.6rem, 5.6vw, 4rem);
  line-height: 1.15;
  color: #EDAE1D;
}

/* TWO LINES, IN QUOTATION MARKS (Sarah, 22 Aug 2026). The words are unchanged;
   what changed is that they are now set as something said rather than something
   argued, so they are the one line in the band that is punctuated as speech.
   The size came down and the measure went up to hold it at two lines across the
   desktop column: at the old 2rem in a 22ch measure the same sentence set in
   four. text-wrap:balance then keeps the two lines close to even instead of
   leaving one word stranded on the second. */
.who__h2 {
  font-family: Inter, sans-serif;      /* the Inter Default Rule */
  font-weight: 700;                    /* the Heavy-Head Rule */
  font-size: clamp(1.28rem, 2.1vw, 1.7rem);
  line-height: 1.24;
  letter-spacing: -.018em;
  text-wrap: balance;
  max-width: 40ch;
  margin: 1rem 0 0;
}

/* THE HIGHLIGHTER (Sarah, 22 Aug 2026).
   This line used to carry the amber as a small rule BESIDE the words, because
   amber type on cream measures about 1.8:1 and this is a real sentence rather
   than a decorative phrase. Putting the amber behind the words instead of in
   them settles that argument rather than dodging it: forest ink over amber at
   55 percent on cream measures about 7.6:1, which is better than the plain ink
   on cream it replaces. So this is the one place on the page where the brand
   yellow can carry a whole line and be read.

   The sweep is left to right, from the left edge, because that is the direction
   a hand drags a marker. It is NOT on a loop: it happens once, and then the line
   simply is highlighted. */
.who__kicker {
  margin: 1.25rem 0 0;
  font-size: .82rem;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: #1E3E39;
}
.who__hl {
  position: relative;
  z-index: 0;                          /* its own stacking context, so the
                                          highlight can sit at -1 and still land
                                          above the band's cream ground */
  display: inline-block;
}
/* MADE TO LOOK LIKE A PEN (Sarah, 22 Aug 2026). The first pass grew a tidy
   rounded rectangle out of nothing with scaleX, and it read as a loading bar,
   because that is what it was. Three things fix it, and none of them is a longer
   duration:

     the shape    a real stroke has four different corners and is not level. The
                  slash radius gives each corner its own two axes, and the whole
                  stroke sits at .7 of a degree off horizontal.
     the ink      a marker is thin where the nib lands, heavy through the drag,
                  and thin again where it lifts. That is the gradient, and its
                  96deg angle is why the ink is not level either.
     the reveal   clip-path, NOT scaleX. Scaling grows the shape, so the far end
                  arrives squashed and unrolls, which no pen does. Clipping keeps
                  the stroke exactly the shape it will end as and uncovers it left
                  to right, which is precisely what laying ink down looks like.
                  It is the one property outside transform and opacity used here,
                  and it is the sanctioned one.

   Then a SECOND PASS, .28s behind the first and shorter, because nobody
   highlights anything exactly once. It is faint on its own and its only job is to
   make the density uneven, which is the difference between ink and fill. */
.who__hl::before,
.who__hl::after {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
}
.who__hl::before {
  left: -.45em;
  right: -.5em;
  top: -.3em;
  bottom: -.34em;
  border-radius: .55em .26em .44em .32em / .8em .44em .62em .5em;
  background: linear-gradient(96deg,
              rgba(237, 174, 29, .30) 0%,
              rgba(237, 174, 29, .64) 18%,
              rgba(237, 174, 29, .68) 64%,
              rgba(237, 174, 29, .34) 100%);
  transform: rotate(-.7deg);
}
/* the second pass: shorter, lower, tilted the other way */
.who__hl::after {
  left: -.2em;
  right: .9em;
  top: .04em;
  bottom: -.4em;
  border-radius: .4em .5em .3em .46em / .5em .7em .45em .6em;
  background: linear-gradient(92deg,
              rgba(237, 174, 29, 0) 0%,
              rgba(237, 174, 29, .30) 26%,
              rgba(237, 174, 29, .26) 74%,
              rgba(237, 174, 29, 0) 100%);
  transform: rotate(.55deg);
}
/* With motion off the line simply arrives already highlighted, which is the
   whole point of the mark and costs a reduced motion visitor nothing. With
   motion on it starts empty and the pen goes across five seconds after the band
   comes into view. Five seconds from IN VIEW, not from page load: this band sits
   most of a screen down, and a sweep that fires while it is still below the fold
   is a sweep nobody sees. about.js does the watching. */
html[data-gb-motion="on"] .who__hl::before,
html[data-gb-motion="on"] .who__hl::after { clip-path: inset(0 100% 0 0); }
html[data-gb-motion="on"] .who__hl.is-lit::before {
  animation: whoLit .72s var(--gb-ease) 5s forwards;
}
html[data-gb-motion="on"] .who__hl.is-lit::after {
  animation: whoLit .52s var(--gb-ease) 5.28s forwards;
}
/* -1% at the end, not 0: an inset that stops exactly on the edge can leave a
   hairline of un-inked pixel on a fractional layout, and the last thing a stroke
   should have is a clean vertical edge at the right hand end. */
@keyframes whoLit {
  from { clip-path: inset(0 100% 0 0); }
  to   { clip-path: inset(0 -1% 0 0); }
}

/* The ink collage drifts. site-paper.css hangs that float off .gb-play, a class
   index.html's boot script adds and this page's gate deliberately does not, so
   the drawings would otherwise sit dead still while everything around them moved.
   Same keyframes, same 7.5s, same per drawing delay, hung off this page's own
   switch instead. gbSkFloat animates transform while .gb-sk holds its tilt in the
   separate `rotate` property, which is exactly why the two never fight. */
html[data-gb-motion="on"] .who .gb-sk {
  animation: gbSkFloat 7.5s ease-in-out infinite;
  animation-delay: var(--d, .4s);
}

/* The seal turns. Only the RING OF TYPE is in the spinning group: the leaf and
   the two rules stay where they are, which is the difference between a stamp and
   a sticker on a fan. transform-box:view-box makes 50% 50% resolve against the
   144 unit viewBox, so the centre of the spin is the centre of the badge without
   anybody having to type 72 twice. */
html[data-gb-motion="on"] .who__sealspin {
  transform-box: view-box;
  transform-origin: 50% 50%;
  animation: whoSeal 26s linear infinite;
}
@keyframes whoSeal {
  to { transform: rotate(360deg); }
}

.who__p {
  margin: 1.1rem 0 0;
  max-width: 56ch;
  font-size: 1rem;
  line-height: 1.7;
  color: #364946;                      /* --muted-foreground, a warm ink */
}
/* The mission line is the one sentence in the band that is about the work rather
   than the history, so it carries the ink and the weight the rest does not. */
.who__p--mission {
  margin-top: 1.6rem;
  font-weight: 600;
  color: #1E3E39;
}

/* THE NO-SELL NOTE, AS ONE LONG PILL ACROSS THE BAND (Sarah, 22 Aug 2026).
   A white card hairlined in amber, because it is the page's actual promise and
   the last thing the band says. It had a "Book my free review" pill under it and
   Sarah took that out: a CTA sitting directly under "we don't sell anything here"
   argues with the sentence above it. So the card is copy only, and .who__notep
   carries no bottom margin, because there is nothing left under it to clear.

   It is no longer INSIDE the copy column. It used to be the last block there,
   which made it the width of the prose and so read as the end of the prose. It is
   the opposite of prose: it now runs the full width of the band, under the
   photograph and the story both, with its text centred, which is what stops it
   being read as one more paragraph of the story.

   A TRUE PILL RADIUS, 999px, and that only works because the sentence sets in two
   lines at this width: on a shape this wide the radius resolves to half the
   height, so the ends are full half circles and the thing is a stadium rather
   than a rounded box. If the copy is ever made long enough to take a third line
   this wants a fixed radius back, because a pill four lines tall stops reading as
   a pill and starts reading as a mistake. The measure below is what holds it to
   two, and the narrow query gives the radius up rather than let it become a
   lozenge on a phone. */
.who__note {
  margin: clamp(2.5rem, 5vw, 3.5rem) auto 0;
  padding: clamp(1.5rem, 3vw, 2.1rem) clamp(2rem, 5vw, 4rem);
  text-align: center;
  background: #FFFFFF;
  border: 1px solid rgba(237, 174, 29, .35);
  border-radius: 999px;
  box-shadow: 0 8px 30px rgb(0 0 0 / .08);   /* DESIGN.md card soft */
}
@media (max-width: 900px) {
  .who__note {
    padding: clamp(1.35rem, 4vw, 1.8rem) clamp(1.4rem, 4vw, 2rem);
    border-radius: 1.75rem;
  }
}
.who__notep {
  margin: 0 auto;
  max-width: 104ch;                    /* holds it to two lines on a wide band */
  font-size: 1rem;
  line-height: 1.65;
  color: #1E3E39;
}

/* ---------------------------------------------------------------------------
   The community band's title (Sarah, 22 Aug 2026)
   ---------------------------------------------------------------------------
   The headline itself is index.html's, classes and all, with only the words
   changed (build_about_page.py). This is the one part that is new: the site's
   Label style from DESIGN.md, centred over it with a hairline either side.
   Brand amber on the forest band measures 5.9:1, so here, unlike on paper, the
   accent is a colour the words can actually be set in. */
.cmt__eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin: 0 0 1.35rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: #EDAE1D;
}
.cmt__eyebrow::before,
.cmt__eyebrow::after {
  content: "";
  flex: 0 0 auto;
  width: clamp(1.5rem, 4vw, 3rem);
  height: 1px;
  background: rgba(237, 174, 29, .45);
}

/* ---------------------------------------------------------------------------
   The advisors band (Sarah, 22 Aug 2026)
   ---------------------------------------------------------------------------
   Eleven hand drawn advisors grouped under Sarah's title, on the paper canvas so
   the band separates from the cream founder band above it, over a background
   that moves. The figures are built by scripts/build_about_page.py; everything
   that moves is here.

   THE CONVERSATION RUNS ON ONE CLOCK. --adv-cycle is 18s, six of the eleven
   speak, and their beats are 3s apart in x order, so the turns walk left to
   right, which is the direction Sarah asked for. Every animation on that clock
   is 18s long and says nothing for the part of the cycle that is not its beat;
   the negative delay calc(var(--b) * var(--adv-beat) - var(--adv-cycle)) starts
   each one already part way through, rather than making the reader wait out five
   turns before the group does anything.

   TWO VARIABLES, AND THEY ARE NOT THE SAME NUMBER. --b is the figure's beat, its
   place in the conversation. --i is its index in the group, and it exists only to
   scatter the things that must NOT land on the beat: breathing and blinking. If
   eleven people inhaled on the clock they would read as one machine with eleven
   heads.

   PROPERTIES: transform and opacity only, everywhere, including the background.
   Nothing here touches a box, so nothing here can lay the page out a second
   time, and the whole band composites on the GPU while the rest of the page is
   still loading fonts.

   WHY IT IS GATED ON AN ATTRIBUTE AND NOT ON @media (prefers-reduced-motion).
   site-paper.css already records the answer at .gb-step: a media query cannot see
   the ?motion=1 review flag, so motion stays dead in review even with the flag
   on and the work reads as never having shipped. That happened twice. The switch
   is html[data-gb-motion="on"], set by the gate script in this page's head, and
   the suppression is the same either way: no attribute, no animation.

   WHAT A READER WHO ASKED FOR REDUCED MOTION SEES. Not an empty band. All six
   bubbles carry opacity 1 at rest, so the group arrives already speaking, every
   line legible at once, with the waving hands simply held up. The background
   keeps its glow, its ribbons and its motes, still. Fewer and gentler, which is
   what the preference asks for, rather than nothing. */
.adv {
  position: relative;
  overflow: hidden;                    /* the ribbons drift a couple of dozen
                                          pixels past the edges */
  isolation: isolate;                  /* so z-index:-1 lands above this band's
                                          own ground and below its content, the
                                          same stack .ab uses at the top */
  background: #FCFAF8;                 /* paper, so this band is not a second
                                          helping of the cream one above it */
  color: #1E3E39;
  padding: clamp(3.5rem, 7vw, 5rem) 0 clamp(3.5rem, 7vw, 5rem);
  text-align: center;
  --adv-cycle: 18s;
  --adv-beat: 3s;
}

/* ---------------- the ribbons ---------------- */
/* Not new artwork. river-01 and river-02 are the cream ribbons already behind
   community.html's sections and already used at the top of this page by
   .ab__river, and reusing them is what keeps an animated background from
   arriving as a second design. The inset is negative so the drift never walks an
   edge of the image into view. */
.adv__river {
  position: absolute;
  inset: -8% -12%;
  z-index: -1;
  pointer-events: none;
  background-repeat: no-repeat;
  background-size: 120% auto;
}
.adv__river--upper {
  background-image: url(assets/img/river-02.png);
  background-position: left -14% top 22%;
  opacity: .34;
}
.adv__river--lower {
  background-image: url(assets/img/river-01.png);
  background-position: right -10% bottom -10%;
  transform: scaleX(-1);
  opacity: .3;
}
@media (max-width: 900px) {
  .adv__river { background-size: 210% auto; opacity: .4; }
}

.adv__wrap {
  position: relative;
  z-index: 1;
  width: min(1200px, 100% - 2 * clamp(20px, 4vw, 48px));
  margin-inline: auto;
}

/* The community band's centred Label, on paper. The WORDS cannot be amber here:
   #EDAE1D on #FCFAF8 measures about 1.8:1 and this is 12px text, not a phrase
   inside a heading that is carried by the words either side of it. So the label
   takes the forest muted ink and the accent moves off the text onto the two
   hairlines, exactly the trade .who__kicker makes. */
.adv__eyebrow {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin: 0 0 1.35rem;
  font-size: .75rem;
  font-weight: 600;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: #364946;
}
.adv__eyebrow::before,
.adv__eyebrow::after {
  content: "";
  flex: 0 0 auto;
  width: clamp(1.5rem, 4vw, 3rem);
  height: 1px;
  background: rgba(237, 174, 29, .55);
}

.adv__h2 {
  font-family: Inter, sans-serif;      /* the Inter Default Rule */
  font-weight: 700;                    /* the Heavy-Head Rule */
  font-size: clamp(1.55rem, 3.1vw, 2.45rem);
  line-height: 1.16;
  letter-spacing: -.022em;
  text-wrap: balance;
  max-width: 24ch;
  margin: 0 auto;
}
/* ONE yellow on this page: the same #EDAE1D .ab__h1 and the community headline
   take, and the same knowingly accepted 1.8:1. #B8820A is the fix if that call
   is ever reversed, and it has to be reversed in all three places at once. */
.adv__h2 em { font-style: normal; color: #EDAE1D; }

.adv__lede {
  margin: 1.15rem auto 0;
  max-width: 52ch;
  font-size: 1rem;
  line-height: 1.65;
  color: #364946;
}

/* ---------------- the group ---------------- */
/* The stage is the scroll container and the scene is the picture. They are two
   elements rather than one because the scene has to keep an exact aspect ratio
   for the bubbles to be parked on it by percentage, and a scrolling box cannot
   also be the box that ratio is measured from.

   The COPY keeps the page's 1200px column and the picture does not, which is why
   the stage sits outside .adv__wrap: a picture measured at the width of a
   paragraph read as an island in the middle of a wide screen. */
.adv__stage {
  position: relative;
  z-index: 1;
  width: min(1440px, 100% - 2 * clamp(20px, 4vw, 48px));
  margin: clamp(1.25rem, 3vw, 2rem) auto 0;
  /* app.js's scroll reveal is opted into with Tailwind's .transition-all, which
     is the site's own reveal and is kept. What is NOT kept is `all` on an element
     whose WIDTH is a clamp against the viewport: it made the group animate its
     own width over 700ms on every resize and orientation change. Naming the two
     properties the reveal actually moves leaves the reveal exactly as it was and
     takes the resize out of it. */
  transition-property: opacity, transform;
}
.adv__scene {
  position: relative;
  width: 100%;
  aspect-ratio: 1240 / 500;            /* the SVG's own viewBox, so a bubble at
                                          left:50% lands on the head at x=620 */
}
.adv__art {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  overflow: visible;                   /* the outermost swoosh arcs sit a few
                                          units outside the box */
}

/* ---------------- what they say ---------------- */
/* HTML over the picture, not SVG <text>. The words are then selectable, they
   translate, they wrap if a phrase is ever made longer, and they are set in the
   page's own Inter at the page's own size instead of at whatever size a 1240
   unit viewBox happens to scale to. */
.adv__say {
  position: absolute;
  left: var(--x);
  top: var(--y);                       /* each figure's own crown, so no bubble
                                          trails a tail through empty air */
  transform: translate(-50%, -100%);
  z-index: 2;
  pointer-events: none;
}
.adv__bubble {
  position: relative;
  margin: 0;
  padding: .55rem 1rem;
  background: #FFFFFF;
  color: #1E3E39;
  border: 1px solid rgba(237, 174, 29, .45);
  border-radius: 1.1rem;
  box-shadow: 0 8px 22px -10px rgba(0, 0, 0, .3);   /* DESIGN.md card soft */
  font-size: clamp(.75rem, 1.05vw, 1rem);
  font-weight: 600;
  line-height: 1.25;
  white-space: nowrap;
  /* the tail, so the bubble grows out of the person who is speaking rather than
     out of its own middle */
  transform-origin: 50% 100%;
}
.adv__bubble::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -8px;
  width: 14px;
  height: 14px;
  background: #FFFFFF;
  border-right: 1px solid rgba(237, 174, 29, .45);
  border-bottom: 1px solid rgba(237, 174, 29, .45);
  border-bottom-right-radius: 3px;
  transform: translateX(-50%) rotate(45deg);
}
/* The talking one says dots instead of words. */
.adv__bubble--dots {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: .72rem .85rem;
}
.adv__bubble--dots span[aria-hidden] {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: #364946;
  opacity: .42;
}

.adv__hint {
  display: none;                       /* mobile only, see the query below */
  position: relative;
  z-index: 1;
  margin: .6rem 0 0;
  font-size: .78rem;
  color: #364946;
  opacity: .8;
}

.adv__actions { position: relative; z-index: 1; margin-top: clamp(1.5rem, 3vw, 2.25rem); }

/* ---------------------------------------------------------------------------
   Motion
   ---------------------------------------------------------------------------
   Every rule below is behind the gate. Read the block at the top of this section
   for why that is an attribute and not a media query. */

/* ---- the background ----
   Its whole discipline is that the reader never catches it at it. Everything
   back here runs at least four times slower than anything the people in front of
   it do: 9s for the light, 13s and up for the motes, more than a minute for the
   ribbons. */

/* The light the group stands in. A scale of six percent either side of rest is
   nothing to look at directly and is exactly enough to keep the band from
   reading as a flat sheet of paper. */
/* OPACITY ONLY, no scale. Scaling an ellipse filled with a radial gradient
   repaints the gradient every frame across a thousand by four hundred unit area,
   which is the most expensive thing that was happening in this band and the
   least visible. Opacity alone composites on the GPU and, on a light this soft,
   is indistinguishable from the version that also breathed outward. */
html[data-gb-motion="on"] .adv__glow {
  animation: advGlow 9s ease-in-out infinite alternate;
}
@keyframes advGlow {
  from { opacity: .62; }
  to   { opacity: 1; }
}

/* The motes. Each carries its own period and its own offset as inline custom
   properties, taken off its index in the list, so no two of them ever rise
   together for more than a moment. --o is the mote's own resting opacity, which
   is also the attribute it falls back to when nothing is moving. */
html[data-gb-motion="on"] .adv__mote {
  animation: advMote var(--t) ease-in-out infinite;
  animation-delay: var(--d);
}
@keyframes advMote {
  0%   { transform: translateY(18px); opacity: 0; }
  25%  { opacity: var(--o); }
  70%  { opacity: var(--o); }
  100% { transform: translateY(-34px); opacity: 0; }
}

/* The ribbons. --lower already carries a scaleX(-1) at rest, so its keyframes
   have to carry it too: a transform animation replaces the property outright and
   a flip left out of the keyframes is a flip that snaps back the moment the
   animation starts. */
html[data-gb-motion="on"] .adv__river--upper { animation: advDriftA 68s ease-in-out infinite alternate; }
html[data-gb-motion="on"] .adv__river--lower { animation: advDriftB 84s ease-in-out infinite alternate; }
@keyframes advDriftA {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-28px, 12px, 0); }
}
@keyframes advDriftB {
  from { transform: scaleX(-1) translate3d(0, 0, 0); }
  to   { transform: scaleX(-1) translate3d(-32px, -14px, 0); }
}

/* ---- the people ---- */

/* Breathing. Off the conversation's clock, on --i rather than --b, because
   eleven people who inhale in turn look like a machine. 4.2s, alternating, and
   the small per figure offset is all it takes to break the unison. */
html[data-gb-motion="on"] .adv__body {
  animation: advBob 4.2s ease-in-out infinite alternate;
  animation-delay: calc(var(--i) * -.37s);
}
@keyframes advBob {
  from { transform: translateY(0); }
  to   { transform: translateY(-6px); }
}

/* The wave. transform-box:fill-box puts the origin inside the arm group's own
   bounding box, which is identical for every figure, so one origin serves all of
   them however far along the group they stand and whichever way they are turned:
   the box is measured before the mirror, not after. 0% 100% is the shoulder, the
   corner where the arm leaves the sleeve. */
html[data-gb-motion="on"] .adv__arm {
  transform-box: fill-box;
  transform-origin: 0% 100%;
  animation: advWave var(--adv-cycle) ease-in-out infinite;
  animation-delay: calc(var(--b) * var(--adv-beat) - var(--adv-cycle));
}
@keyframes advWave {
  0%    { transform: rotate(0deg); }
  3%    { transform: rotate(-15deg); }
  6.5%  { transform: rotate(14deg); }
  10%   { transform: rotate(-15deg); }
  13.5% { transform: rotate(14deg); }
  17%   { transform: rotate(-12deg); }
  21%   { transform: rotate(0deg); }
  100%  { transform: rotate(0deg); }   /* held up, still, for the other 14s */
}

/* The two arcs beside the hand. They only exist while the hand is moving, which
   is the only time a motion line means anything. */
html[data-gb-motion="on"] .adv__swoosh {
  animation: advSwoosh var(--adv-cycle) ease-out infinite;
  animation-delay: calc(var(--b) * var(--adv-beat) - var(--adv-cycle));
}
@keyframes advSwoosh {
  0%, 2%    { opacity: 0; }
  6%        { opacity: .7; }
  17%       { opacity: .7; }
  21%, 100% { opacity: 0; }
}

/* Blinking. On --i, off the conversation's clock, and on a period that shares no
   factor with the breathing, so no two of them ever settle into lockstep. Only
   the front rank blinks: at 0.87 and behind somebody's shoulder, a back rank eye
   is four pixels of ink and the animation would be work nobody can see. */
html[data-gb-motion="on"] .adv__eye {
  transform-box: fill-box;
  transform-origin: 50% 50%;
  animation: advBlink 6.4s ease-in-out infinite;
  animation-delay: calc(var(--i) * -1.13s);
}
@keyframes advBlink {
  0%, 93%, 100% { transform: scaleY(1); }
  95.5%         { transform: scaleY(.08); }
  97.5%         { transform: scaleY(1); }
}

/* The talking mouth. Origin on the upper lip, so the jaw swings down and the
   face does not pulse. linear, because a mouth forming words does not ease. At
   rest it sits at scaleY(1), which is the open smile every other figure wears,
   so the figure is only ever talking or smiling and never caught mid gape. */
html[data-gb-motion="on"] .adv__mouth {
  transform-box: fill-box;
  transform-origin: 50% 4%;
  animation: advTalk var(--adv-cycle) linear infinite;
  animation-delay: calc(var(--b) * var(--adv-beat) - var(--adv-cycle));
}
@keyframes advTalk {
  0%, 2%    { transform: scaleY(1); }
  4%        { transform: scaleY(.4); }
  6%        { transform: scaleY(.95); }
  8%        { transform: scaleY(.42); }
  10%       { transform: scaleY(1); }
  12%       { transform: scaleY(.38); }
  14%       { transform: scaleY(.9); }
  16%       { transform: scaleY(.46); }
  18%       { transform: scaleY(1); }
  20%       { transform: scaleY(.5); }
  22%, 100% { transform: scaleY(1); }
}

/* Steam off the mug. Its own short loop rather than the conversation's, because
   coffee does not wait its turn. Origin at the bottom, so it grows off the cup
   instead of sliding past it. */
html[data-gb-motion="on"] .adv__steam {
  transform-box: fill-box;
  transform-origin: 50% 100%;
  animation: advSteam 3.8s ease-in-out infinite;
}
@keyframes advSteam {
  0%   { transform: translateY(5px) scaleY(.65); opacity: 0; }
  28%  { opacity: .85; }
  68%  { opacity: .45; }
  100% { transform: translateY(-17px) scaleY(1.2); opacity: 0; }
}

/* The bubbles. Each is up for about 4.9s of the 18s, which overlaps its
   neighbour by roughly two seconds: long enough that the group reads as a room
   in conversation, short enough that it is never all six at once. */
html[data-gb-motion="on"] .adv__bubble {
  opacity: 0;
  animation: advSay var(--adv-cycle) var(--gb-ease) infinite;
  animation-delay: calc(var(--b) * var(--adv-beat) - var(--adv-cycle));
}
@keyframes advSay {
  0%   { opacity: 0; transform: scale(.9) translateY(6px); }
  2.5% { opacity: 1; transform: scale(1.03) translateY(0); }
  5%   { opacity: 1; transform: scale(1) translateY(0); }
  23%  { opacity: 1; transform: scale(1) translateY(0); }
  27%  { opacity: 0; transform: scale(.96) translateY(-4px); }
  100% { opacity: 0; transform: scale(.96) translateY(-4px); }
}

html[data-gb-motion="on"] .adv__bubble--dots span[aria-hidden] {
  animation: advDot 1.15s ease-in-out infinite;
}
html[data-gb-motion="on"] .adv__bubble--dots span[aria-hidden]:nth-of-type(2) { animation-delay: .14s; }
html[data-gb-motion="on"] .adv__bubble--dots span[aria-hidden]:nth-of-type(3) { animation-delay: .28s; }
@keyframes advDot {
  0%, 55%, 100% { transform: translateY(0); opacity: .42; }
  28%           { transform: translateY(-4px); opacity: 1; }
}

/* ---------------------------------------------------------------------------
   Off screen, nothing runs
   ---------------------------------------------------------------------------
   This band has forty five animations in it and for most of a visit it is not on
   the screen. Left alone the browser keeps every one of them ticking anyway, and
   on a phone that is battery spent on a picture nobody is looking at.

   about.js hangs .is-idle on the section whenever it is out of view and takes it
   off when it comes back. The default is RUNNING and .is-idle is what stops
   things, deliberately that way round: if the script never runs, or an old
   browser has no IntersectionObserver, the band behaves exactly as it did before
   any of this existed. A version that had to be switched ON would go silent on
   the first browser that could not switch it.

   Paused, not cancelled, so a beat that was half way through is exactly where it
   was when the reader scrolls back to it. */
html[data-gb-motion="on"] .adv.is-idle :is(.adv__body, .adv__arm, .adv__swoosh,
  .adv__eye, .adv__mouth, .adv__steam, .adv__bubble, .adv__mote, .adv__glow,
  .adv__river, .adv__bubble--dots span[aria-hidden]),
html[data-gb-motion="on"] .who.is-idle :is(.who__sealspin, .gb-sk) {
  animation-play-state: paused;
}

/* ---------------------------------------------------------------------------
   Phones
   ---------------------------------------------------------------------------
   TWO PROBLEMS, ONE OF THEM SELF INFLICTED.

   The first: eleven figures across a phone are about forty pixels tall and stop
   being faces. The first answer was a horizontal strip you swiped along, and it
   was wrong, because it solved the size by cutting seven people off at the edge
   of a box. Nobody should have to find the rest of a picture by dragging it.

   The answer now is a WINDOW. The SVG carries preserveAspectRatio slice, so
   giving the scene a 724 by 500 box crops it to the middle 724 units of the
   stage and scales what is left up to fill. Seven whole people at close to 200px
   tall on a 390px screen, where the strip was showing four. The four figures
   whose bodies straddle the crop are hidden outright by the build rather than
   sliced, and the two speech bubbles that belong to them go with them.

   The second: forty five animations is a lot to ask of a phone for a decorative
   band. Everything below the fold of legibility comes off. The motes are three
   pixels across at this scale and eleven of them; the blinking is an eye four
   pixels wide; and the ribbons are the expensive one, because at 210% background
   size they are a very large layer to move. All three stop. What is kept is
   everything a reader can actually see doing something: the breathing, the wave,
   the talking mouth, the steam, the bubbles and their dots.
   That takes the phone from forty five running animations to nineteen. */
/* 1180px, not a phone width. The crop is not a phone concession, it is what the
   picture needs whenever eleven people do not fit: the deciding number is how
   wide each face ends up. At 820px, a tablet at 1024 was still getting all
   eleven across a 944px stage, which is 86px a person and a face about twenty
   pixels wide. Everything below 1180 takes the window instead. */
@media (max-width: 1180px) {
  .adv__scene {
    aspect-ratio: 724 / 500;           /* the crop window, and the only number
                                          here that decides who is on screen */
  }
  .adv__art { overflow: hidden; }      /* slice only crops if the viewport clips;
                                          the desktop rule leaves this visible so
                                          the swoosh arcs can sit outside the box */
  .adv__fig--edge,
  .adv__say--edge { display: none; }
  .adv__say { left: var(--xn); }

  /* Wrapped, not nowrap. The longest line is "Start wherever you are" and it
     belongs to the rightmost figure on screen, so at one line it would hang off
     the side of the section. 33vw keeps every bubble inside the page at 320px
     and up, and two short lines read better on a phone than one long one. */
  /* This branch now spans a phone to a small laptop, so neither number can be
     fixed. 33vw keeps the rightmost bubble inside the page at 320px, and the
     190px ceiling stops it sprawling at 1180. */
  .adv__bubble {
    font-size: clamp(12px, 1.15vw, 15px);
    max-width: min(33vw, 190px);
    white-space: normal;
    padding: .42rem .72rem;
  }
  .adv__bubble--dots { padding: .55rem .7rem; }

  /* WHAT COMES OFF, AND WHY EACH ONE.
     display:none stops a thing being PAINTED. It does not stop it being
     ANIMATED: the browser keeps ticking a keyframe on a hidden element and
     keeps recalculating its style every frame. So everything dropped here is
     dropped twice, once from the paint and once from the clock, and the four
     figures the crop hides have to be silenced by hand for the same reason.

       the motes    eleven of them, three pixels across at this scale
       the blinks   an eye is four pixels wide here
       the ribbons  the expensive one: at 210% background-size they are a very
                    large layer, and moving a large layer is the most costly
                    thing this band was asking a phone to do
       the cropped  four figures and two bubbles nobody can see */
  .adv__motes { display: none; }
  html[data-gb-motion="on"] .adv__mote,
  html[data-gb-motion="on"] .adv__eye,
  html[data-gb-motion="on"] .adv__river--upper,
  html[data-gb-motion="on"] .adv__river--lower,
  html[data-gb-motion="on"] .adv__fig--edge :is(.adv__body, .adv__arm, .adv__swoosh,
    .adv__eye, .adv__mouth, .adv__steam),
  html[data-gb-motion="on"] .adv__say--edge .adv__bubble,
  html[data-gb-motion="on"] .adv__say--edge .adv__bubble--dots span[aria-hidden] {
    animation: none;
  }
}

/* The founder band's own phone trim. The ink collage is already hidden under
   1280px by site-paper.css, so the float has nothing left to move and the rule
   would keep six animations alive over six display:none elements. */
@media (max-width: 1279px) {
  html[data-gb-motion="on"] .who .gb-sk { animation: none; }
}
