/* ===========================================================================
   Wedding e-invite, FRIENDS' EDITION — mobile-first, sent as a WhatsApp link.

   A fork of ../../site/styles.css. Two things came out and almost nothing
   went in:

   1. Tamil. No --tamil family, no .tamil-text overrides, no .om / .kural /
      .invocation / .lineage / .hosts-grid roles, no #s-tamil-* rules.

   2. THE ENTIRE TALL-PAGE SYSTEM. The live edition has two scenes taller than
      the viewport, and nearly a third of its CSS exists to serve them: the
      .scene--tall block, twelve `calc(var(--col) * r)` type rules that restore
      the phone type/art ratio above phone width, and a `min-width: 520px`
      override that rests copy on the base of the arch so a laptop cannot scroll
      into a screenful of empty stretch. Here every scene is exactly one
      viewport, so none of it applies -- and neither can the two bugs recorded
      in ../../docs/liveliness-backlog.md, both of which were failures of that
      system (an IntersectionObserver ratio threshold a too-tall target can
      never reach, and the dead-stretch problem above 520px).

   Layout note, unchanged from the live edition: the interior artwork is
   pre-extended to 1024x2220 (0.461) by tools/build-assets.py, so it already
   matches a phone viewport and needs no crop. The cover is NOT extended -- its
   ornate frame is the point -- so it is presented as a contained card.
   =========================================================================== */

:root {
  /* Ink is the cover's own navy sky, sampled from the artwork. Measures
     8.1-10.8:1 against all three washes. */
  --ink:          #162b3d;
  --ink-soft:     #3a5068;
  --paper-cover:  #f2e4ce;
  --paper-closing:#d5d6c7;

  --serif: "Cormorant Garamond", Cormorant, "Iowan Old Style", Georgia, serif;

  /* Fraction of scene height that stays clear of artwork detail. The build
     script publishes the measured value per artwork as --clear-*; this is the
     fallback for scenes that do not set their own. */
  --safe: 52%;

  --dur: 700ms;
  --ease: cubic-bezier(.22,.61,.36,1);
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  height: 100%;
  background: var(--paper-cover);
  color: var(--ink);
  font-family: var(--serif);
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

/* --- deck ---------------------------------------------------------------- */
/* NB: never put overflow:hidden on body -- it breaks scroll-snap in the
   WhatsApp in-app browser. The deck owns the scrolling. */
.deck {
  height: 100vh;
  height: calc(var(--vh, 1vh) * 100);
  height: 100dvh;
  overflow-y: auto;
  overflow-x: hidden;
  /* The live edition needs `proximity` because two of its scenes are taller
     than the viewport and `mandatory` fights a mid-scene scroll. Every scene
     here is exactly one viewport, so snapping can be mandatory: the deck now
     always comes to rest on a composed page rather than between two. */
  scroll-snap-type: y mandatory;
  overscroll-behavior-y: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.deck::-webkit-scrollbar { display: none; }

.scene {
  position: relative;
  height: 100vh;
  height: calc(var(--vh, 1vh) * 100);
  height: 100dvh;
  scroll-snap-align: start;
  overflow: hidden;
  display: grid;
  /* Beyond phone width the artwork would distort, so the deck becomes a
     centred column on tablet/desktop. The cap only ever bites ABOVE phone
     width -- min(100vw, N) is 100vw on every phone -- so it is a desktop knob
     and changing it cannot affect a phone.

     600px is not arbitrary: on a 1512x900 desktop the venue scene (the one
     carrying a QR plus the actions) ends 77.2% down the artwork against a
     foliage skyline at 78.9%. Wider pushes that copy onto the planting. */
  --col: min(100vw, 600px);
}

/* Paper grounds, sampled from each built image by the build script, so a scene
   is the right colour before its artwork has painted. */
.scene--cover { background: var(--paper-cover); }
#s-english    { background: var(--paper-english-sampled); }
#s-reception,
#s-venue,
#s-closing    { background: var(--paper-closing-sampled); }

/* The English artwork's detail begins lower than the closing artwork's, so it
   gets its own measured clear zone rather than the 52% default. */
#s-english { --safe: var(--clear-english); }

.scene__art {
  position: absolute;
  inset: 0;
  width: var(--col);
  margin-inline: auto;
  overflow: hidden;
}

.scene__art img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center bottom;
  display: block;
  /* Oversized so parallax can translate without exposing an edge. */
  transform: scale(calc(1.08 * var(--enter, 1.035))) translate3d(0, calc(var(--par, 0%) * var(--bg-depth, 1)), 0) scaleX(var(--flip, 1));
  transition: transform 1500ms cubic-bezier(.16,.8,.3,1);
  background-size: cover;
  background-position: center bottom;
}

/* Promote what moves. In the live edition this excluded the tall pages, which
   take no parallax; here every interior scene does, so every one is promoted. */
.scene__art img,
.scene--cover .card img { will-change: transform; }

/* Scenes 3 and 4 reuse the closing artwork mirrored: the composition is
   near-symmetric, so it reads as a companion page at zero extra bytes. */
.scene--mirror { --flip: -1; }

/* --- depth layer ---------------------------------------------------------
   The near planting, matted out of the closing artwork by the live build
   script and laid back over its own inpainted base. It travels on the same
   --par the base uses, multiplied by a larger depth, and THAT DIFFERENCE is
   the parallax -- real separation rather than the simulated kind flat art is
   limited to.

   The two depths are chosen so the larger excursion still lands inside the 4%
   of overscan the 1.08 scale provides. Exceed that and the layer's own edge
   walks into view, which is unrecoverable. */
/* NB the selector: .scene__fg is itself an <img> inside .scene__art, so the
   plain `.scene__art img` rule above (0,1,1) outranks a bare `.scene__fg`
   (0,1,0) and the layer silently inherits the BACKGROUND depth -- which zeroes
   the differential and makes the whole layer pointless while looking fine. */
.scene__art img.scene__fg {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; object-position: center bottom;
  display: block; z-index: 1; pointer-events: none;
  transform: scale(calc(1.08 * var(--enter, 1.035))) translate3d(0, calc(var(--par, 0%) * var(--fg-depth, 1)), 0) scaleX(var(--flip, 1));
  transition: transform 1500ms cubic-bezier(.16,.8,.3,1);
  will-change: transform;
}

/* Only the closing artwork is layered -- the cover was tried and rejected (its
   peacock straddles the pond, so no colour rule cuts it cleanly), and the
   English page is a flat wash with no near planting to separate. */
#s-reception, #s-venue, #s-closing { --bg-depth: .55; --fg-depth: .92; }

/* --- cover --------------------------------------------------------------- */
/* The scroll cue is position:absolute, so .cover-stack's margin:auto happily
   centres the artwork straight through it -- on a 320x568 phone the date line
   overlapped the cue by 30px. Reserve the band the cue actually occupies
   (its own offset plus its height) so the auto-centring happens in the space
   that is really free. Tall phones have slack and simply centre a little
   higher; short phones shrink the card a few px instead of colliding. */
.scene--cover {
  padding-bottom: calc(max(3.4vh, env(safe-area-inset-bottom)) + 3.2rem);
  /* .cover-stack caps itself with max-height:100%, but an implicit grid row
     sizes to its own content -- so that 100% resolved to the stack's own
     height and constrained nothing. Pin the row to the free space and the cap
     becomes real, which is what lets the card shrink instead of overflow. */
  grid-template-rows: minmax(0, 1fr);
}

.cover-stack {
  margin: auto;
  width: var(--col);
  display: flex;
  flex-direction: column;
  max-height: 100%;
}

.scene--cover .card {
  position: relative;
  width: 100%;
  aspect-ratio: 1024 / 1536;
  flex: 0 1 auto;
  min-height: 0;
}

.scene--cover .card img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  display: block;
  /* Starts under 1 so the slow breathe never overflows and clips the frame. */
  transform: scale(calc(var(--breathe, .98) * var(--enter, 1.02)));
}

/* Anchored to the cover's clear navy, measured off the artwork rather than
   guessed. The band is tighter than it looks: the scalloped crown cuts in from
   above and the flowering tree's blossoms come up from below, so across the
   eyebrow's own x-range (34.5%-65.5%) the navy only runs from ~8.6% down to
   ~28.9%.

   So the box IS the measured band (8.6% -> 28.6%) and the title centres itself
   inside it, rather than being positioned by a number that has to be re-guessed
   whenever the type changes. */
.cover-title {
  position: absolute;
  top: 8.6%; height: 20%; left: 9%; right: 9%;
  display: flex; flex-direction: column; justify-content: center;
  color: var(--paper-cover);
  text-align: center;
}
.cover-title .eyebrow { color: rgba(242,228,206,.72); margin: 10px 0 .7em; letter-spacing: .24em; }
.cover-title .names   { font-size: clamp(1.5rem, 7.2vw, 2.15rem); }

.cover-foot {
  flex: 0 0 auto;
  padding: 1.1em 10% 0;
  text-align: center;
}
.cover-foot .meta { margin: 0; }

/* --- text ---------------------------------------------------------------- */
/* Centred within the arch's clear zone rather than top-aligned, so short and
   long copy both sit correctly inside the arch instead of riding its crown. */
.scene__text {
  position: relative;
  z-index: 2;
  width: var(--col);
  margin-inline: auto;
  /* Against --col, never against the containing block. Percentage padding
     resolves against the containing block's width, and this element's
     containing block is the full-width scene, not the column it is drawn in.
     On a phone those are identical, which is exactly why this survived. */
  padding: calc(var(--col) * .07) calc(var(--col) * .11);
  height: var(--safe);
  /* --safe is a fraction of the SCENE, and a scene is one viewport tall -- so
     the box shrinks with viewport HEIGHT while the type does not. Above phone
     width every font-size has hit the rem ceiling of its clamp, so on a
     1280x720 laptop the copy is phone-sized in a box 50px shorter than a
     phone's, and `overflow: hidden` silently ate the last line of the
     invitation and of the reception details. (The live edition has the same
     failure on its venue scene, which clips by 18px at this height.)

     min-content lets the box grow by exactly the overflow and no more: on a
     phone the copy already fits, so this resolves to the same 53.6% and
     nothing moves. It is a floor, not a new layout. */
  min-height: min-content;
  display: flex;
  flex-direction: column;
  justify-content: center;
  text-align: center;
  overflow: hidden;
}

.scene--closing { --safe: 44%; }

/* The venue scene carries a QR plus the actions -- more than the closing art's
   44.9% clear zone holds on a short phone. It reaches a little further down,
   into the pale mandapam band where ink still measures ~7:1. */
#s-venue .scene__text { height: 67%; }

.eyebrow {
  font-size: clamp(.62rem, 2.9vw, .78rem);
  letter-spacing: .34em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 1.1em;
}

.names {
  font-size: clamp(2.1rem, 11vw, 3.4rem);
  font-weight: 300;
  line-height: 1.06;
  letter-spacing: .01em;
  margin: 0;
}

.title {
  font-size: clamp(1.7rem, 8.4vw, 2.5rem);
  font-weight: 500; line-height: 1.14; margin: 0 0 .9em;
}
.title--sm { font-size: clamp(1.3rem, 6vw, 1.7rem); letter-spacing: .16em; text-transform: uppercase; }

/* The ampersand's margins are the only slack in the cover title. The block has
   to clear the scalloped crown above (navy starts ~8.6% across the title's own
   x-range) AND the flowering tree's blossoms below (~28.6%), and the title is
   taller than the gap between them. Tightening the connector by .3em each side
   buys back ~1% of card height, which is what lets the whole block sit lower
   without "Gowri" landing in the blossoms. */
.amp {
  display: block;
  font-size: .42em;
  font-style: italic;
  letter-spacing: .1em;
  margin: .2em 0;
  opacity: .8;
}

/* On the invitation page the names are set at .title size, where stacking them
   over three lines would eat the clear zone the dates and venue need. Same
   italic connector, run inline. */
.amp--inline {
  display: inline;
  font-size: .62em;
  margin: 0 .16em;
  vertical-align: .04em;
}

.line   { font-size: clamp(1rem, 4.4vw, 1.22rem); line-height: 1.62; margin: 0 0 .85em; font-weight: 300; }
.strong { font-size: clamp(1.24rem, 5.6vw, 1.6rem); line-height: 1.34; margin: .3em 0 .5em; font-weight: 400; }
.meta   { font-size: clamp(.84rem, 3.5vw, .96rem); letter-spacing: .05em; color: var(--ink-soft); margin: 0 0 .5em; }

/* Ornamental divider, replacing a plain 1px rule. */
.rule { margin: 1.4em auto; line-height: 0; }
.divider {
  width: clamp(96px, 34vw, 132px); height: auto;
  fill: currentColor; stroke: currentColor;
  stroke-width: .9; opacity: .34;
}
.divider path:first-child { fill: none; }
.divider path + path { stroke: none; }

/* Orphan control. Balancing the lines prevents a year, a unit or a postcode
   being left alone on a line at every width, and is ignored harmlessly by
   browsers that do not support it. It does not cross a <br>, so the
   non-breaking spaces in index.html do the rest of the work. */
.line, .strong, .meta, .cover-foot .meta { text-wrap: balance; }

/* --- actions ------------------------------------------------------------- */
.actions { display: flex; flex-direction: column; gap: .7rem; align-items: center; margin-top: 1.2em; }

.btn {
  display: inline-flex; align-items: center; gap: .5em;
  padding: .74em 1.5em;
  font-family: var(--serif);
  font-size: clamp(.9rem, 3.8vw, 1rem);
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink); text-decoration: none;
  border: 1px solid currentColor; border-radius: 999px;
  background: rgba(255,255,255,.42);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  transition: background .25s var(--ease), transform .25s var(--ease);
}
.btn:active { transform: scale(.97); background: rgba(255,255,255,.62); }
.btn--quiet { border-color: transparent; background: transparent; opacity: .78; text-transform: none; letter-spacing: .05em; }

/* QR must stay crisp and high-contrast or it will not scan off a screen.
   Bounded by viewport HEIGHT as well as width: on a short phone (iPhone SE,
   375x667) a width-only cap overflowed the clear zone by ~90px, and the dvh
   term is what keeps the venue scene inside its zone on a short laptop window.
   Never drops under the ~170px floor for scanning off a screen, and it is
   tappable regardless. */
a.qr { display: block; text-decoration: none; }
.qr {
  width: min(52vw, 26dvh, 216px); aspect-ratio: 1; margin: .2em auto .6em;
  padding: 12px; background: #fff; border-radius: 6px;
  box-shadow: 0 1px 12px rgba(22,43,61,.14);
}
.qr img {
  width: 100%; height: 100%; display: block;
  /* NOT image-rendering: pixelated -- the source is 640px scaled down into a
     ~195px card, and nearest-neighbour downscaling drops whole dot modules.
     Smooth downscaling is what keeps the code readable. */
  image-rendering: auto;
}

/* --- scroll cue ---------------------------------------------------------- */
.cue {
  position: absolute; left: 0; right: 0; bottom: max(3.4vh, env(safe-area-inset-bottom));
  z-index: 3; text-align: center;
  font-size: .62rem; letter-spacing: .3em; text-transform: uppercase;
  color: var(--ink-soft); opacity: .6;
  animation: drift 2.8s var(--ease) infinite;
}
.cue span { display: block; margin-top: .5em; font-size: 1rem; letter-spacing: 0; }
@keyframes drift { 0%,100% { transform: translateY(0); } 50% { transform: translateY(5px); } }

/* --- water shimmer ------------------------------------------------------- */
/* Two sweeps, not one. A single travelling band reads as a bar crossing a
   photograph; two at different angles and coprime periods (13s / 21s, so the
   pair does not visibly repeat for minutes) interfere, and interference is what
   water actually looks like. Both are deliberately near-invisible.

   --pond-x insets the band horizontally. The interior pages run their pond to
   the full bleed, but the cover's pond sits inside an ornate cream frame and a
   sweep must never cross onto it. */
.shimmer {
  position: absolute;
  left: var(--pond-x, 0); right: var(--pond-x, 0);
  top: var(--pond-top, 70%); height: var(--pond-h, 12%);
  z-index: 1; pointer-events: none; opacity: .042;
  background: linear-gradient(100deg, transparent 30%, #fff 50%, transparent 70%);
  background-size: 260% 100%;
  animation: sweep 13s linear infinite;
  /* Feather the band's top and bottom, the way .iridesce feathers its
     rectangle. Without this the sweep is a hard-edged strip: invisible at
     these opacities on the artwork, but it is the only hard geometry in the
     whole effect, and water has no straight edges. */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 22%, #000 78%, transparent);
          mask-image: linear-gradient(to bottom, transparent, #000 22%, #000 78%, transparent);
}
@keyframes sweep { from { background-position: 130% 0; } to { background-position: -130% 0; } }

/* The counter-sweep: shallower angle, longer period, opposite direction. */
.shimmer--slow {
  opacity: .028;
  background: linear-gradient(84deg, transparent 34%, #fff 50%, transparent 66%);
  background-size: 320% 100%;
  animation: sweep-slow 21s linear infinite;
}
@keyframes sweep-slow { from { background-position: -140% 0; } to { background-position: 140% 0; } }

/* --- countdown ----------------------------------------------------------- */
/* Whole days, set as an aside under the date -- not a second fact competing
   with it, and never a ticking clock. Empty until main.js fills it, and empty
   is invisible: no JS, no line, no reserved space. */
.countdown {
  font-style: italic;
  letter-spacing: .08em;
  opacity: .72;
  margin-top: .35em;
}
.countdown:empty { display: none; }

/* --- reveal -------------------------------------------------------------- */
.reveal > * { opacity: 0; transform: translateY(12px); }
.is-visible .reveal > * {
  opacity: 1; transform: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  transition-delay: calc(min(var(--i, 0), 6) * 110ms);
}

/* ===========================================================================
   Arch opens
   Each scene's artwork settles the last few percent as you arrive -- the
   mandapam as a gateway you step through rather than a picture that is simply
   there. One variable folded into the transforms that already exist, so it
   costs no extra compositing.
   =========================================================================== */
.scene.is-visible,
.scene--cover.is-visible { --enter: 1; }

/* ===========================================================================
   Peacock iridescence
   A slow band of warm light travelling across the tail, the way the eyes catch
   the sun. Masked to the bird's own rectangle -- hand-tuned against each built
   image, NOT computed by the build script -- so it needs no layer separation.
   If the artwork is ever re-cropped these four numbers must be re-checked by
   eye; nothing will fail loudly.
   Long rest between passes: it should be caught out of the corner of the eye.
   =========================================================================== */
.iridesce {
  position: absolute;
  left: var(--pk-x0); top: var(--pk-y0);
  width: calc(var(--pk-x1) - var(--pk-x0));
  height: calc(var(--pk-y1) - var(--pk-y0));
  z-index: 1;
  pointer-events: none;
  mix-blend-mode: screen;
  opacity: 0;
  background: linear-gradient(102deg,
    transparent 40%, rgba(255,240,205,.9) 50%, transparent 60%);
  background-size: 280% 100%;
  background-repeat: no-repeat;
  /* feather the rectangle away so no edge of the mask is ever visible */
  -webkit-mask-image: radial-gradient(ellipse 62% 58% at 50% 50%, #000 30%, transparent 100%);
          mask-image: radial-gradient(ellipse 62% 58% at 50% 50%, #000 30%, transparent 100%);
  animation: iridesce 15s ease-in-out infinite;
}

@keyframes iridesce {
  0%   { background-position: 150% 0; opacity: 0; }
  6%   { opacity: .55; }
  26%  { opacity: .55; }
  32%  { background-position: -60% 0; opacity: 0; }
  100% { background-position: -60% 0; opacity: 0; }
}

/* Peacock rectangles, as a fraction of each artwork. The English page has no
   peacock and therefore no .iridesce element. */
#s-cover     { --pk-x0: 44%; --pk-x1: 74%; --pk-y0: 72%; --pk-y1: 87%; }
#s-reception,
#s-venue,
#s-closing   { --pk-x0: 18%; --pk-x1: 74%; --pk-y0: 74%; --pk-y1: 91%; }

/* The cover's iridescence belongs to the card, which is the framed artwork. */
.scene--cover .card { position: relative; }

/* ===========================================================================
   Kolam
   A sikku kolam is one unbroken looping line drawn around a grid of dots, and
   it is how a Tamil household marks a threshold on the morning of something
   important. Generated as a hypotrochoid, which has exactly that topology and
   is guaranteed to be a single closed path -- which is what lets it draw
   itself in one continuous stroke.

   The pulli appear first, then the line loops around them: the order it is
   actually drawn in.
   =========================================================================== */
.kolam-wrap { line-height: 0; margin: 0 0 1.6em; }

.kolam {
  width: clamp(84px, 26vw, 116px);
  height: auto;
  overflow: visible;
}

.kolam .stroke {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.15;
  stroke-linejoin: round;
  stroke-linecap: round;
  opacity: .5;
  /* pathLength="1" normalises the geometry, so the dash maths is independent
     of the path's real length. */
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
}

.kolam .pulli circle { fill: currentColor; opacity: 0; }

.is-visible .kolam .stroke {
  stroke-dashoffset: 0;
  transition: stroke-dashoffset 3200ms cubic-bezier(.45,.05,.25,1) 620ms;
}
.is-visible .kolam .pulli circle {
  opacity: .42;
  transition: opacity 420ms ease-out;
  transition-delay: calc(var(--d, 0) * 55ms);
}

/* ===========================================================================
   Dawn
   The cover is a moonlit navy garden; every page after it is pale daylight.
   Left alone that reads as an inconsistency, so make it the point: as the
   cover scrolls away, first light comes up behind the mandapam. The wedding is
   at 7.30 in the morning -- the invitation opens at dawn.

   Driven by --dawn, which main.js sets from the cover's scroll position, so
   scrolling back up puts the night back.
   =========================================================================== */
.dawn {
  position: absolute;
  left: 9%; right: 9%; top: 5%; height: 44%;
  z-index: 1;
  pointer-events: none;
  opacity: var(--dawn, 0);
  mix-blend-mode: screen;
  background:
    radial-gradient(120% 82% at 50% 100%,
      rgba(255,196,132,.85) 0%,
      rgba(255,146,110,.45) 34%,
      rgba(150,120,160,.18) 62%,
      transparent 82%);
  /* feather every edge so the rectangle never shows against the cream frame */
  -webkit-mask-image: radial-gradient(105% 92% at 50% 100%, #000 42%, transparent 88%);
          mask-image: radial-gradient(105% 92% at 50% 100%, #000 42%, transparent 88%);
}

/* ===========================================================================
   Reduced motion
   Keep the framing, drop the movement. Every scene here carries the 1.08
   overscan the parallax needs, so unlike the live edition there is no tall-page
   exception to make.
   =========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .scene__art img,
  .scene__art img.scene__fg { transform: scale(1.08) scaleX(var(--flip, 1)); }
  .scene--cover .card img { transform: none; }
  .scene__art img,
  .scene__art img.scene__fg,
  .scene--cover .card img { transition: none; }
  .scene { --enter: 1; }
  /* Stopping the sweep would leave a static pale band frozen across the pond --
     an artifact, not a picture. Hide it outright, the way .iridesce does. */
  .shimmer  { animation: none; opacity: 0; }
  .iridesce { animation: none; opacity: 0; }
  .dawn     { opacity: 0; }
  .cue      { animation: none; }
  .reveal > * { opacity: 1; transform: none; transition: none; }
  .kolam .stroke { stroke-dashoffset: 0; transition: none; }
  .kolam .pulli circle { opacity: .42; transition: none; }
  .deck { scroll-behavior: auto; }
}

/* ===========================================================================
   Compact tier — 320px-class phones (iPhone 5 / SE 1st gen)

   At 320x568 the clear zone inside the arch is short of what the invitation
   page and the venue actions need. Everything is trimmed proportionally rather
   than letting text spill onto the architecture. The QR stays above ~5px per
   module, which is comfortably scannable, and it is tappable regardless.
   =========================================================================== */
@media (max-width: 350px) {
  .scene__text { padding-left: calc(var(--col) * .07); padding-right: calc(var(--col) * .07); }

  .line       { font-size: .93rem; line-height: 1.52; margin-bottom: .7em; }
  .strong     { font-size: 1.14rem; }
  .title      { font-size: 1.5rem; margin-bottom: .7em; }
  .meta       { font-size: .8rem; }
  .eyebrow    { margin-bottom: .8em; }
  .rule       { margin: .9em auto; }
  .kolam-wrap { margin-bottom: 1.1em; }

  .cover-foot { padding-top: .7em; }
  .countdown  { margin-top: .2em; }

  .actions    { gap: .5rem; margin-top: .9em; }
  .btn        { padding: .6em 1.2em; font-size: .86rem; }
  .qr         { width: min(52vw, 27dvh, 200px); padding: 9px; margin-bottom: .45em; }

  /* the venue scene carries the most furniture, so it reaches furthest down */
  #s-venue .scene__text { height: 75%; }
}
