/* ============================================================
   MOTION — the layer Home, The Book and Our Course share.

   Until 22 September 2026 every line below lived in course.css and
   was loaded by the two course pages alone. Cata asked for the same
   treatment on the home page and the book page, and three hand-kept
   copies of a 200-line stylesheet is how three pages stop matching.
   So it moved here whole. Nothing in it changed in the move except
   the two selectors noted at their own lines, which were keyed to
   the course page by id and could not be.

   Class names stay prefixed `c-`. Renaming them would have meant
   editing every pinned section on two course pages in two languages
   to gain nothing; the prefix already does the job it was added for,
   which is not colliding with ink.css.

   THE RESTING STATE OF EVERYTHING HERE IS VISIBLE. motion.js adds
   `cjs` to <html> and only then does any of this park anything. With
   JavaScript off, or with reduced motion on, these pages are
   ordinary readable pages. Invert that and a failed request serves a
   blank page, silently, forever. tools/test-course.mjs checks it.
   ============================================================ */

:root{
  /* The one token this layer adds. Everything else comes from ink.css. */
  --c-ease:cubic-bezier(.16,1,.3,1);
}

/* ============================================================
   MOTION
   The resting state is VISIBLE. The script adds `cjs` to <html>
   and only then parks anything, so with JavaScript off — or with
   reduced motion on, where the script returns before touching
   the class — this is an ordinary readable page. Nothing on it
   depends on an animation having run.
   ============================================================ */
/* THE PARKED STATE IS WIDER THAN THE PAGE, AND THAT HAS TO BE CLIPPED.
   Anything waiting to slide in from the right is sitting 90px to the right
   of where it will end up, and a full-width item plus 90px is wider than a
   phone. Measured at 375px: the document was 445px wide and the whole page
   scrolled sideways — headings, menu and all — until the reveals fired,
   which is exactly the moment a visitor is most likely to be swiping.

   `clip` and not `hidden`: `overflow:hidden` makes an element a scroll
   container, and a scroll container between a sticky column and the
   viewport is what stops the column sticking. `clip` cuts the overflow
   without creating one, so both pinned sections still work. Verified in
   Chromium at all seven widths. */
/* Was `[data-pp-page="course"] main` when this file was course.css and
   there was one page with motion on it. This stylesheet is loaded only by
   the pages that carry the motion layer, so the page id was doing nothing
   the file name does not already do. */
main{overflow-x:clip}
.c-r{opacity:1;transform:none;filter:none}

html.cjs .c-r{
  opacity:0;
  transition:opacity 1s var(--c-ease),transform 1s var(--c-ease),
             filter .9s var(--c-ease);
  transition-delay:calc(var(--i,0)*110ms);
  will-change:opacity,transform,filter;
}
html.cjs .c-r[data-c="up"]   {transform:translateY(68px) scale(.965);filter:blur(10px)}
html.cjs .c-r[data-c="rise"] {transform:translateY(118px) rotate(-1.6deg) scale(.93);filter:blur(15px)}
html.cjs .c-r[data-c="left"] {transform:translateX(-90px);filter:blur(12px)}
html.cjs .c-r[data-c="right"]{transform:translateX(90px);filter:blur(12px)}
html.cjs .c-r.c-in{opacity:1;transform:none;filter:blur(0)}

/* ---------- the two wipes, and why neither one uses clip-path ----------
   The first version of this parked the accent rule and the two price cards
   at `clip-path: inset(0 100% 0 0)` and un-clipped them on reveal. It looked
   right and it never fired: measured in Chromium, an IntersectionObserver
   does not report an element its OWN clip-path has reduced to zero area as
   intersecting, ever. The rule under the hero and both price cards would
   have shipped invisible, on every screen, with nothing in the console.

       plain div      observer fires
       scaleX(0)      observer fires
       opacity:0      observer fires
       clip-path zero observer NEVER fires

   So the wipe is done twice over, with two mechanisms that were measured
   firing. `bar` scales a rule out of its own left edge. `wipe` leaves the
   element alone and slides a curtain of the page's own ground off it, which
   is the only way to wipe a block of text without squashing the text. */

/* A rule that draws itself, left to right. */
html.cjs .c-r[data-c="bar"]{opacity:1;transform:scaleX(0);transform-origin:left}
/* ...and it has to be un-done here rather than by the shared .c-in rule
   above, which carries exactly the same specificity and loses on order.
   Measured: without this line the rule under the hero stays at scaleX(0)
   after the class lands, so it is never drawn at all. */
html.cjs .c-r[data-c="bar"].c-in{transform:none}

/* A curtain the colour of the ground, retracting to the right. The element
   under it is never hidden, so the observer always sees it. */
html.cjs .c-r[data-c="wipe"]{opacity:1;transform:none;filter:none;position:relative}
html.cjs .c-r[data-c="wipe"]::after{
  content:"";position:absolute;inset:0;z-index:1;
  background:var(--paper);
  transform:scaleX(1);transform-origin:right;
  transition:transform .95s var(--c-ease);
  transition-delay:calc(var(--i,0)*110ms);
}
html.cjs .c-r[data-c="wipe"].c-in::after{transform:scaleX(0)}

/* ---------- split text: words masked, rising out of the line ----------
   The mask is padding + negative margin rather than a fixed height,
   so descenders and accented capitals are not clipped. Spanish
   headlines carry both. */
.c-sp{
  display:inline-block;overflow:hidden;vertical-align:bottom;
  padding:.14em .03em .24em;margin:-.14em -.03em -.24em;
}
.c-sp > i{
  display:inline-block;font-style:inherit;
  transform:translateY(118%) rotate(4deg);
  transition:transform .9s var(--c-ease);
  transition-delay:calc(var(--i,0)*62ms);
}
.c-in .c-sp > i{transform:none}
/* ---------- marquee ----------
   aria-hidden, because it is the same six facts the page already
   states in sentences. A screen reader reading them twice is not
   an enhancement. The track is written out twice in the HTML so
   the -50% loop is seamless with no JavaScript. */
.c-marquee{
  background:var(--paper-raised);
  border-block:1px solid var(--rule);
  padding-block:16px;overflow:hidden;white-space:nowrap;
}
.c-marquee__track{display:inline-flex;animation:c-slide 34s linear infinite}
.c-marquee__run{
  font-family:var(--mono);font-size:13px;font-weight:500;
  letter-spacing:.22em;text-transform:uppercase;color:var(--accent);
}
@keyframes c-slide{from{transform:translateX(0)}to{transform:translateX(-50%)}}
.c-marquee:hover .c-marquee__track{animation-play-state:paused}

/* ============================================================
   THE TWO PINNED SECTIONS
   One column sticks while the other scrolls past it. Pure CSS
   position:sticky — no scroll listener, no library, nothing to
   fall out of sync on a slow phone.

   Below 880px both columns unstick and stack. A pinned column on
   a 393px screen is a pinned column covering the thing you are
   trying to read.
   ============================================================ */
.c-pin{
  display:grid;grid-template-columns:minmax(0,.92fr) minmax(0,1.08fr);
  gap:clamp(36px,6vw,88px);align-items:start;
}
.c-pin--mirror{grid-template-columns:minmax(0,1.08fr) minmax(0,.92fr)}
.c-pin__stick{position:sticky;top:14vh;display:flex;flex-direction:column;gap:28px}
.c-pin--mirror .c-pin__stick{order:2}
.c-pin--mirror .c-pin__flow{order:1}
.c-pin__flow{display:flex;flex-direction:column;gap:clamp(48px,9vh,104px)}
.c-pin__item{
  display:flex;flex-direction:column;gap:14px;
  min-height:46vh;justify-content:center;
  border-top:1px solid var(--rule);padding-top:26px;
}
.c-pin__n{
  font-family:var(--mono);font-size:11px;font-weight:500;
  letter-spacing:.18em;color:var(--accent);margin:0;
}
.c-pin__h{
  font-family:var(--display);font-size:clamp(26px,3.6vw,44px);
  line-height:1.08;letter-spacing:-.012em;margin:0;
}

@media (max-width:880px){
  .c-pin,.c-pin--mirror{grid-template-columns:1fr}
  .c-pin__stick{position:static;top:auto}
  .c-pin--mirror .c-pin__stick,.c-pin--mirror .c-pin__flow{order:initial}
  .c-pin__item{min-height:0}
  .c-pin__flow{gap:clamp(34px,7vh,60px)}
}

/* A grid item will not shrink below its widest child unless it is
   told it may. Same rule that was keeping the admin 1130px wide
   inside a 393px phone — see tools/test-touch.mjs. */
.c-pin > *{min-width:0}
/* ---------- photo slots ----------
   Empty on purpose. A slot says what belongs in it and at what
   shape; it is not a stock photograph of somebody else's office.
   To fill one: put an <img> inside the figure and add .c-slot--has.
   The aspect ratio and the border come off automatically. */
.c-slot{
  border:1px dashed var(--rule-strong);border-radius:3px;
  background:var(--paper);display:grid;place-items:center;
  text-align:center;padding:24px;margin:0;
}
.c-slot--portrait{aspect-ratio:4/5}
.c-slot--wide{aspect-ratio:16/10}
.c-slot span{
  font-family:var(--mono);font-size:11px;font-weight:500;
  letter-spacing:.18em;text-transform:uppercase;color:var(--muted);
  line-height:1.9;
}
.c-slot--has{border:1px solid var(--rule);padding:0;aspect-ratio:auto;background:none}
.c-slot--has img{border-radius:3px;width:100%}
/* ============================================================
   STICKY BAR
   It does not appear until the hero has gone, so the first thing
   on the page is not a bar covering the second thing.
   ============================================================ */
.c-stickybar{
  position:fixed;left:0;right:0;bottom:0;z-index:40;
  background:var(--paper-raised);border-top:1px solid var(--rule-strong);
  padding:14px var(--gutter);
  padding-bottom:calc(14px + env(safe-area-inset-bottom,0px));
  transform:translateY(130%);transition:transform .55s var(--c-ease);
}
body.c-past-hero .c-stickybar{transform:none}
.c-stickybar__in{
  max-width:var(--shell);margin-inline:auto;display:flex;
  align-items:center;justify-content:space-between;gap:18px;flex-wrap:wrap;
}
.c-stickybar__t1{
  font-family:var(--mono);font-size:11px;font-weight:500;
  letter-spacing:.18em;text-transform:uppercase;color:var(--accent);margin:0;
}
.c-stickybar__t2{font-size:15px;color:var(--ink-2);margin:0}
.c-stickybar .btn{min-height:44px;padding:11px 22px;font-size:14.5px}

/* On a narrow screen the two halves stack and the button goes full
   width, which is the only shape that stays tappable at 375px. */
@media (max-width:560px){
  .c-stickybar__in{flex-direction:column;align-items:stretch;gap:10px}
  .c-stickybar .btn{justify-content:center;min-height:48px}
  .c-stickybar__t2{font-size:14px}
}

/* The bar sits over the bottom of the page, so the footer ends above it
   rather than behind it. Keyed on the bar actually being present rather
   than on `[data-pp-page="course"]`, which is what this said when only one
   page had a bar — a home page that loads this file and has no sticky bar
   should not grow 120px of dead space under its footer. */
body:has(.c-stickybar) .foot{padding-bottom:calc(120px + env(safe-area-inset-bottom,0px))}
/* ---------- touch ----------
   Keyed on the pointer, not the viewport: an iPad Pro in landscape
   is 1194px wide and still has no mouse. */
@media (pointer:coarse){
  .c-pin__stick{top:8vh}
  .c-marquee__track{animation-duration:46s}
}

/* ============================================================
   REDUCED MOTION
   Belt and braces. The script already returns before adding the
   `cjs` class, so nothing is ever parked; these rules mean that
   even if it did, the page reads.
   ============================================================ */
@media (prefers-reduced-motion:reduce){
  html.cjs .c-r{
    opacity:1!important;transform:none!important;
    filter:none!important;transition:none!important;
  }
  .c-sp > i{transform:none!important;transition:none!important}
  .c-marquee__track{animation:none}
  .c-stickybar{transition:none}
}
