/* ==========================================================================
   Al-Ghazali Club at Rutgers — Legacy of Light III
   Design language taken from the club's own Instagram identity
   (@rutgersghazaliclub): warm cream ground, deep maroon ink, tan organic
   blobs bleeding off the corners, gold palm-fan, four-point sparkles.
   Every colour below is sampled from that artwork.
   ========================================================================== */

/* ---------- palette ----------
   One palette, and it is not a default that something else can override: the
   preview-only switcher and its three alternates (noir, aubergine, forest)
   came out when the site launched on its own domain. Every value below is
   sampled or measured off the club's own artwork, and the comments say which.
   ------------------------------------------------------------------------ */
:root{
  --paper:      #EFEAD4;   /* sampled: IG post ground            */
  --paper-hi:   #F8F4E4;
  --paper-lo:   #E4DDC2;
  --ink:        #57292C;   /* measured: "Biweekly Online Class", script  */
  --ink-deep:   #35171B;
  --ink-soft:   #6F533B;   /* measured: "Al Ghazali Presents" subtext    */
  --wine:       #7B4249;   /* measured: the big title + the quote        */
  --tan:        #BB9773;   /* sampled: corner blobs, base band   */
  --gold:       #B4894F;
  --gold-hi:    #D8C08C;   /* sampled: bismillah highlight       */
  --gold-deep:  #8A6635;   /* fan gradient only — never used as text */
  --ink-mute:   #6D533C;
  --shadow-rgb: 60 32 26;
}

:root{
  --gap: 42px;
  --edge: clamp(18px, 4vw, 46px);
  --font-display: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  --font-caps: "Cormorant SC", "EB Garamond", Georgia, serif;
  --font-body: "Inter", system-ui, -apple-system, "Segoe UI", sans-serif;
  /* The ROMAN of Cormorant SC, which the headings already use — the same
     superfamily, not a new voice. Used by one block: the Imam al-Ghazali
     biography, ~3,300 characters of continuous prose. Inter is a UI face,
     excellent for a fact list on a dark plate and tiring over a page of
     reading. Loaded in index.html; see the note on that <link>. */
  --font-read: "Cormorant Garamond", "EB Garamond", Georgia, serif;
  --measure: 68ch;
  /* derived, so it follows whichever palette is active. Raw --gold is only
     2.6-4.2:1 on --paper; this holds 4.6-6.7:1 and still reads gold. */
  --gold-ink: color-mix(in srgb, var(--gold) 55%, var(--ink));
  /* --paper is not the darkest cream on the page. On the TINT (tan 17% +
     paper) --gold-ink measures 4.10:1 and the old .conf__k at gold 40%
     measured 4.43:1 — both under the 4.5 small text needs. Measured against
     the painted pixel by tools/verify-conf-lab.js, not guessed. Small caps in
     gold on any cream darker than --paper use this instead. */
  --gold-label: color-mix(in srgb, var(--gold) 28%, var(--ink));
  --ease: cubic-bezier(.22,.61,.36,1);
  /* Decelerating: leaves fast, arrives slowly, no overshoot. Used by every
     transition in the speakers section.

     It has to exist. A transition whose value contains an undefined var() is
     invalid at computed-value time and falls back to the initial `all 0s` —
     which is not a slower animation or a broken one, it is NO animation, with
     nothing reported anywhere. That is exactly how the book came to snap open
     while it still closed correctly: the closing rule sets plain
     transition-duration with no var in it, so it survived while all sixteen
     declarations that opened the book quietly died. */
  --ease-out: cubic-bezier(.23,1,.32,1);
  --z-deco: 0;
  --z-lb: 50;
}

/* ---------- reset ---------- */
*,*::before,*::after{ box-sizing:border-box; }
html{ -webkit-text-size-adjust:100%; }
body{
  margin:0;
  min-height:100dvh;
  background:var(--paper);
  color:var(--ink);
  font-family:var(--font-body);
  font-size:17px;
  line-height:1.65;
  overflow-x:hidden;
  position:relative;
  -webkit-font-smoothing:antialiased;
  transition:background-color .45s var(--ease), color .45s var(--ease);
}
img{ max-width:100%; display:block; }
button{ font:inherit; color:inherit; }

/* Grain utility — keeps large flat fills from banding / dithering. */
.noise-overlay{
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/></svg>");
}

/* ==========================================================================
   FRAME — the club's own artwork, not a redraw of it

   The square Instagram plate is cut into four alpha corner pieces
   (tools/frame-cut.py keys out the cream ground and feathers the edges that
   face the middle, so the sweeping hairlines trail off instead of ending on
   a cut). The pieces decorate the HERO and scroll away with it: the top pair
   pins to the top of the page, the bottom pair to the end of .lede, so the
   masthead and flyer sit inside the plate the way the lockup does in the
   original artwork.

   The pieces carry the artwork's own tan and gold. That used to matter
   because they could not follow the palette switcher; with one palette it is
   simply why they are images and not redrawn SVG.
   ========================================================================== */
.frame{
  position:absolute;
  left:0; right:0;
  height:0;                       /* a rail to hang the corners off, no box */
  pointer-events:none;
  /* the plate is 1:1 and a piece is one quadrant of it, so sizing by width
     keeps each corner motif the size it is in the source artwork */
  --corner:clamp(150px, 29vw, 452px);
}
.frame--top{ top:0; z-index:var(--z-deco); }
/* inside .lede, which is a stacking context — -1 keeps the artwork under the
   flyer and the copy while still sitting on the page's cream ground */
.frame--bot{ bottom:0; z-index:-1; }

.frame__corner{
  position:absolute;
  width:var(--corner); height:var(--corner);
  background-repeat:no-repeat;
  background-size:100% 100%;
}
.frame__corner--tl{ top:0;    left:0;  background-image:url("../img/frame/corner-tl.webp"); }
.frame__corner--tr{ top:0;    right:0; background-image:url("../img/frame/corner-tr.webp"); }
.frame__corner--bl{ bottom:0; left:0;  background-image:url("../img/frame/corner-bl.webp"); }
.frame__corner--br{ bottom:0; right:0; background-image:url("../img/frame/corner-br.webp"); }

/* The hero's lower edge is a section boundary, not the edge of a page. Fade
   the bottom pair into it so the blobs and the palm fan dissolve rather than
   being sliced by the tint band that follows. */
.frame--bot .frame__corner{
  -webkit-mask-image:linear-gradient(to top, transparent 0, #000 20%);
          mask-image:linear-gradient(to top, transparent 0, #000 20%);
}

/* Every drawn element recolours via CSS vars; the plate cannot follow, and it
   should not — it is the club's own artwork. The house palette's paper is
   within a step or two of the plate's cream, so it needs no nudge. Only the
   green preview palette is far enough away to want one. */

/* Scenery must never crowd a small screen. Below 820px the gutters are gone,
   so copy would run under the solid tan — ink on tan is 4.4:1, just under AA.
   Shrink the pieces back into the corners and lift them off the text. */
@media (max-width:820px){
  .frame{ --corner:clamp(128px, 33vw, 240px); }
  /* the top pair sits over the lockup and the flyer, so it can carry its full
     weight; the bottom pair sits behind body copy, so it stays a whisper */
  .frame--bot .frame__corner{ opacity:.55; }
}

/* ---------- a11y ---------- */
.skip{
  position:fixed; top:10px; left:10px; z-index:100;
  transform:translateY(-160%);
  background:var(--ink); color:var(--paper-hi);
  padding:.7rem 1.1rem; border-radius:999px; text-decoration:none;
  font-family:var(--font-caps);
  font-size:.9rem; font-weight:600; letter-spacing:.06em;
  transition:transform .2s var(--ease);
}
.skip:focus-visible{ transform:translateY(0); }

:where(a,button,[tabindex]):focus-visible{
  outline:2px solid var(--ink);
  outline-offset:3px;
  border-radius:2px;
}


/* ==========================================================================
   MASTHEAD — compact horizontal lockup; the intro animation lands here
   ========================================================================== */
.mast{
  position:relative; z-index:20;
  padding:clamp(14px,1.8vw,22px) 0 clamp(12px,1.6vw,18px);
}
.mast__lockup{
  display:flex; align-items:center; justify-content:center;
  gap:clamp(10px,1.2vw,16px);
}
.mast__emblem{
  width:var(--mast-emblem, 40px);
  aspect-ratio:512 / 517;
  flex:none;
  /* fallback: the mask PNG is black-ink-on-transparent, so it stands alone */
  background:url("../img/emblem-mask.png") center/contain no-repeat;
}
.mast__wordmark{
  margin:0;
  width:var(--mast-word, 168px);
  aspect-ratio:480 / 42;
  background:url("../img/wordmark-mask.png") center/contain no-repeat;
}
@supports ((-webkit-mask-image:url("")) or (mask-image:url(""))){
  /* The lockup is the club's mark, not palette furniture: it stays black in
     every palette. (The no-mask fallback above is already black-on-transparent
     PNG, so the two paths agree.) */
  .mast__emblem{
    background:#000;
    -webkit-mask:url("../img/emblem-mask.png") center/contain no-repeat;
            mask:url("../img/emblem-mask.png") center/contain no-repeat;
  }
  .mast__wordmark{
    background:#000;
    -webkit-mask:url("../img/wordmark-mask.png") center/contain no-repeat;
            mask:url("../img/wordmark-mask.png") center/contain no-repeat;
  }
}
.mast__sub{
  margin:.42rem 0 0;
  text-align:center;
  font-family:var(--font-caps);
  font-size:.95rem; font-weight:600;
  text-transform:none; letter-spacing:.14em;
  color:var(--ink-soft);
}
.mast::after{
  content:"";
  display:block;
  width:clamp(46px,5vw,74px); height:1px;
  margin:clamp(12px,1.4vw,18px) auto 0;
  background:color-mix(in srgb, var(--ink) 30%, transparent);
}

/* visually hidden but read aloud */
.u-sr{
  position:absolute; width:1px; height:1px;
  padding:0; margin:-1px; overflow:hidden;
  clip:rect(0 0 0 0); white-space:nowrap; border:0;
}

.btn{
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--ink);
  color:var(--paper-hi) !important;
  text-decoration:none;
  padding:.68rem 1.35rem;
  min-height:44px;
  border-radius:999px;                 /* soft, matching the IG shapes */
  font-family:var(--font-caps);
  font-size:1.08rem; font-weight:600;
  text-transform:none; letter-spacing:.045em;
  transition:background-color .25s var(--ease);
}
.btn:hover{ background:var(--wine); }
.btn::after{ display:none; }

/* ==========================================================================
   STAGE + POSTER
   ========================================================================== */


.poster{
  position:relative;
  margin:0;
  width:-moz-fit-content;
  width:fit-content;
}

.poster__btn{
  display:block;
  padding:0; border:0; background:none;
  line-height:0;
  cursor:pointer;
  position:relative;
  border-radius:10px;                  /* soft corners, matching the IG shapes */
  transition:transform .35s var(--ease), box-shadow .35s var(--ease);
  box-shadow:
    0 2px 4px   rgb(var(--shadow-rgb) / .09),
    0 12px 28px -10px rgb(var(--shadow-rgb) / .24),
    0 38px 70px -26px rgb(var(--shadow-rgb) / .32);
}
.poster__btn img{
  max-width:100%;
  width:auto; height:auto;
  border-radius:10px;
}
.poster__btn:hover{
  transform:translateY(-4px);
  box-shadow:
    0 2px 4px   rgb(var(--shadow-rgb) / .10),
    0 18px 36px -10px rgb(var(--shadow-rgb) / .28),
    0 48px 84px -26px rgb(var(--shadow-rgb) / .36);
}

.poster__zoom{
  position:absolute; right:14px; bottom:14px;
  width:42px; height:42px;
  display:grid; place-items:center;
  color:var(--paper-hi);
  background:rgb(var(--shadow-rgb) / .58);
  -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px);
  border-radius:50%;
  opacity:0;
  transition:opacity .25s var(--ease);
}
.poster__zoom svg{ width:19px; height:19px; }
.poster__btn:hover .poster__zoom,
.poster__btn:focus-visible .poster__zoom{ opacity:1; }

/* ==========================================================================
   LIGHTBOX
   ========================================================================== */
.lb{
  position:fixed; inset:0;
  z-index:var(--z-lb);
  display:grid; place-items:center;
  padding:clamp(14px,3vw,40px);
}
.lb[hidden]{ display:none; }
/* real two-stop gradient, never a flat dark fill */
.lb__scrim{
  position:absolute; inset:0;
  background:linear-gradient(160deg,
    color-mix(in srgb, var(--ink-deep) 94%, #000) 0%,
    var(--ink-deep) 55%,
    color-mix(in srgb, var(--ink-deep) 88%, #000) 100%);
}
.lb__grain{
  position:absolute; inset:0;
  opacity:.035;
  mix-blend-mode:overlay;
  pointer-events:none;
}
/* swapped between posters by site.js; hidden until the new file has decoded
   so the previous poster is never shown under the new one's name */
.lb__img.is-loading{ visibility:hidden; }
.lb__img{
  position:relative;
  max-height:calc(100dvh - 5rem);
  max-width:100%;
  width:auto;
  border-radius:10px;
  box-shadow:0 30px 80px -20px rgb(0 0 0 / .6);
  animation:pop .3s var(--ease) both;
}
@keyframes pop{ from{ opacity:0; transform:scale(.975); } to{ opacity:1; transform:none; } }

.lb__close{
  position:absolute; top:clamp(12px,2.4vw,26px); right:clamp(12px,2.4vw,26px);
  width:46px; height:46px;
  display:grid; place-items:center;
  background:rgb(255 255 255 / .09);
  color:#fff;
  border:1px solid rgb(255 255 255 / .22);
  border-radius:50%;
  cursor:pointer;
  transition:background-color .2s var(--ease);
}
.lb__close:hover{ background:rgb(255 255 255 / .2); }
.lb__close svg{ width:20px; height:20px; }

body.is-locked{ overflow:hidden; }

/* ==========================================================================
   PREVIEW-ONLY palette switcher (delete with its markup to ship)
   ========================================================================== */


/* ==========================================================================
   RESPONSIVE
   ========================================================================== */
@media (max-width:860px){
  :root{ --gap:22px; }
  .btn{ padding:.62rem 1.1rem; }
}

@media (max-width:520px){
  :root{ --edge:15px; }
}

/* ---------- reduced motion ---------- */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{
    animation-duration:.01ms !important;
    animation-iteration-count:1 !important;
    transition-duration:.01ms !important;
  }
  .poster__btn:hover{ transform:none; }
}


/* ==========================================================================
   TYPE SCALE + LAYOUT PRIMITIVES
   ========================================================================== */
.wrap{ width:100%; max-width:1180px; margin-inline:auto; padding-inline:var(--edge); }
.wrap--prose{ max-width:716px; }
/* The speakers lineup is the one block that wants the screen: four names at
   display size plus a split stage do not fit the 1180 the rest of the page is
   set to. Everything else still caps at 1180. */
.wrap--wide{ max-width:1520px; }
/* Previous Classes only. The poster is square, so its height IS its column
   width: at wrap--wide it runs 836px and dwarfs a content-sized plate, and at
   the 1180 wrap the two have to overlap to fit. 1300 is where they sit side by
   side, apart, at the size they were designed at. */
/* ABOUT is mostly artwork plus three short columns, so the 1180 reading wrap
   leaves it looking empty — 57% of a 1920 screen. It runs to ~80% instead;
   every text block inside still carries its own ch cap, so nothing gets a
   longer line out of it. Below 1200 it falls back to the normal wrap. */
@media (min-width:1200px){
  .wrap--about{ max-width:min(1900px, 88vw); }
}
/* inside a prose wrap the column IS the wrap, so it lines up with the centred head */
.wrap--prose :is(.prose,.prose-lead,.empty){ max-width:none; }

.section{ position:relative; z-index:1; padding:clamp(56px,7vw,104px) 0; }
.section--top0{ padding-top:clamp(34px,4vw,58px); }
.section--tint{ background:color-mix(in srgb, var(--tan) 17%, var(--paper)); }
/* #classes-current holds a full-bleed band, which supplies the edge the
   padding used to only imply — so the section gives some of its air back,
   and the heading hands straight to the band instead of clearing it. */
.section--now{ padding:clamp(40px,4.8vw,70px) 0 clamp(44px,5.2vw,76px); }
.section--now .h2{ margin-bottom:0; }

.eyebrow{
  margin:0 0 .85rem;
  font-family:var(--font-caps);
  font-size:1.18rem; font-weight:600;
  text-transform:none; letter-spacing:.045em;
  color:var(--ink-soft);
}
.eyebrow--c{ text-align:center; }
.eyebrow__dot{ color:var(--ink-soft); letter-spacing:0; font-size:.82em; }

.h2{
  margin:0 0 clamp(28px,3.4vw,48px);
  font-family:var(--font-caps);
  font-size:clamp(2.15rem,4vw,3.15rem);
  font-weight:700; line-height:1.08;
  text-transform:none; letter-spacing:.045em;
  color:var(--ink);
}
.h2--c{ text-align:center; }

.lead{
  font-family:var(--font-display);
  font-size:clamp(1.08rem,1.55vw,1.3rem);
  font-weight:700; line-height:1.5;
  color:var(--wine);
  max-width:44ch;
  margin:0 0 1.9rem;
}
.prose-lead{
  font-family:var(--font-display);
  font-size:clamp(1.12rem,1.8vw,1.42rem);
  font-weight:700; line-height:1.5;
  color:var(--wine);
  margin:0 0 2rem;
  max-width:var(--measure);
}
.prose{ max-width:var(--measure); }
.prose p{ margin:0 0 1.35em; }
.prose p:last-child{ margin-bottom:0; }

/* ==========================================================================
   HERO
   ========================================================================== */
/* ==========================================================================
   SPEAKER CARDS
   ========================================================================== */
.empty{
  color:var(--ink-soft);
  background:repeating-linear-gradient(
    -45deg,
    color-mix(in srgb, var(--tan) 16%, transparent) 0 8px,
    transparent 8px 16px);
  border:1px dashed color-mix(in srgb, var(--ink) 26%, transparent);
  border-radius:10px;
  padding:1rem 1.1rem;
  font-style:italic;
}
.empty{
  display:flex; align-items:center; gap:.8rem;
  max-width:var(--measure);
  padding:1rem 1.2rem;
}
.empty__mark{ color:var(--ink-soft); font-size:1.3rem; font-style:normal; }
.empty__text{ margin:0; }

/* ==========================================================================
   NOTES (bulleted asides on About)
   ========================================================================== */


/* ==========================================================================
   FOOTER — the tan base band from the Instagram artwork
   ========================================================================== */
.foot{
  position:relative; z-index:1;
  background:var(--tan);
  transition:background-color .45s var(--ease);
}
.foot__inner{
  display:flex; align-items:center; justify-content:space-between;
  gap:1.2rem; flex-wrap:wrap;
  padding-block:clamp(22px,3vw,34px);
}
.foot__mark{
  width:34px; height:34px; flex:none;
  background:url("../img/emblem-mask.png") center/contain no-repeat;
}
@supports ((-webkit-mask-image:url("")) or (mask-image:url(""))){
  .foot__mark{
    background:var(--ink-deep);
    -webkit-mask:url("../img/emblem-mask.png") center/contain no-repeat;
            mask:url("../img/emblem-mask.png") center/contain no-repeat;
  }
}
.foot__name{
  margin:0; flex:1 1 auto;
  font-family:var(--font-caps);
  font-size:1.1rem; font-weight:600;
  text-transform:none; letter-spacing:.045em;
  color:var(--ink-deep);
}
.foot__ig{
  color:var(--ink-deep);
  font-family:var(--font-caps);
  font-size:1.1rem; font-weight:600;
  text-transform:none; letter-spacing:.045em;
  text-decoration:none;
  border-bottom:1px solid color-mix(in srgb, var(--ink-deep) 45%, transparent);
  padding-bottom:2px;
  min-height:44px; display:inline-flex; align-items:center;
}
.foot__ig:hover{ border-bottom-color:var(--ink-deep); }


/* ==========================================================================
   ABOUT — the four-year path

   This was six paragraphs of equal weight in a 716px column: ~360px of dead
   margin down each side of a 1440px screen, and the two things worth leading
   with both buried — the four-year curriculum (a clause in the middle of a
   sentence) and the "last chances" line (the third paragraph).

   The width is filled with STRUCTURE, not with longer lines; no prose here
   passes ~58ch. The trail carries the curriculum, the three seals mark the
   years, and the skyline artwork sits behind it.

   --gold-ink exists because raw --gold on --paper measures 2.6-4.2:1 across
   the four palettes, so it fails AA as small text. The derived tone holds
   4.6-6.7:1 and still reads as gold. Raw --gold is kept for hairlines only,
   where nothing depends on seeing it.
   ========================================================================== */
.eyebrow--gold{ color:var(--gold-ink); }

/* Ink / Paper opening: a strong identity field beside a dedicated reading
   field. The two parts share one silhouette, so the section has presence
   without turning either paragraph into a detached card. */
.about-ink{
  display:grid;
  grid-template-columns:minmax(330px,.84fr) minmax(0,1.16fr);
  min-height:clamp(350px,31vw,470px);
}
.about-ink__identity{
  position:relative;
  display:flex; flex-direction:column; justify-content:space-between;
  overflow:hidden;
  padding:clamp(34px,5vw,76px);
  background:var(--ink); color:var(--paper-hi);
}
.about-ink__identity::after{
  content:"";
  position:absolute;
  right:clamp(30px,4vw,62px); bottom:clamp(30px,4vw,62px);
  width:clamp(70px,8vw,116px); aspect-ratio:1;
  border:1px solid color-mix(in srgb,var(--gold-hi) 52%,transparent);
  border-radius:50%;
}
.about-ink__identity .eyebrow{
  position:relative; z-index:1;
  margin:0; color:var(--gold-hi);
}
.about-ink__title{
  position:relative; z-index:1;
  max-width:8ch; margin:0;
  font-family:var(--font-caps); font-weight:700;
  font-size:clamp(3rem,5.4vw,5.8rem); line-height:.9;
  letter-spacing:.025em; color:var(--paper-hi); text-wrap:balance;
}
.about-ink__story{
  display:grid; align-content:center;
  padding:clamp(38px,6vw,90px);
  border:1px solid color-mix(in srgb,var(--ink) 20%,transparent);
  border-left:0;
  background:var(--paper-hi);
}
.about-ink__story p{
  max-width:54ch; margin:0;
  color:var(--ink-soft);
  font-size:clamp(1.05rem,1.35vw,1.18rem); line-height:1.82;
  text-wrap:pretty;
}
.about-ink__story .about-ink__weekly{
  margin-top:clamp(24px,3vw,38px);
  padding-top:clamp(22px,2.6vw,32px);
  border-top:1px solid color-mix(in srgb,var(--gold) 48%,transparent);
  color:var(--wine);
  font-family:var(--font-caps); font-weight:700;
  font-size:clamp(1.28rem,1.8vw,1.62rem); line-height:1.38;
  letter-spacing:.015em;
}
@media (max-width:780px){
  .about-ink{ grid-template-columns:1fr; min-height:0; }
  .about-ink__identity{ min-height:280px; }
  .about-ink__story{
    padding:34px 26px;
    border-left:1px solid color-mix(in srgb,var(--ink) 20%,transparent);
    border-top:0;
  }
  .about-ink__title{ font-size:clamp(3rem,15vw,4.5rem); }
}

/* The original four-year roadmap remains independent of the opening above. */
.pth{ position:relative; margin:clamp(54px,7vw,104px) 0 0; }

/* ---------- the head ----------
   Held to a reading measure and ranged left, so it reads as an introduction to
   the three stops rather than as a banner over them. */
.pth__head{ max-width:56ch; margin:0 0 clamp(26px,3.4vw,44px); }
.pth__title{
  margin:0 0 .5rem;
  font-family:var(--font-caps); font-size:clamp(1.5rem,2.4vw,2.1rem); font-weight:700;
  letter-spacing:.03em; line-height:1.1;
}
.pth__lede{ margin:0; max-width:46ch; color:var(--ink-soft); font-size:1.02rem; line-height:1.7; }
.pth__art{ position:relative; }
/* bleeds to the section edge, and is 128% tall so the spires clear the trail */
.pth__sky{
  position:absolute; bottom:0; left:calc(-1 * var(--edge));
  /* width is set, never left+right: on a replaced element `width:auto`
     resolves from the intrinsic aspect ratio and ignores the offsets, which
     made this 396px wide inside a 360px viewport. max-width:none is needed
     to escape the global `img{max-width:100%}`. */
  width:calc(100% + 2 * var(--edge)); max-width:none;
  height:128%;
  object-fit:cover; object-position:50% 100%;
  opacity:.72; pointer-events:none; user-select:none;
}
.pth__trail{ position:relative; display:block; width:100%; height:auto; color:var(--gold); }
@media (max-width:779px){
  .pth__trail{ display:none; }
  .pth__art{ height:132px; margin-bottom:clamp(14px,3vw,22px); }
  .pth__sky{ height:100%; object-fit:contain; }
}

.pth__stops{
  list-style:none; margin:0; padding:0;
  display:grid; gap:clamp(26px,3.4vw,38px); text-align:center;
}
@media (min-width:780px){
  .pth__stops{ grid-template-columns:repeat(3,minmax(0,1fr)); gap:0 clamp(16px,2.2vw,32px); }
}
/* the seal repeats above each stacked stop on mobile, where the trail that
   normally carries it is hidden */
.pth__seal{ display:block; width:76px; height:76px; margin:0 auto .75rem; }
@media (min-width:780px){ .pth__seal{ display:none; } }
.pth__n{
  display:block; font-family:var(--font-caps); font-weight:600;
  font-size:.79rem; letter-spacing:.19em; text-transform:uppercase;
  color:var(--gold-ink); margin-bottom:.5rem;
}
.pth__t{
  display:block; font-family:var(--font-caps); font-weight:700; letter-spacing:.03em;
  font-size:clamp(1.35rem,2.05vw,1.7rem); line-height:1.1; color:var(--ink);
}
.pth__rule{
  display:block; width:38px; height:1px; margin:clamp(11px,1.3vw,16px) auto;
  background:color-mix(in srgb, var(--gold) 72%, transparent);
}
.pth__s{ margin:0 auto; color:var(--ink-soft); font-size:.95rem; max-width:27ch; }

/* ==========================================================================
   THE CLOSING PLATE — the Ribs treatment out of lab/closing-design-lab

   The section has spent its whole length on cream. This ends it on the club's
   own artwork instead: the tan ground and gold-frond corners off the Instagram
   plate, the sentence on a cream field, and the three offerings as cards laid
   on the tan like prints on a mat.

   The drawing between them is a fan of ELEVEN ribs, of which three are live.
   A fan with one rib per offering is a fork; the ghost ribs are what make it
   an object, and they are what make the three that matter read as chosen.

   Two numbers hold the whole thing together and are easy to break:

   1. The rib viewBox is 240x600 and the three live ribs land at y = 100, 300
      and 500 — sixths, which is where the centres of three equal rows are. So
      .close__items must stay exactly three 1fr rows with NO gap and NO
      padding-block. The cards separate with a transparent border and
      background-clip instead, which lets the tan show through without taking
      height out of the rows. Add a grid gap here and every live rib will point
      at the space between two cards.

   2. The viewBox is stretched (preserveAspectRatio="none") so the ribs reach
      the rows at any height. Nothing round can live inside it — a circle in a
      squashed viewBox is an ellipse. That is why the pin is a DOM element
      sitting on the same left edge the ribs start from, not an SVG circle.

   The stagger on the cards is an indent, never a margin: a margin moves a
   card's left edge, and that edge is exactly where a rib has to land.
   ========================================================================== */

/* The plate is a direct child of #about, so it is already window-wide. The
   section's own bottom padding would leave a cream strip under it. */
#about{ padding-bottom:0; }

.close{
  position:relative; isolation:isolate; overflow:clip;
  margin-top:clamp(40px,5vw,80px);
  padding:clamp(56px,7vw,108px) clamp(20px,5vw,60px) clamp(60px,7.5vw,116px);
  background:
    radial-gradient(128% 76% at 30% 8%, color-mix(in srgb, var(--paper-hi) 70%, var(--tan)) 0%, transparent 72%),
    linear-gradient(172deg,
      color-mix(in srgb, var(--tan) 44%, var(--paper)) 0%,
      color-mix(in srgb, var(--tan) 60%, var(--paper)) 54%,
      color-mix(in srgb, var(--tan) 48%, var(--paper)) 100%);
}
.close__grain{ position:absolute; inset:0; z-index:-1; pointer-events:none; opacity:.05; mix-blend-mode:overlay; }

/* The artwork carries its own tan and gold and cannot follow the palette
   switcher — same rule as .frame. Only the green preview is far enough away
   from the plate's cream to want a nudge. */
.close__corner{
  position:absolute; z-index:-1; width:clamp(170px,22vw,400px); height:auto;
  pointer-events:none;
  transition:transform 900ms cubic-bezier(.23,1,.32,1); will-change:transform;
}
.close__corner--tl{ top:0; left:0; }
.close__corner--tr{ top:0; right:0; }
.close__corner--bl{ bottom:0; left:0; }
.close__corner--br{ bottom:0; right:0; }

.close__inner{
  display:grid;
  grid-template-columns:minmax(360px,.92fr) minmax(120px,.24fr) minmax(440px,1.1fr);
  align-items:stretch; min-height:600px;
  width:min(1360px,100%); margin-inline:auto;
}

/* The sentence on its own cream field, so it reads as the plate's open centre
   rather than as text dropped on a tint. */
.close__field{
  display:flex; flex-direction:column; justify-content:center;
  padding:clamp(32px,3.6vw,56px) clamp(26px,3vw,46px);
  background:linear-gradient(178deg, var(--paper-hi) 0%, color-mix(in srgb, var(--paper-hi) 74%, var(--paper)) 100%);
  box-shadow:0 24px 56px -34px rgb(var(--shadow-rgb) / .5);
}
.close__quote{
  margin:0; color:var(--wine);
  font-family:var(--font-caps); font-weight:700;
  letter-spacing:-.01em; line-height:.96; text-wrap:balance;
}
.close__line{ display:block; }
.close__line--lead{ font-size:clamp(1.75rem,2.6vw,2.75rem); line-height:1.02; }
.close__line--turn{ margin-block:.05em; color:var(--ink-deep); font-size:clamp(2.7rem,4.3vw,4.7rem); line-height:.86; }
.close__line--tail{ max-width:16ch; font-size:clamp(2rem,2.8vw,3.05rem); line-height:.99; }
.close__appeal{
  max-width:40ch; margin:1.5rem 0 0; padding-top:1.25rem;
  border-top:1px solid color-mix(in srgb, var(--gold) 56%, transparent);
  color:var(--ink-soft); line-height:1.72;
}

.close__fan{ position:relative; }
.close__ribs{ display:block; width:100%; height:100%; overflow:visible; color:var(--gold); }
.close__rib{ transition:opacity 220ms var(--ease), stroke 220ms var(--ease); }
.close__rib--ghost{ stroke-width:1; opacity:.2; }
.close__rib--live{ stroke-width:1.7; opacity:1; }
.close__pin{
  position:absolute; top:50%; left:0;
  width:18px; aspect-ratio:1; border-radius:50%;
  background:var(--paper-hi);
  box-shadow:0 0 0 1.6px var(--gold);
  transform:translate(-50%,-50%);
}
.close__pin::after{
  content:""; position:absolute; inset:0; margin:auto;
  width:5px; height:5px; border-radius:50%; background:var(--gold);
}

.close__items{ display:grid; grid-template-rows:repeat(3,1fr); }
.close__card{
  display:grid; grid-template-columns:auto minmax(140px,.4fr) minmax(0,.6fr);
  gap:clamp(14px,1.6vw,26px); align-content:center; align-items:start;
  padding:clamp(20px,2.2vw,32px) clamp(20px,2.2vw,30px);
  padding-left:calc(clamp(20px,2.2vw,30px) + var(--indent,0px));
  border-block:clamp(7px,.9vw,13px) solid transparent;
  background:color-mix(in srgb, var(--paper-hi) 88%, var(--paper));
  background-clip:padding-box;
  box-shadow:0 14px 32px -24px rgb(var(--shadow-rgb) / .55);
  transition:background-color 240ms var(--ease), box-shadow 240ms var(--ease);
}
.close__card--1{ --indent:0px; }
.close__card--2{ --indent:clamp(10px,1.8vw,30px); }
.close__card--3{ --indent:clamp(22px,3.8vw,64px); }
.close__spark{ width:14px; height:14px; margin-top:.3rem; color:var(--gold); }
.close__lede{
  display:block; margin-bottom:.4rem;
  color:var(--gold-ink); font-family:var(--font-caps); font-size:.68rem;
  font-weight:600; letter-spacing:.2em; text-transform:uppercase;
}
.close__card h3{
  margin:0; color:var(--ink);
  font-family:var(--font-caps); font-weight:700;
  font-size:clamp(1.4rem,1.95vw,1.9rem); letter-spacing:.02em; line-height:1;
}
.close__card p{ max-width:36ch; margin:0; color:var(--ink-soft); font-size:.98rem; line-height:1.66; }

/* The linkage between a card and its rib is the only thing here that needs
   :has(). It is hover-only and decorative — without it the card still lifts,
   the ribs just stop answering. That is the right thing to lose on a browser
   old enough not to have :has(), so there is no JS fallback for it. */
@media (hover:hover) and (pointer:fine){
  .close__card:hover{ background:var(--paper-hi); box-shadow:0 22px 44px -22px rgb(var(--shadow-rgb) / .5); }
  .close__inner:has(.close__card:hover) .close__rib--live{ opacity:.22; }
  .close__inner:has(.close__card--1:hover) .close__rib--1,
  .close__inner:has(.close__card--2:hover) .close__rib--2,
  .close__inner:has(.close__card--3:hover) .close__rib--3{ opacity:1; stroke:var(--wine); }
}

  /* The fan needs three columns and the middle one is only the connector, so
     the two that carry words get about a third of the width each. Below
     ~1200px that leaves the card copy at under 30 characters a line and the
     quote field a tall empty slab — the layout has to stack well before the
     columns technically stop fitting. */
@media (max-width:1200px){
  .close__inner{ grid-template-columns:1fr; min-height:0; }
  .close__fan{ display:none; }
  .close__line--lead{ font-size:clamp(1.8rem,8vw,2.7rem); }
  .close__line--turn{ font-size:clamp(2.8rem,13.5vw,4.5rem); }
  .close__line--tail{ max-width:none; font-size:clamp(2rem,9.5vw,3.1rem); }
  .close__items{ margin-top:22px; }
  .close__card,
  .close__card--1,.close__card--2,.close__card--3{
    --indent:0px; grid-template-columns:auto 1fr; gap:.55rem 14px; margin:0;
  }
  /* Stacked, the copy has the whole plate to run across — 110 characters a
     line at 1024px. Cap it: the rule is the measure, not the container. */
  .close__card p{ grid-column:2; max-width:62ch; }
  .close__corner{ width:clamp(120px,34vw,210px); }
}

/* --------------------------------------------------------------------------
   SCROLL REVEAL

   A [data-motion-root] gets .is-visible when it scrolls into view (site.js);
   everything inside keys off that. Two primitives: [data-reveal] rises and
   fades, .motion-path draws its own stroke. Both honour --reveal-delay, and
   both resolve to their finished state under reduced motion — and with no
   JavaScript at all, since .is-visible is what hides nothing by default.
   -------------------------------------------------------------------------- */
[data-motion-root].js-reveal [data-reveal]{
  opacity:0; transform:translateY(14px);
  transition:
    opacity 480ms cubic-bezier(.23,1,.32,1) var(--reveal-delay,0ms),
    transform 620ms cubic-bezier(.23,1,.32,1) var(--reveal-delay,0ms);
}
[data-motion-root].js-reveal.is-visible [data-reveal]{ opacity:1; transform:none; }

[data-motion-root].js-reveal .motion-path{
  stroke-dasharray:1; stroke-dashoffset:1;
  transition:stroke-dashoffset 1100ms cubic-bezier(.23,1,.32,1) var(--reveal-delay,0ms);
}
[data-motion-root].js-reveal.is-visible .motion-path{ stroke-dashoffset:0; }

@media (prefers-reduced-motion: reduce){
  [data-motion-root].js-reveal [data-reveal],
  [data-motion-root].js-reveal .motion-path{
    opacity:1; transform:none; stroke-dashoffset:0; transition:none;
  }
  .close__corner{ transition:none; }
}


/* ==========================================================================
   RESPONSIVE — content
   ========================================================================== */
@media (max-width:560px){
  .foot__inner{ justify-content:center; text-align:center; }
  .foot__name{ flex:1 1 100%; }
}


/* The poster's script flourish ("Registration Required!") is available but
   unused — nothing on the site has copy that calls for it. To add it, load
   Kaushan Script and apply .script to a single short line.
.script{ font-family:"Kaushan Script",cursive; font-weight:400; color:var(--ink); letter-spacing:0; }
*/


/* ==========================================================================
   CONFERENCE ROW — mirrors the live site's 70 / 30 column split
   ========================================================================== */
.lede{
  position:relative; z-index:1;
  --lede-pad:70px;
  padding:clamp(8px,1.2vw,18px) 0 clamp(40px,5vw,72px);
}
.lede__grid{
  display:grid;
  grid-template-columns:minmax(0,70fr) minmax(260px,30fr);
  gap:clamp(26px,3.4vw,52px);
  align-items:start;
}
.lede__side > p{ margin:0 0 1rem; max-width:38ch; }
.lede__title{
  margin:0 0 1.1rem;
  font-family:var(--font-caps);
  font-size:clamp(1.75rem,2.6vw,2.35rem);
  font-weight:700; line-height:1.08;
  text-transform:none; letter-spacing:.045em;
  color:var(--ink);
}
.lede__cta{ margin:1.35rem 0 1.9rem; }
.sidecard{ margin:0; }
.sidecard img{
  width:100%; height:auto;
  border-radius:12px;
  box-shadow:0 10px 26px -12px rgb(var(--shadow-rgb) / .3);
}

/* the flyer is the wide column now, so let it fill it */
.lede .poster{ width:100%; display:flex; justify-content:center; }
.lede .poster__btn{ display:block; max-width:100%; }
/* fit the first screen: masthead + this image + the row's own padding */
.lede .poster__btn img{
  width:auto; height:auto;
  max-width:100%;
  max-height:calc(100dvh - var(--mast-h, 120px) - var(--lede-pad, 70px));
}

/* ==========================================================================
   PAGE (subpage article)
   ========================================================================== */



/* the live site's small bold sub-heads ("Brief Bio on...") */
.h4{
  margin:clamp(34px,4vw,54px) 0 1.1rem;
  font-family:var(--font-caps);
  font-size:clamp(1.5rem,2.2vw,1.95rem);
  font-weight:700; line-height:1.15;
  text-transform:none; letter-spacing:.045em;
  color:var(--ink);
}
.page .h4:first-of-type{ margin-top:clamp(30px,3.4vw,44px); }

/* ==========================================================================
   SPEAKERS — four bound volumes, and the title of each one is a name

   Was a <details> accordion, then a four-up grid of small columns, then a
   lineup that folded into a split reading view. Each lost on the same point:
   the section had four names and one biography, and every layout that treats
   the four as equal cells spends its whole width advertising how little copy
   there is.

   A book does not have that problem. Four closed covers are a complete,
   finished object at any amount of text, and opening one is the reason the
   text is short — you have only just opened it. So each speaker is a volume
   and their name is its title.

   Opening: the board swings back on the spine while its neighbours narrow
   into spines. The room comes from the row, which is what happens when you
   open a book on a crowded shelf; nothing arrives from elsewhere on the page.

       closed   [spine][      board      ][fore]
       open     [   swung board   ][spine][   pages   ][fore]

   The spine travels to the middle of the widened card and the board hinges on
   it. Hinging at the card's left edge instead looks right on its own and lands
   the opened cover on top of whichever book is next door.

   Body copy never sits on a rotated face — rotateY softens type on most
   engines — so the board carries a monogram at most and the pages carry every
   word.

   THE BINDING IS DRAWN, NOT PHOTOGRAPHED. Cloth, a rounded back, endbands at
   head and tail, raised bands, a lettering piece, a page block. Every part is
   a part of a real binding; take them away and what is left is a coloured
   rectangle. There is no portrait: no photographs of the speakers exist and an
   invented per-person mark is the arbitrary decoration that reads as filler.

   Three of the four biographies do not exist in any source material and carry
   a designed empty state — ruled leaves waiting to be written on — never
   invented copy. An empty book that looks bound and ruled reads as "not yet";
   an empty box reads as broken.

   With JS off the covers never appear: no board, no spine, no fore-edge, just
   four speakers and their bios. See SPEAKERS in site.js.
   ========================================================================== */

/* ---------- the four bindings ----------
   Mixed from palette tokens so they re-colour correctly on all four palettes
   instead of being hard-coded reds and browns. Three dark cloths and one tan
   half-calf: the run needs one light note in it or the row reads as a black
   bar. The lettering piece is the patch of dark morocco a binder glues to the
   spine and stamps the title into — it is always darker than the cloth around
   it and always carries gold, which is why the title reads as a title. */
.spk__book{
  --cloth-a:var(--ink-deep); --cloth-b:var(--ink-deep);
  --foil:var(--gold-hi); --label:var(--paper-hi);
  --piece-a:color-mix(in srgb, var(--ink-deep) 92%, #000);
  --piece-b:color-mix(in srgb, var(--ink-deep) 74%, var(--wine));
  /* Grain, blended over every cloth. Not optional: a flat dark fill
     spatial-dithers into a visible diagonal hairline on 8-bit pipelines, so
     every ground here is a real gradient with noise over it. */
  --cloth-grain:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='180' height='180'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/></filter><rect width='100%25' height='100%25' filter='url(%23n)' opacity='0.5'/></svg>");
}
.spk__book--1{
  --cloth-a:color-mix(in srgb, var(--wine) 82%, var(--ink-deep));
  --cloth-b:color-mix(in srgb, var(--wine) 46%, var(--ink-deep));
  --piece-a:color-mix(in srgb, var(--ink-deep) 94%, #000);
  --piece-b:color-mix(in srgb, var(--ink-deep) 80%, var(--wine));
}
.spk__book--2{
  --cloth-a:color-mix(in srgb, var(--ink-deep) 88%, var(--wine));
  --cloth-b:color-mix(in srgb, var(--ink-deep) 96%, #000);
  --piece-a:color-mix(in srgb, var(--wine) 78%, var(--ink-deep));
  --piece-b:color-mix(in srgb, var(--wine) 52%, var(--ink-deep));
}
/* The light binding. Foil on the CLOTH goes to ink — gold on tan is 1.7:1 and
   would fail on every palette — but the lettering piece is dark morocco, so
   the title on it stays gold like the others. */
.spk__book--3{
  --cloth-a:color-mix(in srgb, var(--tan) 88%, var(--paper-hi));
  --cloth-b:color-mix(in srgb, var(--tan) 72%, var(--ink-deep));
  --foil:var(--ink-deep);
  --label:var(--ink-deep);
  --piece-a:color-mix(in srgb, var(--wine) 74%, var(--ink-deep));
  --piece-b:color-mix(in srgb, var(--wine) 48%, var(--ink-deep));
}
.spk__book--4{
  --cloth-a:color-mix(in srgb, var(--gold-deep) 70%, var(--ink-deep));
  --cloth-b:color-mix(in srgb, var(--gold-deep) 34%, var(--ink-deep));
  --piece-a:color-mix(in srgb, var(--ink-deep) 92%, #000);
  --piece-b:color-mix(in srgb, var(--ink-deep) 72%, var(--gold-deep));
}

/* ==========================================================================
   NO SCRIPT — four speakers and their biographies, and nothing else
   ========================================================================== */
.spk{ display:grid; gap:clamp(22px,2.6vw,38px); }
.spk__prompt{ display:none; }
.spk__row{ list-style:none; margin:0; padding:0; display:grid; gap:clamp(26px,3vw,44px); }
.spk__book{ min-width:0; }
.spk__spine,
.spk__fore,
.spk__leaf,
.spk__close{ display:none; }

/* Without script the name has to be here, at the head of its own biography —
   there is no facing page to carry it. With script it moves to the facing
   page and this becomes the region's accessible heading and nothing else. */
.spk__pagesname{
  margin:0 0 .55rem; font-family:var(--font-caps); font-size:clamp(1.35rem,2.2vw,2.1rem);
  font-weight:700; letter-spacing:.02em; line-height:1;
}
.spk__bio{ font-size:.95rem; line-height:1.72; color:var(--ink-soft); }
.spk__bio p{ margin:0; max-width:62ch; }

/* The empty state, drawn rather than left blank: ruled leaves with the note
   set across them. */
.spk__blank{ position:relative; display:grid; align-content:center; min-height:clamp(110px,14vw,170px); }
.spk__rules{ display:grid; gap:clamp(13px,1.5vw,20px); }
.spk__rules span{ display:block; height:1px; background:color-mix(in srgb, var(--ink) 16%, transparent); }
.spk__rules span:nth-child(3n){ width:82%; }
.spk__rules span:nth-child(4n){ width:91%; }
.spk__rules span:last-child{ width:44%; }
.spk__note{
  position:absolute; inset-inline:0; top:50%; transform:translateY(-50%);
  margin:0; padding:.5em .9em;
  background:color-mix(in srgb, var(--paper-hi) 88%, transparent);
  color:var(--ink-mute); font-family:var(--font-caps); font-size:.92rem;
  font-style:italic; letter-spacing:.03em; text-align:center;
}

/* ==========================================================================
   WITH SCRIPT — the books
   ========================================================================== */
html.js .spk__prompt{
  display:block; margin:0;
  color:var(--ink-mute); font-size:.86rem;
}

/* Closed, the row is held in from the page edges so four covers keep a book's
   proportion instead of stretching into slabs, and the gap gives each one air
   of its own. Opening widens it to the full measure — the row makes room the
   same way the neighbours do. */
html.js .spk__row{
  display:flex; gap:clamp(14px,2.4vw,42px);
  width:min(100%, 1340px);
  height:clamp(280px,30vw,430px);
  transition:width .6s var(--ease-out) .06s, height .6s var(--ease-out) .06s;
}
/* The open row is sized by the one biography that exists. 789 characters have
   to land without a scrollbar appearing inside a book page, and the narrower
   the viewport the taller that column runs — hence the steep vw term. */
html.js .spk__row[data-open]{ width:100%; height:clamp(430px,54vw,565px); }

/* ---------- the opening ----------
   The shelf makes room. The books either side of the one you chose do not just
   narrow, they tip away from the gap, hinged at their feet the way a book
   pivots on the shelf it is standing on — and the nearer one leans harder.
   --side carries the direction and --away the distance, both set by site.js
   against the closed row, so a book at either end leans the correct way.

     0.00-0.18  the chosen volume straightens and comes forward a hair
     0.06-0.66  its neighbours narrow and tip
     0.30-0.92  the board swings open

   All four names stay readable at every frame of it, which is the reason this
   one is here and not something more theatrical. */
html.js .spk__book{
  flex:1 1 0; min-width:0;
  transform-origin:50% 100%;
  transition:
    flex-grow .6s var(--ease-out) .06s,
    transform .6s cubic-bezier(.34,1.18,.5,1) .06s,
    filter .4s ease-out;
}
html.js .spk__row[data-open] .spk__book{ flex-grow:.42; }
html.js .spk__row[data-open] .spk__book[data-active]{ flex-grow:4.6; }
html.js .spk__row[data-open] .spk__book:not([data-active]){
  transform:rotate(calc(var(--side, 1) * (4deg - var(--away, 1) * .9deg))) translateY(2px);
  filter:brightness(.9) saturate(.82);
}
/* The only upright thing in the row. */
html.js .spk__row[data-open] .spk__book[data-active]{
  transform:translateY(-6px);
  transition:flex-grow .6s var(--ease-out) .06s, transform .18s var(--ease-out);
}
/* A leaning book throws its shadow sideways. Without it the tilt reads as a
   flat rotation of a picture rather than an object standing on something. */
html.js .spk__row[data-open] .spk__book:not([data-active]) .spk__face--out{
  box-shadow:
    inset 3px 0 0 rgb(0 0 0 / .34),
    inset 6px 0 10px -6px rgb(0 0 0 / .5),
    inset 0 -3px 7px -4px rgb(0 0 0 / .55),
    inset 0 0 0 1px rgb(255 255 255 / .07),
    calc(var(--side, 1) * 10px) 20px 26px -20px rgb(var(--shadow-rgb) / .75);
  transition:box-shadow .6s var(--ease-out) .06s;
}

/* Closing is not the opening reversed. A CSS transition runs its delays in
   both directions, so without this the reader who wants the book shut waits
   out the whole staggered performance backwards. */
html.js .spk__row:not([data-open]),
html.js .spk__row:not([data-open]) .spk__book,
html.js .spk__row:not([data-open]) .spk__spine,
html.js .spk__row:not([data-open]) .spk__leaf,
html.js .spk__row:not([data-open]) .spk__pages{
  transition-duration:.34s;
  transition-delay:0s;
}

html.js .spk__vol{
  /* --sp is the spine, --fe the sliver of page block past the board. Every
     horizontal measurement is written against these two, so the book stays
     assembled at any width. */
  --sp:clamp(16px,1.9vw,28px);
  --fe:clamp(3px,.45vw,7px);
  /* Where the gutter falls once the book is open. Not the middle: the swung
     board carries a monogram and the pages carry every word, so the text gets
     the larger half. */
  --gut:40%;
  position:relative; height:100%; perspective:2200px;
}

/* ---------- the spine, which does not move when the board does ---------- */
html.js .spk__spine{
  position:absolute; left:0; top:0; bottom:0; z-index:0;
  width:var(--sp);
  display:flex; flex-direction:column;
  padding:2px 0;
  border-radius:3px 0 0 3px;
  background-image:var(--cloth-grain), linear-gradient(163deg, var(--cloth-a) 0%, var(--cloth-b) 62%, color-mix(in srgb, var(--cloth-b) 82%, #000) 100%);
  background-blend-mode:overlay, normal;
  background-size:180px 180px, auto;
  box-shadow:inset -3px 0 5px -2px rgb(0 0 0 / .6), inset 0 0 0 1px rgb(0 0 0 / .3);
  transition:left .6s var(--ease-out) .06s;
}
html.js .spk__book[data-active] .spk__spine{ left:calc(var(--gut) - var(--sp)); }

/* The striped silk cord at the head and tail of a sewn spine. */
.spk__endband{
  position:relative; z-index:2;
  height:6px; margin-inline:1px;
  background:repeating-linear-gradient(90deg,
    color-mix(in srgb, var(--gold-hi) 88%, var(--paper-hi)) 0 2px,
    color-mix(in srgb, var(--wine) 82%, var(--ink-deep)) 2px 4px);
  box-shadow:inset 0 -2px 3px -1px rgb(0 0 0 / .55), inset 0 1px 0 rgb(255 255 255 / .3);
  opacity:.9;
}
/* A raised band sits proud of the leather: highlight on top, shadow beneath,
   a gilt rule tooled either side. */
.spk__band{
  position:relative; z-index:2;
  width:100%; height:8px;
  background:linear-gradient(180deg,
    rgb(255 255 255 / .20) 0 30%,
    rgb(0 0 0 / .05) 30% 62%,
    rgb(0 0 0 / .42) 100%);
  box-shadow:
    0 1px 0 color-mix(in srgb, var(--foil) 46%, transparent),
    0 -1px 0 color-mix(in srgb, var(--foil) 30%, transparent);
}
.spk__spinenum{
  position:relative; z-index:2;
  flex:1;
  display:grid; place-items:center;
  writing-mode:vertical-rl;
  color:var(--foil); font-family:var(--font-caps); font-size:.7rem;
  font-weight:700; letter-spacing:.3em;
  text-shadow:0 1px 0 rgb(0 0 0 / .45);
}
/* Light falling across a rounded back: dark at the joint, bright a third of
   the way over, falling off to the far edge. */
.spk__round{
  position:absolute; inset:0; z-index:3; pointer-events:none; border-radius:inherit;
  background:linear-gradient(90deg,
    rgb(0 0 0 / .50) 0%,
    rgb(0 0 0 / .14) 7%,
    rgb(255 255 255 / .11) 30%,
    rgb(255 255 255 / .03) 52%,
    rgb(0 0 0 / .16) 74%,
    rgb(0 0 0 / .52) 100%);
}

/* The page block: real leaves, so the stripes are uneven — a regular comb
   reads as hatching, not as paper. */
html.js .spk__fore{
  position:absolute; right:0; top:5px; bottom:5px; z-index:0;
  display:block; width:var(--fe);
  border-radius:0 2px 2px 0;
  background:repeating-linear-gradient(90deg,
    var(--paper-hi) 0 1px,
    color-mix(in srgb, var(--paper-lo) 72%, var(--ink)) 1px 2px,
    var(--paper) 2px 4px,
    color-mix(in srgb, var(--paper-lo) 86%, var(--ink)) 4px 5px);
  box-shadow:inset 0 0 6px -2px rgb(var(--shadow-rgb) / .6);
}

/* ---------- the board ---------- */
html.js .spk__leaf{
  position:absolute; left:var(--sp); top:0; bottom:0; z-index:1;
  display:block;
  width:calc(100% - var(--sp) - var(--fe));
  padding:0; border:0; background:none;
  cursor:pointer;
  transform-origin:left center;
  transform-style:preserve-3d;
  transition:transform .62s var(--ease-out) .3s, left .6s var(--ease-out) .06s, width .6s var(--ease-out) .06s;
}
html.js .spk__book[data-active] .spk__leaf{
  left:var(--gut); width:calc(var(--gut) - var(--fe));
  transform:rotateY(-168deg);
}
.spk__leaf:focus-visible{ outline:2px solid var(--gold-hi); outline-offset:4px; }

.spk__face{
  position:absolute; inset:0;
  display:grid; align-content:space-between; justify-items:center;
  padding:clamp(14px,1.7vw,26px) clamp(11px,1.4vw,22px);
  -webkit-backface-visibility:hidden; backface-visibility:hidden;
  text-align:center;
}
.spk__face--out{
  border-radius:2px 4px 4px 2px;
  background-image:var(--cloth-grain), linear-gradient(163deg, var(--cloth-a) 0%, var(--cloth-b) 62%, color-mix(in srgb, var(--cloth-b) 82%, #000) 100%);
  background-blend-mode:overlay, normal;
  background-size:180px 180px, auto;
  color:var(--label);
  box-shadow:
    /* the joint, where the board hinges off the spine */
    inset 3px 0 0 rgb(0 0 0 / .34),
    inset 6px 0 10px -6px rgb(0 0 0 / .5),
    /* the board's own thickness, caught along the foot */
    inset 0 -3px 7px -4px rgb(0 0 0 / .55),
    inset 0 0 0 1px rgb(255 255 255 / .07),
    0 16px 30px -18px rgb(var(--shadow-rgb) / .7);
}
/* A blind-stamped rule inset from the board edge. */
.spk__face--out::before{
  content:""; position:absolute; inset:clamp(8px,1vw,13px);
  border:1px solid color-mix(in srgb, var(--foil) 34%, transparent);
  border-radius:2px; pointer-events:none;
  transition:border-color .22s var(--ease-out);
}
.spk__face--in{
  transform:rotateY(180deg);
  place-content:center; align-content:center; gap:.9rem;
  border-radius:2px;
  background-image:var(--cloth-grain), linear-gradient(155deg, var(--paper-lo), color-mix(in srgb, var(--tan) 34%, var(--paper-lo)));
  background-blend-mode:soft-light, normal;
  background-size:180px 180px, auto;
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--ink) 18%, transparent);
}
/* ---------- the facing page ----------
   The board that swings left is not blank endpaper any more; it is the title
   page, and it carries the name. That leaves the right-hand page with one job
   and one voice — the biography — instead of repeating the name above it at a
   second size. It is also the order a book uses: title verso, text recto. */
.spk__title{
  display:grid; justify-items:center; gap:.3rem;
  text-align:center;
}
.spk__titlevol{
  color:var(--gold-ink); font-family:var(--font-caps); font-size:.68rem;
  font-weight:700; letter-spacing:.2em; text-transform:uppercase;
}
.spk__titlerule{ display:block; width:38px; height:1px; margin:.15rem 0 .4rem; background:var(--gold-ink); }
.spk__titlehon{
  color:var(--ink-mute); font-family:var(--font-caps); font-size:.82rem;
  font-weight:600; letter-spacing:.2em; text-transform:uppercase;
}
.spk__titlename{
  max-width:12ch;
  color:var(--ink);
  font-family:var(--font-caps); font-size:clamp(1.6rem,2.7vw,2.6rem); font-weight:700;
  letter-spacing:.02em; line-height:1.02; text-wrap:balance;
}
.spk__imprint{
  color:color-mix(in srgb, var(--ink) 36%, transparent);
  font-family:var(--font-caps); font-size:.58rem; font-weight:600;
  letter-spacing:.2em; text-transform:uppercase;
}

/* ---------- what is stamped on the board ---------- */
.spk__numeral{
  color:var(--foil); font-family:var(--font-caps); font-size:.7rem;
  font-weight:700; letter-spacing:.22em; opacity:.85;
  transition:opacity .26s var(--ease-out);
}
.spk__plate{
  display:grid; gap:.45rem; align-content:center;
  padding:clamp(12px,1.5vw,22px) clamp(9px,1.2vw,18px);
  border-block:1px solid color-mix(in srgb, var(--foil) 52%, transparent);
  color:var(--foil);
  transition:opacity .26s var(--ease-out);
}
.spk__honorific{
  font-family:var(--font-caps); font-size:clamp(.6rem,.76vw,.74rem); font-weight:600;
  letter-spacing:.2em; text-transform:uppercase; opacity:.82;
}
.spk__name{
  font-family:var(--font-caps); font-size:clamp(1.05rem,1.65vw,1.72rem); font-weight:700;
  letter-spacing:.03em; line-height:1.04;
}
.spk__foot{
  color:var(--foil); font-family:var(--font-caps); font-size:.58rem;
  font-weight:600; letter-spacing:.2em; text-transform:uppercase; opacity:.72;
  transition:opacity .26s var(--ease-out);
}

/* The narrowed state. Cross-faded rather than re-flowed: switching
   writing-mode mid-transition snaps, and a snap in the middle of a 620ms open
   is the one thing that would give the whole trick away. */
.spk__strip{
  position:absolute; inset:0;
  display:grid; place-items:center;
  opacity:0; pointer-events:none;
  white-space:nowrap; writing-mode:vertical-rl;
  color:var(--foil); font-family:var(--font-caps); font-size:clamp(.84rem,1.1vw,1.05rem);
  font-weight:700; letter-spacing:.08em;
  transition:opacity .26s var(--ease-out);
}
html.js .spk__row[data-open] .spk__book:not([data-active]) .spk__plate,
html.js .spk__row[data-open] .spk__book:not([data-active]) .spk__foot,
html.js .spk__row[data-open] .spk__book:not([data-active]) .spk__numeral{ opacity:0; }
html.js .spk__row[data-open] .spk__book:not([data-active]) .spk__strip{ opacity:1; }

@media (hover:hover) and (pointer:fine){
  html.js .spk__leaf:hover .spk__face--out::before{ border-color:color-mix(in srgb, var(--foil) 68%, transparent); }
  html.js .spk__row:not([data-open]) .spk__leaf:hover{ transform:rotateY(-9deg); }
}

/* ---------- the leaves ---------- */
html.js .spk__pages{
  position:absolute; top:0; bottom:0; right:var(--fe); z-index:0; width:0;
  overflow:hidden; opacity:0;
  border-radius:0 3px 3px 0;
  background-image:var(--cloth-grain), linear-gradient(178deg, var(--paper-hi) 0%, var(--paper) 58%, var(--paper-lo) 100%);
  background-blend-mode:soft-light, normal;
  background-size:180px 180px, auto;
  box-shadow:inset 14px 0 26px -20px rgb(var(--shadow-rgb) / .9), 0 16px 34px -22px rgb(var(--shadow-rgb) / .55);
  transition:width .6s var(--ease-out) .06s, opacity .3s var(--ease-out) .52s;
}
html.js .spk__book[data-active] .spk__pages{ width:calc(100% - var(--gut) - var(--fe)); opacity:1; }
html.js .spk__pagesinner{
  display:grid; align-content:center; gap:.5rem;
  height:100%; overflow:auto;
  padding:clamp(22px,2.8vw,44px);
}
/* Spoken, not seen: the facing page is showing the name at display size a few
   centimetres to the left of it. */
html.js .spk__pagesname{
  position:absolute; width:1px; height:1px; margin:-1px; padding:0;
  overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0;
}
html.js .spk__bio{ font-size:clamp(.82rem,.92vw,.93rem); line-height:1.7; }
html.js .spk__bio p::first-letter{
  float:left; margin:.06em .12em 0 0;
  color:var(--wine); font-family:var(--font-caps); font-size:2.5em; font-weight:700; line-height:.82;
}
/* The placeholder is one italic line; a drop cap on it would be a flourish on
   an apology. */
html.js .spk__note::first-letter{ float:none; margin:0; color:inherit; font:inherit; }

html.js .spk__close{
  justify-self:start;
  display:inline-flex; align-items:center; gap:.5rem;
  min-height:44px; padding:0;
  border:0; background:none; cursor:pointer;
  color:var(--wine); font-family:var(--font-caps); font-size:.76rem;
  font-weight:700; letter-spacing:.14em; text-transform:uppercase;
  opacity:0; transition:opacity .24s var(--ease-out) .2s;
}
html.js .spk__row[data-open] ~ .spk__close{ opacity:1; }
html.js .spk__close[hidden]{ display:none; }
.spk__close span{ display:inline-block; transition:transform .2s var(--ease-out); }
.spk__close:hover span{ transform:translateX(-4px); }

@media (prefers-reduced-motion:reduce){
  html.js .spk__row,
  html.js .spk__book,
  html.js .spk__leaf,
  html.js .spk__pages,
  html.js .spk__spine,
  .spk__plate, .spk__strip, .spk__numeral, .spk__foot,
  html.js .spk__close{ transition:none; }
}

/* ==========================================================================
   NARROW — the books lie down

   A front cover only works face on, and face on it wants a page of width to
   sit next to. Below 1000px there is none, so the shelf lies flat instead:
   each book turns spine-out, the way it would in a pile on a desk, and the
   pages open underneath it. The 3D goes entirely — a board fanning open
   inside a 360px column is a novelty, not a design.

   Nothing is added to the markup to do it. The same parts are re-laid:

     .spk__spine   stops being the narrow strip down the left and becomes the
                   binding across the whole bar — endband cap, raised band,
                   volume, raised band, and the far endband pushed to the end
     .spk__plate   stops being two hairlines on cloth and becomes the real
                   lettering piece: dark morocco with a gilt rule, which is
                   what --piece-a/--piece-b were always for
     .spk__leaf    stops being a board and becomes the bar itself

   The name runs along the bar, horizontally, because that is how the title
   sits on a book that is lying down.
   ========================================================================== */
@media (max-width:1000px){
  html.js .spk__row,
  html.js .spk__row[data-open]{ flex-direction:column; width:100%; height:auto; gap:clamp(7px,2vw,11px); }
  html.js .spk__book,
  html.js .spk__row[data-open] .spk__book,
  html.js .spk__row[data-open] .spk__book[data-active]{ flex:0 0 auto; }

  /* Two grid rows, and the binding shares the first cell with the bar rather
     than being absolutely positioned against the whole book. Anchored to the
     book instead, `inset:0` stretches the endbands and raised bands straight
     down over the opened pages — the chrome has to end where the bar ends.
     Grid areas also fix the order: the pages are written before the board in
     the markup, and without this the text lands ABOVE the book it belongs to. */
  html.js .spk__vol{
    display:grid; grid-template-columns:minmax(0,1fr); grid-template-rows:auto auto;
    perspective:none; height:auto;
  }

  /* ---------- the bar ---------- */
  html.js .spk__leaf{
    grid-area:1 / 1;
    position:relative; left:auto; top:auto; bottom:auto;
    margin-left:0; width:100%;
    height:auto;
    transform:none;
  }
  html.js .spk__book[data-active] .spk__leaf{ left:auto; width:100%; transform:none; }
  .spk__face--out{
    position:relative; inset:auto;
    display:grid; grid-template-columns:minmax(0,1fr) auto;
    align-items:center; justify-items:stretch;
    gap:clamp(10px,3vw,20px);
    min-height:clamp(62px,16vw,80px);
    /* left padding clears the binding chrome, right padding clears the far
       endband; both are written against the same widths used below */
    padding:5px clamp(14px,4vw,22px) 5px clamp(64px,17vw,84px);
    border-radius:3px;
    text-align:left;
  }
  /* The blind-stamped rule belongs to a board. There is no board here. */
  .spk__face--out::before{ display:none; }

  /* ---------- the binding, laid across the bar ---------- */
  html.js .spk__spine{
    grid-area:1 / 1;
    position:relative; inset:auto; z-index:2;
    width:auto; height:auto;
    display:flex; flex-direction:row; align-items:stretch;
    padding:2px;
    border-radius:3px;
    background:none;
    box-shadow:none;
    pointer-events:none;
  }
  html.js .spk__book[data-active] .spk__spine{ inset:auto; left:auto; }
  .spk__endband{
    width:6px; height:auto; margin:1px 0;
    background:repeating-linear-gradient(180deg,
      color-mix(in srgb, var(--gold-hi) 88%, var(--paper-hi)) 0 2px,
      color-mix(in srgb, var(--wine) 82%, var(--ink-deep)) 2px 4px);
    box-shadow:inset -2px 0 3px -1px rgb(0 0 0 / .55), inset 1px 0 0 rgb(255 255 255 / .3);
  }
  /* the second endband is the tail of the book, at the far end of the bar */
  .spk__endband:last-of-type{ margin-left:auto; }
  .spk__band{
    width:8px; height:auto;
    background:linear-gradient(90deg,
      rgb(255 255 255 / .20) 0 30%,
      rgb(0 0 0 / .05) 30% 62%,
      rgb(0 0 0 / .42) 100%);
    box-shadow:
      1px 0 0 color-mix(in srgb, var(--foil) 46%, transparent),
      -1px 0 0 color-mix(in srgb, var(--foil) 30%, transparent);
  }
  .spk__spinenum{
    flex:0 0 auto;
    display:grid; place-items:center;
    min-width:clamp(24px,6vw,32px);
    writing-mode:horizontal-tb;
    font-size:.68rem; letter-spacing:.14em;
  }
  /* Light across a rounded back runs the other way once the book is down. */
  .spk__round{
    background:linear-gradient(180deg,
      rgb(0 0 0 / .42) 0%,
      rgb(255 255 255 / .13) 22%,
      rgb(255 255 255 / .02) 55%,
      rgb(0 0 0 / .20) 78%,
      rgb(0 0 0 / .5) 100%);
  }

  /* ---------- the lettering piece ---------- */
  /* Sized to the title, not to the bar. A lettering piece is a patch of
     morocco a binder cuts to fit the words; stretched the full length of the
     spine it stops being a label and becomes a stripe. */
  .spk__plate{
    justify-self:start; max-width:100%;
    display:flex; flex-wrap:wrap; align-items:baseline;
    gap:0 .55em;
    padding:.42em .8em;
    border:0; border-radius:1px;
    background-image:var(--cloth-grain), linear-gradient(158deg, var(--piece-a), var(--piece-b) 70%, color-mix(in srgb, var(--piece-a) 86%, #000));
    background-blend-mode:overlay, normal;
    background-size:180px 180px, auto;
    /* Every piece is dark morocco, on the tan binding too, so the title is
       gold here even where --foil went to ink for the cloth. */
    color:var(--gold-hi);
    box-shadow:
      inset 0 0 0 1px rgb(0 0 0 / .5),
      inset 0 1px 0 rgb(255 255 255 / .07),
      0 1px 2px rgb(0 0 0 / .35);
  }
  /* the gilt rule tooled inside the edge of the piece */
  .spk__plate::before{
    content:""; position:absolute; inset:3px;
    border:1px solid color-mix(in srgb, var(--gold-hi) 55%, transparent);
    border-radius:1px; pointer-events:none;
  }
  .spk__plate{ position:relative; }
  .spk__honorific{ flex:none; font-size:clamp(.56rem,2.4vw,.68rem); letter-spacing:.2em; opacity:.8; }
  /* The name is written with a <br> so it stacks on a cover. Lying down it is
     one line, and the flex gap is what puts the space back between the two
     runs the <br> used to separate. */
  /* Plain inline text, not flex: a flex container turns each run into an
     anonymous item and strips the space between them, which reads correctly
     but leaves the accessible name as "MurabitBenavidez". The markup carries
     a real space after the <br> for exactly this. */
  .spk__name{
    display:block; min-width:0;
    font-size:clamp(.95rem,4vw,1.24rem); line-height:1.14;
    text-shadow:0 1px 1px rgb(0 0 0 / .45);
  }
  .spk__name br{ display:none; }

  .spk__foot{
    justify-self:end;
    font-size:clamp(.5rem,1.7vw,.58rem);
    opacity:.66;
  }
  .spk__numeral{ display:none; }

  html.js .spk__fore,
  html.js .spk__face--in,
  html.js .spk__strip{ display:none; }
  html.js .spk__row[data-open] .spk__book:not([data-active]) .spk__plate,
  html.js .spk__row[data-open] .spk__book:not([data-active]) .spk__foot{ opacity:1; }

  /* ---------- the pages, opening under the book ---------- */
  html.js .spk__pages{
    grid-area:2 / 1;
    position:relative; inset:auto; right:auto;
    width:100%; height:0;
    margin-inline:clamp(8px,3vw,18px) 0;
    border-radius:0 0 3px 3px;
    box-shadow:inset 0 14px 22px -20px rgb(var(--shadow-rgb) / .8);
  }
  html.js .spk__book[data-active] .spk__pages{ width:calc(100% - clamp(8px,3vw,18px)); height:auto; }
  html.js .spk__pagesinner{
    overflow:visible;
    padding:clamp(20px,5vw,32px) clamp(18px,5vw,30px) clamp(24px,6vw,34px);
  }
  html.js .spk__bio{ font-size:clamp(.88rem,3.6vw,.95rem); line-height:1.74; }
  html.js .spk__bio p{ max-width:none; }
}

/* Under 430px the bar has no room for anything but the title. */
@media (max-width:430px){
  .spk__foot{ display:none; }
  .spk__face--out{ padding-left:clamp(56px,16vw,66px); }
  .spk__spinenum{ min-width:22px; font-size:.62rem; letter-spacing:.1em; }
  html.js .spk__bio p::first-letter{ font-size:2.2em; }
}

/* The row and its books are named so the browser tweens each one from its
   closed place to its open place, rather than cutting. Only the layout change
   is ever wrapped in a transition. */
::view-transition-group(*){
  animation-duration:.52s;
  animation-timing-function:cubic-bezier(.22,.61,.36,1);
}
@media (prefers-reduced-motion:reduce){
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*){ animation-duration:1ms !important; }
}

/* ==========================================================================
   CAROUSEL — what is left of it: the round button and the dot

   There is no carousel on this page any more. Previous Classes was the only
   one, and it is a deck now (see PREVIOUS CLASSES below for why). The slide,
   track and viewport rules went with it — .car__viewport in particular, whose
   overflow:hidden is what sheared the plate's drop shadow and made the band
   read as pressed into the page.

   These two survive because two other blocks borrow them, and both are worth
   keeping identical across the page: the gallery bar uses .car__btn for its
   arrow pair (with position:static, see .gal__bar), and the deck's nav uses
   .car__dots / .car__dot verbatim for the same 9px mark on a 44px target.
   ========================================================================== */
.car__btn{
  position:absolute; top:50%; transform:translateY(-50%);
  width:44px; height:44px;
  display:grid; place-items:center;
  background:var(--paper-hi);
  color:var(--ink);
  border:1px solid color-mix(in srgb, var(--ink) 22%, transparent);
  border-radius:50%;
  cursor:pointer;
  box-shadow:0 6px 18px -8px rgb(var(--shadow-rgb) / .35);
  transition:background-color .2s var(--ease), opacity .2s var(--ease);
  z-index:2;
}
.car__btn:hover{ background:var(--paper-lo); }
.car__btn[disabled]{ opacity:.32; cursor:default; }
.car__btn svg{ width:20px; height:20px; }
.car__btn--prev{ left:clamp(-6px,-.4vw,4px); }
.car__btn--next{ right:clamp(-6px,-.4vw,4px); }

.car__dots{ display:flex; justify-content:center; margin-top:.4rem; }
/* 9px dot, 44px hit area — the target is padding, not the visible mark */
.car__dot{
  width:44px; height:44px; padding:0;
  display:grid; place-items:center;
  border:0; background:none; cursor:pointer;
}
.car__dot::before{
  content:"";
  width:9px; height:9px;
  border-radius:50%;
  border:1px solid var(--ink-soft);
  transition:background-color .2s var(--ease), border-color .2s var(--ease);
}
.car__dot[aria-selected="true"]::before{ background:var(--ink); border-color:var(--ink); }
.car__dot:hover::before{ border-color:var(--ink); }

/* ==========================================================================
   CURRENT CLASS — the one class actually running this semester

   THE INK PLATE. Two halves of one full-bleed band: the flyer large on a
   tinted field, the words on a dark plate with a gold frame. They push
   against each other instead of dissolving into one cream field, which is
   what the old layout did.

   What this replaced, and why:
     · a 1180px slab centred in a 1600px window — ~420px of screen doing
       nothing, and the fact rows stopping two thirds of the way across
       INSIDE the slab as well;
     · the poster capped at 440px in a 40% column, i.e. an illustration of a
       flyer rather than the flyer;
     · TEACHER / WHEN / WHERE / STARTS stacked in small caps above their
       values, which reads as a spec sheet. The facts are now one badge
       (lifted off the poster, which prints the same line in the same tan
       chip) and one line under it;
     · nothing to fill the column. A 500px flyer is ~646px tall and the words
       are ~420px, so air sits above and below whatever the type does. The
       five signposts off the poster — Worship, Relationships, Finance,
       Family, Daily Life — now carry that space as content.

   The dark ground is a THREE-STOP GRADIENT with a grain layer over it, never
   a flat fill. In the noir palette --ink and --ink-deep are both #000000, so
   a two-stop gradient between them is flat black, and a flat dark field is
   what the display pipeline spatial-dithers into a visible diagonal hairline.
   Mixing --tan into the first stop and --gold into the last guarantees the
   band is non-flat in all four palettes. Do not "simplify" this to one colour.

   Text on the plate is mixed against --ink-deep, never against `transparent`:
   translucent type over a gradient composites differently at the top of the
   plate than at the bottom, and it cannot be measured.
   ========================================================================== */
.now{
  display:grid;
  margin-top:clamp(26px,3.2vw,44px);
}
@media (min-width:940px){
  /* the flyer takes the smaller half and still reads as the larger object,
     because it is one object and the other half is six */
  .now{ grid-template-columns:minmax(0,46%) minmax(0,54%); }
}

/* ---- left: the flyer, on the club's own tan ---- */
.now__art{
  position:relative;
  display:grid; place-items:center;
  padding:clamp(24px,3vw,46px);
  background:
    radial-gradient(120% 92% at 32% 0%, color-mix(in srgb,var(--tan) 36%,var(--paper)) 0%, transparent 64%),
    linear-gradient(158deg,
      color-mix(in srgb,var(--tan) 46%,var(--paper)) 0%,
      color-mix(in srgb,var(--tan) 23%,var(--paper)) 100%);
}
.now__art > .noise-overlay{
  position:absolute; inset:0; opacity:.03;
  mix-blend-mode:overlay; pointer-events:none;
}
/* .poster is fit-content by default (the hero centres it). Size the FIGURE,
   never the <img>: a percentage width on an <img> inside a shrink-to-fit
   wrapper is a cyclic dependency, and the browser resolves it by dropping the
   aspect ratio and using the intrinsic height — a 1422px-tall flyer. */
.now .poster{ position:relative; width:min(100%,500px); }
.now .poster__btn{ display:block; }
.now .poster__btn img{ width:100%; height:auto; max-width:none; }

/* ---- right: the plate ---- */
.now__plate{
  position:relative; isolation:isolate; overflow:hidden;
  display:grid; align-content:center;
  padding:clamp(34px,4.2vw,64px) clamp(24px,4vw,66px);
  color:var(--paper);
  background:linear-gradient(163deg,
    color-mix(in srgb,var(--ink) 88%,var(--tan)) 0%,
    var(--ink-deep) 54%,
    color-mix(in srgb,var(--ink) 93%,var(--gold)) 100%);
}
.now__plate > .noise-overlay{
  position:absolute; inset:0; z-index:0; opacity:.04;
  mix-blend-mode:overlay; pointer-events:none;
}
.now__frame{
  position:absolute; inset:clamp(12px,1.5vw,22px); z-index:1;
  border:1px solid color-mix(in srgb,var(--gold-hi) 42%,transparent);
  pointer-events:none;
}
.now__body{
  position:relative; z-index:2;
  display:grid; gap:1rem; justify-items:start;
  /* full width of the padded plate, so the signpost strip reaches the frame.
     The prose caps are deliberately LOOSE (34ch / 72ch): at 1440 the plate's
     text column is ~660px, and a tighter cap left the copy hanging in the
     middle of the band with the frame far off to its right. The caps only bite
     on very wide windows, where an unbroken line would run past 80ch. */
  width:100%;
}

.now__kicker{
  display:flex; align-items:center; gap:.55rem; margin:0;
  font-family:var(--font-caps); font-weight:700;
  /* this is the course's NAME, not a label above the real title — it carries
     the poster's lockup into the plate, so it is sized to be read as a title.
     Tracking eases off as it grows: .2em on 1.4rem caps is a picket fence. */
  font-size:clamp(1.12rem,1.5vw,1.45rem); letter-spacing:.13em;
  text-transform:uppercase;
  color:var(--gold-hi);
}
.now__spark{ width:.95em; height:.95em; flex:none; }
/* the caption's opening question, given the weight it has on Instagram */
.now__hook{
  margin:0; max-width:34ch;
  font-family:var(--font-caps); font-weight:700; letter-spacing:.03em;
  font-size:clamp(1.75rem,3vw,2.65rem); line-height:1.14;
  color:var(--paper-hi);
  /* `balance` was here, and it is what kept the question in a narrow stack in
     the middle of the band: balance equalises line lengths, so widening the
     measure bought nothing. `pretty` only guards the last line's orphan. */
  text-wrap:pretty;
}
.now__say{
  margin:0; max-width:72ch; font-size:1.06rem; line-height:1.7;
  color:color-mix(in srgb,var(--paper) 80%,var(--ink-deep));
  text-wrap:pretty;
}
/* the poster prints "Wednesdays, 5PM at LSC201" in a tan chip; this is that
   chip, inverted to gold so it still reads as the badge on a dark ground */
.now__badge{
  margin:0; display:inline-block; padding:.5rem 1.05rem; border-radius:5px;
  background:color-mix(in srgb,var(--gold-hi) 84%,var(--ink));
  color:var(--ink-deep); line-height:1.4;
  font-size:clamp(1.02rem,1.25vw,1.16rem); font-weight:600;
}
.now__from{
  margin:0; font-size:1rem;
  color:color-mix(in srgb,var(--paper) 76%,var(--ink-deep));
}
/* the five signposts along the path in the artwork, in the poster's order.

   SIZING THIS STRIP IS A SPACING PROBLEM, not a taste one. Five equal 1fr
   tracks cap the type at whatever RELATIONSHIPS — 13 caps, by far the longest
   label — can fit inside ONE track, and they also wreck the rhythm: at 1440
   that word ends ~2px from FINANCE while every other pair has ~60px of air, so
   the strip reads as WORSHIP / RELATIONSHIPS-FINANCE / FAMILY / DAILY LIFE.

   So on desktop the row is a flex line with space-between instead: the labels
   are sized on their own, and the leftover width is dealt out equally as
   gutters. Nothing is pinned to the longest word any more, which is what lets
   the type step up. `gap` is the FLOOR under those gutters — space-between
   only distributes what is left after it. Tracking still comes down from .12em
   to .06em: on 13 caps .12em alone is 1.5em of extra width, and at this size
   it is spread enough.

   Below 940 the plate goes full width and the strip stays an auto-fit grid on
   equal tracks — five across on a tablet, folding to 3-up then 2-up on a
   phone. Equal tracks are the right call there: space-between on three labels
   in a 430px plate would deal out ~110px gutters and read as broken.
   min(100%,...) keeps the 112px track floor from overflowing a narrow phone. */
.now__topics{
  list-style:none; margin:.7rem 0 0; padding:0;
  width:100%; display:grid;
  grid-template-columns:repeat(auto-fit,minmax(min(100%,112px),1fr));
  border-top:1px solid color-mix(in srgb,var(--paper) 26%,transparent);
}
@media (min-width:940px){
  .now__topics{
    display:flex; flex-wrap:nowrap;
    justify-content:space-between; gap:clamp(14px,1.5vw,38px);
  }
  .now__topics li{ padding-right:0; white-space:nowrap; }
}
.now__topics li{
  padding:1rem .55rem .2rem 0;
  font-family:var(--font-caps); font-weight:600;
  font-size:clamp(.8rem,1.14vw,1.32rem); letter-spacing:.06em;
  text-transform:uppercase;
  color:color-mix(in srgb,var(--paper) 92%,var(--ink-deep));
}
.now__topics li span{
  display:block; margin-bottom:.3rem;
  /* em, so the numerals grow with the label instead of pinning to root */
  font-size:.74em; letter-spacing:.14em; color:var(--gold-hi);
  font-variant-numeric:tabular-nums;
}

@media (max-width:939px){
  /* stacked, the flyer comes first and the plate carries the rest — the
     tinted field shrinks so the two do not read as two separate sections */
  .now__art{ padding:clamp(20px,5vw,34px); }
  .now__plate{ padding:clamp(28px,6vw,40px) clamp(20px,5vw,32px); }
  .now__frame{ inset:10px; }
}

/* ==========================================================================
   SESSION DATES — the term as fourteen stubs

   Every session is the same weekday, the same hour and the same room, so the
   only variable in the list is WHICH Wednesdays. This used to be a
   three-column ruled list, which read as prose you had to work through; it is
   now one stub per session — WED, the date at ~2rem, the month — laid out
   SEVEN ACROSS so the fourteen fall into two even rows of seven.

   The column count is written out per breakpoint rather than left to
   auto-fit. Auto-fit gave 10 + 4 at 1440, and fourteen items have an obvious
   shape (7 + 7) that a min-track cannot be relied on to find.

   Nothing here is ever bolded. Three states, none of them carried by colour
   on its own:
     · gone     — dimmed to 76% (the first step that still clears 4.5:1 on the
                  paper in all four palettes) AND the numeral struck through;
                  the stub also loses its tint and its gold edge
     · next     — the stub keeps an ink border and a gold ground AND says the
                  word, appended by site.js
     · no class — a week the club is NOT meeting. Not the same as a deleted
                  row: a student who does not know it is off walks to LSC 201
                  for nothing. Mark it in the markup, since only the club knows
                  which weeks they are:
                      <li class="cal__day is-skip">
                        <time datetime="...">…</time>
                        <span class="cal__off">No class</span>
                      </li>
                  site.js will not hand the "next" marker to a skipped week.

   The stub reserves the label line in its own padding-bottom whether or not a
   label is there, and the label is positioned into that reserve. Otherwise the
   one stub carrying a word is a line taller than the rest, and the grid
   stretches every stub on its row to match it.
   ========================================================================== */
.cal{
  margin-top:clamp(34px,4vw,60px);
  --stub-x:clamp(10px,1.1vw,15px);
  --stub-y:clamp(9px,1vw,14px);
  --stub-b:clamp(11px,1.2vw,16px);
  --stub-hair:color-mix(in srgb, var(--ink) 12%, transparent);
}
.cal__h{
  margin:0 0 1rem;
  font-family:var(--font-caps); font-weight:700;
  font-size:clamp(1.1rem,1.7vw,1.35rem); letter-spacing:.045em;
  color:var(--ink);
}
/* the client's own reminder that these are placeholders — delete with the
   placeholder dates themselves */
.cal__note{
  display:inline-block;
  font-family:var(--font-body); font-weight:400; font-style:italic;
  font-size:.86rem; letter-spacing:0; color:var(--ink-soft);
}
.cal__list{
  list-style:none; margin:0; padding:0;
  display:grid; gap:clamp(9px,1vw,14px);
  grid-template-columns:repeat(3,minmax(0,1fr));
}
@media (min-width:480px){ .cal__list{ grid-template-columns:repeat(4,minmax(0,1fr)); } }
@media (min-width:680px){ .cal__list{ grid-template-columns:repeat(5,minmax(0,1fr)); } }
/* 7 across, so fourteen sessions are two even rows and not a ragged tail */
@media (min-width:900px){ .cal__list{ grid-template-columns:repeat(7,minmax(0,1fr)); } }

.cal__day{
  position:relative;
  display:flex; flex-direction:column; align-items:flex-start;
  /* the bottom pad carries the reserve for the label line — see the note above */
  padding:var(--stub-y) var(--stub-x) calc(var(--stub-b) + 1.05rem);
  border:1px solid var(--stub-hair);
  /* the gold rule the poster prints across the head of its chip */
  border-top:2px solid color-mix(in srgb, var(--gold) 62%, transparent);
  background:color-mix(in srgb, var(--tan) 7%, var(--paper));
}
.cal__day time{ display:flex; flex-direction:column; align-items:flex-start; gap:.05rem; }
.cal__dow{
  font-family:var(--font-caps); font-weight:600; font-size:.64rem;
  letter-spacing:.15em; text-transform:uppercase; color:var(--ink-soft);
}
.cal__num{
  font-variant-numeric:tabular-nums; color:var(--ink);
  font-size:clamp(1.5rem,2.1vw,1.95rem); line-height:1.05;
}
.cal__mon{
  font-family:var(--font-caps); font-weight:600; font-size:.8rem;
  letter-spacing:.1em; text-transform:uppercase; color:var(--ink-soft);
}

/* gone, and not meeting: the same quieting either way — the stub drops back to
   bare paper and gives up its gold edge, and the numeral is struck so the
   state is not carried by lightness alone. What separates them is the word. */
.cal__day.is-past,
.cal__day.is-skip{ background:none; border-top-color:var(--stub-hair); }
.cal__day:is(.is-past,.is-skip) :is(.cal__dow,.cal__num,.cal__mon){
  color:color-mix(in srgb, var(--ink) 76%, transparent);
}
.cal__day.is-past .cal__num{ text-decoration:line-through; }
.cal__day.is-skip .cal__num{ text-decoration:line-through; text-decoration-thickness:1.5px; }

/* next — the ink border and the gold ground are the shape; the word is the
   part that survives being read aloud and is what site.js appends */
.cal__day.is-next{
  border-color:var(--ink);
  background:color-mix(in srgb, var(--gold-hi) 40%, var(--paper));
}
/* both labels sit in the reserve the stub already padded for, flush with the
   date above them */
.cal__next,
.cal__off{
  position:absolute; left:var(--stub-x); bottom:var(--stub-b);
  font-family:var(--font-caps); font-weight:600;
  font-size:.7rem; letter-spacing:.13em; text-transform:uppercase;
}
.cal__next{ color:var(--ink); }
.cal__off{ color:var(--ink-soft); }

/* ==========================================================================
   PREVIOUS CLASSES — the archive as a deck

   The posters stacked and dealt through on one side, the copy for whichever
   one is up on the other. This replaced a carousel; index.html carries the
   measurements of what was wrong with it, and lab/prev-classes-lab/ carries
   the five directions this one was chosen from.

   THREE RULES CAME OUT OF THAT REBUILD. All three are load-bearing here.

   1. NO CONTROL IS EVER DRAWN OVER THE ARTWORK. The old .car__btn pair was
      absolutely positioned at top:50% / left:-6px / right:-6px, which put the
      prev arrow at x=350 against a poster starting at x=356 and the next arrow
      on the maroon plate. The nav in here is a grid row. It cannot overlap
      anything, because it is in the flow.

   2. A CLIP IS ONLY ACCEPTABLE AT AN EDGE THE EYE ALREADY READS AS AN EDGE.
      The window is such an edge. A column boundary sitting in open cream is
      not — and neither was .car__viewport, whose overflow:hidden sheared the
      plate's drop shadow while it was still 7 luminance levels short of clean
      cream, holding that step down 643px of height. The single clip in this
      section is on .section--deck, at the window. Everything else is shaped so
      it never reaches even that.

   3. NOTHING MAY SIT DOWNSTREAM OF A VARIABLE-HEIGHT ELEMENT. One of these
      four classes published a 600-character description and three published
      none, so the plate's height is not knowable in advance. Anything laid out
      after it moves when it moves. The nav therefore sits ABOVE the plate, at
      both breakpoints — see the grid-template-areas below.

   THE DARK PLATE is a three-stop gradient with grain over it, never a flat
   fill — same rule as every other dark ground on this page. In the noir
   palette --ink and --ink-deep are both #000000, and a flat dark field is what
   the display pipeline dithers into a visible diagonal hairline.
   ========================================================================== */

/* The window-edge clip. The fan is tuned to stay inside it on a desktop, so in
   practice this only catches the neighbours once the layout stacks and they
   are meant to run off the screen. */
.section--deck{ overflow-x:clip; }

.deck{
  position:relative;

  /* THE CARD SIZE IS A HEIGHT BUDGET, NOT A WIDTH. The posters are square, so
     a card's width is also its height, and that is what decides whether the
     band fits on a screen. Sized off vw alone the section came out 1251px tall
     at 1920x1080 with the copy below the fold. The vh term is the one that
     binds on a normal desktop; the vw term takes over only on a short, wide
     window where height is the scarcer axis. */
  --cw:min(46vw, 55vh, 600px);

  /* THE FAN, in three numbers: how far the active card sits from the left of
     its column, how far each card ahead steps right, how far each card behind
     tucks left. Forward is ~4x back on purpose — cards ahead step out to be
     looked at, cards behind collapse into a tight stack like a hand dealt
     through. That asymmetry is what keeps all four inside the window at every
     position without anything being clipped. A symmetric fan at a readable
     forward spacing puts the far-back card straight through the left of the
     column, which is the cut rule 2 exists to prevent. */
  --lead:clamp(28px, 4vw, 80px);
  --step-fwd:clamp(110px, 11vw, 200px);
  --step-back:clamp(26px, 2.6vw, 48px);

  /* The gutter is measured off 1460px, not the page's 1180px wrap. Held to the
     page wrap the band starts at x=370 on a 1920 window, and with a 400px
     column and a gap the deck could not begin until x=862 — which drove the
     posters into the right-hand third of the screen. A wider notional wrap
     pulls the plate left and hands every one of those pixels to the deck. */
  padding-left:max(var(--edge), calc((100vw - 1460px) / 2));
  padding-right:var(--edge);
}

/* ---- the no-JS baseline ----
   A plain stack: each poster followed by its own copy, everything visible,
   nothing depending on a script. This is what ships, and what renders if
   site.js never runs or throws before it reaches the deck. */
.deck__head{ margin-bottom:clamp(18px,2vw,28px); }
.deck__list{
  list-style:none; margin:0; padding:0;
  display:grid; gap:clamp(34px,4.5vw,62px);
  max-width:760px;
}
.deck__entry{ display:grid; gap:clamp(16px,2vw,24px); }
.deck__fig{ margin:0; }
.deck__fig img{
  width:100%; height:auto; border-radius:12px;
  box-shadow:0 12px 34px -18px rgb(var(--shadow-rgb) / .55);
}

/* ---- the plate ---- */
.deck__plate{
  position:relative; isolation:isolate; overflow:hidden;
  padding:clamp(22px,2.4vw,34px) clamp(22px,2.4vw,32px);
  border-radius:14px;
  background:linear-gradient(158deg,
    color-mix(in srgb, var(--ink) 88%, var(--tan)) 0%,
    var(--ink-deep) 54%,
    color-mix(in srgb, var(--ink) 93%, var(--gold)) 100%);
  box-shadow:0 26px 60px -30px rgb(var(--shadow-rgb) / .6);
}
.deck__plate > .noise-overlay{
  position:absolute; inset:0; z-index:1;
  opacity:.05; mix-blend-mode:overlay; pointer-events:none;
}
.deck__body{
  position:relative; z-index:3;
  display:grid; gap:clamp(12px,1.4vw,18px);
}
/* the gold hairline the page's other plates carry, drawn at the edge of the
   type rather than inset around the whole panel — an inset frame is half of
   why the old block read as engraved into the page */
.deck__lede{
  padding-bottom:clamp(12px,1.4vw,18px);
  border-bottom:1px solid color-mix(in srgb, var(--gold-hi) 30%, transparent);
}
.deck__term{
  margin:0;
  font-family:var(--font-caps); font-weight:700;
  font-size:.78rem; letter-spacing:.2em; text-transform:uppercase;
  color:var(--gold-hi);
}
.deck__title{
  margin:.4rem 0 0;
  font-family:var(--font-caps); font-weight:700; letter-spacing:.035em;
  font-size:clamp(1.5rem,2.2vw,2.1rem); line-height:1.1;
  color:var(--paper-hi); text-wrap:balance;
}
.deck__sub{
  margin:.2rem 0 0;
  font-family:var(--font-caps); font-weight:600;
  font-size:clamp(1rem,1.2vw,1.14rem); letter-spacing:.02em;
  color:color-mix(in srgb, var(--paper) 74%, var(--ink-deep));
}
/* the facts as a definition list — labelled values, not a sentence */
.deck__facts{ display:grid; gap:.5rem; margin:0; }
.deck__facts > div{
  display:grid; grid-template-columns:5.2rem minmax(0,1fr);
  gap:.85rem; align-items:baseline;
}
.deck__facts dt{
  font-family:var(--font-caps); font-weight:600;
  font-size:.7rem; letter-spacing:.13em; text-transform:uppercase;
  color:color-mix(in srgb, var(--paper) 74%, var(--ink-deep));
}
.deck__facts dd{ margin:0; font-size:.95rem; line-height:1.45; color:var(--paper); }
@media (max-width:520px){
  .deck__facts > div{ grid-template-columns:1fr; gap:.1rem; }
}
/* Clamped, not left to run. The one real description is ~600 characters and
   the other three classes published none, so unclamped the plate's height
   changes by a factor of three between entries. */
.deck__desc{
  margin:0;
  font-size:.95rem; line-height:1.6;
  color:color-mix(in srgb, var(--paper) 78%, var(--ink-deep));
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:5;
  overflow:hidden;
}

/* ---- the controls ---- */
.deck__nav{
  display:flex; align-items:center; gap:10px; flex-wrap:wrap;
  margin-bottom:clamp(18px,2vw,26px);
}
.deck__btn{
  width:46px; height:46px; flex:none;
  display:grid; place-items:center;
  background:var(--paper-hi); color:var(--ink);
  border:1px solid color-mix(in srgb, var(--ink) 22%, transparent);
  border-radius:50%; cursor:pointer;
  box-shadow:0 6px 18px -8px rgb(var(--shadow-rgb) / .35);
  transition:background-color .2s var(--ease), transform .2s var(--ease);
}
.deck__btn:hover{ background:var(--paper-lo); }
.deck__btn:active{ transform:scale(.94); }
.deck__btn svg{ width:20px; height:20px; }
.deck__count{
  margin:0;
  font-family:var(--font-caps); font-weight:700;
  font-size:.9rem; letter-spacing:.16em; color:var(--ink-soft);
  min-width:3.4rem; text-align:center;
}
/* .car__dots / .car__dot are reused verbatim — same mark, same 44px target.
   They only need re-aligning out of the centred layout the carousel used. */
.deck__nav .car__dots{ justify-content:flex-start; margin:0 0 0 auto; }

/* ==========================================================================
   ---- LIVE ----  everything below applies only once site.js sets data-live
   ========================================================================== */
.deck[data-live]{
  display:grid;
  gap:clamp(20px,2.4vw,40px) clamp(20px,2.2vw,40px);
  grid-template-areas:"head" "deck" "nav" "plate";
}
/* display:contents dissolves the list and the entries so the figures and the
   plates become grid items of .deck itself, and can be placed in different
   columns. Nothing is reparented in JS to achieve it. The list semantics go
   with them, which is correct here: in live mode this is a deck with a
   tablist, not a list. */
.deck[data-live] .deck__list,
.deck[data-live] .deck__entry{ display:contents; }

.deck[data-live] .deck__nav{ grid-area:nav; margin-bottom:0; }
.deck[data-live] .deck__head{ grid-area:head; margin-bottom:0; }
/* the shared .h2 clamp tops out at 3.1rem, which needs ~470px to set "Previous
   Classes" on one line in Cormorant SC caps — more than the 400px column has,
   and every other heading on the page sets on one line */
.deck[data-live] .h2{ font-size:clamp(1.8rem,2.3vw,2.55rem); }

/* only the class that is up shows its copy; the other three stay in the DOM
   and stay findable by search, they are simply not laid out */
.deck[data-live] .deck__plate{ grid-area:plate; align-self:start; }
.deck[data-live] .deck__entry:not([data-active]) .deck__plate{ display:none; }

/* ---- the fan ---- */
.deck[data-live] .deck__fig{
  /* every figure occupies the same grid area, so they stack with a common
     origin and need no absolute positioning. justify-self:start anchors them
     to the column's LEFT edge: centring the active card in its column put its
     centre at x=1391 on a 1920 window against a window centre of 960, which is
     what made the deck read as shoved into the right-hand side. */
  grid-area:deck;
  justify-self:start; align-self:center;
  width:var(--cw);
  --o:0;
  --depth:0;
  transform:
    translateX(calc(var(--lead)
                    + max(var(--o), 0) * var(--step-fwd)
                    + min(var(--o), 0) * var(--step-back)))
    translateZ(calc(var(--depth) * -50px))
    scale(calc(1 - var(--depth) * .05))
    rotateY(calc(var(--o) * -8deg));
  /* THE STACK HAS TO READ AS DEPTH, NOT AS FOUR POSTERS. A 594px card stepping
     200px leaves ~400px of overlap, and these flyers each set their own title
     large and centred — exactly the content that collides. At a gentle opacity
     ramp "Essentials of Fiqh", "Guidelines" and "Halal and Haram" all landed
     legibly on top of each other. The blur is what fixes it: dimming alone
     keeps the type sharp and the eye still tries to read it, while blur puts
     the neighbours behind the plane of focus so they become colour and shape.
     Only three elements ever carry it. */
  opacity:calc(1 - var(--depth) * .26);
  filter:saturate(calc(1 - var(--depth) * .3)) blur(calc(var(--depth) * 1.5px));
  z-index:calc(10 - var(--depth));
  transition:transform .62s var(--ease), opacity .62s var(--ease), filter .62s var(--ease);
  will-change:transform, opacity;
}
.deck[data-live]{ perspective:1600px; }
.deck[data-live] .deck__entry[data-active] .deck__fig{ filter:none; opacity:1; }
.deck[data-live] .deck__entry:not([data-active]) .deck__fig{ cursor:pointer; }
/* past three deep a card is a smear and still costs a composite. With four
   classes nothing is ever this far back; the rule is here for the term the
   club has taught eight of them. */
.deck[data-live] .deck__fig[data-far]{ opacity:0; pointer-events:none; }

@media (min-width:1060px){
  .deck[data-live]{
    /* The left column is bounded, not fractional: an fr column re-flows the
       plate's measure at every viewport and the one long description's line
       length wanders with it.

       NAV ABOVE THE PLATE — see rule 3 at the top of this block. Pinning the
       nav under a stretched column only works while the plate fits the space
       left for it, and it does not: at 400px "Livingston Student Center
       Rm. 201AB" wraps to two lines on some classes and one on others, which
       moved the nav 94px at 1920 and 107px at 1280 as you stepped. Chasing
       that with tighter clamps is a losing game, because the wrap depends on
       the words and the words are the client's. */
    grid-template-columns:minmax(330px, 400px) minmax(0,1fr);
    grid-template-rows:auto auto 1fr;
    grid-template-areas:
      "head  deck"
      "nav   deck"
      "plate deck";
  }
}

@media (max-width:1059px){
  /* Stacked. The nav stays between the deck and the plate: same requirement,
     and here there is no second column to stretch against, so the only way to
     keep it still is to put it where nothing above it moves. It is also the
     better reading order on a phone — poster, controls, then the detail. */
  .deck{ --cw:min(78vw, 520px); }
  .deck[data-live]{ perspective:none; }
}

@media (max-width:720px){
  /* The fan flattens and re-centres. A rotateY on a phone costs a compositing
     layer per card to render an effect a few degrees wide. --cw is narrowed
     rather than the width overridden, so everything derived from it follows.
     The fan goes back to symmetric: the asymmetry exists to keep cards clear
     of a column edge that does not exist at this width, and the neighbours
     simply run off the window — the one edge a cut is always legible against. */
  .deck{
    --cw:min(74vw, 330px);
    --lead:calc((100vw - var(--edge) * 2 - var(--cw)) / 2);
    --step-fwd:40vw;
    --step-back:40vw;
  }
  .deck[data-live] .deck__fig{
    transform:
      translateX(calc(var(--lead) + var(--o) * var(--step-fwd)))
      scale(calc(1 - var(--depth) * .08));
  }
  .deck__nav .car__dots{ margin-left:0; }
  .deck__nav{ gap:6px; }
}

@media (prefers-reduced-motion:reduce){
  .deck[data-live] .deck__fig{ transition:none; }
  .deck__btn{ transition:none; }
}

/* ==========================================================================
   GALLERY — previous events, as a common-height filmstrip

   These flyers are not the same shape as each other. Measured off the files,
   ratios run 0.773 to 1.000: five are 4:5, "Anchored in Faith" is dead square,
   the conference plate is taller than 4:5. That rules out .car, whose slides
   are `flex:0 0 100%`, and it rules out any fixed-ratio tile — the title,
   speaker, date and room are all baked into the artwork, so a crop deletes
   the content.

   So: one HEIGHT for everybody, each flyer keeping its own width. The rag
   lands in the width, and in a row that scrolls sideways a ragged width is
   invisible — it only changes how many you see at once. Nothing is cropped.

   The trigger is .poster__btn, reused verbatim: site.js already binds every
   one on the page, takes the full-size file from the href and the dialog name
   from data-lb-label, so enlarging a flyer costs no new JavaScript.
   ========================================================================== */
.gal{
  position:relative;
  /* the fade below has to end on whatever the section is actually painted
     with, so the colour is named here and overridden on a tint band */
  --gal-bg:var(--paper);
}
.section--tint .gal{ --gal-bg:color-mix(in srgb, var(--tan) 17%, var(--paper)); }

.gal__track{
  display:flex;
  gap:clamp(16px,2vw,30px);
  overflow-x:auto;
  scroll-snap-type:x mandatory;
  scrollbar-width:none;
  -webkit-overflow-scrolling:touch;
  list-style:none; margin:0;
  /* room for the hover lift and its shadow, or overflow clips both */
  padding:6px 0 14px;
}
.gal__track::-webkit-scrollbar{ display:none; }

.gal__item{
  flex:0 0 auto;
  scroll-snap-align:start;
  margin:0;
  /* the ARTWORK sets the column width, never the caption. Each flyer is a
     different width at a shared height, so the entry has to shrink-wrap the
     image or the link box (and its shadow) juts out past the picture. */
  width:-moz-fit-content;
  width:fit-content;
  max-width:86vw;
}
/* .poster__btn is display:block, which would fill the flex line */
.gal__item .poster__btn{
  width:-moz-fit-content;
  width:fit-content;
}

/* The hero's shadow is a three-layer drop reaching 70px, drawn for a 700px
   plate. On a 264px thumbnail with a caption directly beneath it, that spread
   pools under the text and drags the background dark enough to fail contrast
   on the small type. Same lift, a third of the reach. */
.gal__item .poster__btn,
.conf__item .poster__btn{
  box-shadow:
    0 1px 3px  rgb(var(--shadow-rgb) / .08),
    0 8px 18px -10px rgb(var(--shadow-rgb) / .26);
}
.gal__item .poster__btn:hover,
.conf__item .poster__btn:hover{
  box-shadow:
    0 1px 3px  rgb(var(--shadow-rgb) / .10),
    0 14px 26px -10px rgb(var(--shadow-rgb) / .32);
}
/* one height, every shape. More specific than `.poster__btn img` on purpose:
   that rule is width:auto/height:auto, sized for the hero plate. */
.gal__item .poster__btn img{
  height:clamp(230px, 26vw, 330px);
  width:auto; max-width:none;
  border-radius:8px;
}
.gal__item .poster__btn{ border-radius:8px; }
/* the hero zoom badge is sized for a 700px plate; shrink it for a thumbnail */
.gal__item .poster__zoom{ right:9px; bottom:9px; width:32px; height:32px; }
.gal__item .poster__zoom svg{ width:15px; height:15px; }

/* Nothing mats these palettes apart from each other — that is the trade this
   layout makes. A hairline stops the palest flyers bleeding into the cream. */
.gal__item .poster__btn::after{
  content:""; position:absolute; inset:0; border-radius:8px; pointer-events:none;
  box-shadow:inset 0 0 0 1px rgb(var(--shadow-rgb) / .14);
}

/* width:0 + min-width:100% is the trick that keeps the caption from setting
   the column width: at 0 it contributes nothing to the entry's fit-content,
   then it stretches back out to the width of the flyer above it. The floor
   stops a very narrow poster squeezing its caption into a column of one word. */
.gal__cap{
  width:0; min-width:max(100%, 190px);
  margin-top:.75rem;
}
.gal__title{
  margin:0;
  font-family:var(--font-caps);
  font-size:1.02rem; font-weight:700; letter-spacing:.02em; line-height:1.2;
  color:var(--ink);
  text-wrap:balance;
}
/* The caption is a title and, at most, a year. Speaker, time and room are all
   printed on the flyer itself and are one click away in the enlarged view —
   repeating them under every plate turned the row into a wall of small grey
   text and buried the artwork, which is the thing worth looking at.
   Only three of the six carry a year, because only three flyers give enough
   to derive one. An absent year is left absent, never guessed. */
.gal__year{
  margin:.2rem 0 0;
  font-size:.78rem; line-height:1.4; letter-spacing:.09em;
  color:var(--ink-mute);
  font-variant-numeric:tabular-nums;
}

/* The bleed cue. A snap row cut clean at the container edge reads as "that is
   all of them"; the fade says there is more. site.js retracts it at the end
   of the scroll, so it never lies. */
/* the fade wraps the TRACK only — stretched over the whole component it also
   washes out the hint text sitting on the bar below */
.gal__view{ position:relative; }
.gal__view::after{
  content:"";
  position:absolute; top:0; right:0; bottom:0;
  width:clamp(26px,5vw,70px);
  pointer-events:none;
  transition:opacity .3s var(--ease);
  background:linear-gradient(90deg,
    color-mix(in srgb, var(--gal-bg) 0%, transparent), var(--gal-bg));
}
.gal[data-end] .gal__view::after{ opacity:0; }
/* with JS off nothing sets data-end, so the fade would sit there for good
   over a row that may already be complete. No JS, no fade. */
html:not(.js) .gal__view::after{ display:none; }

.gal__bar{ display:flex; align-items:center; gap:.5rem; margin-top:.2rem; }
/* the arrow pair borrows .car__btn for its look only — in a carousel those
   float over the slides, here they sit on a bar beneath the row */
.gal__bar .car__btn{ position:static; transform:none; }
.gal__hint{
  margin:0 0 0 auto;
  font-size:.72rem; letter-spacing:.08em; text-transform:uppercase;
  color:var(--ink-mute);
}
/* the arrows are an enhancement — the track is a real scroll container and
   swipes without them */
html:not(.js) .gal__bar{ display:none; }
/* every flyer already fits: the controls would be describing nothing */
.gal--static .gal__bar{ display:none; }

/* ==========================================================================
   PREVIOUS CONFERENCES — both plates, side by side, nothing around them

   This was a carousel: one plate at a time, 1012px of section at 1920 to show
   a single 560x560 flyer with ~960px of cream either side of it, the arrows
   pushed out to the wrap edges, and half the content behind a click when
   there are only two conferences to show.

   The carousel existed because the flyers are not the same shape — I is
   square (1:1), II is portrait (0.773) — and side by side at natural size
   that read as a mistake rather than as a series.

   A frame around each plate was tried and rejected: a mat around a flyer is a
   frame around a frame, and it turns the poster into an exhibit instead of
   the point. What actually holds the two shapes together is not a box, it is
   that both are capped to the SAME HEIGHT and hang from the same line — the
   rule .gal__item and the classes rail already use. The width then falls
   where the artwork puts it. Take the box away, keep the shared height, and
   the plates sit on the cream as posters.

   Both plates are capped at the height ONE of them used to have, so nothing
   shrank to make room.

   SIZING IS max-height + max-width WITH BOTH DIMENSIONS AUTO. Never an
   explicit height plus max-width: when the max-width bites, an image with a
   set height is scaled on one axis only and the poster is silently squashed.

   The caption is three short lines — which of the series, the theme, and the
   date and venue. Everything else (speakers, times, who may come) is printed
   on the plate, which enlarges on click, and sits in its alt text.
   ========================================================================== */
/* ==========================================================================
   PREVIOUS CONFERENCES — one at full size, the run along the bottom

   Chosen out of lab/conf-design-lab/ ("Marquee ways", Filmstrip). What it
   replaced: two flyers centred in two columns on tint with a one-line caption
   each, which at 1920 was a 983px band with the artwork covering under a fifth
   of it. The emptiness was a content problem, not a spacing one.

   THREE THINGS HOLD THIS UP.

   1. THE GROUND IS THE FLYER. Each panel's --art is its own poster, scaled up,
      blurred 30px and scrimmed. The 2024 marble arcade and the 2025 sepia ruin
      each bring their own light, so moving between them changes the weather of
      the whole band — which is the argument for a strip over a fade.

   2. NEVER A FLAT DARK FILL. The scrim is two gradients over a photograph and
      carries a grain layer on top. A flat charcoal field is what an 8-bit
      display pipeline spatial-dithers into a visible diagonal hairline; there
      is nothing flat here for it to latch onto. Do not "simplify" it.

   3. THE PANELS SHARE ONE GRID CELL. All of them sit in 1/1 of .conf__stage,
      so the band is as tall as the tallest conference and switching cannot
      move the page under the reader. That is also why the inactive ones are
      hidden with visibility rather than display: display:none would collapse
      the cell and take the height with it.

   WITHOUT JAVASCRIPT the panels are simply stacked and visible and the strip
   is removed from the layout — both flyers reachable, both still opening full
   size. Everything one-at-a-time is scoped to .conf[data-live], which site.js
   sets only once it has wired the tablist.
   ========================================================================== */
.conf{ position:relative; }

/* The heading keeps the page measure; the band below it goes edge to edge. */
.conf__head{ display:flex; flex-wrap:wrap; align-items:end; justify-content:space-between; gap:10px 26px; }
.conf__head .h2{ margin-bottom:0; }
.conf__meta{
  margin:0; color:var(--gold-label); font-family:var(--font-caps);
  font-size:.8rem; font-weight:600; letter-spacing:.2em; text-transform:uppercase;
}

.conf__stage{ display:grid; background:var(--ink-deep); }

.conf__panel{
  position:relative; isolation:isolate; overflow:hidden;
  display:grid; align-items:center;
  padding-block:clamp(36px,4.2vw,70px);
}
.conf[data-live] .conf__panel{
  grid-area:1 / 1;
  opacity:0; visibility:hidden;
  transition:opacity .5s var(--ease-out), visibility 0s linear .5s;
}
.conf[data-live] .conf__panel[data-active]{
  opacity:1; visibility:visible;
  transition:opacity .5s var(--ease-out), visibility 0s;
}

/* background-image is set inline, per panel, in index.html — see the comment
   there. A url() inside a custom property resolves against this file, not the
   document, so --art would have gone looking in assets/css/. */
.conf__art{
  position:absolute; inset:0; z-index:0;
  background-size:cover; background-position:50% 42%;
  filter:blur(30px) saturate(1.3) brightness(1.05);
  /* the blur softens the edges of the box; scale pushes them back out */
  transform:scale(1.14);
}
/* Two layers. The vertical one seals the band's top and bottom edges; the
   directional one puts the weight under the TYPE and lets the plate side open
   up, so the picture the band is made of is actually visible around it. */
.conf__scrim{
  position:absolute; inset:0; z-index:1;
  background:
    linear-gradient(to bottom,
      color-mix(in srgb, var(--ink-deep) 74%, transparent) 0%,
      transparent 24%, transparent 76%,
      color-mix(in srgb, var(--ink-deep) 74%, transparent) 100%),
    linear-gradient(270deg,
      color-mix(in srgb, var(--ink-deep) 90%, transparent) 0%,
      color-mix(in srgb, var(--ink-deep) 86%, transparent) 46%,
      color-mix(in srgb, var(--ink-deep) 44%, transparent) 78%,
      color-mix(in srgb, var(--ink-deep) 22%, transparent) 100%);
}
/* Stacked, the type is no longer beside the plate — it is under it, across the
   full width, and a wash that runs left-to-right then puts its lightest end
   directly behind the copy. On the 2024 marble that measured 1.85:1. Below
   900px the weight turns through ninety degrees with the layout. */
@media (max-width:900px){
  .conf__scrim{
    background:
      linear-gradient(to bottom,
        color-mix(in srgb, var(--ink-deep) 60%, transparent) 0%,
        color-mix(in srgb, var(--ink-deep) 34%, transparent) 22%,
        color-mix(in srgb, var(--ink-deep) 78%, transparent) 46%,
        color-mix(in srgb, var(--ink-deep) 90%, transparent) 100%);
  }
}
.conf__grain{ position:absolute; inset:0; z-index:2; opacity:.05; mix-blend-mode:overlay; pointer-events:none; }

.conf__inner{
  position:relative; z-index:3;
  width:100%; max-width:1520px; margin-inline:auto; padding-inline:var(--edge);
  /* .85/1.15, not .72/1.28: the plate is sized min(100%, cap), so a bigger cap
     does nothing unless its column can hold it — at 1440 a .72fr column is
     462px wide and would have clamped a 540px plate straight back down. */
  display:grid; grid-template-columns:minmax(0,.85fr) minmax(0,1.15fr);
  gap:clamp(22px,3.2vw,64px); align-items:center;
}

/* ---- the plate ---- */
/* Left, not centred: the plate's edge is the same edge as the h2 above it and
   the first thumb below it. Centring it in the column put a third alignment on
   screen and nothing to justify it. */
/* 540, not 360: at 360 the flyer was legible as a picture but not as a poster
   — the speakers and the venue baked into the artwork were too small to read
   without opening it. The column above was widened to hold this. */
.conf__plate{ position:relative; justify-self:start; width:min(100%,540px); }
.conf__plate::before{
  content:""; position:absolute; inset:-10px; pointer-events:none;
  border:1px solid color-mix(in srgb, var(--gold-hi) 34%, transparent);
}
.conf__plate .poster__btn{ border-radius:3px; box-shadow:0 40px 80px -34px rgb(0 0 0 / .8); }
.conf__plate .poster__btn img{ width:100%; height:auto; border-radius:3px; }

/* ---- the type ---- */
.conf__body{ display:grid; gap:clamp(11px,1.2vw,17px); color:var(--paper-hi); }
.conf__slug{ display:flex; align-items:center; gap:.7rem; margin:0; }
/* The numeral sits on the blurred artwork, and over the bright part of the
   2024 marble that measured 1.55:1. A badge with its own dark ground is the
   fix; a text-shadow would only have looked like one. */
.conf__roman{
  display:grid; place-items:center; min-width:32px; height:32px; padding-inline:.4rem; flex:none;
  border:1px solid color-mix(in srgb, var(--gold-hi) 60%, transparent);
  background:color-mix(in srgb, var(--ink-deep) 74%, transparent);
  color:var(--gold-hi); font-family:var(--font-caps); font-size:.88rem; letter-spacing:.04em;
}
.conf__slug span:last-child{
  color:color-mix(in srgb, var(--gold-hi) 74%, var(--paper-hi));
  font-family:var(--font-caps); font-size:.74rem;
  font-weight:600; letter-spacing:.22em; text-transform:uppercase;
}
.conf__t{
  margin:0; color:var(--paper-hi); font-family:var(--font-caps); font-weight:700;
  font-size:clamp(1.7rem,3.2vw,3rem); line-height:1; letter-spacing:.015em;
  text-wrap:balance; max-width:17ch;
  text-shadow:0 2px 26px rgb(0 0 0 / .45);
}
.conf__when{ margin:0; color:var(--paper-hi); font-size:clamp(.98rem,1.2vw,1.12rem); letter-spacing:.02em; }
.conf__where{
  display:flex; flex-wrap:wrap; gap:.35rem clamp(12px,1.4vw,22px);
  padding-top:clamp(9px,1.1vw,15px);
  border-top:1px solid color-mix(in srgb, var(--gold-hi) 26%, transparent);
}
.conf__where span{
  color:color-mix(in srgb, var(--paper-hi) 88%, var(--gold-hi));
  font-family:var(--font-caps); font-size:.93rem; letter-spacing:.05em;
}
.conf__where span + span::before{
  content:"\00b7"; margin-right:clamp(12px,1.4vw,22px);
  color:color-mix(in srgb, var(--gold-hi) 70%, transparent);
}
.conf__names{ display:flex; flex-wrap:wrap; gap:.4rem .55rem; margin:.1rem 0 0; padding:0; list-style:none; }
.conf__names span{
  display:block; padding:.26rem .65rem;
  border:1px solid color-mix(in srgb, var(--gold-hi) 30%, transparent);
  background:color-mix(in srgb, var(--ink-deep) 42%, transparent);
  color:var(--paper-hi); font-family:var(--font-caps); font-size:.95rem; letter-spacing:.04em;
}
/* The line that dates and orders the entry — the club's own words, kept. */
.conf__k{
  margin:.1rem 0 0; color:color-mix(in srgb, var(--paper-hi) 78%, var(--ink-deep));
  font-family:var(--font-caps); font-size:.86rem; letter-spacing:.14em; text-transform:uppercase;
}

/* ==========================================================================
   THE STRIP

   Plates, not icons, and two rules make that work:

   1. COMMON HEIGHT, NATURAL WIDTH. The flyers are not the same shape — 2024 is
      1:1, 2025 is 0.773 — and a square crop throws away the composition that
      makes a poster recognisable, which is the whole reason for choosing from
      artwork rather than from a list of years. Matching height and letting the
      width fall where it does is the same trick the events rail uses.
   2. NOTHING IS CROPPED. There is room under the band, so the plate takes it
      rather than being cut to fit a square.

   The rail is a real scroll container, so a tenth conference costs nothing:
   the strip scrolls and the band above it never changes height.
   ========================================================================== */
.conf__strip{ display:none; }
.conf[data-live] .conf__strip{
  position:relative; z-index:4;
  display:flex; align-items:center; gap:clamp(10px,1.4vw,20px);
  /* The strip's GROUND is full-bleed but its contents are not: they line up
     with the heading and with the panel above, on the same 1520 measure. A
     plain var(--edge) left the first plate 200px out from the h2 at 1920. */
  padding-block:clamp(16px,1.9vw,26px);
  padding-inline:max(var(--edge), calc((100% - 1520px) / 2 + var(--edge)));
  background:color-mix(in srgb, var(--ink-deep) 88%, var(--gold));
  border-top:1px solid color-mix(in srgb, var(--gold-hi) 26%, transparent);
}
.conf__rail{
  display:flex; gap:clamp(10px,1.4vw,18px);
  overflow-x:auto; overscroll-behavior-x:contain; scrollbar-width:none;
  scroll-snap-type:x proximity;
  flex:1 1 auto;
}
.conf__rail::-webkit-scrollbar{ display:none; }

.conf__frame{
  flex:0 0 auto; scroll-snap-align:start;
  display:flex; align-items:center; gap:clamp(.6rem,.9vw,.95rem);
  padding:.5rem .9rem .5rem .5rem;
  border:1px solid transparent; border-radius:5px;
  background:none; cursor:pointer; text-align:left;
  transition:background .3s var(--ease), border-color .3s var(--ease);
}
.conf__frame:hover{ background:color-mix(in srgb, var(--paper-hi) 10%, transparent); }
.conf__frame[aria-selected="true"]{
  border-color:color-mix(in srgb, var(--gold-hi) 62%, transparent);
  background:color-mix(in srgb, var(--paper-hi) 12%, transparent);
}
.conf__thumb{
  position:relative; display:block; flex:none;
  height:clamp(76px,7.6vw,112px);
  overflow:hidden; border-radius:3px;
  box-shadow:0 12px 26px -12px rgb(0 0 0 / .85);
  outline:1px solid color-mix(in srgb, var(--gold-hi) 26%, transparent);
  outline-offset:-1px;
  transition:outline-color .3s var(--ease);
}
.conf__thumb img{ height:100%; width:auto; max-width:none; display:block; }
.conf__frame[aria-selected="true"] .conf__thumb{ outline-color:color-mix(in srgb, var(--gold-hi) 76%, transparent); }
/* the numeral, so a plate is identifiable before its year has been read */
.conf__thumbnum{
  position:absolute; left:0; top:0;
  padding:.1rem .38rem;
  background:color-mix(in srgb, var(--ink-deep) 82%, transparent);
  color:var(--gold-hi); font-family:var(--font-caps); font-size:.7rem;
  font-weight:700; letter-spacing:.1em; line-height:1.5;
}
.conf__framecap{ display:grid; gap:.12rem; min-width:0; }
.conf__frameyear{
  /* raw --gold-hi lands on 4.49:1 here in the forest palette — at the line,
     which is not the same as over it */
  color:color-mix(in srgb, var(--gold-hi) 86%, var(--paper-hi));
  font-family:var(--font-caps); font-weight:700;
  font-size:clamp(1.02rem,1.2vw,1.24rem); letter-spacing:.1em; line-height:1.1;
}
.conf__frametitle{
  color:color-mix(in srgb, var(--paper-hi) 86%, var(--ink-deep));
  font-family:var(--font-caps); font-size:clamp(.8rem,.9vw,.9rem); letter-spacing:.05em;
  max-width:22ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.conf__frame[aria-selected="true"] .conf__frametitle{ color:var(--paper-hi); }

.conf__stripnav{ display:flex; gap:6px; flex:none; }
.conf__btn{
  width:34px; height:34px; display:grid; place-items:center;
  border:1px solid color-mix(in srgb, var(--gold-hi) 34%, transparent);
  border-radius:50%; background:none; color:var(--paper-hi); cursor:pointer;
  transition:background .25s var(--ease), border-color .25s var(--ease);
}
.conf__btn:hover{ background:color-mix(in srgb, var(--paper-hi) 12%, transparent); }
.conf__btn svg{ width:16px; height:16px; }

/* The band supplies its own edge, so the section gives some padding back —
   the same trade #classes-current makes. */
.section--conf{ padding:clamp(46px,5.4vw,84px) 0 clamp(30px,3.4vw,52px); }
.section--conf .conf__head{ margin-bottom:clamp(22px,2.6vw,38px); }

@media (max-width:900px){
  .conf__inner{ grid-template-columns:1fr; gap:20px; }
  .conf__plate{ justify-self:start; width:min(100%,420px); }
  .conf__t{ max-width:none; }
  .conf__frametitle{ display:none; }
  .conf__stripnav{ display:none; }
  .conf__thumb{ height:clamp(64px,17vw,84px); }
  .conf__frame{ padding:.4rem .7rem .4rem .4rem; }
}
@media (prefers-reduced-motion: reduce){
  .conf[data-live] .conf__panel,
  .conf__frame, .conf__thumb, .conf__btn{ transition:none; }
  .conf__rail{ scroll-behavior:auto; }
}

/* .section--tight outlived the crosslink line it was written for; the line is
   gone from the page and both .crosslink rules with it. */
.section--tight{ padding:clamp(30px,4vw,54px) 0 clamp(34px,4.5vw,62px); }

/* ==========================================================================
   RESPONSIVE — content
   ========================================================================== */
@media (max-width:900px){
  .lede__grid{ grid-template-columns:1fr; }
  .lede__side{ max-width:640px; }
  .lede__side > p{ max-width:none; }
  .sidecard img{ max-width:420px; }
}


/* ==========================================================================
   VIDEO INTRO — the club's promo reel, full-bleed on open
   The reel is 9:16. On a wide screen a real `cover` crops rows 415-865 of
   1280 while the title text spans ~407-911, so "2026" would be lost. The
   sharp copy is `contain` and a blurred, scaled copy behind it fills the
   screen, which reads full-bleed without cropping the message.
   ========================================================================== */
.vintro{
  position:fixed; inset:0;
  z-index:60;
  display:none;                 /* only shown once html.js is set */
  /* Transparent on purpose. The page's own cream ground and .frame layer
     (tan blobs, gold fan, sparkles) are NOT in the reveal-gated set, so they
     are already painted underneath — the reel ends up sitting on the site's
     real background for free, no extra markup. This replaced a blurred,
     scaled copy of the video itself, which read as exactly what it was: a
     zoomed-in duplicate with smeared lettering down the sides. */
  background:transparent;
  opacity:1;
  transition:opacity .6s ease;
}
.vintro.is-out{ opacity:0; pointer-events:none; }

/* Each source matches its screen's orientation — the 16:9 recut on landscape,
   the original 9:16 reel on portrait — so a plain `cover` fills either one with
   almost nothing cropped. The clamps, band fits and blurred backdrops that used
   to live here existed only because one 9:16 file had to serve both.

     1920x1080 / 1600x900 -> exact match, 0 crop
     1440x900             -> 96 source px off each side (type margin ~520px)
     390x844  (portrait)  -> 64 source px off each side (type margin 71px)   */
.vintro__stage{ position:absolute; inset:0; }
.vintro__v{
  position:absolute; inset:0;
  width:100%; height:100%;
  display:block;
  object-fit:cover;
}

.vintro__skip{
  position:absolute;
  right:clamp(14px,2.4vw,28px); bottom:clamp(14px,2.4vw,28px);
  z-index:2;
  min-height:44px;
  padding:.7rem 1.3rem;
  border-radius:999px;
  border:1px solid color-mix(in srgb, var(--ink) 26%, transparent);
  background:var(--paper-hi);
  color:var(--ink);
  font-family:var(--font-caps);
  font-size:1.02rem; font-weight:600;
  letter-spacing:.045em;
  cursor:pointer;
  box-shadow:0 4px 14px -6px rgb(var(--shadow-rgb) / .3);
  transition:background-color .2s var(--ease), border-color .2s var(--ease);
}
.vintro__skip:hover{ background:var(--paper-lo); border-color:var(--ink); }
.vintro__skip:focus-visible{ outline:2px solid var(--ink); outline-offset:3px; }

/* Shown only when the browser refuses autoplay. Closing the intro in that case
   means the reel is never seen at all — offer to start it instead. */
.vintro__play{
  position:absolute; left:50%; top:50%;
  transform:translate(-50%,-50%);
  z-index:3;
  display:none;
  align-items:center; gap:.6rem;
  min-height:44px;
  padding:.9rem 1.7rem .9rem 1.4rem;
  border-radius:999px;
  border:1px solid rgb(255 255 255 / .45);
  background:rgb(0 0 0 / .55);
  -webkit-backdrop-filter:blur(6px); backdrop-filter:blur(6px);
  color:#fff;
  font-family:var(--font-caps);
  font-size:1.16rem; font-weight:600; letter-spacing:.045em;
  cursor:pointer;
}
.vintro.is-blocked .vintro__play{ display:inline-flex; }
.vintro__play svg{ width:22px; height:22px; }
.vintro__play:hover{ background:rgb(0 0 0 / .72); }
.vintro__play:focus-visible{ outline:2px solid #fff; outline-offset:3px; }

@media (prefers-reduced-motion:reduce){
  .vintro{ display:none!important; }
}

/* ==========================================================================
   THE CLUB'S NAMESAKE — the biography as a manuscript opening

   What this replaced: a 1920x1690 band at 1920x1080. Five paragraphs, ~3,300
   characters, in a 620px measure inside a 716px wrap centred in a 1920px
   window — ~650px of bare cream on each side, wider than the text column
   itself. No subheads, no anchors, no entry point. And it is the LAST section
   on the page, so it was the note the whole site ended on.

   THE DIAGNOSIS THAT MATTERED: the scroll was not actually long. 1690px is
   about two flicks. It FELT endless because nothing told the reader where they
   were or how much was left, and an unmeasured distance always feels further
   than a measured one. So the fix is half length and half measurement — two
   columns halve the first, and the frame, the headpiece and the rubrics give
   the second.

   Five directions were built and measured before this one; the other four,
   with what each costs, are in lab/bio-lab/NOTES.md.

   NOT ONE WORD CHANGED. The rubric spans in index.html were generated by
   splitting each paragraph on a space and rejoining, with an assertion that
   stripping the markup returns the original string character for character.
   Nothing here is scripted, either — the section is static and cannot break.
   ========================================================================== */

/* the leaf. Two stops plus grain, never a flat fill — a cream field this large
   is exactly what the display pipeline dithers into a visible diagonal, and in
   the noir palette the paper tokens sit closest together. */
.illum{ position:relative; }
.illum__page{
  position:relative; isolation:isolate;
  max-width:1360px; margin-inline:auto;
  /* Small at the bottom of the clamp on purpose. A manuscript page wants
     generous margins and at 1360 it gets them, but on a 390px phone the same
     generosity plus the jadwal inset plus the wrap gutter left a 302px column —
     35 characters a line, which is a column in a lift, not a page. */
  padding:clamp(26px,4.4vw,72px) clamp(14px,3.6vw,64px) clamp(24px,3.6vw,56px);
  background:linear-gradient(168deg,
    color-mix(in srgb, var(--paper-hi) 96%, var(--tan)) 0%,
    color-mix(in srgb, var(--paper) 92%, var(--tan)) 100%);
  box-shadow:0 30px 70px -40px rgb(var(--shadow-rgb) / .5);
}
.illum__grain{
  position:absolute; inset:0; z-index:1;
  opacity:.05; mix-blend-mode:multiply; pointer-events:none;
}
/* THE JADWAL — the ruled frame around the written area. Thick rule outside,
   hairline inside, which is how it is actually drawn: one element, a border and
   an offset outline. It is what turns "text that runs until it stops" into a
   page, which has edges, and therefore a size the reader can see up front. */
.illum__jadwal{
  position:absolute; inset:clamp(7px,1.7vw,26px); z-index:2;
  border:2px solid color-mix(in srgb, var(--gold) 52%, transparent);
  outline:1px solid color-mix(in srgb, var(--gold) 34%, transparent);
  outline-offset:5px;
  pointer-events:none;
}

/* THE UNWAN — the illuminated headpiece over the opening of a text. */
.illum__unwan{ position:relative; z-index:3; text-align:center; margin-bottom:clamp(26px,3.2vw,44px); }
.illum__unwan-orn{ color:var(--gold); margin:0 auto .9rem; max-width:min(320px,60%); }
.illum__unwan-orn svg{ width:100%; height:auto; display:block; }
.illum__title{ margin:.3rem 0 0; }
.illum__work{
  margin:.9rem 0 0;
  display:flex; flex-direction:column; gap:.1rem; align-items:center;
}
.illum__work-title{
  font-family:var(--font-caps); font-weight:700;
  font-size:clamp(.95rem,1.15vw,1.1rem); letter-spacing:.06em; color:var(--gold-ink);
}
.illum__work-gloss{
  font-family:var(--font-read);
  font-size:.9rem; font-style:italic; color:var(--ink-mute);
}

/* THE COLUMNS — the half of the fix that shortens the section.
   THE MEASURE IS THE WHOLE GAME. 65-75 characters is the readable band and the
   old single column sat at ~72, right at the top of it. Two columns that halve
   the scroll by halving the measure would not be an improvement, they would be
   a different fault, so the page is capped at 1360: a column then runs ~600px,
   which at this size measures 76 characters. Verified per width, not assumed —
   tools/verify-bio.js reports the real count. */
.illum__cols{
  position:relative; z-index:3;
  /* ONE column below 900px, and it has to be capped: uncapped it inherited the
     page width and ran a 708px line at 834 — 107 characters, half again the
     band, a worse fault than the one this section set out to fix. Above 900 the
     cap comes off because the COLUMN carries the measure instead of the block.
     58ch, not 68: the ch unit is the advance of "0", and this face's zero is
     narrower than its average letter, so 68ch measured out at ~83 real
     characters. */
  max-width:58ch; margin-inline:auto;
  column-count:1;
  column-gap:clamp(30px,3.6vw,64px);
  column-rule:1px solid color-mix(in srgb, var(--gold) 30%, transparent);
  font-family:var(--font-read);
  /* The type has to scale WITH the column or the measure drifts. Above ~1360
     the page is capped, so the columns stop widening at ~600px — but a clamp
     that keeps shrinking the type below that put 85 characters on the same
     590px column at 1440. A higher floor and a steeper middle term hold the
     count roughly flat across the whole two-column range. */
  font-size:clamp(1.12rem,1.4vw,1.3rem);
  line-height:1.62;
  color:var(--ink);
  text-align:left;
}
/* 1100, not 900. Two JUSTIFIED columns need width to sit in: at 900 they came
   out ~380px, and justified text at that measure opens rivers between the
   words. Below this the page keeps one capped column, which reads better than
   two cramped ones. */
@media (min-width:1100px){
  .illum__cols{
    max-width:none; margin-inline:0;
    column-count:2;
    text-align:justify; hyphens:auto;
  }
}
.illum__cols p{ margin:0 0 1.1em; }
.illum__cols p:last-child{ margin-bottom:0; }

/* RUBRICATION — the paragraph's own opening words, coloured. Five places for
   the eye to land in a text with no subheads. It adds NO words: see the note in
   index.html and the generator's assertion. */
.illum__rubric{
  font-family:var(--font-caps); font-weight:700;
  letter-spacing:.02em; color:var(--wine);
}
.illum__first::first-letter{
  float:left; line-height:.74; padding:.04em .12em 0 0;
  font-family:var(--font-caps); font-weight:700; font-size:3.2em; color:var(--gold-deep);
}
.illum__colophon{
  position:relative; z-index:3;
  display:flex; align-items:center; justify-content:center; gap:12px;
  margin:clamp(24px,3vw,40px) 0 0;
  color:var(--gold);
}
.illum__colophon span{ display:block; width:min(120px,22%); height:1px; background:currentColor; opacity:.6; }

/* THE FURNITURE HAS TO EARN ITS HEIGHT.
   The headpiece, the frame and the colophon cost about 350px. Above 1100 the
   two columns repay that several times over — the band goes from 1690px to
   1538 at 1920 and 1425 at 1440. Below 1100 there is only one column, nothing
   is being repaid, and full-size ceremony on a phone is 350px of scroll bought
   for nothing. So the ornament scales down with the layout it belongs to. */
@media (max-width:1099px){
  .illum__unwan{ margin-bottom:clamp(18px,2.4vw,30px); }
  .illum__unwan-orn{ max-width:min(200px,46%); margin-bottom:.6rem; }
  .illum__work{ margin-top:.6rem; }
  .illum__colophon{ margin-top:clamp(16px,2vw,26px); }
}
@media (max-width:700px){
  /* the colophon is a flourish under a finished page. On a phone it is two
     rules and a bullet costing 60px at the end of a long scroll. */
  .illum__colophon{ display:none; }
  .illum__unwan-orn{ max-width:min(160px,40%); }
}
@media (max-width:560px){
  /* the leaf becomes the page: at this width a gutter outside a framed page is
     two frames around the same text */
  #imam-ghazali .wrap{ --edge:10px; }
}
