.mf-p {
  margin: 0 auto;
  text-align: center;
  font-size: clamp(17px, 2vw, 20px);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: -0.005em;
  text-wrap: pretty;
  color: color-mix(in srgb, var(--deep) 66%, transparent);
}

/* .lead-line drops to 18px here so its sentence fits on one line. This is not
   that rule - the nowrap is deliberately NOT copied, this copy is four times
   longer and will never fit - it is only the size, so the two blocks stay the
   same size on the one screen wide enough to show both. */
@media (min-width: 1160px) {
  .mf-p { font-size: min(18px, 1.5vw); }
}

/* =====================================================================
   Still Brewing — /sundays/ (free Sunday Speaking Club landing page)
   ---------------------------------------------------------------------
   Loaded AFTER assets/css/redesign.css and depends on it: every colour,
   radius and surface class here resolves to a :root custom property that
   redesign.css already defines. There is not one literal colour or corner
   radius in this file. If you find yourself wanting one, the answer is in
   design.md, not here.

   One exception, and only one: the @font-face for Caveat immediately below.
   It is a third typeface, it is not in design.md, and it exists because this
   page sets one word by hand. Nothing else on the page may use it.

   Scope: layout otherwise. The genuinely new pieces are the photo hero with
   its chat overlay and the club schedule bar. Everything else on the page
   reuses a component that already ships — the booking card's three steps,
   for one, are the main site's .works block with nothing changed but the
   surface under it.

   The page is white and has no surface alternation. Structure comes from
   objects sitting on it — the hero card, the cream box around the hour, the
   yellow booking card — rather than from bands of colour behind them.
   ===================================================================== */

/* ============================ the one exception ============================
   A third typeface, loaded on this page and nowhere else. It renders exactly
   one word — "Sundays" in the H1 — next to a hand-drawn coffee cup, and it is
   here because Fraunces italic, which is the brand's stand-in for handwriting,
   still reads as typography rather than as a hand.

   Deliberately NOT added to assets/fonts/fonts.css: that file is inlined into
   all fourteen language pages of the main site, so a @font-face there would
   change every page for the sake of one word on this one.

   The file is a static instance pinned to weight 700 and subsetted to ASCII
   (41 KB, from 75 KB variable). tools/subset-fonts.mjs does NOT touch it — it
   subsets from assets/fonts/_full/ and Caveat is not in there, because that
   script would clamp it to Jakarta's weight axis and break it. If Caveat ever
   needs re-subsetting, do it by hand. */
@font-face {
  font-family: "Caveat";
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url(/assets/fonts/caveat-latin.woff2?v=a49e7be5) format("woff2");
  unicode-range: U+0000-00FF;
}
.sunday { --font-hand: "Caveat", "Fraunces", cursive; }

/* ---- the page surface ----
   White, not the cream the main site sits on. Cream is now reserved for one
   section (the hour), where it is doing a job: on a page with no surface
   alternation left, one band of colour is worth more than a page of it.

   Everything that used to be white ON cream has to be re-checked when this
   flips, which is why the exceptions are gathered here rather than scattered:
   a white card on a white page is not a card. */
body.sunday { background: var(--white); }
.sunday .surface-cream { background: var(--white); }

/* Page-level default: the emphasised half of a headline is maroon display,
   UPRIGHT. redesign.css ships .accent-serif italic and leaves the colour to
   the caller; the main site then cancels the italic at every single use, with
   an inline style="font-style:normal" on each H2. That inline repetition is
   the tell that upright is the real convention and the italic in the base
   class is the leftover. This page states it once instead, which is also what
   keeps the template free of inline styles.

   Italic is not gone from the page, it is just not in the headlines — it
   still marks the small asides (.sfoot-tag), which is the one job the main
   site keeps it for too (.club-note, .bento-step, .footer-tag em). */
.sunday .accent-serif {
  color: var(--maroon);
  font-style: normal;
}
.sunday .sfoot-tag { font-style: italic; }

/* Ragged last lines are the default failure mode of a display serif at these
   sizes: without this, headlines strand a single word on a line of its own. */
.sunday .h1,
.sunday .h2,
.sunday .bookcard-title { text-wrap: balance; }

/* ============================ buttons ============================
   Every yellow CTA on this page carries a thin ink outline. On the main
   site the yellow always sits on cream or white, where the fill alone is
   edge enough; here the same button has to hold its shape over the hero
   photograph, and an unoutlined yellow pill on a busy image loses its
   silhouette. (The booking card is yellow, so the button in it is maroon
   and takes none of this — see .bookcta.) Scoped to .sunday, so the
   school's buttons are untouched.

   The border is drawn inside the existing padding rather than added to it,
   which is what keeps this button the same size as the one on the main
   site: 16px of padding minus 1.5px of border. */
.sunday .btn-primary {
  border: 1.5px solid var(--deep);
  padding: 14.5px 26.5px;
}
.sunday .btn-sm.btn-primary { padding: 10.5px 18.5px; }

/* ============================ header ============================
   redesign.css already styles .site-header, .brand, .nav-links and
   .header-cta. Three deltas, all of them because this is a landing page
   rather than the school's site:

   1. The CTA appears at every width, and the frosted pill of links from
      940px rather than the main site's 1200px. This page has the same five
      links the main site does but no language switcher, and the switcher is
      most of what pushes that breakpoint up. Measured: logo 172 + pill 444
      + CTA 128 + breathing room needs about 790px of a row, and 940 is the
      first common width that clears it without the pill touching either
      neighbour. It used to be 720 because there were three links; adding
      Schedule and Start here cost roughly 150px and 720 no longer fits.
   2. No hamburger, no mobile menu. Below 940 the bar is the logo and the
      CTA and nothing else. The links are anchors to sections you reach by
      scrolling anyway, and a menu button on a landing page is a drawer full
      of exits. The CTA stays visible at every width, which is the only
      control here that matters.
   3. Nothing on scroll. The bar is the same floating glass tablet at every
      scroll position, exactly like the main site's: transparent behind the
      logo and the CTA, with the frosted pill carrying the centre links.
      sunday.js still toggles .is-stuck from a 1px sentinel and the class is
      still in the DOM, but as on the main site it now paints nothing — the
      hook is left in place rather than ripped out, because it costs nothing
      and it is the attachment point if a scrolled state is ever wanted back.

   This header also replaces the sticky mini-bar the page used to carry:
   it is already pinned and already holds the CTA, and two stacked bars is
   one too many on a phone. */
#top-sentinel {
  position: absolute;
  top: 0; left: 0;
  width: 1px; height: 1px;
  pointer-events: none;
}

.sunday-header nav { gap: 12px; }
.sunday-header .header-cta { display: inline-flex; }
@media (min-width: 940px) {
  .sunday-header .nav-links { display: flex; }
}

/* From 720px redesign.css reserves 60–84px on the right of the nav row so
   the CTA never slides under the language switcher, which is pinned to the
   far edge of the screen. This page has no switcher, so that reservation is
   just a hole: the CTA ends up floating well short of the right margin
   while the logo sits hard against the left one. Give it back. */
@media (min-width: 720px) {
  .sunday-header nav { padding-right: 0; }
}

/* redesign.css already leaves .site-header transparent with a transparent
   bottom border at every scroll position, and the pill it centres inside is
   permanent. That is the whole header on this page too, so there is
   deliberately no .is-stuck rule here. */

/* Sections open on their heading: there are no kickers on this page, so the
   gap redesign.css reserves for one has to come back out. */
.sunday .section-head .h2 { margin-top: 0; }

/* ---- section rhythm ----
   ONE NUMBER, USED EVERYWHERE. Normalised 2026-08-21: eight sections on this
   page each carried their own bespoke padding pair, every one of them added
   for a good local reason, and the sum of eight good local reasons was a page
   whose gaps were all slightly different and none of them on purpose. They
   are gone. Every section between the hero and the footer sits on --sec-pad,
   so the gap between any two of them is exactly twice it and the same
   everywhere down the page.

   The scale is shorter than the main site's 64/80/96. There, every section
   has its own background colour, so the padding does two jobs: separating the
   sections and letting each colour band breathe. Here the page is one
   continuous white field, nothing needs room to be a band, and the same
   numbers read as holes between the headings.

   --sec-half is for the ONE relationship on this page that is not
   section-to-section: the hero card and the lead line under it are one unit,
   a card and its caption, so they are set half a step apart and the full step
   falls under the pair. If something else ever needs to read as belonging to
   its neighbour, give it the half step. Do not invent a third number.

   THREE EXCEPTIONS EXIST and each is written where it belongs, with its
   reason: .hero-sun's top (it is tight under the sticky header), the closing
   ask's bottom (the footer wants air under the last thing anyone is asked to
   do), and .final-cta on a SHORT viewport only, where the booking card has to
   clear the fold. Nothing else overrides this. */
/* Tightened from 44/56/64 on 2026-08-21 (Daniil's call). The page had got
   long enough that the steps between sections were reading as gaps rather
   than as rhythm — see the #tutor exception directly below, which is where
   that was worst. */
body.sunday { --sec-pad: 36px; --sec-half: 18px; }
@media (min-width: 720px)  { body.sunday { --sec-pad: 44px; --sec-half: 22px; } }
@media (min-width: 1060px) { body.sunday { --sec-pad: 52px; --sec-half: 24px; } }

/* The `.sunday .section#id` shape on the last two is not decoration. An ID
   selector outranks any number of classes, and redesign.css sets a padding on
   #reviews for the main site — so a class-only rule here loses to a rule in
   another file that knows nothing about this page. Written the same way for
   every section that has an id, whether or not redesign.css currently touches
   it, so adding one there can never quietly reopen this. */
.sunday .section:not(.hero-sun),
.sunday .section#tutor,
.sunday .section#different,
.sunday .section#hour,
.sunday .section#reviews,
.sunday .section#after,
.sunday .section#faq,
.sunday .section#save-seat { padding-block: var(--sec-pad); }

/* ---- where the nav's Members link lands ----
   redesign.css sets [id] { scroll-margin-top: 96px } for the whole site, which
   was tuned for a section whose own top padding is small. #after's is a full
   --sec-pad on top of it, so clicking Members in the header put the heading
   148px down a 70px-high sticky header — 78px of white above the thing the
   click asked for, and on a phone the price cards started below the fold.

   Derived, not typed: 94px is the sticky header (70px at desktop, 65px on a
   phone) plus the ~24px of air a heading wants under it, and the section's own
   padding is subtracted because scroll-margin positions the BOX, not the text
   inside it. Lands the heading a consistent step below the header at every
   width. Scoped to #after: the other four nav targets still use the site
   default, and if this ever gets applied to them it should be applied to all
   four at once rather than one at a time. */
.sunday #after { scroll-margin-top: calc(94px - var(--sec-pad)); }

/* A FOURTH EXCEPTION, 2026-08-21. The tutor section is a half step top and
   bottom rather than a full one, so it sits closer to the hero's lead line
   above it and to the comparison below it than those two sit to anything
   else.

   The reason is the shape of the section, not a dislike of the number. From
   900px it is a circle beside a paragraph — one row, no heading band of its
   own above a grid, nothing that needs air to be legible as a block — and a
   full step above and below it left the shortest section on the page framed
   like the tallest. Below 900px it is still the centred column and the half
   step reads as tight-but-deliberate there too, which is what "compact" was
   asking for.

   Written with the ID for the reason given above the block. */
.sunday .section#tutor { padding-block: var(--sec-half); }

/* ============================ hero ============================
   One photograph, edge to edge inside a rounded card, with everything else
   layered on top of it. Four layers, bottom to top: the image, a scrim, the
   grain, then the copy and the chat.
   ------------------------------------------------------------------------
   Below 900px the card is a tall portrait: the chat sits at the top over the
   photo, the copy at the bottom, and the scrim runs vertically to meet it.
   From 900px it becomes a 16:9 landscape with the copy in a left column and
   the chat in a right one, and the scrim turns through 90 degrees to match.
   One set of markup, two compositions. */
/* TOP is the first of the three documented exceptions to --sec-pad: this
   section opens the page directly under a sticky header, and a full step
   there is a band of nothing above the only thing an ad click came to read.
   It is as small as it can be without the card touching the header.

   BOTTOM is the half step, and the lead line below gives the other half. The
   card and that sentence are one unit — a card and its caption — which is the
   only pairing on this page that is not plain section-to-section. See the
   section rhythm block above. */
.hero-sun { padding-block: clamp(4px, 1vw, 8px) calc(var(--sec-half) / 2); }

/* The card breaks out of the 1180px column and runs to within a gutter of
   the viewport edge, as the reference does. The container is kept rather
   than removed so the section still owns its own padding, and the inner
   grid re-imposes a reading width so the copy does not drift to the far
   left of a 1900px screen. */
.hero-sun .container {
  max-width: none;
  padding-inline: clamp(16px, 2vw, 34px);
}

.herocard {
  position: relative;
  isolation: isolate;
  display: flex;
  /* A step past --r-xl (20px). The card is the largest object on the page by
     a long way, and a radius that reads as generous at card size reads as
     tight at 1900px wide: the curve has to grow with the corner it turns. */
  border-radius: clamp(22px, 2.2vw, 30px);
  overflow: hidden;
  /* What shows for the few hundred milliseconds before the photo decodes.
     Dark, so the cream text is already legible against it. */
  background: var(--charcoal);
  min-height: min(80svh, 660px);
}
/* Now that the card is nearly the full width of the window, 16:9 would make
   it 1000px tall on a laptop and push the first section entirely out of
   sight, so the height is set directly instead and the card is left as wide
   as the window allows.

   Deliberately NOT aspect-ratio: a ratio plus a min-height resolves the
   other way round, deriving a width from the height, and a 620px floor on a
   1024px screen produced a 1333px card and a horizontally scrolling page.
   clamp() on vw cannot do that. */
@media (min-width: 900px) {
  .herocard {
    min-height: 0;
    /* svh in the middle of the clamp, not just vw. The lead line under the
       card has to be readable without scrolling, and on a laptop the thing
       that decides that is the height of the window, not its width: at
       1440x745 a 45vw card is 648px tall and pushes the sentence off the
       bottom, where the same card on a 1440x1080 screen has room to spare.
       76svh tracks the window instead.

       The floor is not a taste either. The card cannot be shorter than what
       it holds: the chat is 449px at 1440 and 460px in the 900–1059 band,
       plus the masthead row and the inner padding. 575 clears the tallest of
       those. Below it the content would overflow a fixed-height rounded box,
       which clips rather than scrolls. The inner padding was trimmed and the
       masthead taken out of flow to get this floor down — every pixel of card
       height is a pixel the lead line sits lower on a short laptop. */
    height: clamp(525px, min(45vw, 74svh), 760px);
  }
}

.herocard-img {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* NOT mirrored, deliberately. scaleX(-1) here reverses the "VIDEO CALL"
     label, the three name badges, the toolbar labels and the MacBook
     wordmark, all of which are legible at 1440px, and it swings her cream
     knit sweater under the body copy — which needs MORE darkening, not
     less. Both work against the reason for flipping it.

     A portrait crop of a wide frame keeps roughly a third of its width, so
     the anchor point decides what survives. 47% holds the laptop and the
     faces on it, which are the product, plus her head and shoulder. */
  object-position: 47% center;
}
@media (min-width: 900px) { .herocard-img { object-position: center center; } }

/* One flat tint across the whole frame, not a gradient.
   ------------------------------------------------------------------------
   This used to be a directional ramp that darkened the left of the picture
   and left the right untouched. It measured well and looked wrong: a photo
   that is dark at one edge and bright at the other, with no light source
   that explains it, reads as a text panel someone pasted on top. A single
   even tint reads as a grade — the whole picture is simply darker, the way
   an underexposed frame is darker, and nothing about it announces that it
   was done for the words.

   56% is the floor, and it is a floor rather than a taste. This photograph
   is bright almost everywhere (a window, plants, a pale mug, a lit laptop
   screen), so the tint is the only thing holding the copy up. Measured
   against the brightest pixel under each line, at 1440 and at 390:

       48%   headline 2.97 — under the 3:1 large-text minimum. Fails.
       52%   headline 3.30, "Sundays" 2.88. Fails.
       56%   headline 3.69 / 4.44, "Sundays" 3.25 / 4.02, body 4.48 / 4.07.
       60%   everything passes with room, and the picture is visibly darker.

   Going below 56% takes the H1 itself under, and there is no way to fix that
   from the type: it is already 56px. Two things had to change to make even
   56% legal, and both are noted where they are: the description is bold at
   19px so it counts as large text, and the "Next club" line moved onto its
   own chip so it stopped depending on the photograph at all. */
.herocard-scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: color-mix(in srgb, var(--deep) 56%, transparent);
}

/* Fine film grain, an inline feTurbulence tile. Plain opacity and NO
   mix-blend-mode: a blend layer over a large image is the exact bug that
   made the old hero render washed out in Chrome on P3 displays while Safari
   showed it correctly. */
.herocard-grain {
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: .19;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='g' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23g)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

.herocard-inner {
  position: relative;
  z-index: 3;
  width: 100%;
  display: grid;
  gap: 26px;
  align-content: space-between;
  padding: 22px 20px 26px;
}
@media (min-width: 560px) { .herocard-inner { padding: 28px 30px 34px; } }
@media (max-width: 899.98px) {
  /* Chat at the top over the photograph, copy anchored to the bottom where
     the scrim is deepest. Applies to the 640–900px band only: below 640 the
     chat is gone entirely (see the .chat block below). */
  .chat { order: -1; }
}
@media (min-width: 900px) {
  .herocard-inner {
    /* One row of two: the copy and the chat, centred against each other. The
       chat column is fixed to its own content width, so the copy takes the
       rest and the two are not tied to each other's length. The masthead is
       taken out of the flow entirely (below) rather than given a row of its
       own, because a row costs the card its own height at the top AND the
       same again at the bottom to keep the middle centred — about 60px, and
       every one of those pixels pushes the lead line under the card closer
       to the fold on a short laptop. */
    grid-template-columns: minmax(0, 1fr) auto;
    align-content: center;
    align-items: center;
    column-gap: 40px;
    padding: 30px 44px 34px;
    /* The card is full-bleed; its contents are not. */
    max-width: 1420px;
    margin-inline: auto;
  }
  /* Pinned to the card's top-left corner, on the same padding the grid uses
     so it lines up with the copy below it. Out of flow, so it costs the card
     no height at all. The clearance to the sentence below comes from
     .herocard-copy's padding-top, not from here: 37px at the card's shortest
     and 106px at its tallest. */
  .hero-title {
    position: absolute;
    top: 30px;
    left: 44px;
    margin: 0;
  }
}
@media (min-width: 1120px) {
  .herocard-inner { padding: 32px 60px 38px; }
  .hero-title { top: 32px; left: 60px; }
}

/* The tint above is even now rather than directional, so the copy is no
   longer confined to a dark half and can take a normal measure. Still short
   of 65ch: this column shares a card with a photograph, and a headline that
   runs the width of the frame stops the picture being a picture. */
/* A container, so the headline can be sized against this column rather than
   against the viewport. It has to be: the column is 512px wide from 1060px
   up, drops to 404px in the 900–1059 band where the card first turns
   landscape, and goes back to the card's full width below that. A viewport
   clamp cannot follow a curve like that, and every vw value that fits at 900
   is far too small at 1440. */
.herocard-copy {
  max-width: 32rem;
  container-type: inline-size;
}
/* Wider from 900px up, now that the headline has left this column and only
   the sentence, the button and the meta chip are in it. 42rem against the
   chat's 372px and the 40px gap comes to 1084px, which is inside the 1262px
   the inner grid has at 1440 — and where it is not (1160, where the card is
   narrower than the grid's own maximum) minmax(0, 1fr) simply gives the
   column less and the cqi size below follows it down.

   The padding-top is the masthead's room. The name is out of flow, so
   nothing else reserves space for it, and the copy is centred in the full
   inner box — without this the sentence rides up under it at the card's
   shortest. It is free: even with the padding the column is 429px against
   the chat's 449, so it is not what decides the card's height. Whatever
   changes here, keep the column shorter than the chat or the floor in
   .herocard moves. */
@media (min-width: 900px) {
  .herocard-copy {
    max-width: 42rem;
    padding-top: 52px;
  }
}

/* The masthead. From 900px up this is a small label in the card's top-left
   corner; below that it goes back to being a line above the copy, because a
   corner label on a portrait card with the chat at the top has no corner to
   sit in.

   It is deliberately much smaller than it was — 66px, then 43, then 34, now
   25 at desktop. It is the name of the club, and the sentence under it is the
   reason to join one; a name set at headline size makes a reader weigh the
   two against each other. Moving it out of the copy column is the other half
   of that: they are no longer a stack, so they are no longer compared.

   Still the <h1>, and still one line at every width — at 25px the old
   arithmetic (11.6px of line per 1px of font-size) puts it at about 290px
   against a column that is never narrower than 404px.

   700 weight is not decoration. At this size the line is small text over a
   photograph, which needs 4.5:1; bold from 18.66px counts as large text at
   3:1, which is what the 56% scrim was measured to give. Do not lighten it
   without re-measuring the scrim. */
.hero-title {
  font-size: clamp(21px, 1.75vw, 27px);
  font-weight: 700;
  line-height: 1.06;
  color: var(--cream);
  /* No text-wrap: balance — the line does not wrap any more, and balance on
     a single line only costs the browser a measuring pass. */
  /* A wide, soft shadow rather than a hard one: it never reads as a shadow,
     it just stops a stray highlight in the photo eating a letter. */
  text-shadow: 0 2px 24px color-mix(in srgb, var(--deep) 50%, transparent);
}
/* The narrowest phones. "Still Brewing Speaking Club" needs 283px of line at
   21px and a 320px screen gives the card 248, so the name would wrap into two
   lines and stop being a masthead. 18px is the largest step that keeps it on
   one line there, and no phone wider than 350px ever sees it. */
@media (max-width: 349.98px) {
  .hero-title { font-size: 18px; }
}

/* "Sundays", in the hand, with the cup beside it.
   ------------------------------------------------------------------------
   Caveat's cap height is small for its em, so at the same font-size it would
   read a size smaller than the Fraunces beside it: 1.24em puts the two on the
   same optical line. The 0.06em nudge down is the same correction on the
   baseline, which sits high in this face.

   Yellow, not the coral of the cup beside it, and that is a measurement
   rather than a preference. Coral is a mid-tone: against the brightest patch
   of window behind this word it measures 2.09:1, under the 3:1 large text
   needs, and the only way to fix it in the scrim is to take the whole
   photograph down to about 78% black. Yellow lands at 4.02:1 over the same
   pixels. It also rhymes with the marker sweep two lines below, which is the
   page's other yellow, so the coral is left to the doodle. */
.hero-hand {
  position: relative;
  display: inline-block;
  padding-right: .72em;
  font-family: var(--font-hand);
  font-style: normal;
  font-weight: 700;
  font-size: 1.24em;
  line-height: .8;
  letter-spacing: 0;
  color: var(--yellow);
  transform: translateY(.06em);
}
/* Sitting in the gap the padding above reserves, so it never collides with
   "Speaking" no matter where the line breaks. Tilted, because a sticker
   applied by hand is never straight. */
.hero-sticker {
  position: absolute;
  right: -.30em;
  top: -.46em;
  width: 1.12em;
  height: auto;
  transform: rotate(9deg);
  pointer-events: none;
}
@media (max-width: 559.98px) {
  /* At phone sizes the cup starts overlapping the letters rather than
     sitting beside them, and a doodle that clips a word is just noise. */
  .hero-sticker { right: -.24em; top: -.38em; width: .88em; }
}

/* Handwriting beside the button, no box behind it. Nothing on this line
   competes with the CTA now: one filled yellow pill, and next to it a note
   in the same hand as "Sundays" in the headline. The lack of a container is
   the point — it reads as something written next to the button rather than
   as a second button.

   Caveat's cap height is small for its em, so 26px here sits at about the
   size 21px of Fraunces did. The shadow is not decoration: this face is a
   thin monoline over a photograph, and stroke weight is what a scrim cannot
   compensate for. It darkens the pixels immediately around each stroke. */
.hero-mark {
  display: inline-block;
  color: var(--white);
  font-family: var(--font-hand);
  font-size: 26px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: 0;
  text-shadow: 0 2px 14px color-mix(in srgb, var(--deep) 72%, transparent);
}
@media (min-width: 720px) { .hero-mark { font-size: 29px; } }

.hero-act-row {
  display: flex;
  align-items: center;
  /* Wider than it was: the marker used to have 14px of its own padding
     holding it off the button, and bare text needs that space in the gap. */
  gap: 20px;
  flex-wrap: wrap;
}
/* Small enough to sit beside the button rather than under it. On a 390px
   phone the card gives this row about 318px, the button takes 182 of it, and
   at 26px the note needs more than the 128 that leaves — so it wrapped, and a
   note on its own line under a button reads as a caption, not as an aside.
   21px with a 12px gap fits and is still the largest text on the card after
   the headline. The copy carries half of this: the note reads "First club is
   free.", not "Your first club is free.", because the longer sentence does
   not fit beside the button at any size a phone can read. Below about 340px
   it wraps anyway and that is correct: there is no size at which both fit on
   an iPhone SE.

   After the two rules it overrides, not before: a media query adds no
   specificity, so at equal weight the later rule is the one that applies. */
@media (max-width: 559.98px) {
  .hero-mark { font-size: 21px; }
  .hero-act-row { gap: 12px; }
}
/* One more step for the 360–375px phones. There the row is 288px and the
   button takes 182, so the note has 98 to live in: at 18px it needs 106 and
   wraps, at 16px with an 8px gap it needs 94 and fits. Caveat's x-height is
   small enough that 16px here is not far below the button's own label. */
@media (max-width: 385px) {
  .hero-mark { font-size: 16px; }
  .hero-act-row { gap: 8px; }
}

/* The focal point of the hero, and now the largest type on the card — larger
   than the H1 above it. That inversion is deliberate: the H1 is the name of
   the club, this is the reason to join one, and a visitor arriving from an
   ad has to read the reason first. Semantics are unchanged; only the size
   order is.

   700 weight is load-bearing and not emphasis: WCAG counts bold type from
   18.66px as large text, which needs 3:1 rather than 4.5:1, and that
   exemption is what lets the scrim over the photograph stay at 56%. The cqi
   value never resolves below 22px, so the exemption always holds.

   Sized against the column, so it follows the column through the 900px pinch
   rather than tracking the viewport. The ceiling is what the rag decides, not
   taste: 66px is the largest size at which the first sentence still sets on
   two lines in a 672px column, which is what gives the block its shape —
   "Stop rehearsing" / "English in your head." / "Speak it on Sundays." Above
   it the first sentence takes a third line and the second takes two, and the
   whole thing goes ragged. The column and the ceiling are a pair: 42rem was
   chosen because it is the narrowest that holds 66px at 2+1 lines. Change
   the sentence and re-measure both together. */
.hero-lede {
  margin-top: 0;
  font-size: clamp(22px, 9.82cqi, 66px);
  font-weight: 700;
  line-height: 1.22;
  letter-spacing: -0.015em;
  color: var(--cream);
}
/* One block per sentence, so a line break can never fall across the full stop
   and leave "head. Speak" sitting on one line. balance is on the parts rather
   than on the paragraph for the same reason: each sentence is evened out
   against itself. build.mjs makes these — see heroLede(). */
.hero-lede .lede-part {
  display: block;
  text-wrap: balance;
}

/* There is no .hero-desc any more: the description moved out of the card and
   became .lead-line on the cream below it. Worth knowing if you are reading
   the scrim rules above — the 56% tint over the photograph used to be set by
   what an 18.75px bold line needed to stay at 3:1. The only copy left on the
   picture is the headline, which is large text by any measure, and the meta
   line, which carries its own dark chip. The tint has slack it did not have
   before; it is left where it is because the photograph reads correctly at
   56% and nothing is asking it to change.

   The button block now follows the headline directly, so it takes the gap the
   paragraph used to hold rather than the tighter one it had underneath it. */
.hero-act { margin-top: 30px; }
/* The .hero-meta chip — a green pulsing dot beside "Choose your free club.
   They run every Wednesday and Sunday" — was removed 2026-08-21 at Daniil's
   request, together with its .hero-meta-body / .hero-meta-dot / .hero-meta-seats
   rules and the sun-dot and sun-dot-ring keyframes. Nothing else used them.
   The scrim note above still mentions the meta line: the only copy left on the
   photograph is the headline and the button, both large type, so the 56% tint
   has more slack than it did, not less. */

/* ============================ hero chat ============================
   The hero card's right-hand column. It has now been three things and is back
   to the first: a WhatsApp-style chat mockup (until 2026-08-20), then Sasha's
   clip in a rectangular player (until 2026-08-21), then the clip and three
   greetings together (one day), and now the mockup again.

   What settled it is that the page only ever needed ONE video, and the card is
   not where it pays. A clip in the corner of a photograph is one person
   talking at you; the chat is a room already in progress that you are being
   invited into, and the room is the thing this page sells. The clip lost
   nothing by moving — it plays in the tutor circle in the very next section,
   bigger than it ever was here, and that section now sits second on the page.

   The bubble primitives — .bubrow, .bub, .bub-av, .bub-gap, .bub-meta — are
   defined ONCE, in the reviews thread further down this file, and reused here.
   That is deliberate: someone who meets a group chat in the hero and a group
   chat at the foot of the page has been in one room twice, not shown two chat
   widgets. Everything in this block is either the chat's own furniture (the
   head, the Join chip) or the difference between a bubble on cream and a
   bubble on a photograph.

   Real markup rather than a screenshot: it stays sharp at any zoom, reflows
   when the card goes to one column, and its words live in the content file.
   It is announced as one labelled group and read in DOM order. The Join chip
   is a <span>, not a <button>: it does nothing, and a control that does
   nothing should not claim to be one. */
.chat {
  display: grid;
  /* Room to breathe between messages. A tighter stack reads as a transcript;
     this reads as a conversation happening. */
  gap: 13px;
  width: min(100%, 340px);
  justify-self: end;
  font-size: 14px;
}
/* From 900px this is the second track of .herocard-inner's
   `minmax(0, 1fr) auto` grid, and an `auto` track sizes to its item's
   max-content — so the width has to be definite or the track measures the
   longest unbroken thing inside and hands the headline a gutter. The base
   rule above still governs the 640–899 band, where the card is one column
   and 100% is meaningful. */
@media (min-width: 1120px) { .chat { width: 372px; font-size: 15px; } }

/* Phones get no chat at all. It is a ~300px-tall object stacked on top of the
   headline below 900px, so on a phone it pushed the H1, the button and the
   next-club line down the first screen — the atmosphere was outranking the
   three things an ad click actually has to read. Removed rather than shrunk:
   a two-bubble thread does not read as a room, so there is no smaller version
   worth keeping. display:none takes it out of the accessibility tree too,
   which is right — nothing in it is said only here.

   This has to sit AFTER the .chat block above, not up with the other hero
   breakpoints: it is a single class either way, so the cascade is decided by
   source order and `display: grid` above would win. */
@media (max-width: 639.98px) {
  .chat { display: none; }
}

/* Spans the full width of the message column: the calendar disc and the group
   name on the left, the Join chip pushed to the far right by the auto margin,
   and the whole thing as wide as the widest bubble beneath it.

   Nothing frosted here: the face pile carries its own white pill and the name
   sits directly on the photograph, which is why it takes a shadow. */
.chat-head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  color: var(--cream);
}
/* The group disc IS the call indicator. It was a cream calendar until
   2026-08-21, with a separate green camera disc on a pinned call card
   underneath; two discs for one thing. Green, and the one pulsing thing on
   the page: a camera icon sitting still reads as a call that is SCHEDULED,
   and the whole point of this column is a call that is happening now. Two
   seconds is slow enough to be ambient rather than an alarm. */
.chat-head-ico {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: var(--green);
  color: var(--forest);
  animation: chat-live-pulse 2s ease-in-out infinite;
}
@keyframes chat-live-pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 85%, transparent); }
  70%      { box-shadow: 0 0 0 9px color-mix(in srgb, var(--green) 0%, transparent); }
}
@media (prefers-reduced-motion: reduce) {
  .chat-head-ico { animation: none; box-shadow: 0 0 0 5px color-mix(in srgb, var(--green) 30%, transparent); }
}
.chat-head-body { min-width: 0; display: grid; gap: 4px; }
.chat-head-name {
  font-size: 14px;
  font-weight: 700;
  line-height: 1.2;
  text-shadow: 0 2px 12px color-mix(in srgb, var(--deep) 70%, transparent);
}
/* The face pile and "& 2 others" share one white pill, which is what stops
   the small grey label needing a shadow of its own to survive the
   photograph. */
.chat-head-people {
  display: flex;
  align-items: center;
  gap: 7px;
  width: fit-content;
  padding: 2px 9px 2px 3px;
  border-radius: var(--r-pill);
  background: var(--white);
  box-shadow: 0 6px 18px color-mix(in srgb, var(--deep) 26%, transparent);
}
.chat-faces { display: flex; }
/* .avatar ships at 44px for the reviews row; here it is a stacked face pile
   inside a white pill, so the separating ring is white too. */
.chat-faces .avatar {
  width: 22px; height: 22px;
  font-size: 10px;
  margin-left: -7px;
  border: 2px solid var(--white);
}
.chat-faces .avatar:first-child { margin-left: 0; }
.chat-head-more { font-size: 11px; font-weight: 600; color: var(--stone); }

/* The pinned call card that stood here from 2026-08-21 — .chat-call,
   .chat-call-ico, .chat-call-body, .chat-call-title, .chat-call-meta and
   @keyframes chat-call-pulse — is gone, folded into the group header above.
   It repeated the header's icon, its title and its list of people in a second
   format, which made one call look like two things. The pulse survived; see
   .chat-head-ico. Git has the card if it is ever wanted back. */

/* The Join chip, at the right-hand end of the header. It is honest there now
   that the disc beside it is a live call rather than a calendar. It sits on
   the photograph, not on a card, so it carries the same shadow as the white
   faces pill next to it — without one it floats. Still a <span>: see the note
   in template-sunday.html. */
.chat-join {
  display: inline-flex;
  align-items: center;
  align-self: center;
  margin-left: auto;
  flex: 0 0 auto;
  padding: 7px 15px;
  border-radius: var(--r-pill);
  background: var(--green);
  color: var(--forest);
  font-size: 13px;
  font-weight: 700;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--deep) 30%, transparent);
}

/* ---- what a bubble on a photograph needs that a bubble on cream does not ----
   Everything below is scoped to .chat so the reviews thread at the foot of the
   page is untouched. Three differences, and only three:

   1. COLOUR. The reviews thread's bubbles are green-tinted, which is right on
      cream and disappears into foliage. These are white.
   2. WEIGHT. The reviews shadow is 8% of --deep, which is also right on cream
      and also invisible on foliage.
   3. REACTIONS and the attachment tile, which a testimonial thread has no use
      for.

   DIRECTION used to be the first of these. This chat had outgoing messages
   until 2026-08-21 and the rules that made them — .bub--out, .bubrow--out,
   .bub-ticks, and a wider .bub-gap--out to clear the delivery ticks — went
   with them when the thread was cut to two messages from Sasha. Everything
   here is incoming now, exactly as in the reviews thread. Git has the four
   rules if a visitor ever speaks in this mockup again. */
.chat .bub--in {
  background: var(--white);
  color: var(--ink);
  border-bottom-left-radius: 4px;
  border-bottom-right-radius: var(--r-card);
}
.chat .bub {
  font-size: inherit;
  box-shadow: 0 10px 26px color-mix(in srgb, var(--deep) 42%, transparent);
}
/* Same reason as the bubble shadow: the 1.5px white ring that holds an avatar
   against cream is not enough against leaves. */
.chat .bub-av {
  border-width: 2px;
  border-color: color-mix(in srgb, var(--cream) 70%, transparent);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--deep) 30%, transparent);
}
.chat .bub-meta {
  display: flex;
  align-items: center;
  gap: 3px;
}

/* ---- the attachment ----
   A file tile inside the bubble, above the text, where a chat client puts an
   attachment. It exists to make one claim checkable: the page says the words
   are sent before you have to use them, and a document with a name on it is
   cheaper proof of that than another sentence saying so.

   Tinted rather than white so it reads as a thing inside the message and not
   as a second message: a white tile on a white bubble is invisible, and a
   bordered one is a form field. */
.bub-doc {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 2px 0 8px;
  padding: 8px 11px;
  border-radius: 11px;
  background: var(--green-tint);
}
.bub-doc-ico {
  display: grid;
  place-items: center;
  width: 28px; height: 28px;
  flex: 0 0 auto;
  border-radius: 8px;
  background: var(--white);
  color: var(--green-deep);
}
.bub-doc-body { min-width: 0; display: grid; gap: 1px; }
/* Both lines clip. A wrapped file name turns a two-line tile into a four-line
   one and the bubble stops looking like a message with a file in it. */
.bub-doc-name,
.bub-doc-meta {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bub-doc-name {
  font-size: 13px;
  font-weight: 700;
  line-height: 1.25;
  color: var(--deep);
}
.bub-doc-meta {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--stone);
}

/* Reactions. A chip hanging off the bottom-left corner of its bubble and
   overlapping it. It has to be out of flow to overlap the bubble's own
   background at all, so the row it belongs to reserves the overhang instead —
   otherwise the chip would sit on top of the next message. */
.bub-react {
  position: absolute;
  left: 10px;
  bottom: -13px;
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--white);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--deep) 22%, transparent);
  font-size: 13px;
  line-height: 1.55;
  letter-spacing: 1px;
}
.chat .bubrow:has(.bub-react) { margin-bottom: 12px; }
/* The tally beside the emoji. Five faces and a number is what a message a
   roomful of people reacted to looks like; five faces alone is what a message
   five people reacted to looks like, and the second is a smaller room. The
   letter-spacing on the chip is there to separate the emoji, so it is
   cancelled here or the digit sits adrift of them. */
.bub-react-n {
  margin-left: 3px;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--stone);
  letter-spacing: 0;
}

/* ---- where the avatar sits in a run ----
   In the reviews thread the avatar is on the FIRST message of a run and the
   continuations are indented past it. In the hero chat it is on the LAST:
   one face at the foot of everything Sasha just said, level with the bottom
   bubble, which is what WhatsApp and Telegram do and what reads as a person
   finishing a thought rather than starting one.

   So the two classes swap jobs here, and only here. The run's opening row
   takes the indent .bubrow--cont normally carries, and the continuation row
   gives it back because it has an avatar to fill the space. */
/* The indent is a pseudo-element the width of an avatar rather than
   padding, so both rows are the same flex geometry and `.bub`'s
   `max-width: calc(100% - 40px)` resolves against the same width in each.
   With padding it resolved against a box 35px narrower and the run's two
   bubbles could stop at different right edges. */
.chat .bubrow--runtop::before {
  content: "";
  width: 27px;
  flex: 0 0 auto;
}
.chat .bubrow--runtop { padding-left: 0; }
.chat .bubrow--cont { padding-left: 0; }

/* Two media blocks stood here until 2026-08-21, hiding .bub--extra below
   900px and .bub--last on a short landscape phone. Both are gone with the
   messages they hid: the thread is a banner and two bubbles now, roughly a
   third of the height that made those rules necessary, and nothing in it is
   spare enough to be worth hiding at a width. The chat is still removed
   outright below 640px — that rule is up with the .chat block. */


/* ==================== "not your typical speaking club" ====================
   The comparison is redesign.css's .skip-brew component, reused whole. Two
   rules are added here and nowhere else, both of them about the fact that
   this page's two columns are not the same length — unlike the main site's,
   where the four items on each side match up.

   Letting the cards stretch to a shared height put about 90px of nothing
   under "No preparation." and made the left card look like a card someone had
   forgotten to finish. Sized to their own content, the empty column IS the
   point: four fragments against four sentences, and the shape of the two
   blocks says it before either is read. */
.sunday .skip-brew { align-items: start; }

/* Below 640px it is redesign.css's .sb-carousel instead: one card per screen,
   the next one peeking, a dot rail underneath — the same swipe the main site
   gives this component. Stacked, the short card on top read as an
   introduction to the long one rather than as its opposite, which is the one
   thing this section must not do. Swiped, they are two answers to the same
   question and the reader arrives at them one at a time.

   align-items: start applies here too, unlike on the main site's copy of this
   component. A carousel normally wants its slides stretched to a common
   height, but these two are four fragments against four sentences: stretched,
   the short card carried 180px of empty white below "No preparation." with
   nothing on screen to explain why. Sized to its content it simply ends. The
   top edges are aligned either way, so nothing jumps as it slides in. */
@media (max-width: 639.98px) {
  .sunday .skip-brew { gap: 14px; }
}
/* 640–719.98px is the one range with neither: the grid is still one column
   (redesign.css goes two-up at 720) and the carousel is already off. Stacked
   cards with a clear gap, which is what it was before any of this. */
@media (min-width: 640px) and (max-width: 719.98px) {
  .sunday .skip-brew { gap: 16px; }
}

/* ---- the heading, edge to edge on one line ----
   Set outside .container (see the template) so nothing caps it, and sized off
   the viewport rather than off the type scale. Not a .h2: the scale tops out
   at 46px and this is the one heading on the page allowed to be the size of
   the page.

   THE OPTICAL SIZE AXIS HAS TO BE PINNED FOR ANY OF THIS TO WORK. Fraunces is
   a variable font with an `opsz` axis and the browser applies it
   automatically, so the face gets measurably narrower as it gets bigger: this
   string measures 16.09x its font-size at 30px and 13.13x at 100px. One vw
   coefficient against a moving ratio cannot fit — tuned at 1280px it
   overflowed a 640px window by 90 pixels, which is how this was found. Pinned
   to `opsz 144` the ratio is a flat 13.312 at every size, and that is the
   right end of the axis to pin to: a headline this big wants the display cut,
   not body letterforms enlarged. `wght` is restated because
   font-variation-settings overrides font-weight.

   So 100 / 13.312 = 7.51vw would fill the window exactly. 6.1vw fills 93% of
   it, and the other 7% is not slack for its own sake — `vw` counts the
   scrollbar and the layout does not, so a coefficient that fits on macOS
   overflows on Windows. nowrap makes it one line; the coefficient makes that
   line fit.

   THE COEFFICIENT IS SET BY THE LONGEST ROTATING PHRASE, NOT BY THE ONE THE
   HEADLINE ENDS ON. The last phrase rotates once through three (see .rot
   below), and the line must fit every one of them or a phrase mid-sequence
   wraps and the section jumps. Measured at font-size 100px, upright Fraunces
   pinned to opsz 144 / wght 600 at -0.035em:

     "This is not your typical " ............ 815.0
     + "speaking club" ...................... 1318.3   (the resting state)
     + "conversation class" ................. 1478.7
     + "language exchange" .................. 1517.1   <- the binding one

   1517.1 is what 6.1vw is derived from. Note that the longest phrase is
   "language exchange" and not the one with the most characters. Add or edit a
   phrase in compare.rotate and re-measure; this headline cost 7.05vw (a 13%
   larger face) before the rotation, and that is the price of the joke.

   overflow-x: clip is for the font-swap window only. Georgia, the fallback,
   is ~23% wider than the pinned Fraunces, so for the few milliseconds before
   the woff2 lands (it is preloaded, so it is few) the line is too long. Clip
   it there rather than let it hand the whole page a horizontal scrollbar. It
   is also the backstop for the rotator, whose slot overflows to the right for
   a few frames of each transition. */
.compare-title {
  margin: 0 0 clamp(30px, 4vw, 52px);
  padding-inline: clamp(10px, 1.6vw, 22px);
  text-align: center;
  line-height: 1;
  letter-spacing: -0.035em;
  font-variation-settings: "opsz" 144, "wght" 600;
  overflow-x: clip;
}
@media (min-width: 640px) {
  .compare-title {
    white-space: nowrap;
    font-size: 6.1vw;
  }
}
/* A phone cannot have both. One line of 43 characters at 390px is 18px type —
   smaller than the body copy under it — so below 640px it breaks between the
   static half and the rotating one, and each line is set as large as ITS OWN
   length allows. 10.7vw puts "This is not your typical" (815.0) at 87% of the
   window; 1.16em puts the longest phrase (702.1) at the same 87%, so the two
   lines stack flush at the widest point of the sequence. They do not stay
   flush — "speaking club" is 72% of the width of the line above it, and that
   is simply what a rotating word costs. The line is sized so the widest
   phrase fits, and the shortest is the one you are left looking at. */
@media (max-width: 639.98px) {
  .compare-title {
    display: grid;
    justify-items: center;
    font-size: 10.7vw;
  }
  .compare-title .rot { font-size: 1.16em; }
}

/* ---- the argument (.manifesto, in #different) ----

   ONE PARAGRAPH, SET AS A SECOND LEAD LINE. Every declaration on .mf-p below
   is copied from .lead-line further down this file, on purpose: same face,
   size, weight, colour, centring and measure as the sentence under the hero
   card. The two are the only centred body copy on the page and they bookend
   the argument — the first says what the thing is, this one says why it
   exists — so if you restyle one, restyle the other or they stop rhyming.

   IT CANNOT JUST USE .lead-line. That class carries a min-width:1160px rule
   forcing its sentence onto ONE line at a font size derived by hand from that
   specific string's width (60.1px of width per 1px of size). This copy is
   about four times longer and will never fit; the class would push it out
   through the container's padding. So the look is mirrored and that one rule
   is left behind. Keep the two blocks in sync by hand — there are seven
   declarations and a shared class would have to carry the nowrap rule too.

   HOW IT GOT HERE, so nobody re-derives it: this was four paragraphs in two
   uneven columns with a hairline between them, then four paragraphs with a
   name band boxed by two rules, then four paragraphs in one wide column at
   one size. Daniil's read on every version was that the section looked
   random. It did: four short blocks that each differ from their neighbour
   read as assembled parts, and the very next object in the section is a pair
   of bordered cards, so the arrangement was also competing with the thing it
   exists to introduce. There is now exactly one type style in the block and
   nothing left to arrange. Do not reintroduce a rule, a column, a size step
   or a second colour without a better reason than "the parts run together" —
   that reason has now been tried three times. */
.manifesto {
  margin: 0 auto clamp(34px, 5vw, 56px);
}
/* Centres the "keep reading" button under .mf-p on a phone: .mf-p carries its
   own text-align, which does not reach a button sitting beside it as a
   sibling in .mf-wrap. */
.mf-wrap { text-align: center; }

/* FULL WIDTH OF THE COMPARISON CARDS BELOW, at Daniil's call. .manifesto is
   already the same box as .skip-brew (both are the direct child of
   #different's .container, so both land on 1100px at desktop); this drops the
   42em cap that was holding the paragraph to 756px inside it, so the argument
   and the evidence it introduces now share one left and one right edge. That
   alignment is the whole point of the change - two centred blocks of
   different widths stacked on each other read as two unrelated things.

   TYPE MATCHES .lead-line EXACTLY - size, weight, line-height, tracking and
   colour, including .lead-line's own step down to 18px above 1160px. Daniil's
   call, and it is the second time these two have been pulled back into line:
   they are the only centred body copy on the page and they bookend the
   argument, so any gap between them reads as a mistake rather than as a
   hierarchy. If you restyle one, restyle the other.

   THE COST, KNOWN AND ACCEPTED: at 1100px and 18px this runs about 122
   characters a line, well past the 65-75 that reads comfortably, and it is
   the reason the 42em cap existed in the first place. Three things make it
   survivable and all three are load-bearing - the block is only three lines
   deep at that width, so the long return sweep happens twice; the three
   maroon phrases give the eye landmarks on the way across; and below 430px it
   left-aligns. If this copy ever grows past four lines at desktop, the fix is
   to bring the WIDTH back in (max-width: 42em, as before), not to shrink the
   type - the type is now pinned to the lead line by decision. */
/* THREE PHRASES, IN MAROON. The problem, the answer and the action - read the
   maroon alone and the argument still stands, which is the test for whether a
   mark belongs. Do not add a fourth: at four, nothing is emphasised.

   MAROON, NOT THE CORAL THE LEAD LINE USES. #FF8071 is 2.45:1 on cream and is
   a knowingly-accepted exception up there for three tiny phrases in one short
   line (see the .lead-key note below); the same colour spread across a
   300-character paragraph would not be one. var(--maroon) is 12.7:1 and is
   already the accent of every heading on this page.

   The weight step is not decoration. It is what stops colour being the only
   thing marking a phrase, which is what WCAG 1.4.1 actually asks for - a
   reader who cannot separate the hues still sees three emphasised phrases.
   Keep it if the colour ever changes again.

   NO BACKGROUND, NO UNDERLINE, NO MARKER STROKE. This block had a yellow
   highlighter stroke for one afternoon and it turned three phrases into three
   objects sitting on top of the paragraph rather than parts of it. */
.mf-key {
  font-weight: 700;
  color: var(--maroon);
}

/* PHONES: STAY CENTRED. Daniil's call, 2026-08-22, reversing a left-align
   that lasted one build.

   The case for left was that this runs NINE lines at 390px, and nine centred
   lines is two ragged edges with no left margin for the eye to return to.
   The case against, which won: centred is what the block IS. It matches
   .lead-line at the top of the page, which is the whole design of it, and
   .lead-line stays centred on a phone too. Breaking that on the one screen
   where the two are closest together traded a typographic nicety for the
   thing a reader actually notices.

   The tracking step below is the same quarter-pixel-per-character .lead-line
   takes at this width: invisible as tracking, there purely to win line breaks
   on the one screen with no width to spare. */
@media (max-width: 430px) {
  .mf-p { letter-spacing: -0.015em; }
}

/* ---- the rotating phrase ----
   "This is not your typical ___" runs its last phrase through four values
   once and stops on the true one. sunday.js drives it; everything here is
   written so that the ANIMATION is what is optional, never the headline.

   THE RESTING STATE IS THE FINISHED SENTENCE. .rot-home is the only item in
   normal flow, so it alone gives .rot its width and its height, and it holds
   the phrase the headline has to read as. The other three are taken out of
   flow, start below the slot at opacity 0, and can only move once sunday.js
   has added .rot-on — which it does not do without JS, under
   prefers-reduced-motion, or on a page whose <html lang> is not English. In
   every one of those cases this renders as plain text and nothing moves.

   clip-path AND NOT overflow: hidden. The slot sits inline in the middle of a
   headline and has to share its baseline with the words either side of it, and
   an inline-block with `overflow` other than visible takes its baseline from
   its bottom margin edge instead of from its text — which drops the phrase
   about a quarter of an em below the rest of the line. clip-path clips
   without touching baseline alignment. The insets are outsets: the box is one
   em tall at line-height 1, so the top and bottom are opened by .10em and
   .20em to clear ascenders and descenders, and the ±120% travel puts a moving
   item past even that. The right is opened all the way because the incoming
   phrase can be wider than the slot for the first frames of a transition (the
   slot is still growing), and it has nothing to its right to collide with —
   it is the last thing in the headline. The left is held nearly closed for
   the opposite reason: "typical" is there.

   The items are anchored left rather than centred so that a phrase wider than
   the slot can only ever spill right, into that empty space, instead of
   across the static half of the line. At rest the slot is exactly the width
   of the phrase, so left-anchored and centred are the same thing.

   The width transition is expo-out on purpose. It is what keeps the centred
   line from jumping when the phrase changes length, and the front-loaded
   curve gets the slot to nearly its full width inside the first ~100ms, while
   the incoming phrase is still low and still transparent — so the spill it
   was sized to allow is over before there is anything to see. */
.rot {
  position: relative;
  display: inline-block;
  clip-path: inset(-0.10em -100vw -0.20em -0.04em);
}
.rot-item {
  /* display: block IS LOAD-BEARING, on .rot-home especially, and for two
     reasons that both look like something else when they break.

     A <span> is inline, transforms do not apply to non-replaced inline boxes,
     and the browser drops them silently — so .rot-home, the one item that is
     not absolutely positioned (abspos blockifies the other two for free),
     would sit still while everything around it moved, and only opacity would
     animate. The phrase would fade in place instead of sliding out.

     And an inline .rot-home leaves the newlines between these spans as
     real collapsed spaces in the flow, which pushed the resting phrase a
     few space-widths to the right of its own slot and made .rot's auto width
     that much too wide. As blocks, the whitespace between them is dropped,
     which is also what makes the no-JS width correct. */
  display: block;
  position: absolute;
  inset: 0;
  white-space: nowrap;
  opacity: 0;
  transform: translateY(120%);
  /* The three alternates are decoration for the eye only: aria-hidden in the
     markup so the heading has one accessible name, and unselectable so it has
     one name when it is copied, too. */
  user-select: none;
}
.rot-home {
  position: relative;
  opacity: 1;
  transform: none;
  user-select: auto;
}
.rot-item.is-on  { opacity: 1; transform: none; }
.rot-item.is-out { opacity: 0; transform: translateY(-120%); }

.rot.rot-on { transition: width 460ms cubic-bezier(.16, 1, .3, 1); }
.rot.rot-on .rot-item {
  transition: transform 460ms cubic-bezier(.22, .61, .36, 1), opacity 260ms ease;
}
/* sunday.js already refuses to add .rot-on here, so this is a backstop for
   the case where the setting changes after the sequence has started. */
@media (prefers-reduced-motion: reduce) {
  .rot.rot-on,
  .rot.rot-on .rot-item { transition: none; }
}

/* This section used to carry a bespoke padding pair — a trimmed top and a
   bottom of ZERO — because the lead line followed it and was meant to read as
   its closing sentence. The lead line moved up under the hero on 2026-08-21
   and the pairing went with it, so the section is back on the page's one
   rhythm like every other. Nothing here overrides --sec-pad. */

/* ============================ the lead line ============================
   The sentence that says what this is. It took the place of a three-fact
   strip — "60 minutes / Every Sunday / Completely free" — which in turn had
   replaced a whole section of figure cells. Each step was the same trade: the
   same facts, less of the reader's scroll.

   It has moved twice. It was the hero card's caption until 2026-08-20, then
   its own section under the comparison, and now it is the comparison's
   closing line: Daniil's note was that after two columns of what a club is
   and is not, the sentence that says it is free and eighty minutes long is
   doing the job of a call to action, so it should look like it belongs to
   them.

   THE ASYMMETRY IS THE WHOLE MECHANISM, and it is the only asymmetry left on
   the page. Every section sits on the same white field — .surface-cream is
   overridden to white on .sunday — so there is no band, no rule and no colour
   change anywhere to group things with. Spacing is the only grouping signal
   the page has, which means what is above a block and what is below it is not
   decoration; it is the sentence's punctuation.

   Above: half of --sec-half from the hero card, half of it from here, so the
   two are half a step apart and read as a card and its caption. Below: the
   full step, like every other pair of sections on the page. The sentence
   belongs to the card above it, not to Sasha below it. */
.sunday .section.lead-sec { padding-block: calc(var(--sec-half) / 2) var(--sec-pad); }

/* max-width in em rather than px so the measure tracks the font size instead
   of fighting it. 42em is about 72 characters, which lands this sentence on
   two lines rather than three everywhere below the nowrap breakpoint. That is
   past the 65-character guideline by a few characters, deliberately: the
   guideline is about a reader's eye finding the next line down a paragraph,
   and this is one centred sentence with nothing under it to lose. The third
   line was costing 30px directly above the fold. */
.lead-line {
  max-width: 42em;
  margin-inline: auto;
  text-align: center;
  font-size: clamp(17px, 2vw, 20px);
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: -0.005em;
  text-wrap: balance;
  color: color-mix(in srgb, var(--deep) 66%, transparent);
}
/* PHONES ONLY: stop balancing, and tighten the tracking a hair.

   text-wrap: balance is right for this sentence at every width where it
   settles on two lines — it evens the pair instead of leaving a stub under a
   full line. It is wrong the moment the sentence needs THREE, which is every
   phone: balance minimises the longest line, so at 390px it produced lines of
   65% / 74% / 77% of the measure — a narrow centred column with 120px of
   empty gutter beside the first line, on the one screen size that has no
   width to give away. Greedy wrapping on the same box is 90% / 94%.

   The letter-spacing step is not cosmetic. At 360px, "completely" misses the
   end of line one by about a pixel at -0.005em, so the line breaks after
   "Club" and fills 71%; -0.015em pulls the word up and the line fills 98%.
   It is a quarter of a pixel per character and invisible as tracking — it is
   here purely to win that break. Re-measure at 360 if the copy changes.

   `wrap`, NOT `pretty`, and that is measured rather than assumed. `pretty` is
   usually the right upgrade over a bare reset, but it avoids short LAST
   lines, and .after-next ends on "...two options for you:" — a short tail
   that pretty fixes by pulling the first line back in. Measured, that left it
   at 61% fill, i.e. exactly the problem balance was causing. Plain greedy
   wrapping gets both lines to 86%. If the copy changes, re-measure before
   putting pretty back.

   430px because that is the widest phone in portrait and also where the
   sentence starts fitting on two lines — above it balance is doing its job
   again, and past about 480px greedy wrapping leaves "80 minutes." stranded
   on a line of its own. */
@media (max-width: 430px) {
  .lead-line {
    text-wrap: wrap;
    text-wrap: pretty;
    letter-spacing: -0.015em;
  }
}

/* One line from 1160px up, which is where there is finally room for it.
   The arithmetic, because it is the kind that gets broken by a later tweak:
   the sentence is 60.1px of width per 1px of font-size, and the container is
   min(1180, 100vw) minus its 80px of padding. 1.5vw stays under that at every
   width in this range — 1046px of a 1080px container at 1160, 1081 of 1100
   once the container stops growing — and the 18px ceiling is what holds it
   there on a very wide screen. It is 18 rather than the 18.5 it was because
   the copy got longer: at 18.5 the line is 1111px and spills 11px into the
   container's own padding, which nothing clips but which is not the measure.

   Below 1160 it wraps to two lines and keeps its 32em measure, because one
   line at those widths means a font size small enough to lose the sentence.
   Change the copy and re-measure: a few words longer and this stops fitting,
   in which case lower the ceiling or drop this block and let it wrap. */
@media (min-width: 1160px) {
  .lead-line {
    max-width: none;
    white-space: nowrap;
    font-size: min(18px, 1.5vw);
  }
}
/* Brand coral, at Daniil's call, on a page whose only other coral is the cup
   in the headline and the step-2 disc in the booking card.

   KNOWN AND ACCEPTED: #FF8071 on white is 2.45:1. That is under the 4.5:1
   WCAG asks of body text and under the 3:1 it asks of large text, so these
   three phrases are the one piece of copy on the page that does not clear the
   bar. Two things keep the damage contained and both are load-bearing — do
   not "simplify" either away:

     1. The weight step (500 → 700) is still here. Colour is therefore never
        the only thing marking a phrase, which is what WCAG 1.4.1 actually
        requires, and someone who cannot separate the hues still sees four
        emphasised phrases.
     2. The rest of the sentence stays at 66% of --deep, 7.2:1. The sentence
        can be read without reading the coral. Nothing is said in coral alone
        — every one of these three facts is repeated in the booking steps.

   If this ever has to pass an audit, the fix is one line: swap var(--coral)
   for var(--maroon), which is 12.7:1 and is already the accent colour of
   every headline on the page. */
.lead-key {
  font-weight: 700;
  color: var(--coral);
}

/* ---------------------------------------------------------------------
   The easter egg trigger.

   Lives on the footer tagline now — "Start speaking, while you're still
   brewing." already says the joke's punchline, so the whole line doubles as
   the button. This is a <button>, and every one of these declarations exists
   to make it stop looking like one: it must be indistinguishable from the
   text it replaced, because the joke is that nothing tells you it is there
   until your cursor is over the words.

   Note what is NOT reset: font-family, font-weight and colour all come from
   .accent-serif, so the button inherits the same maroon display face the
   tagline has. `font: inherit` would be the usual reset here and is wrong
   for exactly that reason — it would win over .accent-serif (equal
   specificity, later in the cascade) and hand the phrase back to the body
   face.

   .accent-serif also forces font-style: normal, which would silently
   de-italicise the button even though the rest of .sfoot-tag is italic —
   .sfoot-tag .statement-egg below puts the italic back.

   display: inline, not the button default of inline-block, so it sits in
   the tagline's text flow rather than as its own box. */
.statement-egg {
  display: inline;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  -webkit-appearance: none;
  appearance: none;
  font-size: inherit;
  line-height: inherit;
  letter-spacing: inherit;
  text-align: inherit;
  cursor: pointer;
  /* The affordance, and the only one. An underline that is there but
     transparent, so hovering changes a colour and never a metric: the
     sentence cannot reflow under the cursor. Dotted rather than solid
     because solid reads as a link, and this does not navigate anywhere.
     text-decoration follows line boxes, so it survives the phrase wrapping
     — which a positioned pseudo-element would not. */
  text-decoration: underline dotted transparent;
  text-decoration-thickness: .06em;
  text-underline-offset: .16em;
  transition: text-decoration-color .18s ease;
}
.statement-egg:hover { text-decoration-color: color-mix(in srgb, var(--maroon) 45%, transparent); }
.sfoot-tag .statement-egg { font-style: italic; }
/* Keyboard users get a real ring, not the hover hint: they cannot see where
   a cursor is, so "something is here" has to be unambiguous. */
.statement-egg:focus-visible {
  outline: 3px solid var(--maroon);
  outline-offset: 4px;
  border-radius: 4px;
}
@media (prefers-reduced-motion: reduce) { .statement-egg { transition: none; } }

/* ---------------------------------------------------------------------
   The egg itself.

   A native <dialog> opened with showModal(), which is doing more work than
   it looks: the focus trap, Escape, making the page behind it inert and
   painting in the top layer are all browser behaviour rather than code. The
   top layer is the part that matters most here — it means this can never
   land behind the Cal.com booking modal.

   The <dialog> element ships with a border, a padding and a white
   background, so most of this is undoing that. */
.egg {
  max-width: min(400px, 88vw);
  margin: auto;
  padding: 10px;
  border: 0;
  border-radius: var(--r-xl);
  background: var(--cream);
  box-shadow: 0 30px 80px color-mix(in srgb, var(--deep) 40%, transparent);
  overflow: visible;
}
.egg::backdrop {
  background: color-mix(in srgb, var(--deep) 74%, transparent);
}
.egg-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: calc(var(--r-xl) - 6px);
}
/* In the hand, like the note beside the hero button — it is an aside, not a
   caption, and the face is what says so. */
.egg-caption {
  margin: 10px 6px 4px;
  text-align: center;
  font-family: var(--font-hand);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.2;
  color: var(--deep);
}
/* Outside the top-right corner rather than over the picture: the meme is
   three panels and every one of them has something in the corner. 44px is
   the touch-target floor, and the visible disc is smaller than the hit area
   on purpose. */
.egg-close-form { position: absolute; top: -14px; right: -14px; margin: 0; }
.egg-close {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--deep);
  color: var(--cream);
  cursor: pointer;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--deep) 45%, transparent);
  transition: background-color .18s ease;
}
.egg-close:hover { background: var(--maroon); }
.egg-close:focus-visible { outline: 3px solid var(--cream); outline-offset: 3px; }

@media (prefers-reduced-motion: no-preference) {
  .egg[open] { animation: eggIn .26s cubic-bezier(.2, .8, .25, 1); }
  .egg[open]::backdrop { animation: eggFade .26s ease; }
}
@keyframes eggIn {
  from { opacity: 0; transform: scale(.94) translateY(10px); }
  to   { opacity: 1; transform: none; }
}
@keyframes eggFade { from { opacity: 0; } to { opacity: 1; } }

/* The .level-picker dialog and its three .level-row tints stood here until
   2026-08-21. They routed a CTA click to one of three per-level Cal.com
   events; the club is one event again, so the dialog, its markup, its copy
   and the LEVELS array in sunday.js all went together. Git has the set —
   restore it whole or not at all. */

/* ============================ split section heads ============================
   Heading on the left, the sentence that qualifies it on the right, with a
   hairline under both. Two columns rather than a stack because these two
   sections open onto a horizontal object — a card track, a schedule bar —
   and a centred or stacked head above a horizontal thing wastes the full
   width twice before the thing itself gets to use it.

   No rule under the head. There was one — it drew the join between the head
   and the object beneath it — but with the hour section now carrying its own
   cream band, the two sections are already separated by colour, and a
   hairline as well was one boundary too many. The padding below does the
   same job quietly. Below 900px it all stacks, because two columns of 40em
   would be two columns of four words. */
.split-head {
  /* redesign.css caps .section-head at 720px, which is right for a stacked
     head and wrong for a two-column head that has to reach both edges. */
  max-width: none;
  padding-bottom: 22px;
}
.split-head .lead { margin-top: 12px; }
.split-aside { display: contents; }
@media (min-width: 900px) {
  .split-head {
    display: grid;
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
    align-items: end;
    column-gap: clamp(32px, 5vw, 72px);
    padding-bottom: 26px;
  }
  /* The right column is one block again here, so whatever it holds stacks
     inside it instead of being auto-placed into the grid one after another. */
  .split-aside { display: block; }
  /* Aligned to the bottom of the headline rather than the top: the two
     blocks are different heights and a shared baseline at the foot is the
     only alignment a reader actually perceives here. */
  .split-head .lead {
    margin-top: 0;
    max-width: 34em;
    padding-bottom: 0;
  }
}

/* ============================ inside the club ============================
   A static, proportional schedule bar. It was an accordion of five buttons
   with a paragraph behind each until 2026-08-10; the paragraphs and the JS
   that opened them are both gone. Nothing in here is focusable, hoverable or
   scripted, which is why there are no :hover, :focus-visible or
   [aria-expanded] rules below — a block that changes under the cursor but
   does nothing on click is the worst of both.

   The proportion is the whole idea. Each item is `flex: var(--span) 1 0` with
   --span set inline from the content file in 5-minute units, so the widths on
   screen are the real minutes rather than five equal boxes pretending to be.
   `min-width: 0` on the item is load-bearing: flex children floor at their
   content's min-width by default, so without it "Sharing thoughts with the
   group" would widen its own column and the bar would silently stop being to
   scale.

   From 1000px the time and its label are both inside the tinted block, which
   is the only arrangement where the five blocks are guaranteed the same
   height. With the label under the bar the bar was `flex: 1 1 auto` in a
   stretched item, so it grew to eat whatever the label left over: "Deeper
   discussion or debate" fits on one line where the others take two, so that
   one block hung a line lower than its neighbours. Inside the block there is
   nothing left to absorb — the block is the item, and the row has one bottom
   edge.

   That costs the narrow end. 70–80 is 2/16 of the bar, about 96px of text
   width on a large screen and 77px at 1000px, so its label is set a size
   smaller than the phone one and is the reason the labels are what they are:
   they are written to wrap to two lines in the narrowest column, not chosen
   and then hoped for.

   Below 1000px there is not enough width for any of that to be legible, so it
   stacks into rows and drops the proportion rather than shrinking it into a
   lie — and there the label goes back beside the block, not in it. Same
   markup at both sizes. */

/* The section used to be a full-bleed cream band. Edge to edge it read as
   "the page changes colour here" rather than as a thing; contained and
   rounded it reads as one object, which is what the section is arguing —
   that the club is a container with a plan in it. Cream rather than white
   because the five tinted blocks inside need a surface to sit on, and on
   white they would be five floating colours. */
.hour-box {
  background: var(--cream);
  border-radius: var(--r-xl);
  padding: 32px 20px 34px;
}
@media (min-width: 720px)  { .hour-box { padding: 30px 40px 42px; } }
/* Tighter than the other boxes on the page on purpose. This section is the one
   that has to fit a laptop screen whole — heading, slide, rail and controls —
   because its controls are the only thing telling a reader there are seven
   more slides, and a control below the fold may as well not exist. The last
   ~20px of that budget comes out of here rather than out of the section's own
   padding, which is load-bearing: .surface-cream is white on this page, so the
   gaps between sections are the only thing grouping them. */
@media (min-width: 1060px) { .hour-box { padding: 34px 56px 48px; } }
/* .section-head ships with a max-width for a page-width heading. Inside the
   box it is already inset by the box's own padding, so it takes the full
   width of it and the split layout keeps its two columns. */
/* Centred, unlike every other heading on the page, because what follows it is
   a symmetrical object running the full width of the box rather than a column
   of text starting at the left margin. A left-aligned heading over a centred
   bar reads as two different grids. */
.hour-box .section-head { max-width: none; text-align: center; }
/* .section-head ships with no bottom margin because it is normally followed by
   a block with a top margin of its own. The deck has none — its first row is
   the rail and the slide frame, both flush — so the heading ended up sitting
   directly on top of the slide and reading as part of it. The gap is put here
   and taken back off .hour-box's padding-top above, so the heading moves UP
   the card as the deck moves down and the section keeps its height. */
.hour-box .section-head { margin-bottom: 18px; }
@media (min-width: 1060px) { .hour-box .section-head { margin-bottom: 26px; } }

/* ---- the deck: rail beside slide ----
   The shape of this section is one decision: at >=1060px the rail is a COLUMN
   to the left of the slide rather than a row above it. That is worth three
   things at once.

   Height. The frame is locked to 16:9, so its height is purely a function of
   the slide's width. Above the slide the slide was ~988px wide and the frame
   556px, and the section came to 1232px — taller than a laptop viewport, so
   the arrows and the progress at the foot sat below the fold and a reader
   arriving mid-section saw one photo with no sign there were seven more.
   Beside the slide the slide is ~740px and the frame ~416px, and the rail
   stops costing a row of its own. That is ~250px off the section for free.

   Legibility. Eight labelled steps across the top would be eight columns of
   ~90px. Eight stacked rows read like a contents page, which is what they are.

   Room for the sticky note, which would otherwise be dead space under a
   vertical rail.

   Below 1060px there is no width for a side column, so the whole thing goes
   back to one column and the rail collapses to dots — see the mobile block at
   the end. Same markup at both sizes; nothing is duplicated or hidden. */
/* minmax(0, 1fr), not the implicit `auto`. Below 1060px .deck-side is
   `display: contents`, so the rail becomes a grid item here — and an `auto`
   column sizes to its widest item's MAX-CONTENT, which for a flex row is the
   sum of its children regardless of flex-wrap. Eight dots then demanded 636px
   and pushed the whole page into horizontal scroll at phone widths. The floor
   of 0 lets the column be as narrow as the viewport and the rail wrap into it. */
.deck {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 22px;
}

/* ---- the "80 minutes" pill ----
   The one fact the seven slides never state. They show what happens; none of
   them shows how long it takes, and "how much of my evening is this" is asked
   before "what is in it". It replaced the four-phase timings footnote that
   used to sit under the slide — that footnote was a second list beside a rail
   that already names every beat in order, and its one piece of information the
   rail could not carry was the number.

   White on the cream .hour-box, which is the one place on this white page
   where cream appears — so a white pill reads as an object sitting on a
   surface rather than as a rectangle drawn on the page. Not yellow and not
   green: on this page yellow means press me, and the Join chip in the hero has
   green. This is a label and must not look like either.

   It sits above the rail in the left column at desktop. Below 1060px
   .deck-side is display:contents and its children are ordered against the
   slide — the rail is order 1, so this keeps the default 0 and lands first
   without needing a number of its own. */
.deck-badge {
  justify-self: center;
  width: fit-content;
  margin: 0;
  padding: 8px 15px;
  border-radius: var(--r-pill);
  background: var(--white);
  box-shadow: 0 4px 14px color-mix(in srgb, var(--deep) 10%, transparent);
  color: var(--green-deep);
  font-size: 13.5px;
  font-weight: 700;
  letter-spacing: -0.005em;
  line-height: 1.3;
  white-space: nowrap;
}
@media (min-width: 1060px) {
  /* In the left column the pill is the top of a stack, so it aligns with the
     rail under it rather than centring over a 200px column. */
  .deck-badge { justify-self: start; }
}
@media (max-width: 1059.98px) {
  /* .deck-side is display:contents here, so the pill is a grid item of .deck
     in its own right and needs its own bottom gap: the rail's dots follow it
     directly and the two would otherwise touch. */
  .deck-badge { margin-bottom: 4px; }
}

/* ---- the rail ----
   Table of contents and navigation in one object. It is the piece that makes
   this a walkthrough rather than a gallery: the shape of the section is
   legible before the first slide has been read.

   Real <button>s in a real <ol>. The order is the content, the controls are
   controls, and with JS off they do nothing while the track still scrolls. */
.deck-rail {
  --rail-gap: 12px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0;
  margin: 0;
  padding: 0;
  list-style: none;
}
.deck-railitem { position: relative; display: grid; }
.deck-step {
  display: grid;
  align-items: center;
  padding: 17px 10px;
  border: 0;
  background: none;
  cursor: pointer;
  color: var(--stone);
  text-align: left;
  transition: color .2s ease;
}
/* Opaque and above the connector: at desktop the line runs the full height of
   the item and the disc punches through it, which is what makes the rail read
   as one continuous line with eight stops rather than eight dashes. */
.deck-step-num {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--warm-gray);
  border: 0;
  font-family: var(--font-display);
  font-size: 0;
  font-weight: 600;
  color: transparent;
  transition: background-color .25s ease, border-color .25s ease, color .25s ease, transform .25s ease;
}
.deck-step-label {
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .01em;
  line-height: 1.25;
}
.deck-step:hover { color: var(--deep); }
.deck-step:hover .deck-step-num { background: var(--green-deep); }
.deck-step:focus-visible { outline: 2px solid var(--green-deep); outline-offset: 3px; border-radius: 8px; }

/* The step in view. aria-current is the state — styling off the attribute
   rather than off a class means the visual and the announced state cannot
   drift apart, because there is only one of them. */
.deck-step[aria-current="true"] { color: var(--deep); }
.deck-step[aria-current="true"] .deck-step-num {
  background: var(--green-deep);
  transform: scale(1.35);
}

/* Dots on a phone, a numbered list at desktop. Below 1060px the labels are
   VISUALLY hidden rather than removed: the buttons keep their accessible names
   and their tab order, and the current step is named in full by the counter
   above the slide instead. */
@media (min-width: 1060px) {
  .deck-rail {
    display: block;
  }
  .deck-railitem + .deck-railitem { margin-top: var(--rail-gap); }
  /* The connector runs the whole item and out through the gap below it, so
     consecutive items join up regardless of whether a label wraps to two
     lines. First and last are trimmed to their own disc so the line begins and
     ends on a stop rather than in mid-air. 12px = the 26px row's centre, less
     the line's own 2px. */
  .deck-railitem::before {
    content: "";
    position: absolute;
    left: 12px;
    top: 0;
    bottom: calc(-1 * var(--rail-gap));
    width: 2px;
    border-radius: 2px;
    background: var(--hairline);
  }
  .deck-railitem:first-child::before { top: 13px; }
  .deck-railitem:last-child::before { bottom: auto; height: 13px; }

  .deck-step {
    grid-template-columns: 26px 1fr;
    align-items: center;
    gap: 12px;
    padding: 0;
    min-height: 26px;
  }
  .deck-step-num {
    width: 26px; height: 26px;
    background: var(--cream);
    border: 1px solid var(--hairline);
    font-size: 13px;
    color: var(--stone);
  }
  .deck-step:hover .deck-step-num { background: var(--cream); border-color: var(--green-deep); }
  .deck-step[aria-current="true"] .deck-step-num {
    background: var(--green-deep);
    border-color: var(--green-deep);
    color: var(--white);
    transform: none;
  }
  .deck-step-label { font-size: 15px; }
}
@media (max-width: 1059.98px) {
  .deck-step-label {
    position: absolute;
    width: 1px; height: 1px;
    padding: 0; margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* ---- the sticky note ----
   Rule 2, quoted off the slide the reader is three clicks from seeing. The
   form is not a decoration: the decks themselves use yellow sticky notes
   throughout — the warm-up phrases, the whole-group board, the reflection wall
   — so this is the page quoting the class in the class's own handwriting.
   Caveat is already on this page (see .hour-hand and the founder cards).

   It sits under the rail at desktop, filling what would otherwise be dead
   space beside a 416px slide, and below the whole deck on a phone. Rotated
   just enough to read as placed rather than laid out; any more and it fights
   the grid it is sitting in. */
.deck-sticky {
  position: relative;
  margin: 0;
  max-width: 21em;
  padding: 22px 20px 16px;
  background: var(--yellow-tint);
  border-radius: 2px;
  transform: rotate(-2.2deg);
  box-shadow: 2px 7px 18px color-mix(in srgb, var(--deep) 12%, transparent);
}
/* The strip of tape, as on the agenda slide. It must be lighter than BOTH
   surfaces it crosses — the cream box above the note and the yellow note below
   it — or it disappears against the box and only its two side edges show,
   which reads as a pair of stray lines rather than as tape. Hence white at
   ~68%: translucent enough that the yellow shows through the lower half, opaque
   enough to sit on the cream. */
.deck-sticky::before {
  content: "";
  position: absolute;
  top: -9px; left: 50%;
  width: 64px; height: 21px;
  transform: translateX(-50%) rotate(2.6deg);
  background: color-mix(in srgb, var(--white) 68%, transparent);
  border-left: 1px solid color-mix(in srgb, var(--warm-gray) 34%, transparent);
  border-right: 1px solid color-mix(in srgb, var(--warm-gray) 34%, transparent);
  box-shadow: 0 1px 3px color-mix(in srgb, var(--deep) 8%, transparent);
}
.deck-sticky-quote {
  margin: 0;
  font-family: var(--font-hand);
  font-size: 25px;
  line-height: 1.16;
  color: var(--ink);
}

/* ---- the counter ----
   Phone only, and aria-hidden: it says exactly what the rail's aria-current
   already says. It exists because eight dots can show POSITION but cannot show
   a name, and the name is the half that tells you what you are looking at. */
.deck-counter {
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 8px;
  margin: 0 0 10px;
}
.deck-counter-num {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  color: var(--green-deep);
}
.deck-counter-step {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--deep);
}
@media (min-width: 1060px) { .deck-counter { display: none; } }

/* ---- the track ----
   One slide per view, snapped. A scroll container and nothing more: it scrolls
   with a finger, a trackpad, a scrollbar or the keyboard before any script
   runs, and sunday.js only adds the arrows, the counter and the rail syncing. */
.deck-track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 100%;
  gap: clamp(14px, 2vw, 22px);
  margin: 0;
  padding: 0;
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}
.deck-track::-webkit-scrollbar { display: none; }
.deck-track:focus-visible { outline: 2px solid var(--green-deep); outline-offset: 4px; }
.deck-cell { scroll-snap-align: center; }
.deck-slide { margin: 0; }

/* THE FRAME IS A FIXED 16:9 BOX AND THAT IS THE WHOLE POINT OF IT.
   The eight exports are re-encoded to exactly 16:9 by hand, so a fixed ratio
   crops nothing — and it means the track never changes height between slides,
   which is what stops the section jumping as it is scrolled. A slide exported
   at another shape is cropped rather than letterboxed; see sunday.en.json.

   The white mount and the hairline are what make a screenshot read as a slide
   rather than as part of the page: the deck's own background is the same paper
   cream as this box, so an untreated <img> would bleed into it.

   It is a <button> — it opens the slide in the lightbox — so the first four
   declarations are undoing the button and nothing more. The frame keeps the
   full 16:9 hit area rather than carrying a separate zoom affordance: on a
   phone the picture IS the target, and a corner icon over a slide would sit
   on top of the content the tap is there to reveal. */
.deck-frame {
  display: block;
  width: 100%;
  padding: 0;
  font: inherit;
  color: inherit;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--r-card);
  background: var(--white);
  border: 1px solid var(--hairline);
  box-shadow: 0 10px 30px color-mix(in srgb, var(--deep) 9%, transparent);
  cursor: zoom-in;
  transition: box-shadow .22s ease, border-color .22s ease;
}
.deck-frame:hover {
  border-color: var(--green-deep);
  box-shadow: 0 14px 36px color-mix(in srgb, var(--deep) 16%, transparent);
}
.deck-frame:focus-visible { outline: 2px solid var(--green-deep); outline-offset: 4px; }
/* No <dialog>, no lightbox: sunday.js disables the frames rather than leaving
   seven buttons that look tappable and do nothing. The slide is unchanged. */
.deck-frame[disabled] { cursor: default; }
.deck-frame[disabled]:hover {
  border-color: var(--hairline);
  box-shadow: 0 10px 30px color-mix(in srgb, var(--deep) 9%, transparent);
}
.deck-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Fixed min-height, not auto: without a floor the arrows and the timings below
   would step up and down as the track scrolls, and in a section built around
   never changing height that is the one thing that must not happen.

   Two floors, because the two breakpoints are solving opposite problems.

   At >=720px every caption is ONE line, and the floor is set just above one
   line rather than above two — 48px instead of 70px. That 22px matters: this
   section exists to fit a laptop screen whole and it clears it by about 10px.
   So a caption long enough to wrap at desktop does not merely look untidy, it
   reintroduces the exact jump the floor is here to prevent. Measured max at
   720–1440px is 46px today. If a caption has to grow, raise this to 4.4em and
   take the height back somewhere else; do not leave it at 3.0em and hope.

   Below 720px there is no fitting-the-viewport budget to protect and the
   captions wrap to two lines whatever they say, so the floor is simply set
   above the longest of them (66px at 390px) with room to spare. */
.deck-cap {
  margin-top: 14px;
  min-height: 4.6em;
}
@media (min-width: 720px) { .deck-cap { min-height: 3.0em; } }
/* The step name repeated on the slide itself, so a reader whose eye is on the
   slide is not made to look back at the rail to place it. */
.deck-cap-step {
  display: block;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--green-deep);
}
.deck-cap-text {
  display: block;
  margin-top: 5px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--stone);
}
@media (min-width: 900px) { .deck-cap-text { font-size: 15px; } }

/* ---- the foot: the two arrows ----
   It was a three-column grid with the four-phase timings footnote between the
   arrows until 2026-08-21. The footnote is gone (its one useful fact is the
   pill above the rail now), so the row is just the pair, centred under the
   slide where a pair of controls belongs. */
.deck-foot {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 14px;
  margin-top: clamp(14px, 1.8vw, 20px);
}
.deck-arrow {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border: 1px solid var(--hairline);
  border-radius: 50%;
  background: var(--white);
  color: var(--deep);
  cursor: pointer;
  transition: background-color .2s ease, border-color .2s ease, opacity .2s ease;
}
.deck-arrow:hover { background: var(--green-tint); border-color: var(--green-deep); }
.deck-arrow[disabled] { opacity: .32; cursor: default; }
.deck-arrow[disabled]:hover { background: var(--white); border-color: var(--hairline); }
/* The arrows are a convenience on top of a scroll container that already
   works, so wherever they are not the better control they come off — and now
   that they are the only thing in the row, the row goes with them rather than
   leaving a margin behind. Two conditions, not one: touch, where the finger is
   the better control; and any narrow viewport, where two 44px discs are 100px
   of vertical space spent on something a thumb does by swiping. The width rule
   catches a small window with a mouse attached, which `hover: none` does not.

   The deck still advances on its own at these widths — see the autoplay block
   in sunday.js — so a reader who never touches it still sees more than one
   slide. */
@media (hover: none), (max-width: 719.98px) {
  .deck-foot { display: none; }
}

/* ---- the two columns ----
   200px is the narrowest the rail reads at without "Final question" wrapping
   to two lines at 15px, measured rather than guessed. Everything left over
   goes to the slide, and the 16:9 frame takes its height from that.

   align-items: start so the side column does not stretch to the slide's
   height, which would push the sticky note to the bottom of it; the note is
   meant to sit just under the rail. */
@media (min-width: 1060px) {
  .deck {
    grid-template-columns: 200px minmax(0, 1fr);
    gap: 36px;
    align-items: start;
  }
  .deck-side { display: grid; gap: 30px; align-self: start; }
}
/* Below the two-column breakpoint the side column stops being a column at
   all, and `display: contents` promotes the rail and the note into the deck's
   own grid so they can be ordered around the slide independently: dots above
   it, note below the whole thing. Without this the note would be trapped
   directly under the dots and would push the slide down the screen. */
@media (max-width: 1059.98px) {
  .deck-side { display: contents; }
  .deck-rail { order: 1; }
  .deck-main { order: 2; }
  .deck-sticky { order: 3; justify-self: center; margin-top: 4px; }
}

@media (prefers-reduced-motion: reduce) {
  .deck-step,
  .deck-step-num,
  .deck-arrow,
  .deck-frame { transition: none; }
}

/* ---- the lightbox ----
   A native <dialog> opened with showModal(), same mechanics and the same
   reasons as the egg above: focus trap, Escape, inert page, top layer.

   It fills the viewport rather than sitting in it as a card. The slide is
   16:9 and the phone is 9:19.5, so every pixel of width is the whole point —
   a card with a margin would hand a third of the gain straight back.

   Two states. Fit-to-screen, which is the honest default and already about
   2.3x the deck's own drawing of the slide at 390px; and zoomed, where the
   image is 2.6x the stage and the stage is panned with a finger by ordinary
   scrolling. `safe center` on the alignment is what makes the second state
   work at all: plain centring pins an oversized item to the middle and puts
   its left edge out of reach behind the scroll origin. */
.zoom {
  width: 100vw;
  max-width: 100vw;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  overflow: hidden;
  color: var(--cream);
}
.zoom[open] {
  display: grid;
  grid-template-rows: minmax(0, 1fr) auto;
}
.zoom::backdrop { background: color-mix(in srgb, var(--deep) 88%, transparent); }

.zoom-stage {
  display: flex;
  justify-content: safe center;
  align-items: safe center;
  overflow: auto;
  padding: clamp(10px, 2.4vw, 28px);
  cursor: zoom-in;
  scrollbar-width: none;
  overscroll-behavior: contain;
}
.zoom-stage::-webkit-scrollbar { display: none; }
.zoom-img {
  display: block;
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
  border-radius: 10px;
  background: var(--white);
  box-shadow: 0 20px 60px color-mix(in srgb, #000 45%, transparent);
}
/* Zoomed: the image stops fitting and starts being panned. Width is measured
   against the stage, not the picture, so the multiplier means the same thing
   on every screen. */
.zoom.is-zoomed .zoom-stage { cursor: zoom-out; }
.zoom.is-zoomed .zoom-img {
  max-width: none;
  max-height: none;
  width: 260%;
  border-radius: 0;
}

/* The caption comes with the slide — a screenshot of a slide out of its deck
   needs the line that says which beat of the lesson it is. */
.zoom-cap {
  margin: 0;
  padding: 14px clamp(16px, 5vw, 32px) max(16px, env(safe-area-inset-bottom));
  text-align: center;
}
.zoom-cap-step {
  display: block;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--green-tint);
}
.zoom-cap-text {
  display: block;
  margin-top: 5px;
  font-size: 14px;
  line-height: 1.5;
  color: color-mix(in srgb, var(--cream) 78%, transparent);
}
/* Said once, and only while it is still true. */
.zoom-hint {
  margin: -6px 0 12px;
  text-align: center;
  font-size: 12px;
  letter-spacing: .04em;
  color: color-mix(in srgb, var(--cream) 55%, transparent);
}
.zoom.is-zoomed .zoom-hint { visibility: hidden; }
/* A mouse has the zoom-in cursor to say the same thing, and the copy says
   "tap". */
@media (hover: hover) { .zoom-hint { display: none; } }

/* Over the picture, not outside it: the dialog runs to the edge of the screen
   so there is no outside. Inset far enough to clear a notch. */
.zoom-close-form {
  position: absolute;
  top: max(10px, env(safe-area-inset-top));
  right: max(10px, env(safe-area-inset-right));
  z-index: 2;
  margin: 0;
}
.zoom-close {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: color-mix(in srgb, var(--deep) 82%, transparent);
  color: var(--cream);
  cursor: pointer;
  box-shadow: 0 6px 18px color-mix(in srgb, #000 40%, transparent);
  transition: background-color .18s ease;
}
.zoom-close:hover { background: var(--maroon); }
.zoom-close:focus-visible { outline: 3px solid var(--cream); outline-offset: 3px; }

@media (prefers-reduced-motion: no-preference) {
  .zoom[open] { animation: zoomIn .2s ease; }
  .zoom[open]::backdrop { animation: eggFade .2s ease; }
}
@keyframes zoomIn {
  from { opacity: 0; transform: scale(.98); }
  to   { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) { .zoom-close { transition: none; } }

/* ============================ the tutor ============================ */
/* Second on the page, under the hero card and its lead line, at every width.
   A circle that is also the player, with the heading above it and the
   biography below — see the block at the foot of this file. It has been a
   half-width 4:5 portrait (600px of section for 60 words) and a compact row
   with the circle beside the copy; this is the third shape and the first one
   built for the section standing on its own rather than fitting between
   others. */
/* The base. It is a centred column at every width — the direction, the order
   and the widths are all set in the "tutor section, rebuilt round the video"
   block at the foot of this file, which is where the reasoning lives. What is
   here is only what does not change: centred on both axes, centred text. */
.tutor-grid {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 24px;
  text-align: center;
}
.tutor-photo {
  position: relative;
  margin: 0;
  width: clamp(150px, 34vw, 200px);
  flex: 0 0 auto;
}
.tutor-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  object-position: center 12%;
  border-radius: 50%;
}

/* The recording plays inside the circle rather than under it: the <video> is
   stacked on the portrait at the same size and revealed on the first press, so
   the shape holds and the copy beside it never moves.

   The file is a square crop, and the box is square, so object-fit has nothing
   to do while it plays. It only ever acts on the poster — which is the tall
   700x1050 portrait — and the values here are the ones on .tutor-photo img
   above, so the still the video paints before its first frame is framed
   identically to the photograph underneath it. Change one, change both. */
.tutor-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center 12%;
  border-radius: 50%;
  background: var(--deep);
  opacity: 0;
  pointer-events: none;
  cursor: pointer;
  transition: opacity .25s ease;
}
/* Two states, because pausing must not drop the viewer back to the photograph
   mid-sentence: .is-active spans the whole playthrough and holds the video up,
   .is-playing covers only the moving part and drives the button's icon. Both
   are cleared on ended, which is what restores the portrait and the play
   triangle for a replay. */
.tutor-photo.is-active .tutor-video { opacity: 1; pointer-events: auto; }

/* The play control, laid over the portrait. The ring is an SVG stroke rather
   than a border so it can be drawn round by stroke-dashoffset as the recording
   plays; a border cannot be partially drawn. It is rotated a quarter turn so
   it starts at twelve o'clock. */
.tutor-ring {
  position: absolute;
  inset: -7px;
  width: calc(100% + 14px);
  height: calc(100% + 14px);
  transform: rotate(-90deg);
  overflow: visible;
  pointer-events: none;
}
/* Stroke width in user units, NOT vector-effect: non-scaling-stroke. That
   property computes the dash pattern in device pixels while stroke-dasharray
   below is written in user units, so the two disagree and the ring renders
   part-drawn before a single frame of audio has played. 1.2 user units on a
   100-unit box drawn at ~214px is about 2.6 real pixels. */
.tutor-ring circle {
  fill: none;
  stroke-width: 1.2;
}
.tutor-ring-track { stroke: var(--hairline); }
.tutor-ring-fill {
  stroke: var(--green-deep);
  stroke-linecap: round;
  /* 2πr for r=48, set here so a browser with no JS still shows an empty ring
     rather than a complete one. */
  stroke-dasharray: 301.6;
  stroke-dashoffset: 301.6;
  transition: stroke-dashoffset .2s linear;
}
.tutor-play {
  position: absolute;
  left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 56px; height: 56px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: var(--yellow);
  color: var(--deep);
  cursor: pointer;
  box-shadow: 0 8px 24px color-mix(in srgb, var(--deep) 30%, transparent);
  transition: transform .2s ease, opacity .2s ease;
}
.tutor-play:hover { transform: translate(-50%, -50%) scale(1.06); }
/* The playing state lands on .tutor-photo, the figure, because the circle is
   the player: there is no separate bar element to carry it. */
.tutor-play .tplayer-pause { display: none; }
.tutor-photo.is-playing .tplayer-play { display: none; }
.tutor-photo.is-playing .tplayer-pause { display: block; }

/* Once it is running the triangle would sit on her face, and on a 200px circle
   that is most of the picture, so it fades out and the video itself takes over
   as the pause target. It never leaves the DOM, never loses its 56px hit area
   and never stops being a real <button>, so a tap in the middle, a hover and a
   Space keypress all still pause it — only the paint is gone. */
.tutor-photo.is-playing .tutor-play { opacity: 0; }
.tutor-photo.is-playing:hover .tutor-play,
.tutor-photo.is-playing .tutor-play:focus-visible { opacity: 1; }

/* The sound toggle, phone only — the clip autoplays here and an autoplay the
   browser will allow is a muted one.

   Placed by polar coordinates rather than by edge offsets: the frame is a
   circle, so "bottom right" is empty space. 79%/79% of the box is the
   south-east diagonal at ~0.41r from centre, which keeps the whole 34px
   button inside the disc and clear of the progress ring at inset -7px.

   aria-pressed carries the state and the icon swap reads the same attribute,
   so there is one source of truth for which way round this is — same contract
   as the hero chat's own controls once did. */
.tutor-sound {
  position: absolute;
  left: 79%; top: 79%;
  transform: translate(-50%, -50%);
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: color-mix(in srgb, var(--deep) 66%, transparent);
  color: var(--cream);
  cursor: pointer;
  opacity: 0;
  visibility: hidden;
  transition: opacity .25s ease, background .2s ease;
  z-index: 2;
}
/* Only once there are frames on screen: before that it is a control for a
   thing that is not happening. .is-active is set on the first play. */
.tutor-photo.is-active .tutor-sound { opacity: 1; visibility: visible; }
.tutor-sound:hover { background: color-mix(in srgb, var(--deep) 82%, transparent); }
.tutor-ico--loud { display: none; }
.tutor-sound[aria-pressed="true"] .tutor-ico--muted { display: none; }
.tutor-sound[aria-pressed="true"] .tutor-ico--loud { display: block; }

/* "keep reading". Base state is: no button, whole biography — which is the
   desktop rendering and also the no-JS rendering. Everything below is put on
   by sunday.js on a phone and by nothing else. */
.tutor-more {
  display: none;
  align-items: center;
  gap: 5px;
  margin-top: 8px;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 15px;
  font-weight: 700;
  color: var(--green-deep);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.tutor-more svg { transition: transform .2s ease; }
.tutor-more[aria-expanded="true"] svg { transform: rotate(180deg); }

/* The duration, and what it is. It sits in the copy column rather than under
   the circle so the circle stays a circle. */
.tutor-cue {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-top: 16px;
  font-size: 14px;
  font-weight: 600;
  color: var(--stone);
}
.tutor-cue::before {
  content: "";
  width: 22px;
  height: 1px;
  background: var(--warm-gray);
}
.tutor-cue-time {
  font-variant-numeric: tabular-nums;
  color: var(--green-deep);
  font-weight: 700;
}

/* Retired 2026-08-20, at every width, but kept in the markup on purpose.
   It was written as an invitation — "0:11, Saying Hi" under a still portrait
   with a play triangle on it — and nothing needs pressing any more: the hero
   plays it on desktop and the circle autoplays it on a phone. Left as a
   countdown ticking under a loop it read as a stray timer.

   Not deleted because it is the ONLY affordance in the `tutor.media: "voice"`
   variant, where the media element is sr-only and there is no picture to
   press. Flip that key back and this rule is the one to delete. */
.tutor-cue { display: none; }
.tutor-cue { justify-content: center; }
/* Centred, like everything else in the column. The 760px rule that pushed it
   left belonged to the row layout and went with it. */

.tutor-copy .h2 { margin-top: 0; }
.tutor-role {
  margin-top: 12px;
  font-size: 14px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--green-deep);
}
.tutor-bio {
  margin-top: 14px;
  color: var(--charcoal);
}
/* The cap is on the column, not on the paragraph: it is what stops the row
   from spanning the page, which is what lets justify-content centre the pair
   as a unit. 34em of Jakarta is about 62 characters — inside the 65–75 line
   length target with the circle taking the rest of the width. */
.tutor-copy {
  min-width: 0;
  max-width: 34em;
}

/* A separate inline player bar (.tplayer: a pill button, a fill bar and a
   duration counter, sitting under the biography) was removed on 2026-08-11.
   Both the video and the voice variant play inside the portrait circle now, so
   the circle IS the player — see .tutor-video / .tutor-ring / .tutor-play
   above. The .tplayer-play / .tplayer-pause class names survive on the two
   icons inside .tutor-play, which is why those rules are still up there. */

/* ============================ club reviews ============================
   Seven real messages as a WhatsApp-style thread. The bubbles below are a
   rebuild of the hero chat mockup this page carried until 2026-08-20 —
   .bubrow / .bub / .bub-av / .bub-gap / .bub-meta are the same names doing
   the same jobs — minus everything a testimonial thread has no business
   showing: no outgoing bubbles, no delivery ticks, no reactions, no group
   header. Every message here is incoming, because every one of them is
   somebody else talking about the club.

   The old four-card .review-stack that stood here lives on in redesign.css,
   untouched, because the main site still uses it. */
/* ---- the two-column layout ----
   Heading and pull quote left, thread right, from 920px up — the breakpoint
   and the column ratio are the main site's #reviews (.reviews-layout in
   redesign.css), because this section is the same argument in a different
   form and the two pages should place it the same way.

   Until 2026-08-21 this was a single 620px column centred on the page, with
   the heading let out of it by a symmetric negative margin so that it could
   stay on one line. Both are gone: a heading that has its own column is meant
   to break over two or three lines, and the negative margin only existed to
   stop it doing that inside a 620px wrapper.

   Base is one min-content-safe column so nothing can blow past the viewport,
   and the row gap is what puts air between the attribution and the first
   bubble once the columns stack. */
.thread-layout {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: clamp(40px, 7vw, 52px);
}
@media (min-width: 920px) {
  .thread-layout {
    grid-template-columns: minmax(0, 0.92fr) minmax(0, 1fr);
    gap: 56px;
    align-items: center;
  }
}

/* CENTRED AT EVERY WIDTH, including inside its own column from 920px up. It
   was flush left there (the main site's .reviews-intro rule) until
   2026-08-21; Daniil's call is that the pull quote reads as the caption of
   the heading rather than as the first line of a left-hand column, and a
   caption sits under the middle of the thing it captions. The heading centres
   with it — a centred quote under a flush-left heading is two alignments in
   one block, which reads as a mistake rather than as a decision.

   .section-head ships with a 720px cap and that cap is wider than this
   column, so it is released and the column does the measuring. */
.thread-intro { text-align: center; }
.thread-intro .section-head { max-width: none; }

/* ---- the pull quote ----
   One line lifted out of the thread and set in the display serif, so the
   section states its claim before it starts proving it. Fraunces roman, not
   italic: .accent-serif in the heading directly above is italic Fraunces, and
   two italic serifs stacked read as one run-on headline.

   It was deliberately the same sentence as Stefano's opening bubble, the
   callout making the claim and the bubble standing as the receipt for it. The
   bubble was cut on 2026-08-21 and this is the only copy of those words on
   the page now. See reviews.pullNote in sunday.en.json before changing it. */
.pullquote {
  margin: 22px 0 0;
}
@media (min-width: 920px) { .pullquote { margin-top: 26px; } }
.pullquote-q {
  margin: 0 auto;
  max-width: 15em;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 23px;
  line-height: 1.34;
  letter-spacing: -0.01em;
  color: var(--deep);
  text-wrap: balance;
}
@media (min-width: 720px) { .pullquote-q { font-size: 30px; } }
/* `margin-inline: 0` used to land here at 920px, to sit the quote on the left
   edge of its column. It went with the flush-left intro above: the auto
   margins are what centre a 15em measure inside a wider column. */
/* Closer to the quote than the quote is to the heading, so it reads as the
   quote's signature rather than as a line of its own. */
.pullquote-by {
  margin-top: 9px;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--stone);
}

/* Narrower than its column. A chat thread that runs the full width of a
   laptop stops looking like a phone screen and starts looking like a table of
   quotations, which is the one thing this section is trying not to be.
   Centred while the layout is stacked; left-aligned in its own column, where
   the bubbles should start on the column's edge. */
.thread {
  display: grid;
  gap: 10px;
  margin: 0 auto;
  max-width: 620px;
  padding: 0;
  list-style: none;
}
@media (min-width: 920px) { .thread { margin-inline: 0; } }

/* A row is one message: an avatar and a bubble. align-items: flex-end puts
   the avatar level with the bottom of the bubble, where a chat client puts
   it, rather than floating beside the name. */
.bubrow {
  display: flex;
  align-items: flex-end;
  gap: 8px;
}
/* A run of messages from the same person ships without an avatar, so the
   bubble needs the avatar's width back as an indent or the run would step
   left out of alignment. 27px + the 8px gap. */
.bubrow--cont { padding-left: 35px; }
/* Tighter inside a run than between speakers: it is one person still typing,
   and the gap is the only thing that says so once the name is gone. */
.bubrow--cont { margin-top: -5px; }

/* .avatar ships at 44px for the main site's review row. Here it is a message
   avatar: small, ringed so it holds against the cream behind it. */
.bub-av {
  width: 27px; height: 27px;
  font-size: 11px;
  align-self: flex-end;
  margin-bottom: 2px;
  border: 1.5px solid var(--white);
}

.bub {
  position: relative;
  max-width: calc(100% - 40px);
  margin: 0;
  padding: 9px 13px 7px;
  border-radius: var(--r-card);
  border-bottom-left-radius: 4px;
  background: var(--green-tint);
  color: var(--deep);
  line-height: 1.45;
  font-size: 15px;
  box-shadow: 0 6px 18px color-mix(in srgb, var(--deep) 8%, transparent);
}
/* The bubbles run to their text and no further, which is what the brief asked
   for and what a real thread looks like: a five-word message is a five-word
   bubble. max-width caps the long ones; nothing sets a minimum. */
.bub-name {
  display: block;
  margin-bottom: 1px;
  font-size: 12px;
  font-weight: 700;
  color: var(--maroon);
}
/* The country, on the same line as the name and quieter than it — the only
   other thing anyone here agreed to have published. */
.bub-place {
  margin-left: 6px;
  font-weight: 600;
  color: var(--stone);
}
.bub-text { display: block; }

/* The timestamp sits on the LAST LINE of the message rather than on a line of
   its own under it, which is what the reference does and the reason a short
   message there is not twice as tall as it needs to be.

   The mechanism is WhatsApp's own and nothing measures anything at runtime:
   an empty inline-block at the end of the text reserves exactly the width the
   time needs, and the time itself is taken out of flow and pinned to the
   bottom right. The text wraps around the reservation, so a last line with
   room keeps the time beside it and a last line without room pushes the
   reservation — and the time with it — onto a line of its own. */
.bub-gap {
  display: inline-block;
  width: 40px;
  height: 1px;
}
.bub-meta {
  position: absolute;
  right: 13px;
  bottom: 7px;
  font-size: 11px;
  line-height: 1.45;
  color: var(--stone);
}

/* ---- the stagger ----
   Rows carry .reveal, so redesign.css already has them at opacity 0 with a
   translate, and sunday.js adds .is-in. What is different here is WHEN: a
   dedicated queue in sunday.js releases one row every 160ms (120ms on a
   phone, where there is more scroll between them), so a screenful that
   arrives at once plays in sequence and a row that scrolls in on its own
   plays immediately instead of waiting out six other rows' delays.

   The transition-delay below is the fallback for that queue never running —
   no JS, no IntersectionObserver — where redesign.css leaves every .reveal
   visible and these delays do nothing at all. It is written anyway so the
   resting order is legible from the markup. prefers-reduced-motion is handled
   where every other .reveal is handled: redesign.css drops the transition and
   sunday.js sets .is-in on all of them at once, with no queue. */
.thread .reveal { transition-delay: calc(var(--i, 0) * 160ms); }
@media (max-width: 639.98px) {
  .thread .reveal { transition-delay: calc(var(--i, 0) * 120ms); }
  .bub { font-size: 14.5px; }
}

/* ============================ the booking card ============================
   Named .final-cta because it was the last section on the page until
   2026-08-20; three sections now follow it (#after, #faq, #save-seat) and the
   real closing ask is .club-close down there. The class stays: it is what
   redesign.css keys the base styles off, and renaming it would touch the
   template, this file and nothing else that matters.

   redesign.css trims .final-cta's top padding because on the main site it
   follows the founders section directly. Here it is a section like any other
   and takes the page's one rhythm. It carried a tightened pair (32/40/44)
   until 2026-08-21, spent against the one job this block cannot do while
   scrolled — a visitor has to see the three steps AND the button in one
   screenful. That budget is now bought where it is actually short, in the
   max-height rule further down this section, rather than by making this
   section's gaps different from every other section's at every height. Every
   other number from here to .bookcta is still set against that budget,
   measured on a 1440x900 window where the viewport is ~810px tall. */
.sunday .final-cta { padding-block: var(--sec-pad); }

/* Yellow, not maroon. This is the loudest colour the brand owns and it is
   spent exactly once on this page, on the last thing a visitor is asked to
   do. Everything sitting directly on it has to be dark, so the title is
   --deep; the steps do not have to be, because they sit on white cards.
   The one thing that must NOT be yellow is the button, which is why it is
   maroon — see .bookcta. */
.bookcard {
  background: var(--yellow);
  color: var(--deep);
  border-radius: var(--r-xl);
  padding: 0;                 /* the padding lives on .bookcard-body */
  overflow: clip;             /* clip, not hidden: never a scroll container */
  text-align: center;
}
.bookcard-body { padding: 32px 20px 34px; }
@media (min-width: 720px)  { .bookcard-body { padding: 36px 34px 40px; } }
@media (min-width: 1060px) { .bookcard-body { padding: 40px 44px 44px; } }

/* .works-title carries the size ramp; this only pins the colour and kills the
   bottom margin the main site's version needs (there, the grid follows it
   directly; here .works-grid brings its own top margin). */
.bookcard-title { margin-top: 0; margin-bottom: 0; color: var(--deep); }
@media (min-width: 1060px) { .bookcard-title { font-size: 46px; } }

/* ---- the three steps: the main site's .works components, on yellow ----
   Everything structural comes from redesign.css. What follows is only what
   changes when that block moves off a cream band and onto a yellow card. */
.bookcard .works-grid { margin-top: 24px; }
@media (min-width: 720px) { .bookcard .works-grid { margin-top: 28px; } }

/* The cards are centred on the main site's parent and left-aligned inside;
   .bookcard centres everything, so the alignment has to be handed back. */
.bookcard .works-card {
  text-align: left;
  /* On cream, the hairline is what separates a white card from its ground. On
     yellow the value contrast already does that, and the hairline reads as a
     grey outline drawn around a bright shape. A soft shadow in the card's own
     colour is the cleaner lift. */
  border-color: transparent;
  box-shadow: 0 12px 30px color-mix(in srgb, var(--deep) 10%, transparent);
  padding: 24px 22px;
  /* The card is the query container for its own step title below. Nothing
     else in here reads it. */
  container-type: inline-size;
}
@media (min-width: 720px) { .bookcard .works-card { padding: 26px 26px 24px; } }

/* One line, always. "Get the link by email" is the longest step title and at
   the inherited 27px it broke over two lines in the middle card only, which
   made that card taller than the other two and pushed a band of empty yellow
   under every doodle. Sizing the title against the card's own width rather
   than the viewport's is what keeps it on one line at every column width
   without a breakpoint per card size: the longest title measures ~9.7x its
   font size, so 9.8cqi is the largest size that still fits the content box
   with a little slack. The clamp only bites on a phone, where one card spans
   the screen and 9.8cqi would run away. */
.bookcard .works-step-title {
  margin-top: 16px;
  font-size: clamp(19px, 9.8cqi, 26px);
}

/* The gap between a step's last line of copy and its doodle. The doodle is
   pinned to the card bottom (margin-top: auto, from redesign.css) so the
   three line up as a set, which means this margin is the floor of that gap
   and the card's spare height is added on top of it. 26px of floor plus the
   slack read as a hole; 10px does not. */
.bookcard .works-step-copy { margin-top: 10px; margin-bottom: 10px; }

/* The doodle, capped rather than full-bleed. A 247px square per card is most
   of the reason this section did not fit a laptop screen, and the drawings
   carry perfectly well smaller — they are a step's punctuation, not its
   subject. Capped and centred at every width now, which is what the phone
   layout already did (the main site turns these into a swipe track, which
   needs the dot rail main.js builds and sunday.js does not). */
.bookcard .works-fig { width: min(100%, 186px); margin-inline: auto; }
@media (max-width: 639.98px) {
  .bookcard .works-fig { width: min(64%, 210px); }
}

/* ---- the tint under the doodles: a ground, not a backing ----
   What this replaces: a 76%-wide rounded rect shifted ±6% off the
   drawing's centre, with the shift alternating direction card to card,
   while all three drawings sit dead-centre in their square (ink centre
   49.9–50.1%). So the margin between drawing and backing was different on
   every card and disagreed with itself. Worse, the feet landed within
   0.4% of the block's bottom edge while the heads cleared its top by
   10.8% — every figure cropped and floating at the same time. That offset
   is a riso-print idea authored against the main site's doodles, which
   are framed differently; it did not survive the transplant.

   So the colour stops being a plane behind the figure and becomes the
   ground under it. There is no top or side edge left to crop anything
   against, which is the whole point: the shape can no longer disagree
   with a drawing, because it is only ever under the feet.

   Colour still comes from --works-wash, set per step further up, so this
   does not touch the palette. --works-wash-inset and --works-wash-rot go
   unused inside .bookcard — inset and transform are both overridden
   here — which is why the coral rule above no longer sets them. */
.bookcard .works-fig::before {
  inset: auto var(--puck-right) 1% var(--puck-left);
  height: 23%;
  border-radius: 50%;
  transform: none;
}

/* One ellipse for all three would repeat the mistake it is fixing. The
   footprints — the ink's horizontal extent across the bottom eighth of
   each drawing, which is what a shadow actually answers to — are nothing
   like each other:

     pick   39.5–65.3%   width 25.8%   centred 52.4%
     link   18.1–82.0%   width 63.9%   centred 50.1%   (mid-stride)
     talk   29.6–56.5%   width 26.9%   centred 43.1%   (stands left)

   A centred 80% ellipse would be two and a half times too wide under
   steps 1 and 3 and visibly off under step 3. So each puck is set a
   little wider than its own footprint and pulled toward its own centre.
   They are not equalised: a walking figure genuinely casts a longer
   shadow than a standing one, and flattening that would look more wrong
   than the variation does. Driven off the colour classes rather than
   :nth-child so reordering the steps cannot silently mismatch them. */
.bookcard .works-fig--green { --puck-left: 23%; --puck-right: 19%; }
.bookcard .works-fig--coral { --puck-left: 14%; --puck-right: 14%; }
.bookcard .works-fig--blue  { --puck-left: 15%; --puck-right: 27%; }

/* Step 2 is yellow on the main site. It cannot be here: a yellow disc and a
   yellow wash inside a white card that is itself sitting on yellow flattens
   the middle card. Coral is the page's other warm accent and it is the one
   that holds its shape next to --yellow. */
.works-num--coral { background: var(--coral); }
/* Colour only. The inset and rotation this used to carry described the old
   tilted backing block and are dead now — see the ground puck below, which
   overrides inset and transform outright. */
.works-fig--coral { --works-wash: var(--coral-tint); }

/* One button where the inline booker used to be. Maroon, because the page's
   primary button is yellow and a yellow button on a yellow card is a hole:
   this is the one CTA on the site that cannot be the house colour. The
   outline .sunday adds to every .btn-primary is not wanted here either, which
   is why this is .btn plus .bookcta rather than .btn-primary. */
.bookact { margin-top: 26px; }
/* Phone only, and it is the dot rail that makes this necessary rather than
   taste. Each dot is a 44x44 tap target around an 8px dot, so there are 18px
   of invisible box above and below the only thing you can see: the rail's own
   18px margin plus that padding put about 42px of blank card between the
   cards and the dots, and another 44 between the dots and the button. The
   button ended up a swipe away from the thing it belongs to. These two
   numbers are the gaps the eye actually reads, so they are the small ones —
   the tap target is untouched and still 44px. */
@media (max-width: 639.98px) {
  .bookcard .sb-dots { margin-top: 6px; }
  .bookcard .bookact { margin-top: 6px; }
}
.bookcta {
  background-color: var(--maroon);
  color: var(--cream);
  font-size: 17px;
  padding: 18px 34px;
  box-shadow: 0 10px 24px color-mix(in srgb, var(--deep) 22%, transparent);
}
.bookcta:hover { background-image: var(--scrim); }
.bookcta:focus-visible { outline: 2px solid var(--deep); outline-offset: 3px; }

/* The one place on this page that asks the viewport how TALL it is. The block
   above is sized so the whole card clears a 900px-tall window with the sticky
   header counted in; below that the same layout would push the button off the
   fold, and a call to action you have to scroll to find is the one thing this
   section cannot afford. So on a short laptop screen the padding, the heading
   and the doodles all step down together — the composition is identical, just
   drawn smaller. Guarded by min-width so a phone, which is short and scrolls
   anyway, never sees it. */
@media (min-width: 720px) and (max-height: 780px) {
  .sunday .final-cta { padding-block: 30px; }
  .bookcard-body { padding: 28px 40px 32px; }
  .bookcard-title { font-size: 38px; }
  .bookcard .works-grid { margin-top: 20px; }
  .bookcard .works-card { padding: 22px 24px 20px; }
  .bookcard .works-fig { width: min(100%, 150px); }
  .bookact { margin-top: 20px; }
}

/* ==================== what happens after (#after) ====================
   The only priced block on the page. It is one heading, one sentence and two
   options, and everything below exists to keep it that size.

   NO CARD. The page is a single white field and things that matter here are
   grouped by space, not by boxes (see the .lead-line note above); wrapping two
   prices in bordered panels would make them the third and fourth "objects" on
   a page whose objects are the hero card, the cream hour box and the yellow
   booking card. The options are separated by a hairline instead — the same
   forest hairline the FAQ index below uses, so the last third of the page
   reads as one editorial run rather than as two components.

   The free line leads and is set larger than the options' copy on purpose:
   it is the offer, the amounts are its footnote, and the type sizes are the
   only thing saying so once the card is gone. */
/* NO MEASURE CAP, at Daniil's call (2026-08-23). It used to be capped at 34em
   (~600px), which made it the third distinct centred width in the page's last
   third — narrower than the two options under it and much narrower than the
   manifesto paragraph in #different, the other centred body block on the page.
   Three centred blocks at three widths read as three accidents. It now runs
   the full .container like .mf-p does, so the page has ONE centred measure.

   If you cap this again, cap .mf-p with it. (.after-guarantee was the third
   name in this sentence until 2026-08-27; that rule went with the old
   .after-opts block.) */
.after-free {
  margin: 22px auto 0;
  text-align: center;
  /* TYPE WAS MATCHED TO .stack-lede, declaration for declaration (2026-08-23,
     Daniil's call): #after and #stack were adjacent sections that both opened
     with one centred sentence under the heading, and this was the louder of
     the two for no reason a reader could see. #stack was deleted on
     2026-08-27, so this is now simply the size these numbers settled on —
     regular weight, 1.55, .78 opacity — and there is nothing left to keep it
     in step with. Change it on its own merits.

     The WIDTH was never .stack-lede's: this runs the full .container to stay
     level with the manifesto paragraph (see the note above). */
  font-size: clamp(16px, 1.8vw, 18px);
  font-weight: 400;
  line-height: 1.55;
  opacity: .78;
  /* `pretty`, not `balance`, since the cap came off: balance minimises the
     longest line, so on a 1100px measure it kept pulling this sentence back
     onto two short centred lines and the block LOOKED narrower than the
     manifesto paragraph it now matches, cap or no cap. `pretty` fills the
     measure and only tidies the last line — same as .mf-p. */
  text-wrap: pretty;
  color: var(--deep);
}
@media (min-width: 720px) { .after-free { margin-top: 26px; } }

/* PHONES: STOP BALANCING. This is the same trap .lead-line falls
   into at the same width and it is worth stating twice, because `balance`
   looks like a free improvement and is not.

   balance minimises the LONGEST line. That is right for a sentence that
   settles on two roomy lines, where it evens the pair instead of leaving a
   stub. At narrow phone widths a short sentence has no room to give and
   balance strands it as a narrow strip with empty gutters either side.
   Greedy wrapping fills it out to the page.

   Stays CENTRED — the fix is the wrapping, not the alignment. `wrap`, not
   `pretty`: every browser that understands `balance` understands `wrap`, so
   the reset always lands. */
@media (max-width: 480px) {
  .after-free {
    text-wrap: wrap;
  }
}

/* ==================== the plan cards (#after) ====================
   REBUILT 2026-08-27, Daniil's brief: "mimic the pricing section on the main
   landing page". What stood here was .after-opts — two bare centred columns
   split by a vertical hairline, no cards, no feature lists, one button — and
   the comment on it argued at length that cards would cost the yellow booking
   card its position as the page's one object. That whole block is in git and
   its reasoning is in after.supersededNote.

   THE YELLOW BOOKING CARD IS STILL THE PAGE'S ONE OBJECT and this is how these
   two stay under it: they are WHITE on cream with a hairline, not yellow, not
   tinted, and the only saturated thing in either of them is the Members
   button. Two white cards on a cream field read as a table; the yellow card
   above reads as a thing. That is the whole hierarchy and it is worth
   re-checking if anyone ever fills one of these with colour.

   Everything type-level here is redesign.css's #pricing, on purpose: the
   dashed feature rules, the display-serif amount, the .guarantee box above a
   dashed rule above a full-width pill. The brand now has two pricing sections
   and one pricing idiom.

   The one structural departure: #pricing is one cream VALUE card beside one
   white PRICE card, because the main site sells a single thing and the left
   card is its contents. This page sells two, so both are price cards and the
   contents live inside each of them. */
.plan-grid {
  list-style: none;
  margin: 30px auto 0;
  padding: 0;
  max-width: 940px;
  display: grid;
  gap: 20px;
  /* NOT `stretch`. Stretching the pair to a common height was the obvious
     reading of "mimic the main landing page" and it is wrong here: #pricing
     stretches two cards that hold DIFFERENT things (a value list and a price),
     so the taller one simply sets the height. These two hold the SAME things
     in the same order, plus one guarantee box on Members that Drop-in has no
     counterpart for — stretched, that box became 190px of nothing above
     Drop-in's rule, measured, which is a card that looks like it failed to
     finish rendering.

     Sized to content and with the guarantee moved below the button, the two
     cards are identical line for line all the way down to the pill and Members
     simply continues for one more box. Nothing is padded out and nothing
     drifts. */
  align-items: start;
}
/* 860px, not the 900px #pricing uses and not the 720px the old two columns
   used. These cards are wider than the old bare columns (they carry a padded
   feature list with a control on the right edge) and narrower than #pricing's
   pair (1.1fr/0.9fr against 1fr/1fr here). 860 is where the longest feature
   name — "3-Min Routine To Improve Fluency" — stops sharing its line with the
   (i) button. Below it the cards stack and each one is full width, which is
   also the only arrangement in which a 6-row comparison is legible on a
   phone: you read one card, then the other. */
@media (min-width: 860px) {
  .plan-grid {
    grid-template-columns: 1fr 1fr;
    gap: 24px;
    margin-top: 36px;
  }
}

/* ---------------- the mobile switch (below 860px) ----------------
   Added 2026-08-27. Above 860px this does not exist: the cards are side by
   side and there is nothing to switch. Below it they used to stack, which put
   the Drop-in price about a screen below the Members one — two offers read a
   minute apart rather than a comparison.

   EACH SEGMENT CARRIES ITS OWN PRICE and that is the point of the control, not
   decoration on it. A switch shows one card at a time, so it spends the
   side-by-side comparison the section exists to make; €39 and €12.50 both
   sitting in the control is what buys it back. If this is ever simplified to
   two words, that is what is being given up.

   `hidden` in the markup, unhidden by sunday.js — scripts-off gets the two
   stacked cards it always had, not a control that switches nothing. The
   attribute selector below is what makes that work without a second class:
   [hidden] wins over `display: flex` only because it is written after it. */
.plan-tabs {
  position: relative;
  margin: 26px auto 0;
  max-width: 420px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0;
  padding: 4px;
  background: var(--white);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
}
.plan-tabs[hidden] { display: none; }
@media (min-width: 860px) {
  /* Belt and braces. sunday.js re-hides the control above this width, but a
     resize past the breakpoint with the panel machinery mid-teardown must
     never leave a switch above a pair of already-visible cards. */
  .plan-tabs { display: none; }
}

/* THE SLIDING THUMB, and it is a real element rather than a ::before on the
   track: the selected label is cream on ink, so the fill has to sit UNDER the
   text. A pseudo-element on the track paints over its own contents.

   translateX(100%) lands it exactly on the second segment because its width is
   one segment: the track's padding is 4px a side, the thumb is inset by that
   4px, and 100% of `calc(50% - 4px)` moves it by exactly the segment width. No
   measured pixel values, so it survives any track width. */
.plan-thumb {
  position: absolute;
  z-index: 0;
  top: 4px;
  left: 4px;
  bottom: 4px;
  width: calc(50% - 4px);
  background: var(--ink);
  border-radius: var(--r-pill);
  transition: transform .24s cubic-bezier(.22,.61,.36,1);
}
.plan-tabs[data-active="1"] .plan-thumb { transform: translateX(100%); }
@media (prefers-reduced-motion: reduce) {
  .plan-thumb { transition: none; }
}

/* Two lines — name over price — for three reasons at once: it is what lets the
   price be in the control at all at 390px, where "DROP-IN €12.50" on one
   tracked uppercase line does not fit; it gives a 53px target without inventing
   padding to reach the 44px minimum; and it puts the two amounts on a shared
   baseline so they read as a pair of numbers being compared. */
.plan-tab {
  position: relative;
  z-index: 1;
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: none;
  padding: 9px 8px 11px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  border-radius: var(--r-pill);
  cursor: pointer;
  /* The colour is the only thing that moves between states and it is timed to
     the thumb, so the label lights as the fill arrives under it rather than
     before or after. */
  transition: color .24s ease-out;
}
.plan-tab-name {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .11em;
  text-transform: uppercase;
  color: var(--green-deep);
  transition: color .24s ease-out;
}
/* The display serif, the same face the €39 in the card is set in — the control
   is showing the same number, so it is set in the same type. */
.plan-tab-price {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 19px;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--maroon);
  transition: color .24s ease-out;
}
.plan-tab-per {
  margin-left: 3px;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  color: var(--stone);
  transition: color .24s ease-out;
}
/* Cream on ink. Not the house yellow: yellow is spent once on this page, on
   the booking card and the buttons that lead to it, and a yellow thumb would
   put a fourth yellow object above a card whose own CTA is yellow. Ink is the
   same material the (i) tooltips are made of, so the page's two "this is on
   top of the page rather than in it" surfaces match. */
.plan-tab[aria-selected="true"] .plan-tab-name,
.plan-tab[aria-selected="true"] .plan-tab-price { color: var(--cream); }
.plan-tab[aria-selected="true"] .plan-tab-per { color: color-mix(in srgb, var(--cream) 72%, transparent); }

.plan-tab:focus-visible {
  outline: 2px solid var(--deep);
  outline-offset: 2px;
}
/* The unselected half only. Lightening the selected one on hover would fight
   the thumb, and on a phone there is no hover to speak of anyway — this is for
   the narrow laptop window that is still under 860px. */
.plan-tab[aria-selected="false"]:hover .plan-tab-name { color: var(--maroon); }

/* WITH THE SWITCH UP, THE CARD DROPS ITS OWN NAME LABEL. The control directly
   above it already says MEMBERS or DROP-IN in the same tracked uppercase, and
   the two sit about 60px apart — the same word twice, once as a control and
   once as a heading for the thing it controls.

   The PRICE stays in both. 19px inside a pill and 42px of display serif are
   not the same object: the first is a number you are choosing between, the
   second is the number the card is about. Losing it would leave the card
   opening on a paragraph.

   Scoped to .has-switch, which sunday.js adds only when the control is
   actually up — with scripts off there is nothing above the card to have said
   the name, so the label stays. */
@media (max-width: 859.98px) {
  .plan-grid.has-switch .plan-name { display: none; }
  .plan-grid.has-switch .plan-price { margin-top: 0; }
}

/* The panel the switch is not showing. `display: none`, not visibility or a
   height of zero: the card is gone and the section is the height of the one
   card on screen.

   THE HEIGHT DIFFERENCE IS REAL AND IS ACCEPTED — Members is ~130px taller,
   the guarantee box under its button. Switching to Drop-in shrinks the
   section and everything below it slides up. It is anchored at the top: the
   switch and the card's top edge never move, so what a reader sees is a
   shorter card, not the page jumping. Reserving the taller height instead
   would cost 130px of empty cream under the Drop-in card on every view of it,
   which is a permanent cost to avoid a momentary one. */
.plan-card.is-off { display: none; }
@media (min-width: 860px) {
  /* If a resize crosses the breakpoint while a panel is hidden, the pair must
     come back whole. sunday.js also strips the class; this is what makes the
     frame in between correct rather than half a pricing table. */
  .plan-card.is-off { display: flex; }
}

/* A flex column so .plan-feats can absorb the leftover height and both cards
   end their rule and their button on the same line — the Members card carries
   a guarantee box the Drop-in card does not, so there is always slack in one
   of them. Same mechanism as .pricing-grid > .card on the main site. */
.plan-card {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  padding: 24px 22px;
  /* Everything else in this section is centred — heading, lede — and these
     two are not. A price card is read down its left edge: label, amount,
     sentence, then six rows whose ticks have to line up under each other. */
  text-align: left;
}
@media (min-width: 720px) { .plan-card { padding: 28px 26px; } }

/* The option being sold, marked the way .price-card is on the main site: a
   1.5px ink border against the other card's 1px hairline. No badge, no tint,
   no shadow — see the note at the top of this block about staying under the
   yellow card. The half-pixel is doing real work; at 1px the two borders are
   the same object in different colours and the emphasis reads as an accident. */
.plan-card--lead { border: 1.5px solid var(--ink); }

/* Small, uppercase, tracked, green: a label for the option, not a heading
   competing with the section's own — and green so the maroon amount below it
   is the only maroon in the card and therefore the thing the eye lands on.
   Inherited unchanged from .after-name, which is the one part of the old
   section that needed nothing done to it.

   It is NOT a link any more. It used to point at #stack, and #stack is gone —
   the six things it linked to are in this very card, four rows down. */
.plan-name {
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--green-deep);
}

.plan-price {
  margin-top: 8px;
  display: flex;
  align-items: baseline;
  gap: 6px;
}
/* Smaller than #pricing's 56/66px: that card is one of a pair where the other
   half is a list, so the number is the section's whole scan line. Here there
   are two numbers to compare and each has six rows under it, so the amount has
   to be the biggest thing in its own card without being the only thing on the
   screen. */
.plan-amount {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 42px;
  line-height: 1;
  letter-spacing: -0.01em;
  color: var(--maroon);
}
@media (min-width: 720px) { .plan-amount { font-size: 48px; } }
.plan-per {
  font-size: 15px;
  font-weight: 600;
  color: var(--stone);
}

.plan-copy {
  margin-top: 10px;
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--stone);
}
/* THIS RESERVATION IS WHAT MAKES THE TWO CARDS A TABLE. Side by side, the two
   feature lists have to START ON THE SAME LINE or the twelve rows below them
   never line up and there is nothing to read across — which is the entire
   point of the section. The intro lines are two different sentences of two
   different lengths, so the taller one's height is reserved on both.

   BOTH NUMBERS ARE MEASURED, and there are two of them because the columns
   only grow between 860px and 940px — .plan-grid caps at 940px, so above that
   the cards are a fixed width and the lines stop rewrapping. Measured across
   860 / 880 / 940 / 1024 / 1280 / 1600: Members sets to TWO lines and Drop-in
   to one in that first band, and both to one line from 940px up. So 3.2em
   (2 x 1.6) where the wrap happens and 1.6em where it does not — a flat 3.2em
   everywhere would hang an empty line under both cards at every normal desktop
   width, which is what it did before this was split.

   It was 6.4em until 2026-08-27, for a Members line that ran to four.
   RE-MEASURE BOTH IF EITHER STRING CHANGES — the number that matters is the line
   count of the LONGER one. build.mjs prints a warning when the two strings
   drift far enough apart in characters that this is likely to be wrong.

   Stacked, the two cards are never on screen at the same top edge, so there is
   nothing to align and a reserved fourth line would just be a gap. */
@media (min-width: 860px) {
  .plan-copy { min-height: 3.2em; }
}
@media (min-width: 940px) {
  .plan-copy { min-height: 1.6em; }
}

/* `flex: 1` makes the list absorb the card's leftover height, which is what
   pins the rule and the button to the foot of both cards at once.

   AND THE ROWS DO NOT GROW WITH IT — this is where it departs from the main
   site's .value-list, which puts `flex: 1 0 auto` on its rows so they share the
   slack out between them. That is right for ONE list: the card fills itself
   instead of ending in a void. It is wrong for TWO. The Members card carries a
   guarantee box the Drop-in card does not, so there is ~90px more slack in the
   second card than the first; shared out across six rows it made Drop-in's rows
   15px taller than Members', and by row three the two lists were 60px out of
   step. Measured: 460/490/530/570/610/650 against 435/485/546/606/667/727.
   Two lists that do not line up are not a comparison.

   So the slack pools at the FOOT of the shorter card, between its last feature
   and its dashed rule, exactly as it does on .price-card. A gap above a rule
   reads as a card with room in it; rows that drift out of step read as a bug. */
.plan-feats {
  flex: 1;
  list-style: none;
  margin: 18px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* Three columns: mark / name / (i). The name column is the only flexible one,
   so the ticks line up down the left edge and the buttons down the right no
   matter how the names wrap.

   position: relative is load-bearing — it is the containing block for the
   tooltip, which is what keeps the tooltip's right edge on the card's right
   edge without a line of JS. */
.plan-feat {
  position: relative;
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  column-gap: 10px;
  padding: 9px 0;
  border-top: 1px dashed var(--hairline);
  font-size: 14.5px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--ink);
}
.plan-feat:first-child { border-top: 0; padding-top: 0; }

.plan-mark {
  display: flex;
  color: var(--green-deep);
}

/* The rows this option does NOT include. Greyed rather than struck through or
   hidden: struck through, six rows of it reads as a list of things being taken
   away from you; hidden, the two cards are different lengths and there is
   nothing left to compare. Grey says "that one is on the other card", which is
   the sentence this section exists to say.

   The COLOUR IS NOT THE ONLY SIGNAL — the dash is the second and an sr-only
   "Not included:" is the third, which is what makes the rows legible to
   someone who cannot see either. */
.plan-feat--off { color: var(--warm-gray); }
.plan-feat--off .plan-mark { color: var(--warm-gray); }

/* The (i). Sized for a thumb, not for the 15px glyph inside it: 9px of padding
   round the icon makes a 33px target, and the negative margins take that
   padding back out of the row so a control with a proper hit area does not set
   the row height or push its own glyph in off the card's right text edge. */
.plan-info {
  appearance: none;
  -webkit-appearance: none;
  border: 0;
  background: none;
  padding: 9px;
  margin: -6px -9px -6px 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: var(--stone);
  opacity: .5;
  cursor: pointer;
  transition: opacity .18s ease-out, color .18s ease-out;
}
.plan-info:hover,
.plan-info[aria-expanded="true"] { opacity: 1; color: var(--maroon); }
.plan-info:focus-visible {
  outline: 2px solid var(--deep);
  outline-offset: 0;
  opacity: 1;
}
/* Half-lit on the rows that are greyed out, or six bright controls on the
   Drop-in card draw the eye straight to the three things it does not get. */
.plan-feat--off .plan-info { opacity: .34; }

/* THE TOOLTIP. Dark, because it is the one thing on this page that has to read
   as floating ABOVE the card rather than as part of it — every other panel in
   the brand is white, cream or green tint, and a fourth pale box would look
   like a row that had unfolded.

   Opened three ways and only one of them needs JS: :hover for a mouse,
   :focus-visible for a keyboard, and [aria-expanded="true"] for a tap, which
   sunday.js sets. With scripts off the first two still work, so no reader
   loses the copy — see the note in template-sunday.html.

   Hidden by visibility rather than by `hidden` or display:none: visibility
   keeps it out of the accessibility tree while closed AND leaves it
   referenceable by the button's aria-describedby, so a screen reader gets the
   description on focus without anything being opened at all. It also leaves
   something for the fade to animate. */
.plan-tip {
  position: absolute;
  z-index: 6;
  top: calc(100% - 6px);
  right: 0;
  width: min(272px, 100%);
  padding: 11px 13px;
  /* --ink (#262625), not --deep (#050500). Both are "the dark one"; --deep is
     effectively black and against a cream page it reads as a hole rather than
     as a panel. --ink is the body colour, so the box is the page's own darkest
     material turned inside out. */
  background: var(--ink);
  color: var(--cream);
  border-radius: var(--r-card);
  box-shadow: 0 10px 26px rgba(5,5,0,.22);
  font-size: 13px;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: 0;
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  /* The visibility delay is what lets the fade OUT finish before the box is
     taken out of the tree; on the way in it is switched to 0s so the box is
     there for the whole fade in. */
  transition: opacity .16s ease-out, transform .16s ease-out, visibility 0s linear .16s;
}
/* The tail. A rotated square sharing the box's background rather than a border
   triangle — same reason the old guarantee bubble used one: a flat triangle
   butted against a rounded corner shows its seams. `right: 8px` puts it under
   the centre of the (i), which sits 9px past the card's right text edge with
   a 33px box. */
.plan-tip::before {
  content: "";
  position: absolute;
  top: -4px;
  right: 8px;
  width: 11px;
  height: 11px;
  margin-right: -5.5px;
  background: var(--ink);
  border-radius: 2px;
  transform: rotate(45deg);
}
.plan-info:hover ~ .plan-tip,
.plan-info:focus-visible ~ .plan-tip,
.plan-info[aria-expanded="true"] ~ .plan-tip {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity .16s ease-out, transform .16s ease-out, visibility 0s;
}
/* The last two rows sit close enough to the foot of the card that a tooltip
   below them hangs over the button. Flipping the bottom rows to open UPWARDS
   costs nothing and keeps the pill clear. */
.plan-feat:nth-last-child(-n + 2) .plan-tip {
  top: auto;
  bottom: calc(100% - 6px);
  transform: translateY(4px);
}
.plan-feat:nth-last-child(-n + 2) .plan-tip::before {
  top: auto;
  bottom: -4px;
}
.plan-feat:nth-last-child(-n + 2) .plan-info:hover ~ .plan-tip,
.plan-feat:nth-last-child(-n + 2) .plan-info:focus-visible ~ .plan-tip,
.plan-feat:nth-last-child(-n + 2) .plan-info[aria-expanded="true"] ~ .plan-tip {
  transform: none;
}
@media (prefers-reduced-motion: reduce) {
  .plan-tip,
  .plan-feat:nth-last-child(-n + 2) .plan-tip { transform: none; transition: none; }
}

/* Closes the list the way the last line of a receipt does. It is a seventh
   dashed rule under six of them on purpose — the rows are separated by the
   same hairline, so this reads as the list's bottom edge rather than as a new
   divider, and it is what stops the button looking like a seventh feature. */
.plan-rule {
  margin-top: 18px;
  border-top: 1px dashed var(--hairline);
}
.plan-cta {
  width: 100%;
  margin-top: 18px;
  font-size: 15.5px;
}

/* The .guarantee component from redesign.css, unrestyled — same green tint,
   same shield, same 13.5px. Members only; see after.guaranteeNote for the two
   homes it had before this one.

   BELOW THE BUTTON, and that is measured rather than preferred. On the main
   site this box sits ABOVE the rule (.price-card .guarantee) because there is
   only one price card and nothing to fall out of step with. Here it is 150px
   that exists in one card of a matched pair and not the other: put it above
   the rule and Members' feature list, rule and pill all sit 150px below
   Drop-in's, and the twelve rows the section is asking you to read across stop
   lining up. Below the button it costs nothing — the two cards are identical
   line for line down to the pill, and Members simply continues.

   It also still lands where it belongs by the older argument: directly under
   the button it de-risks, which is the last thing read before the click. */
.plan-guarantee {
  margin-top: 16px;
  align-items: flex-start;
}

/* THE PLACEHOLDER PILL. Drop-in's checkout URL does not exist yet (2026-08-27)
   and this is what ships until it does: a <span>, not an <a>, aria-disabled
   and out of the tab ring, greyed and with pointer-events off, so there is
   nothing to click and nothing that can 404.

   IT IS A PLACEHOLDER AND MUST LOOK LIKE ONE. Do not tidy this rule away to
   make the pair look finished — the build prints a warning on every run for
   the same reason. Paste the URL into after.options[1].ctaHref and both this
   class and this state disappear on their own. */
.plan-cta.is-pending {
  opacity: .4;
  pointer-events: none;
}


/* ============================ faq (#faq) ============================
   The component is redesign.css's, unchanged. Two things need saying here.

   1. It was built for .surface-green and this page has no green: the answer
      cards are var(--cream) and on the school site they sit on the green
      tint. On this page's white field the same cream card still reads, and
      it is the same cream the hour box uses — so the FAQ answers and the
      deck box are visibly the same material, which is correct.

   2. It carried a bespoke top and bottom until 2026-08-21, both trimmed
      against neighbours that are no longer where they were. It is on the
      page's one rhythm now, like everything else. */
/* The index sits under a page-width heading and rule, so it takes the page
   width too — but the answers must not: a 1180px line of 15.5px text is
   unreadable, and .faq-panel-body's own max-width only governs the text
   inside the card, not the card. */
.sunday .faq-index { margin-top: 8px; }

/* ==================== the last ask (#save-seat) ====================
   Quiet by construction. The yellow booking card above is the loudest object
   on this page and yellow is spent exactly once on it (see .bookcard); this
   section is the same white field as everything else, and the only saturated
   thing on it is the button — which is the standard yellow .btn-primary, the
   same pill in the header and the hero, so the page closes on a control the
   reader has already learned rather than on a new one.

   The top is the page's standard step, like every other section. The BOTTOM
   is the third and last documented exception to it: this is the final thing
   on the page before the footer, and the footer wants air between it and the
   last thing anyone is asked to do. A step and a half, derived from --sec-pad
   rather than written out, so it moves with the scale instead of drifting
   away from it. */
.sunday .section#save-seat.club-close { padding-block: var(--sec-pad) calc(var(--sec-pad) * 1.6); }

.close-wrap { max-width: 40em; margin-inline: auto; text-align: center; }
.close-wrap .h2 { margin-top: 0; }

/* .close-lede is GONE with close.lede, 2026-08-27: the line it styled said
   what .close-cap now says directly above the button that does it. */
/* The one line on the page that argues rather than informs, so it is set like
   a line of argument: display serif, maroon, tight. It is not an .h3 — it is a
   sentence, and heading markup for it would put a fake outline entry between
   the section's own heading and its button. */
.close-kicker {
  /* Was 14px, when this line followed .close-lede. With the lede gone it sits
     directly under the h2 and takes over the lede's step so the section does
     not close up on itself. */
  margin-top: 18px;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 21px;
  line-height: 1.3;
  letter-spacing: -0.01em;
  text-wrap: balance;
  color: var(--maroon);
}
@media (min-width: 720px)  { .close-kicker { font-size: 25px; } }
@media (min-width: 1060px) { .close-kicker { font-size: 28px; } }

/* A 2x2 GRID, not a flex row. The captions are row 1 and the buttons row 2,
   which is what makes the two pills share a baseline no matter how many lines
   either caption runs to — a flex row of two columns can only bottom-align
   them, and that leaves uneven whitespace above the shorter one. This is the
   fix for "awkward and UNEVEN"; see close.capsNote before editing either
   caption.

   DOM order is caption / button / caption / button, which is the reading
   order. The desktop rule re-columns them with explicit grid-area, so the
   markup never has to be reshuffled for the layout. */
.close-act {
  margin-top: 26px;
  display: grid;
  gap: 10px;
  /* Phone: one column, and the pills are as wide as the widest label rather
     than full-bleed — 300px is roughly "Become a Member" plus its arrow plus
     air, and a pill wider than that on a 390px screen stops reading as a
     button. */
  max-width: 300px;
  margin-inline: auto;
}
/* Stacked, the second caption needs to detach from the first button or the
   two choices run together as one list. Reset in the two-column layout, where
   the grid gap does this job. */
.close-act > .close-cap + .btn + .close-cap { margin-top: 14px; }

@media (min-width: 620px) {
  .close-act {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: auto auto;
    column-gap: 20px;
    row-gap: 12px;
    /* Both pills the same width, and the pair no wider than the kicker above
       it. minmax(0, 1fr) rather than 1fr: the columns must be free to be
       narrower than the button text, or a long label blows the grid out past
       its own max-width. */
    max-width: 540px;
    /* Captions hug the buttons they belong to when one wraps and one does not. */
    align-items: end;
  }
  .close-act > .close-cap + .btn + .close-cap { margin-top: 0; }
  .close-act > :nth-child(1) { grid-area: 1 / 1; }
  .close-act > :nth-child(2) { grid-area: 2 / 1; }
  .close-act > :nth-child(3) { grid-area: 1 / 2; }
  .close-act > :nth-child(4) { grid-area: 2 / 2; }
}
@media (min-width: 720px) { .close-act { margin-top: 30px; } }

/* Both pills stretch to their column, which is the other half of the symmetry:
   two centred buttons of different intrinsic widths sitting under two equal
   captions is the same lopsidedness one level down. */
.close-btn { width: 100%; }

/* Small, quiet, stone — a label on a button, not a third line of argument. It
   must not compete with .close-kicker above it, which is the one line in this
   section allowed to be loud. Both captions are set identically on purpose:
   different weights here would turn a choice into a recommendation, and the
   colour of the pill below already does that job. */
.close-cap {
  font-size: 14.5px;
  line-height: 1.45;
  text-wrap: balance;
  color: var(--stone);
}

.sunday { --green-bright: #84CE64; }
.btn-member {
  background-color: var(--green-bright);
  border: 1.5px solid var(--deep);
  /* Same trick as .sunday .btn-primary twenty lines into this file: the
     border eats its own 1.5px out of the 16px padding instead of adding to
     it. Without this the green pill stands 3px taller than the yellow one it
     shares a baseline with, which is visible when they sit side by side. */
  padding: 14.5px 26.5px;
  color: var(--deep);
}

/* ============================ footer ============================ */
.sunday-footer { padding-block: 48px 28px; }
.sfoot {
  display: grid;
  gap: 24px;
  align-items: start;
}
@media (min-width: 640px) {
  .sfoot { grid-template-columns: 1fr auto; align-items: center; }
}
.sfoot-brand { font-weight: 800; color: var(--deep); }
.sfoot-brand img { width: 30px; height: 30px; }
.sfoot-tag { margin-top: 10px; font-size: 17px; color: var(--maroon); }
.sfoot-links {
  display: grid;
  gap: 10px;
  justify-items: start;
}
@media (min-width: 640px) { .sfoot-links { justify-items: end; } }
.sfoot-links .tlink {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 600;
  color: var(--charcoal);
}
.sfoot-email { cursor: pointer; }

/* ============================ reduced motion ============================
   design.md §7: everything renders in its final state. redesign.css already
   handles .reveal, .reveal--now and the arrow nudge, and the other case on
   this page (.statement-egg) declares its own override beside the rule it
   undoes.

   The two rules that used to live here were for the club accordion's panel
   and segment transitions. Both were deleted with the accordion on
   2026-08-10: the section is static now and animates nothing, so there is
   nothing left to switch off. */

/* ==================== the tutor section, rebuilt round the video ====================
   This was a phone-only block until 2026-08-21. It is the layout at every
   width now, because the two things that made it phone-only both went:

   1. THE CLIP IS THE ONLY ONE ON THE PAGE. The hero card used to carry a
      second, rectangular copy for desktop, and this circle was switched off
      above 640px to stop two players holding state against each other. The
      hero has its chat mockup back (see the .chat block), so there is one
      clip, it plays here, and the player ships at every width.
   2. THE SECTION IS SECOND ON THE PAGE, in the DOM, at every width. It used
      to be moved here for phones by an `order` block listing every section
      longhand. That block is deleted: the markup order is now the reading
      order everywhere, which is the only arrangement that cannot drift.

   ---- why this shape and not the old desktop row ----
   Desktop used to read circle-then-column: a portrait with a name and a
   biography beside it, built to be COMPACT because it sat between other
   sections. It is a moment of its own now, and the old row put an autoplaying
   face on screen beside text that had not yet said whose it was.

   So the section is cut as a caption instead: who this is, what she does,
   then her, then what she has to say. Heading and role above the circle, the
   circle large, the biography under it, the whole thing centred.

   `display: contents` is what makes that possible without touching the
   markup. .tutor-copy stops generating a box and its four children become
   flex items of .tutor-grid alongside .tutor-photo, so all five can be
   ordered against each other. The cost is that .tutor-copy's own .reveal fade
   is dropped — an element with no box has nothing to fade — which is why the
   copy simply appears. It is a fair trade for not shipping a second copy of
   the section, and the circle beside it still fades in.

   gap goes to 0 and the vertical rhythm is left to each element's own
   margin-top, because a single flex gap cannot be 6px between a heading and
   its role and 18px around a photograph. */
.tutor-grid {
  flex-direction: column;
  /* nowrap, and the width rules below, both guard the same thing. Turning
     .tutor-copy into display:contents throws away its max-width, and a
     wrapping column flex container sizes its cross axis from its content
     rather than from its parent — so the biography, once expanded, laid
     itself out at max-content and hung off both edges of the phone. Given a
     definite width the items wrap normally again. */
  flex-wrap: nowrap;
  gap: 0;
}
.tutor-copy { display: contents; }
.tutor-copy .h2,
.tutor-role,
.tutor-bio-wrap { width: 100%; }
/* The measure, which the promoted children lost with their parent's box. A
   biography running the full 1180px of the container is not a paragraph
   anybody finishes. The heading is left wide: it is one short line and
   centring it in the column is the composition. */
.tutor-bio-wrap { max-width: 34em; margin-inline: auto; }
.tutor-copy .h2   { order: 1; }
.tutor-role       { order: 2; margin-top: 10px; }
.tutor-photo      { order: 3; margin: 20px 0 2px; }
.tutor-bio-wrap   { order: 4; }

/* It is the only picture in the section and the thing the section is built
   around, so it gets the width. Capped rather than left open so it cannot
   outgrow the play button sitting in the middle of it, and floored so the
   burned-in captions in the clip stay readable on a small phone. */
.tutor-photo { width: clamp(220px, 46vw, 320px); }
@media (max-width: 639.98px) { .tutor-photo { width: min(74vw, 300px); } }

/* The half behind "keep reading". Scoped to the phone on purpose: a visitor
   who collapses this and then rotates into landscape crosses 640px, where
   .tutor-more is display:none — an unscoped rule would leave them with a
   truncated biography and no control to open it. sunday.js applies
   .is-collapsed only below the same breakpoint, so the two agree. */
@media (max-width: 639.98px) {
  .tutor-bio-wrap.is-collapsed .tutor-bio-rest { display: none; }
  .tutor-bio-wrap.is-collapsible .tutor-more { display: inline-flex; }
}

/* The manifesto paragraph's "keep reading", same mechanism and same
   breakpoint as the tutor biography above — see the note on that rule. */
@media (max-width: 639.98px) {
  .mf-wrap.is-collapsed .mf-p-rest { display: none; }
  .mf-wrap.is-collapsible .tutor-more { display: inline-flex; margin-top: 10px; }
}

/* ---- opening it ----
   sunday.js animates the WRAPPER's height (see disclose() there); this is
   the other half of the same movement, on the text that arrives. The height
   tween alone wipes the paragraph into view from the top, which on its own
   reads as the box growing rather than as the words appearing — the fade is
   what makes the first revealed line, which shares a line with text that was
   already there, arrive with the rest of it instead of snapping in ahead.

   An ANIMATION rather than a transition, because the span goes from
   display:none: there is no previous computed opacity for a transition to
   start from. It is keyed off .is-open, which sunday.js adds in the same
   frame as it removes .is-collapsed, so the two start together.

   Slightly shorter than the 380ms height tween and eased out, so the text
   has settled by the time the box stops moving rather than the other way
   round. */
@keyframes sunday-disclose-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@media (max-width: 639.98px) {
  .tutor-bio-wrap.is-open .tutor-bio-rest,
  .mf-wrap.is-open .mf-p-rest {
    animation: sunday-disclose-in 300ms ease-out both;
  }
}

/* The resting state IS the finished paragraph: sunday.js's disclose() flips
   the class and skips the tween under reduced motion, and this stops the
   fade from running behind it. */
@media (prefers-reduced-motion: reduce) {
  .tutor-bio-wrap.is-open .tutor-bio-rest,
  .mf-wrap.is-open .mf-p-rest { animation: none; }
}

/* ---- the tutor section at desktop: the circle left, the words right ----
   Added 2026-08-21. The centred column above is now the PHONE AND TABLET
   layout only. From 900px it becomes a row again — video left, heading, role
   and biography right — because the column was spending a full screen of a
   1440px page on one circle and sixty words, with the widest part of the
   viewport empty on both sides of it. The row reads in the same order (who
   she is, then her, then what she says) once you read it as a picture and a
   caption side by side, which is how a two-column block is read.

   `display: contents` on .tutor-copy is UNDONE here rather than worked
   around. The column needs it — the five elements have to interleave with the
   circle in one flex order — and the row needs the opposite: .tutor-copy is
   the second column and must generate a box to be one. So the ordering rules
   go with it (order is meaningless on a block's children) and the DOM order,
   photo then copy, is the layout.

   Below this breakpoint nothing changes, per Daniil: mobile stays as it is. */
@media (min-width: 900px) {
  .tutor-grid {
    flex-direction: row;
    align-items: center;
    justify-content: center;
    gap: clamp(36px, 5.4vw, 76px);
    text-align: left;
  }
  /* Back to a box, and back to being the thing its own max-width measures. */
  .tutor-copy { display: block; max-width: 40em; }
  /* The margins and the auto-centring belonged to the column: in a row the
     column edge is what aligns them, and 38em on the parent is the measure. */
  .tutor-copy .h2,
  .tutor-role,
  .tutor-bio-wrap { width: auto; }
  .tutor-bio-wrap { max-width: none; margin-inline: 0; }
  /* order:3 put the circle between the role and the biography. In the row it
     is simply the first column, which is also where the markup has it. */
  .tutor-photo { order: 0; margin: 0; width: clamp(230px, 26vw, 340px); }
  .tutor-role { margin-top: 8px; }
  .tutor-bio { margin-top: 12px; }
}
