/* FitoNova site -- hand-written, no build step, no external requests.
   The demo band: the six-phone fan, its copy and its controls.

   Owner's ruling, 2026-08-10: the fan is driven by BUTTONS, not by the
   scroll. The 360vh runway and the sticky stage went with it, and so did the
   trap they carried -- nothing here is sticky.

   One of the ordered sheets in public/css/; the load order in every page's
   <head> IS the cascade, so a rule moved between files is a rule moved in
   the cascade. Owner's ruling 2026-08-10: no source file over 500 lines. */

/* ----------------------------------------------------------- demo band */
/* Static by default: with JS off or motion suppressed the six phones sit in
   a captioned 3x2 grid, every copy item is on the page and the controls are
   absent. The arc is opt-in, under .js and no-preference motion.

   The band clips in EVERY state, and that is the new invariant. Until the
   2026-08-10 button ruling the arc rode a 360vh runway under a sticky stage,
   and a sticky child does not stick inside a clipping ancestor -- so the band
   had to hand its clip back to the stage exactly where the arc was built.
   Nothing is sticky here any more, so the clip stays on: it holds in the two
   vines in every state, plus (under the arc) the outer phones, which hang off
   a centre origin and reach past the viewport by design. Do not put a width
   on it, and do not reintroduce `position: sticky` below. */
.demo {
  position: relative;
  overflow: hidden;
  background: var(--band);
  color: #eef6fa;
  padding: 96px 0 104px;
}

.demo-stage {
  position: relative;
  display: flex;
  flex-direction: column;
}

.demo-head {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 40px;
  flex-wrap: wrap;
}

.demo-head h2 {
  margin-top: 12px;
  font-size: clamp(34px, 4.2vw, 58px);
  line-height: 1;
}

.demo-head p {
  max-width: 300px;
  font: 400 15px/1.6 "Barlow", sans-serif;
  color: rgba(238, 246, 250, 0.8);
}

.fan-area {
  position: relative;
  flex: 1;
  min-height: 0;
  margin-top: 40px;
}

.fan {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px 24px;
  justify-items: center;
  transform: scale(var(--ffit, 1));
}

/* --fo is the arc's fade and is read only by the arc: a static grid that
   honoured it would sit at 6% opacity with five of six phones invisible. */
.fan-phone {
  --pwn: 216;
  position: relative;
  margin: 0;
  width: calc(var(--pwn) * 1px);
}

.fan-phone .phone-frame {
  padding: 9px;
  border-radius: 38px;
  background: linear-gradient(160deg, #333b41, #0e1418 44%, #262e34);
  box-shadow: 0 30px 60px -24px rgba(3, 20, 29, 0.7);
}

.fan-phone .phone-screen {
  --as: calc((var(--pwn) - 18) / 360);
  height: calc((var(--pwn) - 18) * 800px / 360);
  border-radius: 30px;
}

.fan-phone .phone-notch {
  top: 9px;
  width: 76px;
  height: 21px;
  border-radius: 12px;
}

.fan-phone .phone-sheen {
  border-radius: 30px;
}

.fan-cap {
  display: block;
  margin-top: 14px;
  font: 600 22px/1.1 "Barlow Condensed", sans-serif;
  color: #eef6fa;
  text-align: center;
}

.fan-copy {
  position: relative;
  z-index: 3;
  margin-top: 48px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 18px;
}

.fan-copy-item {
  max-width: 640px;
}

.fan-copy-head {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 12px;
  font: 600 12px/1 "Barlow", sans-serif;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(238, 246, 250, 0.78);
}

.fan-num {
  font: 600 30px/1 "Barlow Condensed", sans-serif;
  letter-spacing: 0;
  color: #fff;
}

.fan-title {
  margin-top: 10px;
  font-size: clamp(26px, 2.6vw, 36px);
  line-height: 1.05;
  color: #fff;
}

.fan-body {
  margin: 10px auto 0;
  max-width: 56ch;
  font: 400 16px/1.6 "Barlow", sans-serif;
  color: rgba(238, 246, 250, 0.76);
}

/* The controls only mean anything once the arc is built, so they are absent
   from the static page rather than dead buttons on it: the captioned grid
   already shows all six screens at once. Both halves -- the two arrows and
   the six dots -- are hidden here and shown by the motion-gated block. */
.fan-nav {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 12px;
}

.fan-dots {
  display: none;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

/* The arrows are the fan's primary control (the dots are the shortcut): real
   <button>s with their own per-language aria-label in the markup. 44px. */
.fan-arrow {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  background: transparent;
  color: rgba(238, 246, 250, 0.8);
  border: 1px solid rgba(238, 246, 250, 0.32);
  transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}

.fan-arrow svg {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
}

.fan-arrow:hover {
  border-color: #fff;
  color: #fff;
}

.fan-arrow:focus-visible {
  outline-color: #fff;
}

/* 44px each way: the dots are a control on a touch screen like any other, and
   a 34px target is a miss waiting to happen. */
.fan-dots button {
  cursor: pointer;
  font: 600 12px/1 "Barlow", sans-serif;
  letter-spacing: 0.1em;
  min-width: 44px;
  min-height: 44px;
  padding: 10px 12px;
  background: transparent;
  color: rgba(238, 246, 250, 0.7);
  border: 1px solid rgba(238, 246, 250, 0.32);
  transition: background 0.25s ease, color 0.25s ease, border-color 0.25s ease;
}

.fan-dots button:hover {
  border-color: #fff;
  color: #fff;
}

.fan-dots button.is-on {
  background: #fff;
  color: #0d4f6e;
  border-color: #fff;
}

.fan-dots button:focus-visible {
  outline-color: #fff;
}

/* The arc needs room the way a fan needs a table -- so on a small table it
   gets a smaller fan, not no fan. Owner's ruling, 2026-08-10: the six phones
   turn on a phone screen too. Width only picks a coarse size tier (the blocks
   under this one); the only gate is motion -- under `reduce`, and with JS
   off, the band stays the captioned grid the markup already is.

   The stage is a three-part flex column -- head, fan-area (flex: 1), copy --
   and NOTHING in it may overlap at any aspect ratio (owner, with screenshots
   from 2000x1164, where the old fixed 0.78 scale pushed the phones up under
   the h2 and a long copy item down over the dots). Two measured numbers keep
   the stack honest, written by js/demo.js on resize and continuous, so the
   size follows the viewport instead of jumping at a breakpoint:

     --fcopy  on .fan-copy -- the tallest copy item plus the control row. The
              items are absolutely positioned, so without this the box is a
              guess and the longest of the six lands on the controls.
     --ffit   on .fan -- the plane's scale, the fan-area's own height over the
              phone's, so the phone can never be taller than the gap the flex
              column leaves it. Width is the other clamp.

   Because --ffit is measured against the phone, a --pwn tier change is
   invisible: fit * phoneHeight is the same either side of it. */
@media (prefers-reduced-motion: no-preference) {
  .js .demo {
    padding: 0;
  }

  /* One screen, not four: the band was 360vh of runway under a sticky stage,
     and the arrows replaced the scroll that spent it. The lopsided bottom
     padding is the floating dock's clearance (header.css: fixed, 62px up from
     the bottom edge) -- this is the one band whose controls sit at the very
     bottom of a full-height stage. Every tier below keeps it. */
  .js .demo-stage {
    height: 100svh;
    min-height: 560px;
    overflow: hidden;
    padding: 52px 0 80px;
  }

  /* Six phones on one plane, hung off a single origin. No translateZ: the arc
     is shallow and nothing sits deeper than anything else. JS writes
     --fx/--fy/--fr/--fs/--fo per phone and --ffit on the plane. */
  .js .fan-area {
    perspective: 1500px;
    perspective-origin: 50% 42%;
    margin-top: 0;
  }

  .js .fan {
    position: absolute;
    left: 50%;
    top: 50%;
    width: 0;
    height: 0;
    display: block;
    transform: scale(var(--ffit, 0.7));
  }

  /* Hung centred on the plane's origin, arithmetically rather than per tier:
     the frame is (pwn - 18) * 800/360 + 18 tall, so half of it is
     1.1111 * pwn - 11. Centre the phone and the fit scale alone decides
     whether it clears the heading above and the copy below. */
  .js .fan-phone {
    --pwn: 300;
    position: absolute;
    left: calc(var(--pwn) * -0.5px);
    top: calc(11px - var(--pwn) * 1.1111px);
    will-change: transform, opacity;
    opacity: var(--fo, 1);
    transform: translate3d(var(--fx, 0), var(--fy, 0), 0) rotateZ(var(--fr, 0)) scale(var(--fs, 1));
    /* js/demo.js ranks the phones by distance from centre; without this the
       DOM order wins and the centred phone can sit under a neighbour. */
    z-index: var(--fz, 1);
  }

  .js .fan-cap {
    display: none;
  }

  .js .fan-copy {
    min-height: var(--fcopy, 190px);
    margin-top: 0;
  }

  .js .fan-copy-item {
    position: absolute;
    left: 50%;
    top: 0;
    width: 100%;
    transform: translateX(-50%);
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.32s ease;
  }

  .js .fan-copy-item.is-on {
    opacity: 1;
    pointer-events: auto;
  }

  .js .fan-nav {
    display: flex;
    margin-top: auto;
  }

  .js .fan-dots {
    display: flex;
  }
}

/* ---- the height tiers come first, so a narrow tier below can still tighten
   what they set. A short viewport is the axis the owner's 2000x1164 report
   was really about: 100svh shrinks, the head and the copy do not, and the arc
   is left the difference. --ffit absorbs it, but only if the two fixed blocks
   give up some of their own height first -- otherwise a wide, short window
   spends its whole stage on type and renders a thumbnail. */
@media (prefers-reduced-motion: no-preference) and (max-height: 900px) {
  .js .demo-stage {
    padding: 32px 0 76px;
  }

  .js .fan-title {
    font-size: clamp(24px, 2.2vw, 30px);
  }

  .js .fan-body {
    font-size: 15px;
  }
}

@media (prefers-reduced-motion: no-preference) and (max-height: 720px) {
  .js .demo-stage {
    min-height: 0;
    padding: 24px 0 74px;
  }

  .js .demo-head h2 {
    margin-top: 6px;
    font-size: clamp(26px, 3vw, 38px);
  }

  .js .fan-title {
    font-size: clamp(22px, 2vw, 26px);
  }
}

/* ---- the arc's coarse size tiers. --pwn is a raster, not a size: it fixes
   the phone's own pixel grid (the in-phone container query and the 360x800
   aspect read it), while the fluid part of the answer is --ffit, measured.
   Two steps are enough because the fit scale is continuous through them --
   they exist only to keep the scale factor near 1, where the in-phone
   container units stay sane. The `left`/`top` above are written in terms of
   --pwn, so a tier is one declaration and cannot fall out of step. */
@media (prefers-reduced-motion: no-preference) and (max-width: 899px) {
  .js .fan-phone {
    --pwn: 240;
  }

  .js .demo-stage {
    padding: 40px 0 76px;
  }
}

@media (prefers-reduced-motion: no-preference) and (max-width: 599px) {
  .js .fan-phone {
    --pwn: 200;
  }

  .js .demo-stage {
    padding: 24px 0 70px;
  }

  /* On a phone the stage's vertical budget is the whole argument: every line
     the head and the copy give back is a bigger phone in the middle, because
     --ffit is measured against what is left. These are type trims, not cuts
     -- all six copy items still say what they said. */
  .js .demo-head p {
    font-size: 13px;
    line-height: 1.45;
  }

  .js .demo-head h2 {
    margin-top: 6px;
  }

  .js .fan-copy {
    gap: 12px;
  }

  .js .fan-title {
    margin-top: 6px;
    font-size: 23px;
  }

  .js .fan-body {
    margin-top: 6px;
    font-size: 14px;
    line-height: 1.5;
  }
}

/* Narrow AND short -- a phone with its address bar showing. This is the one
   viewport where the head's hint ("step through with the arrows") costs more
   than it gives: the two arrows are right there under the phone, labelled and
   44px each way, so the sentence describes what it sits above; its three
   wrapped lines are a third of the phone's height. Nothing else goes. */
@media (prefers-reduced-motion: no-preference) and (max-width: 599px) and (max-height: 720px) {
  .js .demo-head p {
    display: none;
  }
}

/* ------------------------------------------------------------ responsive */
@media (max-width: 980px) {
  .fan {
    grid-template-columns: repeat(2, 1fr);
  }
}

@media (max-width: 768px) {
  /* Match the paper sections' phone rhythm; 96/104 was written for a band
     that a phone reads as a wall of padding. */
  .demo {
    padding: 72px 0 80px;
  }

  .demo-head {
    gap: 20px;
  }

  .demo-head p {
    max-width: none;
  }

  .fan-copy {
    margin-top: 40px;
    gap: 28px;
  }
}

@media (max-width: 660px) {
  .fan {
    grid-template-columns: 1fr;
    gap: 40px 0;
  }

  .fan-phone {
    --pwn: 260;
  }
}

/* Under reduced motion the demo keeps its static grid and the phones take a
   fit scale rather than a full-size arc; nothing here relies on a script. */
@media (prefers-reduced-motion: reduce) {
  .fan {
    --ffit: 0.72;
    transform-origin: top center;
  }
}
