/* BappaSlides — the course presenter.
 *
 * The look is the reference decks: a drifting pastel blob field, white cards
 * with a thick dark outline and a hard coloured offset shadow, and display type
 * big enough to read at arm's length. It is deliberately the same family as
 * BlobField.astro and the assessment engines, so a lesson and the questions
 * that follow it are visibly one thing.
 *
 * Every value comes from bappa-tokens.css except the four accent colours below,
 * which are named here because "the first card is yellow" is a fact about this
 * component rather than about the brand.
 */

.bs {
  --bs-c1: #FFC400;
  --bs-c2: #FF6B35;
  --bs-c3: #22C3E6;
  --bs-c4: #7E57C2;

  position: relative;
  isolation: isolate;
  display: flex;
  flex-direction: column;
  /* Exactly the screen, not "at least" the screen.
     With min-height alone a tall slide grew the frame past the viewport and it
     was the whole PAGE that scrolled — carrying Bappa, the jaswand and the
     Next button off the bottom with it, and putting a scrollbar down the side
     of a lesson embedded in an iframe. Fixed height instead, and the stage
     inside scrolls on the rare slide that needs it, so the frame and its
     furniture stay exactly where they are. */
  height: 100svh;
  min-height: 100svh;
  /* `clip` rather than `hidden`, and the difference is not cosmetic. `hidden`
     still makes a scroll container: the blob field is inset -10%, so the frame
     has 20% of hidden overflow, and the first time a button takes focus the
     browser quietly scrolls the whole lesson up to reveal it. Twenty-odd
     pixels — enough to push the jaswand off the top of the screen and lift
     Bappa's feet off the bottom, with nothing on screen to explain why.
     `clip` clips identically and creates no scroll container at all, so
     nothing can ever displace the frame. `hidden` first for old browsers. */
  overflow: hidden;
  overflow: clip;
  font-family: var(--bt-font);
  color: var(--bt-ink);
  background: var(--bt-cream);
}

.bs:focus { outline: none; }

/* ── the living background ───────────────────────────────────────────────
   Six soft shapes that morph and drift. Pure CSS: a canvas or SVG animation
   for decoration nobody looks at is the wrong thing to spend a cheap phone's
   battery on. */

.bs__blobs {
  position: absolute;
  inset: -10%;
  z-index: -1;
  filter: blur(0.5px);
}

.bs__blob {
  position: absolute;
  display: block;
  border-radius: 42% 58% 55% 45% / 48% 42% 58% 52%;
  opacity: 0.5;
  animation: bsMorph 26s ease-in-out infinite, bsDrift 34s ease-in-out infinite;
}

.bs__blob--1 { left: -8%;  top: -6%;  width: 46vmax; height: 40vmax; background: var(--bt-gold);    animation-duration: 23s, 31s; }
.bs__blob--2 { right: -10%; top: 4%;  width: 38vmax; height: 36vmax; background: var(--bt-sky);     animation-duration: 27s, 37s; opacity: .42; }
.bs__blob--3 { right: -6%; bottom: -8%; width: 44vmax; height: 38vmax; background: var(--bt-saffron); animation-duration: 31s, 29s; opacity: .34; }
.bs__blob--4 { left: -12%; bottom: -4%; width: 40vmax; height: 42vmax; background: var(--bt-teal);   animation-duration: 25s, 41s; opacity: .26; }
.bs__blob--5 { left: 30%;  top: 42%;  width: 46vmax; height: 34vmax; background: var(--bt-green);   animation-duration: 29s, 33s; opacity: .2; }
.bs__blob--6 { left: 22%;  top: -12%; width: 26vmax; height: 26vmax; background: var(--bt-rose);    animation-duration: 21s, 39s; opacity: .24; }

@keyframes bsMorph {
  0%, 100% { border-radius: 42% 58% 55% 45% / 48% 42% 58% 52%; }
  33%      { border-radius: 58% 42% 40% 60% / 55% 58% 42% 45%; }
  66%      { border-radius: 46% 54% 62% 38% / 40% 52% 48% 60%; }
}

@keyframes bsDrift {
  0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
  50%      { transform: translate3d(3%, -3%, 0) scale(1.06); }
}

/* A background that moves is the first thing to switch off. */
@media (prefers-reduced-motion: reduce) {
  .bs__blob { animation: none; }
}

/* ── the cover, which is also the audio unlock ───────────────────────── */

.bs__cover {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: grid;
  place-items: center;
  padding: var(--bt-s4);
  /* Light enough that the blob field still reads through it — the cover
     should look like the lesson it introduces, not like a dialog over one. */
  background: rgb(255 248 236 / 0.45);
}

.bs__cover-card {
  width: min(34rem, 100%);
  padding: var(--bt-s5) var(--bt-s4);
  text-align: center;
  background: var(--bt-card);
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  box-shadow: 10px 10px 0 var(--bt-c-cover, var(--bt-saffron));
}

.bs__cover-title {
  margin: 0 0 var(--bt-s3);
  font-size: clamp(1.7rem, 7vw, 2.6rem);
  line-height: 1.1;
}

.bs__cover-line {
  margin: 0 0 var(--bt-s4);
  font-size: var(--bt-fs-body);
  color: var(--bt-ink-soft);
}

.bs__go {
  min-height: var(--bt-tap);
  padding: 0 var(--bt-s5);
  font: inherit;
  font-size: var(--bt-fs-prompt);
  font-weight: 700;
  color: #fff;
  background: var(--bt-green);
  border: 0;
  border-radius: var(--bt-radius-pill);
  box-shadow: var(--bt-shadow);
  cursor: pointer;
}

.bs__go:active { transform: translateY(2px); }

/* ── the stage ───────────────────────────────────────────────────────── */

.bs__stage {
  flex: 1 1 auto;
  display: grid;
  place-items: center;
  /* The extra at the bottom is exactly the height Bappa rises out of the bar
     by. He is lifted so he reads as somebody standing there rather than as an
     icon in a toolbar, and that lift is the one thing that can still reach the
     text — so the text stops above it. */
  padding: var(--bt-s4) var(--bt-s3) calc(var(--bt-s4) + 4.8rem);
  /* A slide taller than the screen scrolls rather than being clipped, and the
     corner pieces are in their own rows so nothing scrolls underneath them. */
  overflow-y: auto;
  touch-action: pan-y;
}

.bs__slide {
  width: min(46rem, 100%);
  display: flex;
  flex-direction: column;
  gap: var(--bt-s4);
  animation: bsIn var(--bt-t-slow) var(--bt-ease);
}

/* On a desktop the lesson was sitting in a 46rem band in the middle of a
   2000px screen with nothing either side, because the frame was sized for a
   phone and never told a bigger screen existed. Pictures especially want the
   room — a story panel at 300px is a thumbnail.

   Prose does not: a line of text 1100px wide is genuinely harder to read than
   a short one, so the blocks that are paragraphs keep their own limit below. */
@media (min-width: 62em) {
  .bs__slide { width: min(64rem, 92vw); }

  /* The type scale is capped for a phone — body text tops out at 1.1rem — so on
     a laptop the words were technically legible and visually lost, a postage
     stamp of text in the middle of a large screen. Raised here and only here:
     these tokens are shared with the assessment engines and the rest of the
     site, and this is a statement about a full-screen lesson, not about them.

     It matters more than it looks: a lesson is meant to be played on a laptop
     with somebody watching from across the kitchen. */
  .bs {
    --bt-fs-big: 3rem;
    --bt-fs-prompt: 1.75rem;
    --bt-fs-body: 1.3rem;
    --bt-fs-small: 1.05rem;
  }
}

/* From the right, like the storybook — the same gesture, so the two feel like
   one product. */
@keyframes bsIn {
  from { opacity: 0; transform: translateX(6%); }
  to   { opacity: 1; transform: none; }
}

.bs__pic { margin: 0; text-align: center; }

.bs__pic img {
  max-width: 100%;
  max-height: 34svh;
  object-fit: contain;
}

/* A photo is framed; a cutout sits straight on the background with a soft
   shadow, because a box around a thing that had no edges looks like a bug. */
.bs__pic--photo img {
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  object-fit: cover;
  width: 100%;
}

.bs__pic--cutout img { filter: drop-shadow(0 10px 14px rgb(0 0 0 / 0.18)); }

.bs__pic--diagram img {
  background: #fff;
  border-radius: var(--bt-radius);
  padding: var(--bt-s3);
  width: 100%;
}

/* ── opener ──────────────────────────────────────────────────────────── */

.bs-opener { text-align: center; }

.bs-opener__kicker {
  margin: 0 0 var(--bt-s3);
  font-size: var(--bt-fs-small);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bt-purple);
}

/* A white blob, not a flat tint.
   The peach fill it replaced sat too close to the cream background and to the
   blobs behind it, so the question never separated from the page. White is the
   only fill that stays legible over every blob colour the field can put behind
   it — and it lifts, which a tint cannot. */
.bs-opener__bubble {
  padding: var(--bt-s5) var(--bt-s4);
  background: #fff;
  border: 4px solid var(--bs-c2);
  border-radius: 46% 54% 52% 48% / 16% 18% 16% 18%;
  box-shadow: 0 10px 26px rgb(43 33 24 / 0.14);
}

.bs-opener__ask {
  margin: 0;
  font-size: clamp(1.5rem, 6.4vw, 2.4rem);
  line-height: 1.15;
  color: var(--bt-purple);
}

.bs-opener__line {
  margin: var(--bt-s4) 0 0;
  font-size: var(--bt-fs-body);
  font-weight: 600;
  color: var(--bt-ink-soft);
}

/* ── points ──────────────────────────────────────────────────────────── */

.bs-points__title {
  margin: 0 0 var(--bt-s4);
  font-size: clamp(1.3rem, 5.4vw, 1.9rem);
  text-align: center;
}

.bs-points__list {
  display: flex;
  flex-direction: column;
  gap: var(--bt-s4);
}

.bs-points__card {
  display: flex;
  align-items: flex-start;
  gap: var(--bt-s3);
  padding: var(--bt-s4);
  background: var(--bt-card);
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  /* The hard offset shadow from the reference decks — no blur, full colour. */
  box-shadow: 7px 7px 0 var(--tint);
  animation: bsPop var(--bt-t-slow) var(--bt-ease) both;
  animation-delay: var(--in, 0s);
}

@keyframes bsPop {
  from { opacity: 0; transform: translateY(14px) scale(0.98); }
  to   { opacity: 1; transform: none; }
}

.bs-points__tick {
  flex: 0 0 auto;
  font-size: 1.7rem;
  line-height: 1;
  color: var(--tint);
  /* The ticks in the references are drawn, not typed — a heavier weight and a
     slight tilt is most of that at none of the cost. */
  transform: rotate(-8deg);
}

.bs-points__body { min-width: 0; }
.bs-points__body h3 { margin: 0 0 var(--bt-s1); font-size: var(--bt-fs-prompt); }
.bs-points__body p { margin: 0; font-size: var(--bt-fs-body); color: var(--bt-ink-soft); line-height: 1.45; }

/* ── spotlight ───────────────────────────────────────────────────────── */

.bs-spot { text-align: center; }

.bs-spot__kicker {
  margin: 0 0 var(--bt-s2);
  display: inline-block;
  padding: var(--bt-s1) var(--bt-s4);
  background: var(--bt-gold);
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius-pill);
  font-size: var(--bt-fs-small);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.bs-spot__big {
  margin: var(--bt-s3) 0;
  font-size: clamp(3.4rem, 21vw, 8rem);
  font-weight: 700;
  line-height: 0.95;
  /* Positive, not negative: each character carries a 3px stroke, and tight
     tracking made them run into one another. */
  letter-spacing: 0.03em;
  /* A thick white edge and a soft shadow — the sticker look. White rather than
     dark because the letters sit over a moving blob field: a dark outline
     fights whatever colour drifts behind it, while white always separates the
     word from the page. The shadow is what puts it in front. */
  -webkit-text-stroke: 7px #fff;
  paint-order: stroke fill;
  filter: drop-shadow(0 6px 10px rgb(43 33 24 / 0.28));
}

.bs-spot__line {
  margin: 0;
  display: inline-block;
  padding: var(--bt-s3) var(--bt-s4);
  background: #CFE9EC;
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  font-size: var(--bt-fs-body);
  font-weight: 700;
}

/* ── worked ──────────────────────────────────────────────────────────── */

.bs-worked__title {
  margin: 0 0 var(--bt-s4);
  font-size: clamp(1.3rem, 5.4vw, 1.9rem);
  text-align: center;
}

.bs-worked__rows {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--bt-s2);
  counter-reset: bsstep;
}

.bs-worked__row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--bt-s2) var(--bt-s3);
  padding: var(--bt-s3) var(--bt-s4);
  background: var(--bt-card);
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius-sm);
  font-size: var(--bt-fs-body);
  /* Revealed in turn: the finished working shown all at once is an answer,
     shown in order it is a method. */
  animation: bsStep var(--bt-t-slow) var(--bt-ease) both;
  animation-delay: var(--in, 0s);
}

.bs-worked__row::before {
  counter-increment: bsstep;
  content: counter(bsstep);
  flex: 0 0 auto;
  width: 1.9em;
  height: 1.9em;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--bt-cream);
  border: 2px solid var(--bt-ink);
  font-size: var(--bt-fs-small);
  font-weight: 700;
}

@keyframes bsStep {
  from { opacity: 0; transform: translateX(-10px); }
  to   { opacity: 1; transform: none; }
}

/* The label takes the rest of its line; the answer takes the whole next one.
   Side by side they fought for width and the answer — the part actually worth
   reading — came off worse. */
.bs-worked__what {
  flex: 1 1 60%;
  font-weight: 600;
  color: var(--bt-ink-soft);
}

.bs-worked__is {
  flex: 1 1 100%;
  margin-left: calc(1.9em + var(--bt-s3));
  font-size: var(--bt-fs-prompt);
  line-height: 1.3;
}

/* Bigger than the label, because it is the answer. */
.bs-worked__is { font-weight: 700; color: var(--bt-green); }

.bs-worked__note {
  margin: var(--bt-s4) 0 0;
  font-size: var(--bt-fs-small);
  color: var(--bt-ink-soft);
  text-align: center;
}

/* ── word ────────────────────────────────────────────────────────────── */

.bs-word { display: flex; flex-direction: column; gap: var(--bt-s3); }

/* ── pair ─────────────────────────────────────────────────────────────
   Two pictures and one word — see the `pair` shape in bappa-slides.js.

   The two halves stay SIDE BY SIDE at every width, and get smaller rather
   than stacking. A comparison read one picture above the other is not a
   comparison: the eye has to hold both at once for the gap between them to
   be the thing it notices, and that is the entire method of the card. */
.bs-pair { display: flex; flex-direction: column; gap: var(--bt-s3); }

.bs-pair__pics {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--bt-s3);
  align-items: start;
  width: min(34rem, 88vw);
  align-self: center;
}

.bs-pair__half { margin: 0; display: flex; flex-direction: column; gap: var(--bt-s2); }

.bs-pair__pic {
  aspect-ratio: 1;
  overflow: hidden;
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  background: var(--bt-card);
  box-shadow: 4px 4px 0 var(--bt-gold);
}
.bs-pair__pic > img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The right half is the one the word names; the left is the comparison.
   Without a mark the card is two pictures and a word with no arrow between
   them, so the named half keeps the gold frame and the other steps back. */
.bs-pair__half { opacity: 0.72; }
.bs-pair__half--this { opacity: 1; }
.bs-pair__half:not(.bs-pair__half--this) .bs-pair__pic { box-shadow: none; border-color: var(--bt-ink-soft, #6b5f52); }
.bs-pair__half--this .bs-pair__label { color: var(--bt-green); }

/* Short, and under its own picture. The label is not a sentence — it is the
   one phrase that says what this half IS, so the word can be the difference
   between the two of them. */
.bs-pair__label {
  margin: 0;
  text-align: center;
  font-size: var(--bt-fs-small, 0.85rem);
  font-weight: 700;
  line-height: 1.3;
}

/* Wider screens give the pair more room, the way a single picture gets it. */
@media (min-width: 62em) {
  .bs-pair__pics { width: min(46rem, 56vh * 2); }
}

/* The picture is the lesson while the voice talks, so it gets the room.
   Capped rather than given a ratio: the art is square, and a square that
   fills a phone leaves nothing for the three sentences underneath. The
   height cap is what actually binds on a short landscape phone. */
.bs-word__pic {
  align-self: center;
  width: min(17rem, 44vh);
  aspect-ratio: 1;
  overflow: hidden;
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  background: var(--bt-card);
  box-shadow: 6px 6px 0 var(--bt-gold);
}
.bs-word__pic > img { width: 100%; height: 100%; object-fit: cover; display: block; }

.bs-word__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: center;
  gap: 0 var(--bt-s3);
  text-align: center;
}

.bs-word__word {
  margin: 0;
  font-size: clamp(1.7rem, 7vw, 2.6rem);
  line-height: 1.05;
  color: var(--bt-green);
}

/* Devanagari, and quieter than the word — it is a crutch for the first
   reading, not the thing being learnt. */
.bs-word__sounds {
  margin: 0;
  font-size: var(--bt-fs-body);
  color: var(--bt-ink-soft);
}

/* Held in from the edges. A gloss is one line of small bold text and it was
   running the full width of the phone, which put it hard against both sides
   and made it read as a banner rather than as a note under a word. */
.bs-word__meaning {
  margin: 0 auto;
  max-width: 30ch;
  text-align: center;
  font-size: var(--bt-fs-body);
  font-weight: 700;
}

.bs-word__uses {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--bt-s2);
}

.bs-word__use {
  padding: var(--bt-s2) var(--bt-s3);
  background: var(--bt-card);
  border: 2px solid var(--bt-ink);
  border-radius: var(--bt-radius-sm);
  font-size: var(--bt-fs-body);
  /* In turn, like the worked rows — three sentences arriving together are a
     block to skim, arriving one by one they are three separate uses. */
  animation: bsStep var(--bt-t-slow) var(--bt-ease) both;
  animation-delay: var(--in, 0s);
}

/* Side by side once there is width for it: the picture stops being a hat on
   top of a list and starts being the thing the sentences are about. */
@media (min-width: 62em) {
  /* Five rows for three things. The two 1fr rows are empty and exist only to
     take the slack above and below, which centres the word and its sentences
     against a picture that is now twice their height — without them the text
     sat at the very top of a tall picture with a hand's width of nothing
     under it. */
  .bs-word {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-rows: 1fr auto auto auto 1fr;
    column-gap: var(--bt-s5);
    align-items: start;
  }
  /* Much bigger here than the 20rem it started at. On a desktop the stage is
     775px tall and the whole card was coming out 326 — a 320px picture
     floating in the middle of an empty screen, which is not what a card whose
     whole point is the picture should look like. Bounded by the stage height
     rather than the width now, so it grows into the room that is actually
     there and still never pushes the three sentences off the bottom. */
  .bs-word__pic { grid-column: 1; grid-row: 1 / -1; width: min(36rem, 60vh); align-self: center; }
  .bs-word__head { grid-column: 2; grid-row: 2; justify-content: flex-start; text-align: left; }
  .bs-word__meaning { grid-column: 2; grid-row: 3; margin: 0; max-width: none; text-align: left; }
  .bs-word__uses { grid-column: 2; grid-row: 4; }
}

/* ── contrast ────────────────────────────────────────────────────────── */

.bs-contrast__title {
  margin: 0 0 var(--bt-s4);
  font-size: clamp(1.3rem, 5.4vw, 1.9rem);
  text-align: center;
}

.bs-contrast__pairs {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--bt-s3);
}

.bs-contrast__pair {
  padding: var(--bt-s3) var(--bt-s4);
  background: var(--bt-card);
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius-sm);
  /* One at a time, like worked: two corrections arriving together are a list
     to skim, arriving in turn they are two things that each happened. */
  animation: bsStep var(--bt-t-slow) var(--bt-ease) both;
  animation-delay: var(--in, 0s);
}

/* Smaller, greyer, struck through — recognisable at a glance and not worth
   dwelling on. The mistake is the hook, never the lesson. */
.bs-contrast__wrong {
  margin: 0;
  font-size: var(--bt-fs-body);
  font-weight: 600;
  color: var(--bt-wrong);
  background: var(--bt-wrong-soft);
  padding: var(--bt-s2) var(--bt-s3);
  border-radius: var(--bt-radius-sm);
  /* Struck through in the warm orange, never red. A red line through a
     sentence is the mark on a school book, and that mark is exactly what put
     a lot of these learners off English in the first place. */
  text-decoration: line-through;
  text-decoration-color: var(--bt-wrong);
  text-decoration-thickness: 2px;
}

.bs-contrast__right {
  margin: var(--bt-s2) 0 0;
  font-size: var(--bt-fs-prompt);
  line-height: 1.3;
  font-weight: 700;
  color: var(--bt-right);
}

.bs-contrast__why {
  margin: var(--bt-s2) 0 0;
  font-size: var(--bt-fs-small);
  color: var(--bt-ink-soft);
}

.bs-contrast__note {
  margin: var(--bt-s4) 0 0;
  font-size: var(--bt-fs-small);
  color: var(--bt-ink-soft);
  text-align: center;
}

/* ── say ─────────────────────────────────────────────────────────────── */

.bs-say { text-align: center; }

.bs-say__kicker {
  margin: 0 0 var(--bt-s3);
  font-size: var(--bt-fs-small);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--bt-ink-soft);
}

/* A speech bubble, because the thing inside it is something a person says. */
.bs-say__line {
  position: relative;
  padding: var(--bt-s5) var(--bt-s4);
  background: var(--bt-card);
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  box-shadow: 8px 8px 0 var(--bs-c3);
}

.bs-say__line::after {
  content: '';
  position: absolute;
  left: 2.4rem;
  bottom: -15px;
  width: 26px;
  height: 26px;
  background: var(--bt-card);
  border-right: 3px solid var(--bt-ink);
  border-bottom: 3px solid var(--bt-ink);
  transform: rotate(45deg) skew(6deg, 6deg);
}

.bs-say__text {
  margin: 0;
  font-size: clamp(1.4rem, 5.6vw, 2rem);
  font-weight: 700;
  line-height: 1.25;
}

.bs-say__meaning {
  margin: var(--bt-s5) 0 var(--bt-s4);
  font-size: var(--bt-fs-body);
  color: var(--bt-ink-soft);
  font-weight: 600;
}

.bs-say__again {
  min-height: var(--bt-tap);
  padding: 0 var(--bt-s4);
  font: inherit;
  font-weight: 700;
  color: var(--bt-ink);
  background: var(--bt-card);
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius-pill);
  box-shadow: 4px 4px 0 var(--bt-teal);
  cursor: pointer;
}

.bs-say__again:active { transform: translate(2px, 2px); box-shadow: 2px 2px 0 var(--bt-teal); }

/* ── the way forward ─────────────────────────────────────────────────── */

.bs__bar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--bt-s3);
  padding: var(--bt-s3) var(--bt-s4) calc(var(--bt-s4) + env(safe-area-inset-bottom, 0px));
  background: rgb(255 255 255 / 0.72);
  backdrop-filter: blur(6px);
  border-top: 2px solid var(--bt-line);
}

/* Pause the run-through. Smaller than the two arrows — it is a setting, not
   a way through the lesson — but still a real tap target. */
.bs__auto {
  flex: 0 0 auto;
  width: 2.6rem;
  height: 2.6rem;
  font: inherit;
  font-size: 1rem;
  line-height: 1;
  color: var(--bt-ink);
  background: #fff;
  border: 2px solid var(--bt-line);
  border-radius: 999px;
  cursor: pointer;
}
.bs__auto:active { transform: translateY(1px); }
.bs__step {
  width: var(--bt-tap);
  height: var(--bt-tap);
  flex: 0 0 auto;
  font: inherit;
  font-size: 1.6rem;
  line-height: 1;
  font-weight: 700;
  color: var(--bt-ink);
  background: var(--bt-card);
  border: 3px solid var(--bt-ink);
  border-radius: 50%;
  cursor: pointer;
}

/* Pushed to the right, with the wordmark after it. Back sits next to Bappa on
   the left, so the two things a thumb reaches for are at the far edges. */
.bs__step--next {
  color: #fff;
  background: var(--bt-green);
  border-color: var(--bt-green);
  margin-left: auto;
}

.bs__step:disabled { opacity: 0.3; cursor: default; }
.bs__step:not(:disabled):active { transform: translateY(2px); }

.bs__dots {
  flex: 0 0 auto;
  min-width: 0;
  padding: 0 var(--bt-s3) var(--bt-s2);
  display: flex;
  /* One row, always. With Bappa and the wordmark now in the bar there is less
     room, and wrapping turned the progress into two ragged rows that read as a
     layout fault rather than as progress. A long lesson shrinks its gaps
     instead. */
  flex-wrap: nowrap;
  justify-content: center;
  gap: clamp(3px, 1.2vw, 6px);
  overflow: hidden;
}

.bs__dot { flex: 0 0 auto; }

.bs__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--bt-line);
  transition: background var(--bt-t) var(--bt-ease), transform var(--bt-t) var(--bt-ease);
}

.bs__dot.is-seen { background: var(--bt-saffron); }

.bs__dot.is-now {
  background: var(--bt-green);
  transform: scale(1.5);
}

.bs :focus-visible {
  outline: 3px solid var(--bt-saffron);
  outline-offset: 3px;
}

/* ── picture ─────────────────────────────────────────────────────────────
   One image, large, drifting. The movement is slow enough that nobody watches
   it happen — a still picture reads as a page that has stopped, and the same
   picture breathing reads as something being shown to you. */

.bs-pic { text-align: center; }

.bs-pic__kicker {
  margin: 0 0 var(--bt-s3);
  font-size: var(--bt-fs-small);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--bt-ink-soft);
}

.bs-pic__frame {
  overflow: hidden;
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  background: #fff;
  box-shadow: 8px 8px 0 var(--bs-c3);
}

.bs-pic__frame img {
  display: block;
  width: 100%;
  max-height: 46svh;
  object-fit: cover;
  transform-origin: center;
}

/* Twenty seconds end to end. Any faster and it becomes an effect somebody
   notices instead of a picture they look at. */
.bs-pic__frame.is-in img { animation: bsZoomIn 20s ease-in-out infinite alternate; }
.bs-pic__frame.is-out img { animation: bsZoomOut 20s ease-in-out infinite alternate; }

@keyframes bsZoomIn {
  from { transform: scale(1) translate3d(0, 0, 0); }
  to   { transform: scale(1.12) translate3d(-1.5%, -1%, 0); }
}

@keyframes bsZoomOut {
  from { transform: scale(1.12) translate3d(1.5%, 1%, 0); }
  to   { transform: scale(1) translate3d(0, 0, 0); }
}

.bs-pic__title {
  margin: var(--bt-s4) 0 var(--bt-s2);
  font-size: clamp(1.3rem, 5.4vw, 1.9rem);
}

.bs-pic__line {
  margin: 0;
  font-size: var(--bt-fs-body);
  font-weight: 600;
  color: var(--bt-ink-soft);
}

/* ── who is on the screen with you ───────────────────────────────────────
   Bappa along the bottom and a jaswand hanging into the top corner. Both are
   rows of the frame rather than things floating over it, so a lesson of any
   length can never scroll underneath them. Both are hidden from screen
   readers, because neither says anything. */

.bs__top {
  flex: 0 0 auto;
  display: flex;
  /* Left, and flush. The flower grows in from the corner of the screen with no
     margin at all — a gap would make it look placed rather than growing. */
  justify-content: flex-start;
  align-items: flex-start;
  padding: 0;
  pointer-events: none;
}

.bs__flower {
  /* A quarter of what it was. It is a mark in the corner, not a subject —
     large enough to be recognised as jaswand and small enough that nobody
     looks at it twice. */
  width: clamp(24px, 6vw, 34px);
  /* Flush to the corner, and no further. Pulling it outwards was tried and
     clipped the bloom against the edge of the screen — the stem is already
     drawn running off the top-left of its own box, which is what makes it look
     like it is growing in rather than sitting there. */
  margin: 0;
  transform-origin: 8% 8%;
  /* Hanging from the corner and moving the way a real sprig does — from where
     it is held, not from its middle. */
  animation: bsSway 7s ease-in-out infinite;
}

.bs__flower svg { display: block; width: 100%; height: auto; overflow: visible; }

/* The bloom breathes a little out of step with the stem, which is what stops
   the whole thing reading as one rocking image. */
.bs__flower .bs__fl-bloom {
  transform-box: fill-box;
  transform-origin: center;
  animation: bsBloom 4.5s ease-in-out infinite;
}

@keyframes bsSway {
  0%, 100% { transform: rotate(-2.5deg); }
  50%      { transform: rotate(3deg); }
}

@keyframes bsBloom {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.05); }
}

/* He stands in the bar, and is allowed to rise out of it — a companion tucked
   neatly inside a toolbar looks like an icon, and he is not one. */
.bs__bappa {
  flex: 0 0 auto;
  /* Twice the size he was. He is the teacher, not a badge. */
  width: clamp(116px, 30vw, 156px);
  /* His feet sit on the very bottom edge of the screen, which is what stops
     him looking cut off: the bar's own bottom padding is cancelled out, so
     nothing of him falls past the edge and gets clipped by the frame.
     He rises out of the top of the bar instead, and the stage leaves exactly
     that much room above. */
  align-self: flex-end;
  margin: -4.4rem var(--bt-s1) calc(-1 * (var(--bt-s4) + env(safe-area-inset-bottom, 0px))) 0;
  pointer-events: none;
}

.bs__bappa img { display: block; width: 100%; height: auto; }

/* The wordmark, bottom right. Quiet but always there — this is also the corner
   a Short is watermarked in, so it has to survive full-screen. */
.bs__mark {
  flex: 0 0 auto;
  margin-left: var(--bt-s3);
  width: clamp(52px, 14vw, 74px);
  opacity: 0.72;
  pointer-events: none;
}

.bs__mark img { display: block; width: 100%; height: auto; }

.bs__bappa.is-swap { animation: bsSettle var(--bt-t-slow) var(--bt-ease); }

@keyframes bsSettle {
  from { opacity: 0; transform: translateY(10px) scale(0.94); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .bs-pic__frame img,
  .bs__flower,
  .bs__flower .bs__fl-bloom,
  .bs__bappa.is-swap { animation: none; }
}

/* On a narrow screen the slide owns the middle and the two corners must not
   crowd it, so they shrink and sit further out. */
@media (max-width: 26rem) {
  .bs__bappa { width: 112px; }
  .bs__flower { width: 24px; }
}

/* ── recording ───────────────────────────────────────────────────────────
   `?record` on the lesson. Everything a reader taps disappears; everything
   that IS the lesson stays. Bappa and the wordmark are in the bar, so the bar
   stays too — stripped back to the ground he stands on. */

.bs.is-record .bs__dots,
.bs.is-record .bs__step,
.bs.is-record .bs__auto { display: none; }

.bs.is-record .bs__bar {
  background: none;
  backdrop-filter: none;
  border-top: 0;
  /* Bappa on the left, the wordmark on the right, and nothing between them. */
  justify-content: space-between;
}

/* The stage was leaving room under it for the dots and the buttons. With
   those gone the slide can have that space instead — it is the only thing
   in the frame now. */
.bs.is-record .bs__stage { padding-bottom: calc(var(--bt-s4) + 3.6rem); }

/* ── words ───────────────────────────────────────────────────────────────
   New vocabulary: a picture, and its name beside it. The picture says which
   thing it is; the word is what is being learned. Nothing else on the slide. */

.bs-words { display: flex; flex-direction: column; gap: var(--bt-s3); }

.bs-words__kicker {
  margin: 0;
  font-size: var(--bt-fs-small);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--bt-purple);
}

.bs-words__title { margin: 0; font-size: var(--bt-fs-prompt); }

/* One per row on a phone: a full-width row fits the picture and the whole word
   comfortably, where two narrow columns squeezed "banana" until it clipped.
   Two columns once there is room for them, three on a desktop.

   Explicit breakpoints rather than `auto-fit` with a minimum. auto-fit divides
   whatever width it is given and will happily make five columns on a wide
   screen — which is exactly what it did, at 131px each, cutting the ends off
   half the words. The number of columns is a reading decision, not an
   arithmetic one. */
.bs-words__grid {
  display: grid;
  /* Two columns from the start. Six full-width rows fitted a phone and made
     each picture a thumbnail beside its word — technically fine and visually
     nothing. Two columns halves the width and roughly triples the picture,
     because the picture stops sharing the row and takes the whole cell. */
  grid-template-columns: 1fr 1fr;
  /* Tight, because three rows of these have to fit a phone screen. Each cell
     is a bordered card and reads as separate without help from the gap. */
  gap: var(--bt-s1);
}



.bs-words__item {
  /* The picture on top and the word underneath, rather than side by side. A
     row splits the cell between them and neither gets enough; stacked, the
     picture gets the whole width and the word sits under it where a label
     belongs. */
  display: flex;
  flex-direction: column;
  gap: var(--bt-s1);
  padding: var(--bt-s1);
  background: var(--bt-card);
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  animation: bsBeat var(--bt-t-slow) var(--bt-ease) both;
  animation-delay: var(--in, 0s);
  transition: transform var(--bt-t) var(--bt-ease),
              box-shadow var(--bt-t) var(--bt-ease),
              border-color var(--bt-t) var(--bt-ease),
              background var(--bt-t) var(--bt-ease);
}

/* The one being named right now.
   Six items sitting identically while a voice reads six words leaves the
   listener matching them up by counting; this just points. */
.bs-words__item.is-lit {
  background: var(--bt-lit);
  border-color: var(--bt-green);
  transform: translateY(-3px);
  box-shadow: 0 6px 0 var(--bt-green);
}

/* Eight or more: shorter pictures, so four rows still fit the screen without
   the list scrolling. Everything else about the cell stays the same. */
.bs-words__grid.is-many .bs-words__pic { aspect-ratio: 2 / 1; }
.bs-words__grid.is-many .bs-words__meaning { font-size: 0.78em; }
.bs-words__grid.is-many .bs-words__word { font-size: var(--bt-fs-small); }
/* The footnote under a long list is the first thing to give up its space —
   it is a remark, and the words are the lesson. */
.bs-words:has(.is-many) .bs-words__line { font-size: 0.8em; margin-top: 0; }
.bs-words:has(.is-many) { gap: var(--bt-s2); }

.bs-words__pic {
  /* The whole cell wide. Slightly wider than tall so three rows of these still
     fit a phone screen without the list scrolling. */
  width: 100%;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: calc(var(--bt-radius) - 6px);
  border: 2px solid var(--bt-ink);
}
.bs-words__pic img { width: 100%; height: 100%; object-fit: cover; display: block; }

.bs-words__text { min-width: 0; text-align: center; }

/* The English name is the headline — it is the thing being learned. */
.bs-words__word {
  margin: 0;
  font-size: var(--bt-fs-body);
  font-weight: 700;
  line-height: 1.2;
  /* A long word wraps rather than running out of its box. "banana" losing its
     last letter is not a cosmetic problem on a slide whose entire job is to
     show people how the word is spelled. */
  overflow-wrap: break-word;
}

/* The mother-tongue name underneath, quieter: it is the reassurance, not the
   lesson. The reader already knows this word. */
.bs-words__meaning {
  margin: 0.15em 0 0;
  font-size: var(--bt-fs-small);
  color: var(--bt-ink-soft);
  line-height: 1.25;
}

.bs-words__line {
  margin: 0;
  font-size: var(--bt-fs-small);
  color: var(--bt-ink-soft);
}

/* On a desktop each item turns back on its side: the picture down the left at
   the full height of the cell, the word beside it. A wide screen gives a cell
   far more width than height, and a stacked card there is a tall thin picture
   with acres of white either side of the word underneath it.

   Two columns rather than three, because a horizontal cell needs the width —
   three of them would put us back to a thumbnail beside a label. */
@media (min-width: 62em) {
  .bs-words__grid {
    grid-template-columns: 1fr 1fr;
    gap: var(--bt-s3);
  }

  .bs-words__item {
    flex-direction: row;
    align-items: stretch;
    gap: var(--bt-s3);
    padding: var(--bt-s2);
  }

  .bs-words__pic {
    /* The whole height of the cell, and a fixed share of its width. `stretch`
       rather than a set height so a cell that grows for a long meaning keeps
       its picture the same height as its text. */
    width: 42%;
    flex: 0 0 42%;
    /* Wider than tall, so three rows of these fit the screen. Left to its own
       devices the picture is square, which makes each cell 239px and the list
       overflow by 261 — the picture sets the height of the row, so its shape
       is what decides whether the slide fits at all. */
    aspect-ratio: 4 / 3;
    align-self: stretch;
  }

  .bs-words__text {
    flex: 1 1 auto;
    display: flex;
    flex-direction: column;
    justify-content: center;
    text-align: left;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bs-words__item { animation: none; transition: none; }
  /* The highlight stays — it is how you know which word is being said — but it
     stops moving and says so with colour alone. */
  .bs-words__item.is-lit { transform: none; }
}

/* ── story ───────────────────────────────────────────────────────────────
   Bappa telling what happened to somebody else. Two pictures side by side —
   what was said, and what should have been said — with the explanation running
   underneath as it is spoken.

   The beats still arrive in turn, because the order is the teaching: the
   mistake, then the moment it lands, then the fix. Shown all at once it is a
   paragraph, and a paragraph teaches nobody. */

.bs-story {
  display: flex;
  flex-direction: column;
  gap: var(--bt-s3);
}

/* Always two columns. Never stacked, not even on the narrowest phone: the
   whole slide is a comparison, and a comparison you have to scroll through is
   just two pictures. */
.bs-story__pair {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--bt-s3);
  align-items: start;
}

.bs-story__panel {
  display: flex;
  flex-direction: column;
  gap: var(--bt-s2);
  transition: opacity var(--bt-t-slow) var(--bt-ease),
              transform var(--bt-t-slow) var(--bt-ease);
}

/* Beats hold here until the narration reaches them. `visibility` as well as
   opacity so a screen reader does not read out the answer while the voice is
   still asking the question. */
.bs-story__panel.is-waiting,
.bs-story__strip > p.is-waiting {
  opacity: 0;
  visibility: hidden;
}

.bs-story__panel.is-in,
.bs-story__strip > p.is-in {
  animation: bsBeat var(--bt-t-slow) var(--bt-ease) both;
}

/* The one being talked about right now. Deliberately gentle: this runs for
   twenty seconds at a stretch, and anything stronger would be exhausting
   rather than helpful. */
.bs-story__panel.is-lit {
  transform: translateY(-2px);
  /* The same wash the word list uses, so "this is the one being talked about"
     is one idea across the course rather than a different signal per slide.
     Bled out past the panel: the picture and its caption are separate boxes
     with a gap between them, and a background stopping at each edge would
     look like two lit things rather than one. */
  background: var(--bt-lit);
  box-shadow: 0 0 0 var(--bt-s2) var(--bt-lit);
  border-radius: var(--bt-radius);
}
.bs-story__panel.is-lit .bs-story__frame {
  box-shadow: 0 6px 0 rgb(43 33 24 / 0.16);
}

/* Said and done. Still legible — it is half of a comparison, and a reader
   catching up needs to be able to look back at it. */
.bs-story__panel.is-dim { opacity: 0.62; }

.bs-story__frame {
  overflow: hidden;
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  /* Square, so the two frames match whatever the pictures turn out to be —
     a pair at different heights reads as two unrelated images. */
  aspect-ratio: 1 / 1;
  /* But never taller than the screen can afford. Square alone is a rule about
     width only: on a wide, short window each picture came out 487px tall in a
     702px viewport, which left no room for the caption under it, the
     explanation below that, or the bar — and the slide overflowed.
     The pictures crop rather than the slide overflowing. */
  max-height: 34svh;
  background: var(--bt-card);
}
.bs-story__frame img { width: 100%; height: 100%; object-fit: cover; display: block; }

.bs-story__text {
  margin: 0;
  padding: var(--bt-s3);
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  font-size: var(--bt-fs-small);
  line-height: 1.45;
  font-weight: 600;
}

/* What was actually said. Warm orange rather than red — he is not being told
   off, and neither is the person reading. */
.bs-story__panel--wrong .bs-story__text {
  background: var(--bt-wrong-soft);
  border-color: var(--bt-wrong);
}
.bs-story__panel--wrong .bs-story__frame { border-color: var(--bt-wrong); }

/* The right way, and the last thing left on screen. */
.bs-story__panel--fix .bs-story__text {
  background: var(--bt-right-soft);
  border-color: var(--bt-right);
  font-weight: 700;
  box-shadow: 4px 4px 0 var(--bt-right);
}
.bs-story__panel--fix .bs-story__frame { border-color: var(--bt-right); }

/* The explanation, under the pictures, in the order it is spoken. Kept to a
   readable measure and centred, so widening the slide for the pictures does
   not stretch the sentences to the full width of a desktop. */
.bs-story__strip {
  display: flex;
  flex-direction: column;
  gap: var(--bt-s2);
  width: min(46rem, 100%);
  margin-inline: auto;
}

.bs-story__kicker {
  margin: 0;
  font-size: var(--bt-fs-small);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--bt-purple);
}

.bs-story__strip > p {
  margin: 0;
  padding: var(--bt-s3) var(--bt-s4);
  border: 3px solid var(--bt-ink);
  border-radius: var(--bt-radius);
  font-size: var(--bt-fs-body);
  line-height: 1.5;
}

@keyframes bsBeat {
  from { opacity: 0; transform: translateY(12px); }
  to   { opacity: 1; transform: none; }
}

/* The accidental meaning: the funny beat, when there is one. Set apart because
   it is the payoff and not part of the narration. */
.bs-story__means {
  background: var(--bt-gold);
  font-weight: 700;
  box-shadow: 6px 6px 0 rgb(43 33 24 / 0.14);
}

/* What he meant — plain, because this beat is explanation. */
.bs-story__meant {
  background: var(--bt-card);
  color: var(--bt-ink);
}

@media (prefers-reduced-motion: reduce) {
  .bs-story__panel,
  .bs-story__strip > p { animation: none; transition: none; }
  /* The lift goes; the beats still wait their turn. That pacing is the
     teaching — the mistake, then why, then the fix — not an effect. */
  .bs-story__panel.is-lit { transform: none; }
}

/* ── full-bleed ──────────────────────────────────────────────────────────
   The picture IS the slide.

   Everything before this was a white card with a dark border and a hard
   offset shadow, floating on the blob field — one idea used eight times,
   which is what "templatized and boxy" means when somebody says it. A card
   is right for a list of words. It is wrong for the opening shot and wrong
   for a joke.

   Text sits on a gradient here, never on a panel. A panel is another box; a
   gradient is the picture getting darker so words can live on it. */

.bs-full,
.bs-strip__beat {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

/* The box the picture lives in. Absolute and edge to edge here, so portrait —
   where the picture is full bleed behind the words — behaves exactly as it did
   when the img was the child. Landscape gives it a size of its own. */
.bs-stage {
  position: absolute;
  inset: 0;
  overflow: hidden;
}

.bs-stage > img {
  width: 100%;
  height: 100%;
  /* Portrait screens only. The pictures are square, a phone is about 1:2, so
     cover crops the sides a little and keeps the face full-bleed — which is
     what this shape is for. */
  object-fit: cover;
  display: block;
  /* Panned across, not held still.
   *
   * Every picture in this course is square — measured: 1.00, 1.01, 0.96 — and
   * a square in a portrait phone is cropped hard on the sides. At 375x812 a
   * 1:1 picture renders 812 wide against a 375 frame, so 437px of it, more
   * than half, was never seen by anybody reading on a phone.
   *
   * So it travels from the top left corner to the bottom right over the life
   * of the slide, which shows the whole picture rather than the middle third
   * of it. Where the frame leaves no vertical room — which is the usual case,
   * a square being taller than it is wide here — the vertical half of that
   * simply has nowhere to go and it reads as a straight pan across.
   *
   * `alternate` rather than one-way: a slide can outlast the animation, and a
   * pan that finishes and stops dead draws more attention to itself than one
   * that eases back. */
  object-position: 0% 0%;
  animation: bsPan 22s ease-in-out infinite alternate;
}

/* Deliberately object-position and not a transform. A transform pans within
   what `cover` has ALREADY cropped away, so it moves the picture without ever
   revealing the parts that are missing — which is the entire point here. */
@keyframes bsPan {
  from { object-position: 0% 0%; }
  to   { object-position: 100% 100%; }
}

/* Word cards are held still and shown whole, in both orientations.
 *
 * Everything above is written for a drawn scene, where cropping the sides and
 * drifting across is what stops a still picture sitting dead on the screen.
 * A card this project renders itself is the opposite case: its whole content
 * is one word, set to a measurement with a margin that is the design, and any
 * crop or zoom takes a bite out of the letters. COMPLAINT NO. was reaching
 * phones as OMPLAIN.
 *
 * `contain` so the whole card is in view, and no animation at all — a word
 * does not need to breathe. */
.bs-stage.is-card > img {
  object-fit: contain;
  object-position: center;
  animation: none;
  transform: none;
}

/* A story slide builds all four beats up front and then reveals them in turn,
   so without this the fourth picture has been panning quietly behind the other
   three and is halfway through its sweep by the time anybody sees it. Holding
   the animation off until the beat is on screen restarts it — a beat gets its
   own pan, from its own corner, for as long as it is up.

   Shorter than the opener's too: a beat is a quarter of the narration, and a
   22s sweep inside a 12s beat only ever shows half the picture. */
.bs-strip__beat.is-waiting .bs-stage > img,
.bs-strip__beat.is-gone .bs-stage > img {
  animation: none;
}
.bs-strip__beat .bs-stage > img {
  animation-duration: 13s;
}

/* Dark at the bottom where the words are, clear at the top where the face is.
   Tuned to the pictures rather than to a formula: these are bright daytime
   scenes and a gentle scrim leaves them looking washed out. */
.bs-full__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgb(20 12 6 / 0.94) 0%,
    rgb(20 12 6 / 0.86) 18%,
    rgb(20 12 6 / 0.55) 36%,
    rgb(20 12 6 / 0.14) 56%,
    rgb(20 12 6 / 0) 72%
  );
}

.bs-full__copy,
.bs-strip__cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* Clear of Bappa, who stands in the bar and rises out of it. Measured, not
     guessed: at 4.4rem the last line of the opener sat 47px underneath him.
     He is on the left, so the text also stops short of that corner rather
     than running behind his head. */
  padding: var(--bt-s4) var(--bt-s4) calc(var(--bt-s4) + 7.6rem);
  color: #fff;
}

@media (min-width: 30em) {
  /* Room to sit beside him rather than above him, once there is any. */
  .bs-full__copy,
  .bs-strip__cap {
    padding-left: 9.5rem;
    padding-bottom: calc(var(--bt-s4) + 2.2rem);
  }
}

.bs-full__kicker,
.bs-strip__kicker {
  margin: 0 0 var(--bt-s2);
  font-size: var(--bt-fs-small);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--bt-gold);
}

.bs-full__title {
  margin: 0;
  font-size: clamp(1.6rem, 6.4vw, 2.6rem);
  line-height: 1.12;
  /* Not a border, not a box — just enough weight behind the letters that they
     survive a bright patch in the picture. */
  text-shadow: 0 2px 12px rgb(20 12 6 / 0.75);
}

.bs-full__line {
  margin: var(--bt-s3) 0 0;
  font-size: var(--bt-fs-body);
  color: rgb(255 255 255 / 0.9);
  text-shadow: 0 1px 8px rgb(20 12 6 / 0.7);
}

/* ── the strip ───────────────────────────────────────────────────────────
   Three or four beats, one on screen at a time, each arriving when the voice
   reaches it. Both halves of a joke landing together is why the old side-by-
   side version was not funny. */

.bs-strip { position: static; }

.bs-strip__kicker {
  position: absolute;
  top: 0;
  left: var(--bt-s4);
  z-index: 3;
  margin: var(--bt-s4) 0 0;
  text-shadow: 0 1px 8px rgb(20 12 6 / 0.8);
}

.bs-strip__beat {
  opacity: 1;
  transition: opacity var(--bt-t-slow) var(--bt-ease);
}

/* Not yet reached, and already gone. Both are invisible; they are separate
   states because a beat that has passed must not animate back in if the
   reader steps backwards through the lesson. */
.bs-strip__beat.is-waiting,
.bs-strip__beat.is-gone {
  opacity: 0;
  visibility: hidden;
}

/* A beat with no picture — the explaining line between two images. It keeps
   the frame dark so the strip does not flash white in the middle of a joke. */
.bs-strip__beat--words {
  background: linear-gradient(160deg, #2B2118, #4A3524);
}
.bs-strip__beat--words .bs-strip__cap { bottom: auto; top: 50%; transform: translateY(-50%); }

.bs-strip__cap p {
  margin: 0;
  font-size: clamp(1.15rem, 4.6vw, 1.7rem);
  font-weight: 700;
  line-height: 1.35;
  text-shadow: 0 2px 12px rgb(20 12 6 / 0.8);
}

/* The stage centres and pads its slide, which is right for a card and wrong
   for a picture that should reach the edges. */
/* A full-bleed slide is positioned itself rather than leaving that to the
   shape inside it.

   The shape used to be `position: absolute; inset: 0` and resolve against the
   frame — except the slide animates `transform` on entry, and a transform
   makes an element a containing block for its absolutely positioned children.
   So `inset: 0` resolved against the slide's own 1373x280 box and the picture
   was 280px tall in an 800px window, letterboxed by accident.

   The entrance animation goes with it: these shapes have their own slow hold
   on the picture, and sliding the whole screen in from the right is a card
   gesture — wrong for something that fills the screen. */
.bs__slide:has(.bs-full),
.bs__slide:has(.bs-strip) {
  position: absolute;
  inset: 0;
  width: auto;
  gap: 0;
  animation: none;
}

/* Landscape is a different problem, and cover is the wrong answer to it.
   A square picture covering a 2000x1000 window is scaled past twice its size
   and two thirds of it is thrown away — on the opener that left a nose and a
   waistcoat filling the screen.

   Fitted to the HEIGHT instead: the whole picture is visible, and the blob
   field it was always sitting on shows either side. Keyed to orientation
   rather than to width, because that is the actual condition — a short wide
   window has the problem whatever its pixel count. */
/* ── landscape ───────────────────────────────────────────────────────────
   Side by side: the picture on the left, the words on the right.

   Text over the bottom of the picture is right on a phone and wrong here. A
   landscape frame is short, so the caption sits low — which is exactly where
   Bappa and the bar live, and the words disappeared behind them. Meanwhile
   the picture is square and the screen is wide, so there was empty blob field
   either side of it doing nothing.

   Splitting it solves both at once: the picture gets a column, the words get
   the column that was already empty, and nothing is over anything. */
@media (orientation: landscape) {
  .bs-full,
  .bs-strip__beat {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    /* Stretch, not centre. The picture's box is then exactly one grid row
       tall — the stage less the padding above and below — which is how its
       height gets calculated instead of guessed. */
    align-items: stretch;
    gap: clamp(1.25rem, 3.5vw, 3.5rem);
    /* A tenth either side, so the slide sits in the page rather than running
       off both edges of it.

       The foot only has to clear Bappa. The stage already stops above the dots
       and the bar — measured on a 1440x900 window, the stage is 733px ending
       at y=767 while the bar starts at 783 — and the one thing that reaches
       INTO it is Bappa himself, who is 147px wide in the bottom left corner
       and rises 43px past the top of the bar. The old 9rem was reserving room
       for furniture that was never in the way. */
    /* Measured on the running lesson at 1440x900 rather than estimated: the
       header above the slide area is 34px, and the dots and the bar together
       take 153px off the foot. The slide itself is inset:0 on the whole frame,
       so it has to account for both or it lays out behind them. */
    --bs-head: 2.6rem;   /* 42px — the 34px header, plus air */
    --bs-foot: 10.2rem;  /* 163px — the dots and the bar, plus air */
    grid-template-rows: minmax(0, 1fr);
    padding: var(--bs-head) 10% var(--bs-foot) 10%;
  }

  /* The strip's chapter badge is a sibling of the beats, not part of any one
     of them, so it stays out of the flow — lined up with the left edge of the
     picture, in the space the top padding leaves. */
  .bs-strip__beat { padding-top: 4rem; }

  /* ── the picture ──────────────────────────────────────────────────────
     A box that never changes size, with the picture moving inside it.

     It used to be the img itself in the grid, which meant a tall picture and
     a wide one produced different column heights and the words beside them
     jumped. Now the box is the row and the drift happens within it. */
  .bs-stage {
    position: relative;
    inset: auto;
    /* Exactly the space the window leaves: the row, which is the frame less
       the header and less the dots and bar. A function of the viewport and
       nothing else — not of how long the sentence beside it happens to be,
       which is what used to size it.

       An aspect-ratio was tried here and is the wrong tool: with an explicit
       height it is simply ignored, and the box measured 551x705 while claiming
       to be square. The picture is centred inside instead, and the tinted card
       it sits on reads as a frame rather than as a gap. */
    height: 100%;
    width: 100%;
    min-height: 0;
    border-radius: var(--bt-radius);
    overflow: hidden;
    background: rgb(255 255 255 / 0.55);
    box-shadow: 0 8px 0 rgb(43 33 24 / 0.10);
  }

  .bs-stage > img {
    /* The whole picture, never cropped by the box. */
    object-fit: contain;
    object-position: center;
    height: 100%;
    width: 100%;
    border-radius: 0;
    /* Breathing rather than a one-way drift: it runs for the length of a
       narration, and a scale that only ever grows ends the slide zoomed in. */
    animation: bsBreathe 26s ease-in-out infinite alternate;
  }

  /* And not for a word card. `contain` already fits it, but a 9% breath then
     scales it back out past the edges of a box that clips — which is enough to
     eat a letter off each end of a long word. */
  .bs-stage.is-card > img {
    animation: none;
    transform: none;
  }

  /* The picture no longer sits under the words, so nothing needs darkening. */
  .bs-full__scrim { display: none; }

  /* ── the words ────────────────────────────────────────────────────────
     Speech bubbles, because somebody is talking to you. The main line in a
     white one and whatever qualifies it in a quieter one underneath, both
     with the course's own ink outline and hard shadow rather than the soft
     grey of a UI card. */
  .bs-full__copy,
  .bs-strip__cap {
    position: static;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: flex-start;
    gap: clamp(0.55rem, 1.6vh, 1.05rem);
    padding: 0;
    color: var(--bt-ink);
    min-width: 0;
  }

  /* ── the chapter badge ───────────────────────────────────────────────── */
  .bs-full__kicker,
  .bs-strip__kicker {
    margin: 0;
    padding: 0.4em 1.05em;
    background: var(--bt-saffron);
    color: #fff;
    border: 3px solid var(--bt-ink);
    border-radius: 999px;
    box-shadow: 3px 3px 0 rgb(43 33 24 / 0.22);
    font-size: clamp(0.8rem, 1.1vw, 1.15rem);
    font-weight: 800;
    letter-spacing: 0.045em;
    text-shadow: none;
    white-space: nowrap;
  }

  .bs-strip__kicker {
    position: absolute;
    top: clamp(0.6rem, 2.2vh, 1.4rem);
    left: 10%;
    z-index: 2;
  }

  /* ── the main bubble ─────────────────────────────────────────────────── */
  .bs-full__title,
  .bs-strip__cap p {
    position: relative;
    margin: 0;
    padding: clamp(0.85rem, 2.2vh, 1.5rem) clamp(1rem, 1.8vw, 1.7rem);
    background: var(--bt-card);
    border: 4px solid var(--bt-ink);
    border-radius: 26px;
    box-shadow: 8px 8px 0 var(--bt-sky);
    /* Sized to the window rather than fixed: this is read on a laptop and on
       a 27-inch screen, and 1.3rem was a caption on both. */
    font-size: clamp(1.25rem, 2.2vw, 2.4rem);
    line-height: 1.34;
    font-weight: 800;
    text-shadow: none;
    max-width: 100%;
  }

  /* The tail, pointing back at the picture it belongs to. Two triangles: the
     outline behind, the fill a notch in front of it. */
  .bs-full__title::before,
  .bs-strip__cap p::before,
  .bs-full__title::after,
  .bs-strip__cap p::after {
    content: '';
    position: absolute;
    top: clamp(1.1rem, 3vh, 2rem);
    width: 0;
    height: 0;
    border: 13px solid transparent;
  }
  .bs-full__title::before,
  .bs-strip__cap p::before { left: -26px; border-right-color: var(--bt-ink); }
  .bs-full__title::after,
  .bs-strip__cap p::after  { left: -19px; border-right-color: var(--bt-card); }

  /* ── the quieter bubble underneath ───────────────────────────────────── */
  .bs-full__line {
    position: relative;
    margin: 0;
    padding: clamp(0.6rem, 1.6vh, 1.05rem) clamp(0.9rem, 1.5vw, 1.4rem);
    background: #F1E9FF;
    border: 3px solid var(--bt-purple);
    border-radius: 20px;
    box-shadow: 5px 5px 0 rgb(126 87 194 / 0.3);
    color: var(--bt-ink);
    font-size: clamp(1rem, 1.45vw, 1.5rem);
    line-height: 1.42;
    font-weight: 600;
    text-shadow: none;
    max-width: 100%;
  }
}

@keyframes bsBreathe {
  from { transform: scale(1); }
  to   { transform: scale(1.09); }
}

@media (prefers-reduced-motion: reduce) {
  .bs-stage > img { animation: none; }
  .bs-strip__beat { transition: none; }
}

/* ── story mode ───────────────────────────────────────────────────────────
   The mind course: one picture filling the screen, one line of narration
   along the bottom. The voice carries the story; the line on screen is for
   the person who cannot hear it, and it is set like a subtitle rather than
   like a caption under a photograph — centred, short, and out of the way.

   Switched on by `&layout=story` on the player's address. Everything else in
   this file is untouched by it. */

.bs-story .bs-stage > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* The drift, moved onto the stage itself so it works with object-fit: cover.
   Slower than the lesson's twenty seconds — a story slide is held while a
   sentence is spoken, and a faster move would be finished before the voice. */
.bs-story .bs-stage.is-in > img { animation: bsStoryIn 26s ease-in-out infinite alternate; }
.bs-story .bs-stage.is-out > img { animation: bsStoryOut 26s ease-in-out infinite alternate; }

@keyframes bsStoryIn {
  from { transform: scale(1.02); }
  to   { transform: scale(1.11); }
}
@keyframes bsStoryOut {
  from { transform: scale(1.11); }
  to   { transform: scale(1.02); }
}

@media (prefers-reduced-motion: reduce) {
  .bs-story .bs-stage > img { animation: none; }
}

/* Darker and shallower than the opener's scrim. Shallower because there is no
   title to clear, only two or three lines; darker because those lines have to
   survive whatever is under them for twenty slides running, and a story that
   needs squinting at once is a story nobody finishes. */
.bs-story__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(
    to top,
    rgb(12 8 4 / 0.92) 0%,
    rgb(12 8 4 / 0.80) 12%,
    rgb(12 8 4 / 0.42) 26%,
    rgb(12 8 4 / 0.10) 40%,
    rgb(12 8 4 / 0) 52%
  );
}

.bs-story__cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* Clear of Bappa, who stands in the bottom bar. Same allowance the opener
     makes, for the same reason. */
  padding: var(--bt-s4) var(--bt-s4) calc(var(--bt-s4) + 7.6rem);
  color: #fff;
  text-align: center;
}

@media (min-width: 30em) {
  .bs-story__cap {
    /* Bappa stands in the bottom-left corner, so the text is kept off him on
       that side and off the same distance on the other, which puts the middle
       of a centred line in the middle of the space that is actually free. */
    padding-left: 10.5rem;
    padding-right: 10.5rem;
    /* The dots and the bar together, measured by the landscape rule above and
       reused rather than guessed again. 2.4rem put the third line of a Marathi
       caption behind the bar. */
    padding-bottom: 10.6rem;
  }
}

/* A measure, not the full width. Narration runs long in Marathi and Hindi and
   a line that crosses the whole of a wide screen is read twice or not at all. */
.bs-story__line {
  margin: 0 auto;
  max-width: 44ch;
  font-size: clamp(1.05rem, 2.5vw, 1.45rem);
  line-height: 1.5;
  text-wrap: pretty;
  text-shadow: 0 2px 14px rgb(12 8 4 / 0.9), 0 1px 3px rgb(12 8 4 / 0.8);
}

/* Wider measure once there is room for one, but still a measure. */
@media (min-width: 30em) {
  .bs-story__line { max-width: 52ch; }
}

/* Story mode opts out of the landscape split.
   
   The rule above splits `.bs-full` into two columns in landscape, and the
   comment on it is right: a SQUARE picture covering a wide window throws two
   thirds of itself away. That reasoning is about square pictures, which is
   what every picture on this site was when it was written.

   A story is the case it did not have. It is watched rather than read, it has
   no title to place beside the picture, and a picture in a column with an
   empty half beside it is a slide — which is the one thing this shape exists
   not to be. So the split is switched off here and the picture takes the
   screen, which is only correct because the mind course's pictures are drawn
   16:9 rather than square. Draw them square and this crops them in half. */
@media (orientation: landscape) {
  .bs-story {
    display: block;
    padding: 0;
  }

  .bs-story .bs-stage {
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
    border-radius: 0;
    background: none;
    box-shadow: none;
  }

  .bs-story .bs-stage > img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center;
  }
}
