/* ClearToSwim: "Sunlight and Caustics"
   Light mode always. High-key midday sun on a bleached concrete deck, with the
   rippling net of light that lives on a pool floor.

   The site now stands under one sky. A fixed layer behind every page carries the
   weather; the page itself is a run of opaque concrete plates laid over it, with
   the sky showing in the band above the first plate, in the gutters either side
   and in the joints between them.

   The contrast rule that falls out of that, and it is the whole reason the page
   is built this way: no word on this site ever sits on the sky. Every plate is
   opaque, so the background behind a line of type is the same colour at high
   noon and at the black middle of the storm, and the measured contrast ratio
   does not move by a thousandth across the cycle.

   House law: no em dashes anywhere, including comments.
   House law: no left-only borders on anything. Depth is carried by hard offset
   shadows thrown right and down. Accent bars sit on the top edge. */

/* Self-hosted fonts (SIL Open Font License). No third-party font CDN, which keeps
   the site fast, private, and working on restricted networks. Licenses in /fonts/. */
@font-face{font-family:"Open Sans";font-style:normal;font-weight:400;font-display:swap;src:url("/fonts/open-sans-latin-400-normal.woff2") format("woff2")}
@font-face{font-family:"Open Sans";font-style:normal;font-weight:600;font-display:swap;src:url("/fonts/open-sans-latin-600-normal.woff2") format("woff2")}
@font-face{font-family:"Open Sans";font-style:normal;font-weight:800;font-display:swap;src:url("/fonts/open-sans-latin-800-normal.woff2") format("woff2")}
@font-face{font-family:"Yantramanav";font-style:normal;font-weight:700;font-display:swap;src:url("/fonts/yantramanav-latin-700-normal.woff2") format("woff2")}
@font-face{font-family:"Yantramanav";font-style:normal;font-weight:900;font-display:swap;src:url("/fonts/yantramanav-latin-900-normal.woff2") format("woff2")}

:root{
  color-scheme:light;

  /* how much sun is landing on the deck. The sky script moves this between 1
     and 0 as the cloud comes over. It only ever touches light: the hardness of
     the cast shadows, the shine on the water, the brightness of the caustic
     net. It never touches the colour behind a word. */
  --sun:1;

  /* deck: sun-bleached concrete. Warm, but the yellow is pulled back so white
     cards separate cleanly and the whole surface reads dry rather than golden. */
  --deck:#F7F5EF;
  --deck-2:#EFEDE5;      /* poured slab, one shade down */
  --card:#FFFFFF;
  --edge:#D9D5C8;        /* hairline, warm */
  --edge-cool:#C9DDE4;   /* hairline, wet */

  /* the hard shadow the noon sun casts, which softens away under cloud */
  --sh-a:calc(.20 + .80 * var(--sun, 1));
  --deck-shade:rgba(224,221,209,var(--sh-a));

  /* ink: what the deep end looks like in shadow */
  --ink:#072028;         /* 15.5:1 on the deck */
  --ink-2:#3C5964;       /*  6.9:1 on the deck */

  /* painted tile ink: the colour of type stencilled on the pool floor and the
     coping that frames the water. Named so the four places that used to carry
     these hexes by hand cannot drift apart. */
  --tile-ink:#06333F;
  --tile-ink-2:#02222B;
  --coping:#F2F0E8;

  /* water: chlorinated cyan lit from directly above. Untouched. This is the
     part that smells like chlorine and it is not getting quieter, and it is
     also the one surface with type on it, so its colour is held fixed across
     the whole weather cycle on purpose. */
  --water-lite:#A8F3FD;
  --water:#35D2F0;
  --water-mid:#0FB2DC;
  --water-deep:#0A87B8;
  --sea:#06697F;         /* water as text: links, accents */
  --cobalt:#1B6FB8;      /* waterline tile */

  /* state colors, darkened to hold AA on a bright deck and on white */
  --green:#0B7A42;   --green-shade:#C6E4D4;
  --amber:#8A5200;   --amber-shade:#F0DCB2;  --amber-bar:#C9800A;
  --red:#B4232A;     --red-shade:#F0CDCB;
  --lamp-green:#2FD370;
  --lamp-amber:#FFB020;
  --lamp-red:#FF4D4D;
  /* dormant lenses: dark glass with just enough of its own colour left to
     tell you which lamp is which while it is switched off */
  --off-red:#4E2124;
  --off-amber:#4A3615;
  --off-green:#17432E;

  --body:"Open Sans",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  --display:"Yantramanav",var(--body);

  /* one shadow language: the sun is overhead and slightly behind you,
     so every edge throws hard to the right and down. Never to the left. */
  --cast:7px 7px 0 var(--deck-shade);
  --cast-lg:12px 12px 0 var(--deck-shade);
  --cast-sm:4px 4px 0 var(--deck-shade);
  --lift:0 22px 40px -28px rgba(7,32,40,.55);

  /* the sky showing round the edges of the plates */
  --gut:clamp(9px,2.2vw,30px);
  --joint:clamp(10px,1.8vw,26px);

  /* The column down the right of the viewport the mounted fixture stands in.
     The page reserves it as padding on the body, so the pole and the head hang
     in open sky and can never land on a line of type at any width. Everything
     here scales down together on narrow screens.

     The column is set by the longest thing that has to sit in it, which is the
     caution subtitle. The plate is 168px of column less 12px of inset, and the
     sentence falls into three balanced lines inside it with no word left
     standing on its own, which is the same wrap it already takes at 1024px and
     narrower. It used to be 214px, held there to keep that sentence on two
     lines. Two lines is not worth 46px of the page's own width: at 214 the
     content column could not be centred at any width without the mount landing
     on it, and at 168 it can, from 861px up. The head, the arm and the pole are
     drawn at exactly the sizes they were. */
  --rig-gut:168px;   /* what the page gives up to the mount */
  --rig-w:126px;     /* the drawn width of arm plus head */
  --rig-top:104px;   /* where the arm meets the pole */
}

*{box-sizing:border-box;margin:0;padding:0}
html{
  scroll-behavior:smooth;-webkit-text-size-adjust:100%;background:#8CCBE8;
  /* the sticky masthead is opaque, so an anchor jump has to land under it */
  scroll-padding-top:104px;
}

body{
  font-family:var(--body);
  color:var(--ink);
  line-height:1.68;
  font-size:17px;
  background:transparent;
  -webkit-font-smoothing:antialiased;
  text-rendering:optimizeLegibility;
  /* The page is centred on the viewport, not pushed off it. See the note on
     .plate below for how it is kept clear of the mount, and for the one width
     range where centring stops being possible. */
  padding-right:0;
}

/* read by assistive technology, never drawn. Used to hold one stable sentence
   under the numerals that animate. */
.sr-only{
  position:absolute;width:1px;height:1px;margin:-1px;padding:0;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;
}

a{color:var(--sea);text-decoration:none;text-underline-offset:3px}
a:hover{text-decoration:underline}
a:focus-visible,button:focus-visible{outline:3px solid var(--sea);outline-offset:3px;border-radius:4px}
img,svg{max-width:100%}
::selection{background:var(--water-lite);color:var(--ink)}

/* ---------------- the sky -----------------------------------------------
   One fixed layer behind the whole document. The gradient on this element is
   the standing fallback: it is what shows with no script at all, and on any
   browser that cannot hand out a WebGL context, and it is a bright clear
   afternoon, which is the state the page is written for. The canvas covers it
   the moment the shader has produced a frame.

   Flashing, in numbers, because this is the only bright moving thing on the
   site. The wash a close strike throws is capped at .13 of white, which moves
   relative luminance by at most about .066, under the .10 step that makes a
   large area count as a flash at all. It never reaches the plates, so no word
   on the page moves when it fires. The bolt is a thin stroke, so its bright
   area is small. Strikes are seconds apart and every one is a single rise and
   fall, so the rate never approaches three a second. The distant flickers are
   fainter still and never closer together than 1.2 seconds. */
.sky{
  position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden;
  background:
    radial-gradient(46% 40% at 17% 15%, rgba(255,247,222,.92), rgba(255,240,198,0) 66%),
    linear-gradient(180deg,#1C74BE 0%,#4EA3D6 34%,#8CCBE8 62%,#C6E7F3 86%,#DCF0F6 100%);
}
.wx-gl{position:absolute;inset:0;width:100%;height:100%;display:none}
html.wx-gl-on .wx-gl{display:block}

/* The close strike. One channel, five strokes, all of them plotted from the
   same geometry, so the light reads as coming off one bolt rather than as
   several stacked on top of each other. From the outside in:

     b-glow   the halo the channel throws on the air around it, blurred
     b-mid    a tight pale sheath, which is what gives the channel an edge
              against a black storm sky instead of a soft bloom
     b-fork   the branches, thinner than the trunk because they carry less
     b-core   the white filament down the middle
     b-surge  the return stroke: a short bright length of the trunk that runs
              from the ground back up to the cloud, once, as it lands

   The widths are written here for the record and then driven from the script,
   because the channel is thinner while the leader is still groping down than it
   is once the stroke is running back up it, and the script is where the stage
   of the strike is known. The halo is deliberately the one stroke that was not
   widened: it is the part that behaves like a wash, and a wash is the part that
   matters to a photosensitive reader. The extra definition went into the core
   and the sheath, which are thin and measurable. */
.wx-bolt{position:absolute;inset:0;width:100%;height:100%;opacity:0;display:block}
.wx-bolt path{fill:none;stroke-linecap:round;stroke-linejoin:round}
.wx-bolt .b-core{stroke:#FFFFFF;stroke-width:3.9}
.wx-bolt .b-fork{stroke:#F4FAFF;stroke-width:2.4;opacity:.95}
.wx-bolt .b-mid{stroke:#DFEEFF;stroke-width:7.8;opacity:.86;filter:blur(1px)}
.wx-bolt .b-glow{stroke:#A9CBF0;stroke-width:13.5;opacity:.44;filter:blur(5px)}
.wx-bolt .b-surge{stroke:#FFFFFF;stroke-width:6.8;opacity:0;filter:blur(1.3px)}

.wrap{max-width:1080px;margin:0 auto;padding:0 34px}
.mid{max-width:900px}
.narrow{max-width:730px}
/* The masthead is the one measure that is not inside a plate, so it is held to
   the same column by hand. Without this the badges would sit wider than the
   plates under them and the whole left edge of the page would step. */
.site-head .wrap{max-width:min(1080px, calc(100% - 2 * var(--rig-gut) - 2 * var(--gut)))}

/* ---------------- the plates --------------------------------------------
   Poured concrete, laid over the sky. Opaque, always. This is the surface every
   word on the site sits on, so it is the surface the contrast numbers are
   measured against, and it does not change colour for any reason. */
/* Where the page stands, side to side.

   The page is centred on the viewport. It used to be pushed left by a column of
   body padding the width of the mount, which meant that at every width the
   whole site sat off centre by half that column: 107px at 1440. It no longer
   does. Instead every plate is centred and held narrow enough that its right
   edge stops short of the column the mount stands in, so the fixture still
   hangs in open sky and still cannot land on a line of type, and the same gap
   is left on the left, where nothing is standing.

   The arithmetic, which is what the build checks measure rather than trust:
   half the plate is (100% - 2 * rig-gut - 2 * gut) / 2, so the plate's right
   edge sits one full gutter clear of the mount's column at every width, and its
   left edge sits at exactly the same distance from the left edge of the frame.

   Where it stops working. Centring costs the measure twice what pushing the
   page over costs it, because the room has to be given up on both sides. Above
   861px that is affordable: at 1440 the type column is the same 1012px it
   always was, because it was already at its own maximum. Below 861px it is not.
   At 414px the mount's column is 96px of a 414px screen, so a centred plate
   would be 222px wide and carry a 178px measure, against 300px and 256px today.
   So below 861px the page goes back to giving up a column on the right and
   standing left of centre, which at those widths is a 48px offset rather than
   a 107px one. The numbers behind both are in the build checks. */
.plate{
  position:relative;z-index:1;
  width:calc(100% - 2 * var(--gut));
  max-width:min(1148px, calc(100% - 2 * var(--rig-gut) - 2 * var(--gut)));
  margin-left:auto;margin-right:auto;margin-bottom:var(--joint);
  background-color:var(--deck);
  /* dry concrete grain under a wash of overhead sun, dialled back so the
     midtones stay clean and the type keeps its edge */
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='200' height='200' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
  border-radius:clamp(12px,1.6vw,22px);
  box-shadow:0 26px 54px -34px rgba(7,32,40,.72);
}
.plate.band{background-color:var(--deck-2)}

main,footer{position:relative;z-index:1}

/* skip link: off screen until it takes focus, then it lands on the deck.
   Fixed, so it clears the sticky masthead rather than sliding under it. */
.skip{
  position:fixed;left:-9999px;top:0;z-index:40;
  background:var(--card);color:var(--ink);font-weight:600;font-size:14px;
  padding:12px 18px;border-radius:0 0 10px 10px;box-shadow:var(--cast-sm);
}
.skip:focus{left:26px}

/* stencil: the letterforms painted straight onto the deck */
.stencil{
  font-family:var(--display);font-weight:900;
  font-size:12px;letter-spacing:.26em;text-transform:uppercase;
  color:var(--sea);
}
.stencil-ink{color:var(--ink-2)}

/* ---------------- caustics ---------------------------------------------
   The net of white light on a pool floor, drawn by a fragment shader on the
   GPU, one full screen pass per frame. Two clocks run at once, the way water
   does: a broad drift that carries the whole net across the floor, and a much
   faster local shimmer. The fast one is what you actually read.

   Underneath the canvas sit two tiled layers built from one fractal noise
   texture rasterised a single time. They are what shows before the shader is
   up, with no script at all, and on anything that cannot hand out a WebGL
   context. Their only animation is translate3d over a whole number of tiles,
   so the compositor moves a cached texture and nothing repaints.

   The whole net dims with the sun. A pool floor has no caustics under cloud,
   and taking them away is the cheapest true thing the storm can do to the
   water. It only ever removes white light from the floor, so the dark tile
   numerals painted on it gain contrast rather than lose it. */
.caustics{
  position:absolute;inset:0;overflow:hidden;z-index:2;
  pointer-events:none;
  opacity:calc(.16 + .84 * var(--sun, 1));
  transition:opacity 1.4s ease;
}
.cx-gl{
  position:absolute;inset:0;width:100%;height:100%;display:none;
}
/* once the shader has a context the tiles step aside */
.caustics.is-gl .cx-gl{display:block}
.caustics.is-gl .cx-tile{display:none}

.cx-tile{
  position:absolute;top:-34%;left:-34%;width:168%;height:168%;
  background-repeat:repeat;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='320' height='320'%3E%3Cfilter id='c' filterUnits='userSpaceOnUse' x='0' y='0' width='320' height='320' color-interpolation-filters='sRGB'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.021' numOctaves='1' seed='11' stitchTiles='stitch' result='n'/%3E%3CfeColorMatrix in='n' type='matrix' values='0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 .34 .5 .16 0 0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='table' tableValues='0 0 0 0 0 0 .3 1 .3 0 0 0 0 0 0'/%3E%3C/feComponentTransfer%3E%3CfeComponentTransfer%3E%3CfeFuncA type='gamma' exponent='3.0'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='320' height='320' filter='url(%23c)'/%3E%3C/svg%3E");
  will-change:transform;
}
.cx-t1{background-size:430px 430px;opacity:.66;animation:cxTileA 7.6s linear infinite}
.cx-t2{background-size:268px 268px;opacity:.40;animation:cxTileB 5.8s linear infinite}

/* one whole tile per cycle in each axis, so the loop has no seam */
@keyframes cxTileA{
  from{transform:translate3d(0,0,0)}
  to  {transform:translate3d(-860px,430px,0)}
}
@keyframes cxTileB{
  from{transform:translate3d(0,0,0)}
  to  {transform:translate3d(268px,536px,0)}
}

/* ---------------- the pool ---------------------------------------------- */
/* coping: the pale cast stone lip that runs around every pool ever poured */
.pool{
  position:relative;isolation:isolate;overflow:hidden;
  border:11px solid var(--coping);
  border-radius:30px;
  /* Two layers. Underneath, the water, which is one fixed colour for the whole
     weather cycle because it is the one surface on the site with type on it.
     Over it, the shade a storm cloud throws on a pool, which fades out well
     above the tile numerals and never reaches them, so the measured contrast
     down there does not move when the sky goes black. */
  background:
    linear-gradient(0deg,
      rgba(10,40,54,calc(.40 * (1 - var(--sun, 1)))) 0%,
      rgba(10,40,54,calc(.22 * (1 - var(--sun, 1)))) 22%,
      rgba(10,40,54,0) 42%),
    radial-gradient(135% 105% at 16% -12%, var(--water-lite) 0%, var(--water) 30%, var(--water-mid) 62%, var(--water-deep) 100%);
  box-shadow:var(--cast-lg),var(--lift);
}
/* the light a close strike throws on the water. It only ever lifts the water
   toward white, capped at .13, which is under the step that would count as a
   flash and which can only raise the contrast of the dark numerals on it. */
.pool::before{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:3;opacity:0;
  background:linear-gradient(196deg, rgba(255,255,255,.13) 0%, rgba(214,236,255,.06) 54%, rgba(255,255,255,0) 100%);
}
@keyframes poolLit{0%{opacity:0}8%{opacity:1}26%{opacity:.34}100%{opacity:0}}
.sky.is-strike ~ main .pool::before{animation:poolLit .62s ease-out}
/* specular: the flat sheet of noon glare across the shallow end, which goes
   flat and lifeless when the cloud takes the sun */
.pool::after{
  content:"";position:absolute;inset:0;pointer-events:none;z-index:3;
  opacity:calc(.34 + .66 * var(--sun, 1));
  transition:opacity 1.4s ease;
  background:
    linear-gradient(180deg, rgba(255,255,255,.30) 0%, rgba(255,255,255,0) 21%),
    radial-gradient(58% 40% at 13% 7%, rgba(255,255,255,.22), rgba(255,255,255,0) 72%);
}
/* waterline tile: the cobalt band every pool store sells by the sheet */
.tileband{
  position:absolute;top:0;left:0;right:0;height:20px;z-index:4;
  background:
    repeating-linear-gradient(90deg,
      var(--cobalt) 0 23px,
      #E9F5FB 23px 26px);
  box-shadow:inset 0 -2px 0 rgba(7,32,40,.24);
}
.tileband::after{
  content:"";position:absolute;left:0;right:0;bottom:-3px;height:3px;
  background:rgba(255,255,255,.55);
}
/* depth markings, the dark tile numerals set into a pool floor and read through
   moving water. Dark paint rather than white: the caustic net is white, so it
   lightens whatever it crosses. Light letters get eaten by it, dark ones stay
   legible and gain contrast wherever the light lands. The water under them is
   held at one colour for the whole weather cycle for exactly this reason. */
.floor-mark{
  position:absolute;z-index:3;max-width:74%;
  font-family:var(--display);font-weight:900;
  color:var(--tile-ink);
  letter-spacing:.04em;line-height:.92;
  /* the pale lip of the tile catching the sun, down and to the right */
  text-shadow:0 2px 0 rgba(255,255,255,.34);
}
/* Each value stands under its own label, so the pair is a block: label above,
   number below. The old midpoint dot went with the labels' arrival; two
   labelled tiles need no separator. */
.floor-mark .fm-pair{white-space:nowrap;display:inline-block;vertical-align:top}
.floor-mark .fm-pair+.fm-pair{margin-left:1.1em}
/* Narrow frames stack the two values instead of setting them side by side. */
@media (max-width:860px){
  .floor-mark .fm-pair{display:block}
  .floor-mark .fm-pair+.fm-pair{margin-left:0;margin-top:10px}
}
/* The rule line is small type on moving water, so it is painted a shade deeper
   than the numerals above it. Measured against the darkest pixel the water ever
   puts behind a stroke of it, across the whole weather cycle, at every width. */
.floor-mark small{
  display:block;font-family:var(--body);font-weight:800;
  /* The floor was 9.5px, which is where every screen narrower than about 1130px
     landed, phones and tablets alike. That is a decorative size, not a readable
     one, and this line carries the actual rule the product runs on. Raised to
     11.5px. The ceiling and the ramp between are unchanged, so nothing above
     that width moves. */
  font-size:clamp(11.5px,1.02vw,12.5px);letter-spacing:.1em;text-transform:uppercase;
  line-height:1.42;
  color:var(--tile-ink-2);margin-top:1.15em;text-shadow:0 1px 0 rgba(255,255,255,.42);
}
.floor-mark small .fm-caret::after{background:var(--tile-ink-2)}

/* ---------------- the two values -----------------------------------------
   The radius and the wait are the two numbers the reader actually owns, so
   once in a while the graphic quietly retunes them and puts them straight
   back. The numeral takes a selection wash, a text cursor blinks beside it in
   the sentence, the digits back out and new ones arrive, and then the whole
   thing returns to 10 and 30 and rests there.

   Nothing here is a control. There is no box, no outline, no focus ring and
   nothing to click: the mount is not interactive and the caret is a painted
   bar, not a field. It is a suggestion, and it is drawn as one.

   Rate. The caret blinks off the one attribute every other blinking thing on
   this page reads, so it switches on the same instants the amber lamps do
   rather than beating against them. One train at one flash a second, a third
   of the ceiling, and the caret is a hairline rather than an area.

   Screen readers. Every numeral that moves is out of the accessibility tree,
   and one unmoving sentence carrying the real defaults sits beside it, so the
   accessible name is 10 and 30 no matter what the picture is doing. */
/* Both faces carry tabular figures, so any two digits are exactly twice the
   advance of one, and both carry a figure space at exactly the advance of one
   digit. That is what holds the line still: a one digit value is written with a
   figure space in front of it, so it is the same width as a two digit value and
   sits in the right hand cell of the same two cell slot. The number is tight
   against the word after it at every value, nothing around it moves, and the
   digits change in place rather than sliding, because the run of text they
   belong to starts at the same point either way.

   The slot widths below are measured, not guessed, and they only ever come into
   play if the faces fail to arrive: with them, a value is already exactly two
   cells wide on its own. */
.fm-num,.fm-slot{display:inline-block;text-align:left;font-variant-numeric:tabular-nums}
.fm-num{min-width:1.122em}
.fm-slot{min-width:1.372em}
.fm-txt{
  border-radius:2px;
  transition:background-color .18s ease,box-shadow .18s ease;
}
.fm-txt.is-sel{
  background-color:rgba(255,255,255,.72);
  box-shadow:0 0 0 1.5px rgba(255,255,255,.72);
}
/* The cursor takes no width in the line. It is a zero width box with the bar
   painted beside it, so it can come and go between two digits and the word after
   them without moving either of them by a hair. It stands where the text ends,
   which with a padded value is the right hand edge of the slot. */
.fm-caret{
  display:none;position:relative;width:0;height:1.02em;
  vertical-align:-.16em;
}
.fm-caret::after{
  content:"";position:absolute;left:-.06em;top:0;bottom:0;
  width:.12em;min-width:1px;background:var(--tile-ink);border-radius:1px;
}
.fm-caret.is-on{display:inline-block}
[data-blink="off"] .fm-caret.is-on{opacity:0}

/* ---------------- header ------------------------------------------------
   The masthead stands in the open sky above the first plate, so the brand and
   the nav each get their own opaque enamel badge rather than sitting on the
   weather. Same rule as everywhere else: no type on the sky.

   It is stuck to the top of the viewport, so the mark stays on screen for the
   whole scroll. Only the two badges are opaque; the strip between them is left
   open, so the plates slide past behind it and the header never reads as a
   solid bar cutting the sky in half. */
.site-head{
  padding:clamp(11px,1.5vw,15px) 0;
  position:sticky;top:0;z-index:20;
  transition:transform .28s ease;
}
/* the bar steps aside on the way down and returns on the way up; the class
   is written by the small closure at the foot of sky.js */
html.head-away .site-head{transform:translateY(-130%)}
.site-head .wrap{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:8px 16px}
.brand{
  display:inline-flex;align-items:center;gap:12px;color:var(--ink);
  background:var(--card);padding:7px 21px 7px 8px;border-radius:999px;
  box-shadow:var(--cast-sm);
}
.brand:hover{text-decoration:none}
/* The mark in the pill is the tile-less cut: dormant lamps, lit green, sea
   waves, straight on the white. The rounded water tile went back to surfaces
   that need to carry their own background; inside a pill it read as a box in
   a box. No radius: there is no tile to round. */
.brand svg,.brand img{width:40px;height:auto;flex:none;display:block}
.brand .name{font-family:var(--display);font-weight:900;font-size:26px;letter-spacing:-.3px;line-height:1}
.brand .c{color:var(--green)}
.brand .s{color:var(--sea)}
.site-head nav{
  display:flex;flex-wrap:wrap;gap:2px;font-size:14px;margin-left:auto;
  background:var(--card);border-radius:999px;padding:5px;box-shadow:var(--cast-sm);
}
.site-head nav a{
  color:var(--ink-2);font-weight:600;padding:7px 14px;border-radius:999px;
  white-space:nowrap;
}
.site-head nav a:hover{
  color:var(--ink);text-decoration:none;background:var(--deck-2);
}

/* ---------------- hero -------------------------------------------------- */
/* the band of open sky the site arrives on: sun in the upper left, fair weather
   cloud, and the head of the fixture standing up in it */
.hero{
  padding:clamp(52px,7vw,88px) 0 clamp(40px,5vw,64px);
  margin-top:clamp(64px,11vh,132px);
}
.hero-copy{max-width:660px}
.hero .stencil{margin-bottom:20px}
h1{
  font-family:var(--display);font-weight:900;
  font-size:clamp(46px,8vw,84px);line-height:.96;letter-spacing:-2px;
}
h1 .hl{color:var(--green)}
.lede{
  max-width:600px;margin-top:22px;
  font-size:clamp(17px,2.1vw,20.5px);color:var(--ink-2);
}
/* ---------------- the App Store badge ------------------------------------
   The pill that stood here carried a green lamp dot and the words "On the
   App Store". It was the site's own hand, which is exactly what Apple asks
   nobody to do: the badge is their artwork and it goes on unaltered, not
   redrawn in the local idiom. So the pill is gone and this styles an image.

   Their guidelines set two rules a stylesheet can break by accident. The
   badge needs clear space of at least a tenth of its height on every side,
   which is what the padding is for, and it must never be squashed, which is
   what height:auto against a fixed width guarantees when a flex parent tries
   to shrink it. Minimum height on the web is 40px and this sits above it at
   every breakpoint: 50 in the hero, 45 on the inner pages, and it never
   scales with the viewport, because a badge that grows with the page starts
   competing with the headline. */
.store{
  display:flex;align-items:center;gap:16px;flex-wrap:wrap;
  margin-top:22px;
}
.store-badge{
  display:inline-block;flex:none;padding:5px;margin:-5px;
  border-radius:10px;line-height:0;
}
.store-note{
  font-size:13px;line-height:1.4;color:var(--ink-2);
}
/* FOUR SIZES, AND THEY ARE ORDERED SMALLEST SELECTOR FIRST ON PURPOSE.
   The first cut of this said "main .store-badge img{height:45px}" meaning the
   inner pages, and the home page's hero is inside main, so the hero badge was
   45 when it was written to be 50. Measured on the live page at 320 wide
   before it was found. Nothing below leans on where it sits in the document:
   each size is asked for by name. */
.store-badge img{display:block;width:auto;height:45px}
.store-hero .store-badge img{height:60px}
.store-foot .store-badge img{height:40px}
.store-head img{height:40px}

/* The hero and the closing band are the two places the badge IS the point,
   so they get room around them rather than being tucked under a paragraph. */
.store-hero{margin-top:28px;gap:20px}

/* THE PERSISTENT ONE, in the header, and the only badge on the demo page.
   It is hidden below 900 because at 320 the nav already wraps onto a row of
   its own, 276 wide out of 320: a badge there would cost every page a third
   header row. A phone gets Safari's own bar from the apple-itunes-app meta
   instead, which sits above the page rather than inside it. */
/* IT HAS TO BE NAMED AS A NAV LINK TO BEAT THE NAV'S OWN RULES, and the
   first cut of this was not. ".store-head{display:none}" is one class, and
   three rules in this file set display on ".site-head nav a", which is a
   class and an element and therefore wins: the badge showed on every phone
   after all. Worse, the 520 rule gives every nav link "flex:1 1 0", so the
   badge took an equal share of the row, and "img,svg{max-width:100%}" up at
   line 148 then squashed it to fit. Measured live at 390 wide: 51px across on
   a badge that is 120 at its declared height, which is the one thing Apple's
   guidelines say must never happen to it. Hence the specificity here, the
   flex:none, and the max-width:none on the image. */
.site-head nav a.store-head{display:none;flex:none;padding:5px;min-height:0}
.store-badge img{max-width:none}
@media (min-width:900px){
  .site-head nav{align-items:center}
  .site-head nav a.store-head{display:inline-block;margin-left:6px}
}

/* The closing call. It is a band like the other bands, so it does not read as
   an advertisement bolted to the foot of the page, and it is centred because
   it is the one section with nothing to compare or explain. */
.getband{text-align:center}
.getband .store{justify-content:center}
.getband .lede{margin-left:auto;margin-right:auto}

footer .store-foot{margin:0}

/* SHORT PHONES. At 320 by 568 the hero badge's top edge landed at y 550
   against a fold at 568, so eighteen pixels of a sixty pixel badge were on
   screen and the thing the page exists to do was below the crease. The hero
   gives up some of its air on a short viewport and the badge comes up with
   it. Measured, not guessed: the target is the whole badge and its line
   above the fold at 320 by 568, which is the smallest phone still in use. */
@media (max-height:700px) and (max-width:540px){
  .hero{padding-top:14px;padding-bottom:18px}
  .hero .lede{margin-top:10px}
  .store-hero{margin-top:18px;gap:12px}
  .store-hero .store-badge img{height:52px}
}

/* ---------------- the fixture -------------------------------------------
   The stoplight is mounted the way a real one is: a vertical pole standing at
   the edge of the frame, a short horizontal pipe arm clamped to it, and the
   signal head hanging off the end of that arm. The whole assembly is fixed to
   the viewport, so it does not scroll away and the live state stays on screen
   for as long as the reader is on the page.

   The page never runs underneath it. The body gives up a column of its own
   width to the mount, so the pole and the head hang in open sky at every
   viewport width and cannot land on a line of type.

   The word is painted on an opaque concrete plate bolted to the pole under the
   head, which is what keeps it readable through a black storm. */
.rig-mount{
  position:fixed;top:0;right:0;bottom:0;width:var(--rig-gut);
  z-index:12;pointer-events:none;
}
/* the pole, running off the top and bottom of the frame */
.rig-pole{
  position:absolute;top:0;bottom:0;right:8px;width:16px;
  background:linear-gradient(90deg,#04171E 0%,#2A5C6C 34%,#123A47 62%,#04171E 100%);
  /* a hard cast like everything else on the site. The old shadow here was the
     one in the file with both a blur and a horizontal offset, which belongs to
     neither of the two sanctioned patterns. The pole runs off both edges of
     the frame, so the down component of its cast is unobservable; what shows
     is the hard edge thrown right. */
  box-shadow:4px 0 0 rgba(6,48,66,.16);
}
.rig{
  position:absolute;top:var(--rig-top);right:16px;
  display:block;width:var(--rig-w);height:auto;
  filter:drop-shadow(0 12px 16px rgba(7,32,40,.30));
}
.rig-say{
  position:absolute;right:6px;left:6px;
  top:calc(var(--rig-top) + var(--rig-w) * 1.514 + 12px);
  background:var(--deck);border-radius:12px;
  padding:10px 12px 12px;
  box-shadow:5px 5px 0 rgba(6,48,66,.20),0 18px 30px -22px rgba(6,48,66,.6);
}
.rig-word{
  font-family:var(--display);font-weight:900;
  font-size:clamp(13px,1.15vw,16px);line-height:1.12;letter-spacing:.1px;
  color:var(--green);
}
/* The subtitle is balanced rather than filled. Greedy wrapping at this measure
   pushes the last word onto a line of its own; asking for even lines instead
   keeps the ragged edge shallow and never leaves one word standing. Any engine
   that does not know the property falls back to plain wrapping, which the
   width above is already sized to keep clean. */
.rig-note{
  font-size:clamp(10px,.86vw,11.5px);line-height:1.42;color:var(--ink-2);margin-top:7px;
  text-wrap:balance;
}
.rig-note:empty{display:none}
[data-wx="caution"] .rig-word,[data-wx="soon"] .rig-word{color:var(--amber)}
[data-wx="closed"] .rig-word{color:var(--red)}

/* ---- the lamps, both heads ---------------------------------------------
   There are two signal heads on this page: the one hung off the mast arm and
   the smaller one inside the pool card. They are the same fixture drawn at two
   sizes, and every rule below addresses both at once.

   Nothing here holds a state of its own. The sky script writes data-wx and
   data-blink on the root element off the one clock the weather runs on, and
   these selectors read them. That is the whole synchronisation argument: there
   is no second timer, no second animation and no second copy of the rules, so
   the two heads cannot drift apart by so much as a frame.

   Dark glass keeps just enough of its own colour to tell you which lamp is
   which while it is switched off, and a pale bezel round every lens keeps the
   housing reading as three lamps in any weather. */
.rig .lens,.sig .lens{
  fill:var(--off-red);stroke:#FFFFFF;stroke-opacity:.2;stroke-width:1.6;
  transition:fill .3s ease;
}
.lamp-a .lens{fill:var(--off-amber)}
.lamp-g .lens{fill:var(--off-green)}
/* The hood over each lens, which is what makes a stoplight read as a stoplight
   rather than as three dots in a box. It is cast metal bolted to the housing:
   it is never switched, never blinks and never changes opacity. Only the glass
   under it ever changes. */
.rig .visor,.sig .visor{fill:#1B4854;stroke:#4C8494;stroke-opacity:.5;stroke-width:1.2}
.rig .halo,.sig .halo{opacity:0;transition:opacity .3s ease}
/* with no script at all there is no state attribute, so both fixtures rest lit
   on green, which is the state the whole page is written for. The word beside
   each one already says POOL OPEN in the markup. */
:root:not([data-wx]) .lamp-g .lens{fill:var(--lamp-green)}
:root:not([data-wx]) .halo-g{opacity:1}
[data-wx="closed"] .lamp-r .lens{fill:var(--lamp-red)}
[data-wx="closed"] .halo-r{opacity:1}
/* House law: red or green is always lit. Reopening soon keeps red ON and adds
   a steady amber beside it, so the head never shows amber alone while the
   pool is still closed. */
[data-wx="soon"] .lamp-r .lens{fill:var(--lamp-red)}
[data-wx="soon"] .halo-r{opacity:1}
[data-wx="caution"] .lamp-a .lens,
[data-wx="soon"] .lamp-a .lens{fill:var(--lamp-amber)}
[data-wx="caution"] .halo-a,
[data-wx="soon"] .halo-a{opacity:1}
/* Green holds through caution as well as open. The pool is still open, which
   is what green says, and the blinking amber beside it is the caution. The
   word carries the single verdict, so two lit lamps cannot muddy the read.
   Green is solid all the way through: only the amber ever blinks. */
[data-wx="open"] .lamp-g .lens,
[data-wx="caution"] .lamp-g .lens{fill:var(--lamp-green)}
[data-wx="open"] .halo-g,
[data-wx="caution"] .halo-g{opacity:1}
/* Caution blinks at the app's own cadence: a 1 Hz eased on-off cycle, one
   flash a second, a third of the 3 a second ceiling for flashing content
   (brand law; the app's waveform lives in app-expo signal.ts as 140ms eased
   rise, 460ms hold, 180ms eased fall, 220ms dark). The lamp is a small object
   rather than a field. Both heads take the dark half of the blink from the
   same attribute on the same frame, so the two of them together are one event
   in the eye rather than two.

   The edges are eased, never a hard square wave. The transition a change runs
   is the one the destination state carries, so the off rules carry the 180ms
   fall and the lit rules the 140ms rise. Nothing here touches the hood. */
[data-wx="caution"][data-blink="off"] .lamp-a .lens{fill:var(--off-amber);transition:fill .18s ease}
[data-wx="caution"][data-blink="off"] .halo-a{opacity:0;transition:opacity .18s ease}
[data-wx="caution"] .lamp-a .lens{transition:fill .14s ease-out}
[data-wx="caution"] .halo-a{transition:opacity .14s ease-out}

/* the pool itself, dropped on the deck below the headline. The padding at the
   foot is the strip of concrete the status card stands on. */
.hero-pool{position:relative;margin-top:clamp(34px,4.4vw,54px);padding-bottom:118px}
.hero-pool .pool{
  min-height:clamp(310px,32vw,420px);
}
/* The numerals live at the shallow end at every width, where the water is at
   its palest, and the storm shade is thrown from the deep end at the foot of
   the pool. The two are never in the same half of the panel, which is what
   holds the measured contrast of the dark tile type flat across the whole
   weather cycle while the water itself still visibly dampens. */
.hero-pool .floor-mark{
  left:clamp(24px,3.2vw,40px);top:clamp(38px,4.2vw,52px);
  font-size:clamp(30px,4.1vw,54px);
}

/* the status card, standing on the lip of the pool: half over the water, half
   on the deck. It shows the product instead of describing it. */
.signcard{
  position:absolute;z-index:6;
  right:clamp(22px,3vw,44px);bottom:4px;width:min(340px,38%);
  background:var(--card);border:1px solid rgba(255,255,255,.9);
  border-radius:16px;padding:14px 19px 16px;
  box-shadow:10px 10px 0 rgba(6,48,66,.20),0 26px 44px -26px rgba(6,48,66,.6);
}
.sc-verdict{display:flex;align-items:center;gap:16px;padding:2px 0 4px;margin-top:0}
/* the same fixture as the one on the pole, drawn small: cast case, pressed caps,
   a hood over every lens and the two dark lamps still showing their own colour
   through the glass. Same hardware, one scale down. */
.sig{flex:none;display:block;width:42px;height:auto;filter:drop-shadow(0 4px 6px rgba(7,32,40,.30))}
/* The word on the card is the same word the mount is carrying, in the same
   frozen copy, changed by the same writer on the same frame. The block is held
   at the height of its longest state so the card does not resize under the
   reader every time the weather turns. */
.signcard .verdict{
  font-family:var(--display);font-weight:900;color:var(--green);
  font-size:clamp(23px,2.5vw,29px);line-height:1.06;letter-spacing:-.3px;
  min-height:2.12em;display:flex;align-items:center;
}
[data-wx="caution"] .signcard .verdict,
[data-wx="soon"] .signcard .verdict{color:var(--amber)}
[data-wx="closed"] .signcard .verdict{color:var(--red)}
/* the readout: the same three fields the status card carries */
.readout{margin-top:15px;border-top:1px solid var(--edge)}
.readout>div{
  display:flex;justify-content:space-between;align-items:baseline;gap:14px;
  padding:8px 0;border-bottom:1px solid var(--edge);
}
.readout dt{
  font-family:var(--display);font-weight:900;font-size:10.5px;
  letter-spacing:.2em;text-transform:uppercase;color:var(--ink-2);
}
.readout dd{font-weight:600;font-size:14px;color:var(--ink)}
/* The two live readings. Each one is held in a box the width of the longest
   thing it will ever hold and set flush right against the edge of the card, so
   the row it sits in has one geometry for the whole weather cycle and nothing
   around it moves when a value turns over. The figures are tabular and a single
   digit arrives already padded with a figure space, which is exactly one digit
   wide, so a countdown running down from thirty to eight is the same width at
   every step and the digits change in place. */
.readout .ro-v{
  display:inline-block;text-align:right;
  font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;
}
.readout [data-ro="last"]{min-width:5.28em}
.readout [data-ro="wait"]{min-width:5.86em}

/* ---------------- sections ---------------------------------------------- */
section{padding:clamp(60px,8vw,108px) 0}
/* poured slab: a second concrete pour, one shade cooler, to break the run up */
.band{
  box-shadow:inset 0 3px 0 rgba(255,255,255,.7),0 26px 54px -34px rgba(7,32,40,.72);
}
/* The spine: every section opens on a rule that runs the full measure, with the
   headline dropping under it.

   The rule used to carry a small painted label, and every section had one. They
   were cut: the same instinct that took out the section numbers and the chrome
   labels, and for the same reason, which is that a heading that needs a caption
   above it to be understood is not doing its job. What is left is the rule, and
   now that it is not carrying a word it is drawn in ink at the same weight as
   every other opening rule on the site, the ones over the three ideas, over the
   audience table, over the limits, and over every heading on the document
   pages. One mark, used everywhere, rather than a labelled variant here and a
   plain one there. */
.sechead{margin-bottom:clamp(34px,4vw,52px)}
/* a section that carries its head and nothing else closes up behind it */
.sechead-tight{margin-bottom:clamp(24px,2.8vw,36px)}
.sechead h2,.sechead .sub{max-width:660px}
.secrule{
  display:flex;align-items:center;gap:clamp(14px,1.6vw,20px);
  margin-bottom:clamp(16px,1.7vw,22px);
}
.secrule .stencil{flex:none;line-height:1}
.secrule i{display:block;flex:1;height:2px;background:var(--ink)}
h2{
  font-family:var(--display);font-weight:900;
  font-size:clamp(30px,4.8vw,46px);line-height:1.06;letter-spacing:-1px;
}
h3{font-family:var(--display);font-weight:700;font-size:21px;line-height:1.25;letter-spacing:-.2px}
.sub{color:var(--ink-2);margin-top:14px;font-size:17px}
p+p{margin-top:15px}

/* ---------------- state cards -------------------------------------------
   Each card is a painted sign: a bar of its own colour across the top edge,
   and a whole signal head standing beside the words.

   The head is the full three-lamp fixture, not a single dot, because a single
   dot cannot say the one thing the caution state is about: the green stays lit
   while the amber comes on beside it. Two lamps burning on one head is that
   state's entire meaning, and only a whole head can show it.

   These four heads are exhibits, not instruments. Each one is welded to its own
   card's state forever, so their lenses use their own class names (ml-r, ml-a,
   ml-g) rather than the lamp-r/a/g names the two live fixtures answer to. The
   live selectors above key off data-wx on the root and would otherwise reach in
   and relight these lamps with the weather. */
.states{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:18px}
@media (max-width:900px){.states{grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}}
@media (max-width:560px){.states{grid-template-columns:1fr}}
.state{
  min-height:176px;
  background:var(--card);
  /* the accent sits on the top edge and the rest of the hairline is an inset
     ring, so the bar stays the only border on the box */
  border-top:5px solid var(--green);
  border-radius:4px 4px 16px 16px;
  padding:20px 18px 22px;
  display:flex;gap:15px;align-items:flex-start;
}
.minisig{
  flex:none;display:block;width:37px;height:auto;
  filter:drop-shadow(0 4px 5px rgba(7,32,40,.24));
}
.minisig .ml{fill:var(--off-red);stroke:#FFFFFF;stroke-opacity:.22;stroke-width:1.4}
.minisig .ml-a{fill:var(--off-amber)}
.minisig .ml-g{fill:var(--off-green)}
/* the words: verdict at the top of the column, context under it, every card
   aligned to the same first baseline across the row */
.state-say{flex:1;min-width:0}
.state .verdict{
  font-family:var(--display);font-weight:900;
  font-size:19px;letter-spacing:.1px;line-height:1.12;margin-top:.1em;
}
.state .note{font-size:14.5px;color:var(--ink-2);margin-top:9px;line-height:1.55}

.state.open{box-shadow:inset 0 0 0 1px var(--edge),8px 8px 0 var(--green-shade)}
.state.open .verdict{color:var(--green)}
.state.open .ml-g{fill:var(--lamp-green)}
.state.caution{border-top-color:var(--amber-bar);box-shadow:inset 0 0 0 1px var(--edge),8px 8px 0 var(--amber-shade)}
.state.caution .verdict{color:var(--amber)}
/* the caution head burns two lamps: green holding, amber beside it. The amber
   blinks off the same attribute the two live fixtures blink off, so every
   blinking thing on this page changes on one frame off one clock and the whole
   page stays one event in the eye, far under the flash ceiling. With no script
   the attribute never appears and the lamp rests lit. Green never blinks. */
.state.caution .ml-g{fill:var(--lamp-green)}
.state.caution .ml-a{fill:var(--lamp-amber);transition:fill .14s ease-out}
[data-blink="off"] .state.caution .ml-a{fill:var(--off-amber);transition:fill .18s ease}
.state.closed{border-top-color:var(--red);box-shadow:inset 0 0 0 1px var(--edge),8px 8px 0 var(--red-shade)}
.state.closed .verdict{color:var(--red)}
.state.closed .ml-r{fill:var(--lamp-red)}
.state.soon{border-top-color:var(--amber-bar);box-shadow:inset 0 0 0 1px var(--edge),8px 8px 0 var(--amber-shade)}
.state.soon .verdict{color:var(--amber)}
/* reopening soon burns two lamps: red holding, steady amber beside it. Red or
   green is always lit, and the pool is still closed, which is what red says. */
.state.soon .ml-r{fill:var(--lamp-red)}
.state.soon .ml-a{fill:var(--lamp-amber)}

/* ---------------- how it works ------------------------------------------ */
.feats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:clamp(26px,3.4vw,44px)}
@media (max-width:820px){.feats{grid-template-columns:1fr;gap:34px}}
/* each idea opens on its own rule, the same mark every heading on a document
   page gets. It sets the three of them out without counting them off. */
.feat{border-top:2px solid var(--ink);padding-top:18px}
.feat h3{margin-bottom:9px}
.feat p{color:var(--ink-2);font-size:16px}

/* ---------------- who it is for -----------------------------------------
   Not a row of pills. A two column table: the audience on the left, the
   specific thing that happens to them on the right, one hairline per row and
   a painted cobalt tick to start it. */
.who{margin:0;padding:0;list-style:none;border-top:2px solid var(--ink)}
.who li{
  position:relative;
  display:grid;grid-template-columns:minmax(0,290px) minmax(0,1fr);
  gap:6px 34px;align-items:baseline;
  padding:20px 0 20px 28px;
  border-bottom:1px solid var(--edge);
}
.who li::before{
  content:"";position:absolute;top:29px;left:0;
  width:11px;height:11px;background:var(--cobalt);
  /* hard cast in the shade colour, per the law: this tick sits on the deck,
     where the deck's own shade is exactly what its shadow is made of */
  box-shadow:2px 2px 0 var(--deck-shade);
}
.who b{font-family:var(--display);font-weight:700;font-size:20px;letter-spacing:-.2px;color:var(--ink)}
.who span{color:var(--ink-2);font-size:16px}
@media (max-width:760px){
  .who li{grid-template-columns:minmax(0,1fr);gap:4px;padding-top:18px;padding-bottom:18px}
  .who li::before{top:26px}
}

/* lane rope: a real lane line, not a striped bar. The rope runs through the
   middle and pokes out past both ends, and the floats are threaded onto it in
   runs of cobalt and white with a dark seam where each float meets the next,
   which is what reads as separate beads rather than paint. Sun on the top of
   every float, shade under its belly. */
.lane{
  position:relative;height:18px;margin:0 auto;border-radius:999px;
}
.lane::before{
  content:"";position:absolute;top:7px;left:-16px;right:-16px;height:4px;
  border-radius:2px;z-index:0;
  background:linear-gradient(180deg,#EFECE2,#C6C1B0);
  box-shadow:3px 3px 0 var(--deck-shade);
}
.lane::after{
  content:"";position:absolute;inset:0;z-index:1;border-radius:9px;
  background:
    linear-gradient(180deg, rgba(255,255,255,.62) 0%, rgba(255,255,255,.06) 44%, rgba(5,60,84,.26) 100%),
    repeating-linear-gradient(90deg,
      var(--cobalt) 0 25px, rgba(4,42,60,.55) 25px 27px,
      #F4FAFD 27px 52px, rgba(4,42,60,.35) 52px 54px);
  box-shadow:5px 5px 0 var(--deck-shade);
}

/* ---------------- posted rules placard ----------------------------------
   Each limit gets a one word label on the left and the full sentence on the
   right, so the whole list scans in four words before you read a line of it. */
.placard{
  background:var(--card);border:1px solid var(--edge);border-radius:20px;
  padding:clamp(26px,3.4vw,40px);
  box-shadow:14px 14px 0 var(--deck-shade),var(--lift);
}
.limitlist{margin:0;padding:0;list-style:none;border-top:2px solid var(--ink)}
.limitlist li{
  display:grid;grid-template-columns:minmax(0,148px) minmax(0,1fr);
  gap:4px 26px;align-items:baseline;
  padding:16px 0;border-bottom:1px solid var(--edge);
  color:var(--ink-2);font-size:16px;
}
.limitlist b{
  font-family:var(--display);font-weight:900;font-size:11.5px;
  letter-spacing:.22em;text-transform:uppercase;color:var(--sea);line-height:1.9;
}
.limitlist strong{color:var(--ink);font-weight:600}
@media (max-width:700px){
  .limitlist li{grid-template-columns:minmax(0,1fr);gap:2px}
  .limitlist b{line-height:1.6}
}

/* disclaimer: a posted notice. Bar across the top, sun cast to the lower right.
   The hairline is an inset ring drawn with a shadow, not a border, so the bar
   on the top edge stays the only border this box has. */
.disclaimer{
  background:#FBF3E2;border-top:5px solid var(--amber-bar);
  border-radius:4px 4px 12px 12px;
  padding:18px 22px;color:var(--ink);font-size:15px;line-height:1.6;
  margin-top:30px;
  box-shadow:inset 0 0 0 1px #E8D3A4,6px 6px 0 #EFE3C6;
}
.prose .disclaimer{color:var(--ink);font-size:15px}
.disclaimer strong,.prose .disclaimer strong{
  font-family:var(--body);font-weight:800;color:var(--ink);
}

/* ---------------- prose (support, privacy) ------------------------------ */
.pagehead{padding:clamp(38px,5vw,64px) 0 0;margin-top:clamp(54px,9vh,104px)}
.pagehead h1{font-size:clamp(40px,6.6vw,64px)}
.pagehead .stencil{margin-bottom:16px;display:block}
.pagehead .lede{font-size:clamp(16.5px,1.9vw,19px);max-width:640px}
.prose-sec{padding:clamp(46px,5.6vw,74px) 0 clamp(60px,7vw,96px)}
.waterline{
  position:relative;height:118px;margin-top:clamp(30px,4vw,48px);
  border:11px solid var(--coping);
  border-radius:24px;overflow:hidden;isolation:isolate;
  background:radial-gradient(120% 220% at 12% -70%, var(--water-lite) 0%, var(--water) 40%, var(--water-mid) 74%, var(--water-deep) 100%);
  box-shadow:var(--cast),var(--lift);
}
.waterline::after{
  content:"";position:absolute;inset:0;z-index:3;pointer-events:none;
  opacity:calc(.34 + .66 * var(--sun, 1));
  background:linear-gradient(180deg, rgba(255,255,255,.42), rgba(255,255,255,0) 40%);
}
.pagehead .wrap+.wrap{padding-bottom:clamp(30px,4vw,48px)}

/* every heading on a document page starts on its own hairline, the same rule
   the section labels drag across the home page */
.prose h2{
  margin-top:52px;font-size:clamp(25px,3.4vw,32px);
  border-top:2px solid var(--ink);padding-top:18px;
}
.prose>h2:first-child{margin-top:0}
.prose h3{margin-top:30px;color:var(--ink)}
.prose h2+h3{margin-top:22px}
.prose p,.prose li{color:var(--ink-2);font-size:16.5px}
.prose ul,.prose ol{margin:14px 0 0 22px}
.prose li{margin-bottom:9px}
.prose li::marker{color:var(--sea)}
.prose strong{color:var(--ink);font-weight:600}
.updated{font-size:14px;color:var(--ink-2);margin-top:12px}
.callout{
  background:var(--card);border-top:5px solid var(--water-mid);
  border-radius:4px 4px 16px 16px;
  padding:24px 26px;margin-top:32px;
  box-shadow:inset 0 0 0 1px var(--edge-cool),var(--cast);
}
.callout h3{margin-top:0}
.callout p{color:var(--ink-2);margin-top:8px}

/* ---------------- footer ------------------------------------------------ */
footer{padding:clamp(42px,5vw,62px) 0 clamp(40px,4.6vw,58px);color:var(--ink-2);font-size:14.5px}
footer.plate{margin-bottom:clamp(26px,5vh,64px)}
footer .lane{max-width:1028px;margin-bottom:36px}
footer .cols{display:flex;flex-wrap:wrap;gap:18px 40px;justify-content:space-between;align-items:flex-start}
footer nav{display:flex;gap:24px;flex-wrap:wrap}
footer a{color:var(--ink);font-weight:600}
footer a:hover{color:var(--sea)}
/* The wordmark carries its own ink. "To" is a bare text node between the two
   coloured spans, so without this it inherits the footer's --ink-2 and the
   middle word of the wordmark drifts off the governed #072028 (BRAND.md,
   light-surface cut). Same shape as the header's .brand rule. */
footer .mark{font-family:var(--display);font-weight:900;font-size:17px;letter-spacing:-.2px;color:var(--ink)}
footer .mark .c{color:var(--green)}
footer .mark .s{color:var(--sea)}
.legal{margin-top:26px;font-size:13px;line-height:1.62;max-width:660px;color:var(--ink-2)}

/* ---------------- the simulation tag -------------------------------------
   A small stencil pinned to the top edge of the status card. It exists because
   the card looks like an instrument, and an instrument that is actually a
   demonstration owes the reader one word saying so. The sr-only sentence
   beside it carries the full statement for assistive technology. */
/* The simulation callout. It used to be a tab clipped to the top corner of
   the page, which is where a browser puts a warning, not where a pool puts a
   sign. It hangs under the fixture's own verdict now, so it reads as a label
   on the thing it qualifies: this signal, the one you are looking at, is a
   staged one. */
/* The countdown, under the fixture and under the callout, so the number and
   the lamp it belongs to are one object. It was a box in the top left corner
   of the page, a long way from the signal it was counting for. */
.rig-clock{
  margin:8px 0 0;display:none;align-items:baseline;gap:5px;
  font-family:var(--display);font-weight:900;line-height:1;
}
[data-wx="closed"] .rig-clock,
[data-wx="soon"] .rig-clock{display:inline-flex}
.rig-clock b{font-size:30px;font-variant-numeric:tabular-nums;color:var(--red)}
.rig-clock span{font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-2)}
[data-wx="soon"] .rig-clock b{color:var(--amber)}

.sc-sim{
  margin:6px 0 0;
  display:inline-block;
  font-family:var(--display);font-weight:900;font-size:10.5px;
  letter-spacing:.18em;text-transform:uppercase;color:var(--ink-2);
  background:var(--deck);padding:4px 12px;border-radius:999px;
  box-shadow:inset 0 0 0 1px var(--edge),3px 3px 0 var(--deck-shade);
}

/* the tuck-away control on the pinned mobile card. Drawn on every page it is
   needed on and hidden everywhere else; only the phone layout shows it. */
.sc-toggle{
  display:none;margin-left:auto;flex:none;align-items:center;justify-content:center;
  width:44px;height:44px;border:1px solid var(--edge);border-radius:10px;
  background:var(--deck);color:var(--ink-2);cursor:pointer;
  box-shadow:2px 2px 0 var(--deck-shade);
}
/* the button ships hidden and the script that gives it behavior reveals it,
   so without script no one is handed a control that does nothing. This guard
   outranks the phone rule that would otherwise resurrect it. */
.sc-toggle[hidden]{display:none !important}
.sc-toggle svg{width:15px;height:15px;display:block;transition:transform .25s ease}
html.sc-shut .sc-toggle svg{transform:rotate(180deg)}

/* ---------------- floor labels -------------------------------------------
   The two numbers painted on the pool floor each carry a one word label above
   them, so the graphic says what each number is before the reader has met the
   rule. Painted tile, same ink as the numerals, quieter weight. */
.fm-lab{
  display:block;font-style:normal;font-family:var(--body);font-weight:800;
  font-size:clamp(10.5px,.9vw,12px);letter-spacing:.22em;text-transform:uppercase;
  color:var(--tile-ink);opacity:.78;margin-bottom:2px;
}

/* depth tiles on the pool edge, the way a real deck is stencilled. Artwork:
   hidden from assistive technology along with the rest of the pool painting. */
.depth{
  position:absolute;z-index:3;
  font-family:var(--display);font-weight:900;
  font-size:clamp(11px,1.1vw,14px);letter-spacing:.2em;
  color:var(--tile-ink);opacity:.62;text-shadow:0 2px 0 rgba(255,255,255,.3);
}
/* shallow at the top of the panel beside the numerals, deep at the foot where
   the storm shade falls. The card holds the lower right, so 9 FT keeps left. */
.d-shallow{right:clamp(20px,2.8vw,36px);top:clamp(14px,1.8vw,22px)}
.d-deep{left:clamp(24px,3.2vw,40px);bottom:14px}

/* ---------------- deck furniture -----------------------------------------
   Small drawn objects that say "pool" without saying anything else. All of
   them are artwork: aria-hidden, no motion, no flash, and every one of them
   throws its shadow the way everything else here does, right and down. */

/* the ladder into the waterline band on the document pages. It stands proud
   of the band's top edge, so it lives on the wrap, not inside the clipped
   water. */
.pagehead .wrap{position:relative}
.ladder{
  /* feet planted inside the water band: the wrap carries clamp(30px,4vw,48px)
     of padding under the band, so the ladder stands that plus a step above
     the wrap's bottom edge, rails curling up over the coping */
  position:absolute;right:clamp(26px,7vw,90px);
  bottom:calc(clamp(30px,4vw,48px) + 26px);
  z-index:4;width:clamp(56px,7vw,72px);height:auto;
  filter:drop-shadow(0 5px 4px rgba(7,32,40,.3));
}

/* the life ring hung beside the posted rules. Safety gear lives next to the
   rules sign at every real pool. */
.placard{position:relative}
.lifering{
  position:absolute;top:-30px;right:clamp(18px,4vw,44px);z-index:2;
  width:clamp(64px,7.4vw,84px);height:auto;transform:rotate(8deg);
  filter:drop-shadow(0 6px 5px rgba(7,32,40,.28));
}

/* the link out to the pool day demo, standing at the foot of How it works */
.demo-cta{margin-top:clamp(30px,3.6vw,44px)}
.btn-demo{
  display:inline-flex;align-items:center;gap:10px;
  font-family:var(--display);font-weight:700;font-size:17px;letter-spacing:.1px;
  color:var(--ink);background:var(--card);
  border:1px solid var(--edge);border-radius:999px;
  padding:12px 22px;box-shadow:var(--cast-sm);
}
.btn-demo:hover{text-decoration:none;background:var(--deck);color:var(--sea)}
.btn-demo svg{width:13px;height:13px;color:var(--sea)}

/* ---------------- the treeline -------------------------------------------
   The planting stands in the world, not on the page: a fixed band of trees
   along the foot of the viewport, in front of the weather and behind every
   plate, the way the far edge of a pool deck has trees between you and the
   sky. It shows in the gutters, in the joints as the plates scroll past, and
   in full below the last plate.

   It takes the sun the way the water does. The sky script drives --sun from
   1 to 0 as the cloud comes over, and the whole band darkens and desaturates
   with it, so a storm dims the trees on the same frame it dims the caustic
   net on the pool floor. On an engine that cannot do calc() inside a filter
   the filter drops and the trees simply stand at noon, which is the same
   fallback the rest of the weather takes. */
.treeline{
  position:fixed;left:0;right:0;bottom:0;z-index:0;pointer-events:none;
  height:clamp(48px,7.5vh,92px);
  filter:brightness(calc(.45 + .55 * var(--sun, 1))) saturate(calc(.65 + .35 * var(--sun, 1)));
}
.treeline svg{display:block;width:100%;height:100%}

/* ---------------- the pool day demo --------------------------------------
   One community pool, seen from above, running one compressed day on a loop.
   The scene is its own little world with its own clock: the sky overhead keeps
   the ambient weather, and the scene runs the four states fast enough to watch
   in under a minute. The crowd is the display: where the people stand IS the
   state.

   The people are positioned in percent of the scene and moved by transitioning
   left and top. Fourteen elements moving once per state change is nothing, and
   percent coordinates mean one set of waypoints serves every viewport. Every
   moving thing is aria-hidden; the readout above the scene carries the state
   in words for everybody else.

   Nothing in the scene flashes. The amber lamp on the scene board blinks only
   off the site-wide data-blink attribute, the same single train as every other
   lamp, and only while the SCENE is in caution (its own attribute), so the two
   conditions rarely coincide and when they do they are in the same phase by
   construction. */
/* ---------------- the demo page ------------------------------------------
   One screen, edge to edge. The page is a fixed column: masthead, a control
   bar, the scene taking every pixel that is left, and a thin legal strip.
   Nothing scrolls, so the scene is never something you have to go and find.

   The scene fills its box rather than keeping a drawing's aspect, and the
   artwork covers by slicing. That decouples the picture from the viewport,
   which is why the crowd is not positioned against the box: a layer sized in
   script to the sliced viewBox holds them, so a person at 40% of the drawing
   is at 40% of the drawing at every window shape. See the note in demo.html. */
html.demo, body.demo{height:100%;overflow:hidden}
body.demo{padding-bottom:0;display:flex;flex-direction:column}
body.demo .site-head{position:static;flex:none}
body.demo .treeline{display:none}
.demo-main{flex:1;min-height:0;display:flex;flex-direction:column}
.demo-bar{
  flex:none;display:flex;align-items:center;gap:10px 18px;flex-wrap:wrap;
  padding:0 clamp(12px,2vw,24px) 10px;
}
.demo-bar h1{font-size:clamp(17px,1.7vw,23px);letter-spacing:-.4px;line-height:1.05;flex:none}
.demo-bar .stencil{margin:0;flex:none}
.demo-bar .wx-btns{margin-left:auto}
/* The tier line: the only prose this page draws, and the reason the bar is no
   longer an empty row. Held to a readable measure so it never runs the full
   width of a wide window, and coloured as secondary type so it reads as a
   caption on the simulation rather than as a headline over it.

   IT MUST BE ABLE TO SHRINK. This shipped as flex:none with a bare 78ch cap,
   which on a phone is a box 446px wide inside a 375px window that refuses to
   give any of it back. The page is overflow:hidden, so there was no scrollbar
   and no clue: the last eighty pixels of every line were simply cut off, and a
   disclosure nobody can finish reading is worse than none. It shrinks now, and
   100% is the floor under the measure at every width.

   AND IT STANDS ON A PLATE. It was set straight onto the sky, which is fine
   for the ten minutes of the cycle the sky is pale and gone the moment the
   storm arrives: dark type on a dark sky, on the one page where the weather
   is guaranteed to turn. This is the law the rest of the page already keeps.
   The verdict under the head is painted on an opaque plate for exactly this
   reason, and so is the safety notice at the other corner. Same plate, same
   hairline, same hard cast down and right. */
.demo-tier{
  flex:1 1 auto;min-width:0;margin:0;max-width:min(78ch, 100%);
  font-size:12.5px;line-height:1.4;color:var(--ink-2);
  background:var(--card);border-radius:8px;padding:7px 13px 8px;
  box-shadow:inset 0 0 0 1px var(--edge),3px 3px 0 var(--deck-shade);
}
/* The safety notice. It ran the full width of the foot of the page, which
   made it read as a caption belonging to the picture. It is a notice pinned
   in the corner now, held to a readable measure and kept out from under the
   artwork, which is where a notice on a pool fence actually goes. */
.demo-legal{
  flex:none;font-size:11px;line-height:1.45;color:var(--ink-2);
  align-self:flex-start;
  max-width:min(46ch, calc(100% - 2 * var(--gut)));
  margin:0 auto clamp(16px,3.2vh,34px) var(--gut);
  padding:7px 13px 8px;background:var(--card);border-radius:8px;
  box-shadow:inset 0 0 0 1px var(--edge),3px 3px 0 var(--deck-shade);
}
/* The artwork is a panel laid over the weather, not a replacement for it.
   It went edge to edge of the WINDOW, which covered the sky the page is
   arguing about; it is the width of the page's own column now, on the same
   measure as every plate on the site, so the weather runs down both sides
   of it and the mounted fixture has its gutter back. */
.demo-sec .scene-wrap{
  position:relative;flex:1;min-height:0;
  width:calc(100% - 2 * var(--gut));
  max-width:min(1480px, calc(100% - 1.35 * var(--rig-gut) - 2 * var(--gut)));
  margin:0 auto 10px;
}
.scene{
  position:absolute;inset:0;overflow:hidden;
  border-radius:12px;
  box-shadow:inset 0 0 0 1px rgba(7,32,40,.22), 6px 6px 0 rgba(7,32,40,.16);
  background:#7CAF66;
  /* the whole scene takes the sun: deck, grass and furniture dim under the
     cloud the way the ground does, on the same --sun the sky writes */
  filter:brightness(calc(.88 + .12 * var(--sun, 1)));
  transition:filter 1.6s ease;
}
/* the layer the crowd stands on, mapped by script onto the chosen crop */
.crowd{position:absolute;left:0;top:0;width:100%;height:100%}

/* What the cloud does. The site loses its light as a whole, and the water
   loses a little more on top of that, because water under cloud goes flat
   as well as dark.

   All of it is eased. These read a custom property the weather engine
   rewrites every frame, and on a scenario press that property can cross its
   whole range inside a second, which arrived as a switch being thrown. A
   transition on each dependent property turns that into cloud shadow moving
   across the ground, which is what it is.

   Every one of these carries a fallback of full sun. A custom property that
   has not been written yet makes the whole declaration invalid, and an
   invalid opacity does not fall back to the value in the markup, it falls
   back to the INITIAL value, which is 1. For the shine that happened to be
   harmless and for the old water shade it happened to be harmless, so it
   went unnoticed for as long as nothing dark depended on it. A full bleed
   shade defaulting to fully opaque is a black slab over the artwork on every
   page that has not heard from the weather engine yet, which includes every
   page for the first frame and every page that never gets a canvas at all. */
.scene .scene-cloudshade{
  opacity:calc((1 - var(--sun, 1)) * .34);
  transition:opacity 1.6s ease;
}
.scene .scene-shine{opacity:calc(.2 + .8 * var(--sun, 1));transition:opacity 1.6s ease}

/* a one-shot ring where somebody hits the water; retriggered by the script,
   never looping, and dead under the global reduced-motion reset */
/* The splash.

   A ring is not a splash. Water thrown up by a body has four things in it
   and they happen on different clocks: a white foam crown that punches up
   and collapses almost at once, a scatter of droplets thrown outward and
   falling back, and behind them the ripple rings spreading and flattening
   long after the crown is gone. Each part gets its own keyframes and its
   own easing, which is what makes it read as water rather than as a
   shape that grew.

   Everything is drawn around the origin of a centred viewBox, so a plain
   CSS transform scales and rotates about the point of entry with no need
   for transform-box. The whole thing is sized with a floor in pixels as
   well as a share of the scene, because a percentage alone vanishes on a
   phone. */
.splash{
  position:absolute;z-index:7;
  width:max(30px,7.4%);aspect-ratio:1;
  transform:translate(-50%,-50%);
  pointer-events:none;
}
.splash.small{width:max(20px,4.6%)}
/* the one off the chute: a rider arriving at speed throws a lot more water
   than somebody stepping in off the side */
.splash.big{width:max(46px,12.4%)}
.splash.big .sp-ring{stroke-width:3.4}
.splash svg{width:100%;height:100%;display:block;overflow:visible}
.sp-ring{fill:none;stroke:#FFFFFF;opacity:0}
.sp-crown{fill:#FFFFFF;opacity:0}
.sp-drops circle{fill:#FFFFFF;opacity:0}

.splash.go .sp-crown{animation:sp-crown .40s cubic-bezier(.1,.8,.3,1) 1}
.splash.go .sp-ring1{animation:sp-ring .78s cubic-bezier(.15,.75,.3,1) 1}
.splash.go .sp-ring2{animation:sp-ring 1s cubic-bezier(.15,.75,.3,1) .13s 1}
.splash.go .sp-drops circle{animation:sp-drop .72s cubic-bezier(.12,.65,.4,1) 1}
.splash.go .sp-drops circle:nth-child(3n){animation-duration:.86s;animation-delay:.04s}
.splash.go .sp-drops circle:nth-child(4n){animation-duration:.64s;animation-delay:.02s}

/* the crown: up fast, gone fast */
@keyframes sp-crown{
  0%{opacity:.9;transform:scale(.28)}
  40%{opacity:.72}
  100%{opacity:0;transform:scale(1.5)}
}
/* the rings: spreading and thinning as they go */
@keyframes sp-ring{
  0%{opacity:.85;stroke-width:10;transform:scale(.16)}
  100%{opacity:0;stroke-width:1.5;transform:scale(1.55)}
}
/* the droplets: out, and smaller as they fall away */
@keyframes sp-drop{
  0%{opacity:0;transform:rotate(var(--a)) translateY(-5px) scale(1)}
  14%{opacity:1}
  100%{opacity:0;transform:rotate(var(--a)) translateY(calc(-1 * var(--d))) scale(.28)}
}

/* the sun's cast shadows in the scene: each group declares its own full-sun
   depth, and the cloud takes almost all of it away */
.scene .scene-sunshade{opacity:calc(var(--shade-base,.2) * (0.08 + 0.92 * var(--sun, 1)))}

/* water running down the flume, a slow crawl of highlights; the global
   reduced-motion reset stops it dead */
/* The dash pattern is 18 on, 28 off, so it repeats every 46 units and the
   offset has to travel exactly that far to loop without a seam. It used to
   travel 24, which is not a whole pattern, so every cycle the water jumped
   backwards half a dash. */
.flume-flow{animation:flume-run 1.7s linear infinite}
.flume-flow-b{animation:flume-run-b 2.6s linear infinite}
@keyframes flume-run{from{stroke-dashoffset:0}to{stroke-dashoffset:-28}}
@keyframes flume-run-b{from{stroke-dashoffset:0}to{stroke-dashoffset:-34}}

/* the caustic net on the pool floor. Two layers of thin light drifting at
   different speeds and in different directions, which is what stops still
   water reading as a painted shape. Each layer translates exactly one wave
   period, so the loop has no seam. */
/* AND THE DRAWN NET IS OFF. Eleven identical sine rows exactly 38 apart with
   a wave 160 long, over a second set of ten: that is the most literally
   regular thing anywhere on this water, and it is what the eye is reading
   when the reflections are called patterned. It does not even loop. Each
   layer travels 184 per cycle against a wave 160 long and a dash 56 long, so
   neither one lands back on itself and both jump 24 sideways every time they
   restart. The glint tilings at the foot of this file do the same job with
   no repeat in them. The twenty one paths themselves are still in demo.html
   and want deleting whenever somebody is next in that file. */
.cx-net{display:none}
/* broken into glints rather than drawn as continuous lines: an unbroken
   wave across the whole pool reads as a scribble on the water, not as light
   on the floor of it */
.cx-net-a{stroke-width:2;opacity:.1;stroke-dasharray:26 30;animation:cx-a 19s linear infinite}
.cx-net-b{stroke-width:1.5;opacity:.075;stroke-dasharray:18 34;animation:cx-b 27s linear infinite}
@keyframes cx-a{to{transform:translate(184px,0)}}
@keyframes cx-b{to{transform:translate(-184px,0)}}

/* ---- what the rain does -------------------------------------------------
   The ground goes dark and slick, and the pool surface is pocked by drops.
   Both ride data-sky, which the weather engine writes, so the deck is wet
   for exactly as long as the storm is overhead. */
.wx-wet{opacity:0;transition:opacity 2.4s ease}
[data-sky="storm"] .wx-wet{opacity:1}
/* by the height of it the deck is not damp, it is under water */
[data-sky="storm"] .wx-wet rect{opacity:.72}
/* The drops themselves, which are not the same thing as the deck being dark.
   They land a few at a time and build for the length of the storm until the
   whole deck is covered, then dry off afterwards. Drying is the slowest
   thing on the page on purpose: a wet deck outlasts the cloud that made it,
   which is why the sun can be back out and the ground still soaked. */
.wx-drops{opacity:0;transition:opacity 2.4s ease-out}
[data-sky="storm"] .wx-drops{opacity:1;transition:opacity 13s linear}
.wx-rainpock{opacity:0;transition:opacity 1.2s ease}
[data-sky="storm"] .wx-rainpock{opacity:1}
/* fill-box, so each pock opens around the drop that made it. Without it a
   shape inside the scene scales about the middle of the whole drawing and
   goes travelling across the pool as it grows. */
.rp circle{
  fill:none;stroke:#FFFFFF;stroke-width:3;opacity:0;
  transform-box:fill-box;transform-origin:center;
}
[data-sky="storm"] .rp1 circle{animation:rp-ring .78s linear infinite}
[data-sky="storm"] .rp2 circle{animation:rp-ring .95s linear infinite .22s}
[data-sky="storm"] .rp3 circle{animation:rp-ring .86s linear infinite .48s}
@keyframes rp-ring{
  0%{transform:scale(.15);opacity:.85}
  100%{transform:scale(1.5);opacity:0}
}

/* ---- the mouth of the flume ---------------------------------------------
   A slide is a hose. The water arriving at the bottom never stops, so this
   never stops either: aerated foam breathing at the surface, rings leaving
   on a stagger so one is always mid-flight, and two threads of spill being
   pushed out across the water. Three offset clocks, none of them a multiple
   of another, which is what keeps it from pulsing in time with itself. */
.flume-mouth *{transform-box:fill-box;transform-origin:center}
.flm-foam{
  fill:#FFFFFF;opacity:.5;
  animation:flm-breathe 2.3s ease-in-out infinite;
}
.flm-ring{
  fill:none;stroke:#FFFFFF;stroke-width:3;opacity:0;
  animation:flm-ring 2.4s cubic-bezier(.2,.7,.35,1) infinite;
}
/* negative, so all three are already in flight on the first frame rather
   than the mouth going quiet for the first second and a half */
.flm-r2{animation-delay:-.8s}
.flm-r3{animation-delay:-1.6s}
.flm-spill{opacity:.55;animation:flm-spill 1.5s ease-in-out infinite}
@keyframes flm-breathe{
  0%,100%{opacity:.34;transform:scale(.86)}
  50%{opacity:.6;transform:scale(1.06)}
}
@keyframes flm-ring{
  0%{opacity:.75;stroke-width:5;transform:scale(.35)}
  100%{opacity:0;stroke-width:1;transform:scale(1.75)}
}
@keyframes flm-spill{
  0%,100%{opacity:.28;transform:translate(0,0)}
  50%{opacity:.62;transform:translate(5px,2px)}
}

/* wet feet: anybody who has just climbed out tracks prints across the deck,
   and they dry off behind them */
.prints{position:absolute;inset:0;pointer-events:none;z-index:4}
.pr{
  position:absolute;width:0.95%;aspect-ratio:1/1.55;
  border-radius:52% 52% 44% 44%;
  background:rgba(61,87,102,.4);
  transform:translate(-50%,-50%) rotate(var(--a,0deg));
  animation:pr-dry 7s ease-out forwards;
}
@keyframes pr-dry{0%{opacity:.6}70%{opacity:.3}100%{opacity:0}}

/* The plank flexes when somebody bounces on the end of it. Seen from above
   a dip is foreshortening, not travel, so the board shortens from its
   mounting instead of sliding: the tip dips toward the water and springs
   back past level. */
/* The gate on the car park side, the only thing in the picture that moves
   because of the VERDICT rather than because of the weather.

   READ THE DRAWING BEFORE CHANGING THESE ANGLES. The leaf is drawn lying in
   the gap in the fence, vertical, filling it: the position it is drawn in is
   SHUT, not open. So rotating it away from that is what opens it, and the
   untransformed state is the closed one. This was backwards to start with,
   which stood the gate open all through a closure and shut it again the
   moment the pool reopened.

   It hangs off the top pin, so the free end is the one that travels, and it
   swings out over the car park rather than back across the deck people are
   trying to leave by. Open whenever the pool is open. Shut for caution,
   for the closure, and through reopening, because nobody is being let back
   in until the wait is over. */
.gate-leaf{
  transform-box:fill-box;transform-origin:top center;
  transform:rotate(-82deg);          /* swung out of the opening: open */
  transition:transform 1.1s cubic-bezier(.3,.8,.4,1);
}
[data-wx="caution"] .gate-leaf,
[data-wx="closed"] .gate-leaf,
[data-wx="soon"] .gate-leaf{transform:rotate(0deg)}   /* across the gap: shut */

/* The plank is bolted at its TOP now that the board is on the far wall and
   reaches south into the water, so it has to shorten from there. Flexing
   from the old bottom origin would have pinned the diving end and moved the
   mounting, which is the wrong end of a diving board to have move. */
.dive-plank{transform-box:fill-box;transform-origin:top center}
.board-bounce .dive-plank{animation:plank-flex .52s ease-out 1}
@keyframes plank-flex{
  0%{transform:scaleY(1)}
  35%{transform:scaleY(.955)}
  70%{transform:scaleY(1.018)}
  100%{transform:scaleY(1)}
}
.scene svg{position:absolute;inset:0;width:100%;height:100%;display:block}
/* three layers: the ground, then the crowd, then the roofs and canopies the
   crowd can genuinely walk beneath. Stepping into the pool house is a real
   disappearance because the roof is painted over the people, not because
   anyone fades. */
.scene .scene-ground{z-index:1}
.scene .scene-roof{z-index:8;pointer-events:none}

/* The wake. A body moving through water pushes it, and the push spreads and
   dies away behind them. It runs only while somebody is actually swimming a
   leg, which is what separates a swimmer from a floater: the pool is never
   all moving at once, so the ones that are read as the ones that are. It
   sits behind its own person, since a wake is water and they are not. */
/* Over the flume, and only while riding it. Everybody else on this deck
   passes underneath, which is what the flume being on the layer above the
   crowd buys, and this is the single exception to it. */
.pp.on-top{z-index:9}
/* THE FAMILY END. A child is smaller than an adult and wears their floats,
   which is how you can tell at a glance who the walk in corner is for. */
.pp.kid{width:2.35%}
/* THE FLOATS. A ring drawn as a border round the whole dot read as a hoop
   somebody had thrown at them. It is a float they are sitting IN: a torus
   painted with a radial gradient, behind the body, only a little wider than
   the child, with the light on its near side. */
.pp.kid .fl{
  position:absolute;left:50%;top:56%;width:132%;aspect-ratio:1/.92;
  transform:translate(-50%,-50%);border-radius:50%;
  background:radial-gradient(closest-side,
    rgba(0,0,0,0) 0 54%,
    #E9662C 56%, #FF9152 64%, #FFB183 71%, #EE7136 78%,
    rgba(0,0,0,0) 83%);
  filter:drop-shadow(0 2px 2px rgba(7,32,40,.28));
  pointer-events:none;z-index:-1;
}

.pp .wk{
  position:absolute;left:50%;top:56%;
  width:210%;aspect-ratio:1/.62;
  transform:translate(-50%,-50%);
  border-radius:50%;opacity:0;pointer-events:none;z-index:-1;
  background:radial-gradient(closest-side,rgba(255,255,255,.42),rgba(255,255,255,.14) 58%,rgba(255,255,255,0) 78%);
}
.pp.in-w.swim .wk{animation:wk 1.45s ease-out infinite}
/* THE WAKE OFF THE CHUTE. A rider leaving the mouth is travelling, not
   drifting, so what they push is longer than it is wide and trails behind
   them rather than spreading evenly round. */
.pp.in-w.glide .wk{
  width:330%;aspect-ratio:1/.5;left:34%;
  background:radial-gradient(closest-side,rgba(255,255,255,.62),rgba(255,255,255,.24) 52%,rgba(255,255,255,0) 76%);
  animation:wk-glide .62s ease-out infinite;
}
@keyframes wk-glide{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.5,.7)}
  18%{opacity:.95}
  100%{opacity:0;transform:translate(-58%,-50%) scale(1.5,1.15)}
}
/* The per person jitter is a MULTIPLIER now and not a duration. The duration
   itself is worked out from how fast this person is actually going, at the
   foot of this file, and a literal here would have overruled that for every
   second and third body in the crowd. 1.21 and 1.1 are the two old durations
   over the 1.45 they were varying. */
.pp.in-w.swim:nth-of-type(2n) .wk{--dmRflJit:1.21;animation-delay:-.5s}
.pp.in-w.swim:nth-of-type(3n) .wk{--dmRflJit:1.1;animation-delay:-.9s}
@keyframes wk{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.42)}
  22%{opacity:.8}
  100%{opacity:0;transform:translate(-50%,-50%) scale(1.55)}
}

/* the people. A head seen from above: a dot of skin or suit colour with the
   sun's highlight on one shoulder. In the water they carry a splash ring. */
/* A person, seen from directly above: the crown of the head over the line of
   the shoulders. Two elements rather than one dot, because at this scale the
   head is what makes a coloured circle read as somebody. Clothing and skin
   are separate custom properties: the old single colour conflated them, so
   half the crowd was wearing a skin tone. */
.pp{
  position:absolute;z-index:5;width:3.4%;aspect-ratio:1;
  transform:translate(-50%,-50%);
  transition:left 2.1s cubic-bezier(.5,.06,.34,1),top 2.1s cubic-bezier(.5,.06,.34,1),opacity .8s ease,transform .25s ease;
}
/* the shoulders */
.pp::before{
  content:"";position:absolute;inset:16% 2% 4% 2%;
  border-radius:46% 46% 50% 50%;
  background:var(--suit,#4A6FA5);
  background-image:linear-gradient(150deg,rgba(255,255,255,.42),rgba(255,255,255,0) 55%,rgba(4,26,36,.22));
  box-shadow:1.4px 2px 0 rgba(7,32,40,.24);
}
/* the head */
.pp::after{
  content:"";position:absolute;left:50%;top:0;width:56%;aspect-ratio:1;
  transform:translateX(-50%);border-radius:50%;
  background:var(--skin,#E7B98F);
  background-image:radial-gradient(circle at 34% 28%,rgba(255,255,255,.5),rgba(255,255,255,0) 58%);
  box-shadow:1px 1.6px 0 rgba(7,32,40,.2);
}
/* in the water only the head and a shoulder line show, and the swimmer
   carries the ring of disturbed water that follows a body */
/* A body under water is darker and bluer, not see through. This used to
   drop the shoulders to 72% opacity, which let the pool floor show straight
   through everybody swimming in it. The colour is washed toward the water
   instead, so a submerged shoulder reads as submerged and still reads as
   solid. */
.pp.in-w::before{
  inset:30% 12% 10% 12%;box-shadow:none;
  background-image:linear-gradient(150deg,rgba(196,240,252,.42),rgba(255,255,255,0) 42%,rgba(6,74,105,.5));
}
.pp.in-w::after{
  top:6%;width:52%;
  box-shadow:0 0 0 2.4px rgba(255,255,255,.55),0 0 0 5.5px rgba(255,255,255,.16);
}
/* airborne, for the length of a jump: the dot lifts, grows, and its shadow
   falls away beneath it */
/* airborne: the body lifts and grows, and the shadow it casts separates and
   falls behind it, which is the whole read of a jump seen from above */
.pp.air{transform:translate(-50%,-64%) scale(1.3)}
.pp.air::before{box-shadow:5px 9px 6px rgba(7,32,40,.34)}
.pp.air::after{box-shadow:4px 7px 5px rgba(7,32,40,.28)}
/* a little idle bob so the pool reads as alive. Slow, small, and killed
   outright by the reduced-motion reset at the foot of this file. */
/* everyone in the water bobs, on their own cadence, so the pool is never
   perfectly still even between swims */
@keyframes pp-bob{from{margin-top:-0.5%}to{margin-top:0.5%}}
.pp.in-w{animation:pp-bob 2.4s ease-in-out infinite alternate}
.pp.in-w:nth-of-type(2n){animation-duration:3.1s;animation-delay:-.9s}
.pp.in-w:nth-of-type(3n){animation-duration:2.7s;animation-delay:-1.6s}
.pp:nth-child(odd).in-w{animation-duration:3.4s;animation-delay:-1.2s}

/* the scene board: the verdict, the clock and the caption over the scene,
   drawn like the pinned card so the reader has seen this instrument before */
.demo-board{
  position:relative;z-index:6;flex:none;
  display:flex;align-items:center;gap:12px;
  background:var(--card);
  /* the bar on the top edge stays the only border this box has; the ring is
     an inset shadow, per the law for any box that carries a top bar */
  border-top:5px solid var(--green);border-radius:6px 6px 12px 12px;
  padding:6px 14px 8px;
  box-shadow:inset 0 0 0 1px var(--edge),6px 6px 0 var(--deck-shade);
}
/* the wait, in whole minutes, big enough to watch tick down */
.db-clock{
  margin-left:14px;flex:none;display:none;align-items:baseline;gap:5px;
  font-family:var(--display);font-weight:900;line-height:1;
}
.demo-sec[data-demo="closed"] .db-clock,
.demo-sec[data-demo="soon"] .db-clock{display:inline-flex}
.db-clock b{font-size:clamp(24px,2.6vw,34px);font-variant-numeric:tabular-nums;color:var(--red)}
.db-clock span{font-size:12px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-2)}
.demo-sec[data-demo="soon"] .db-clock b{color:var(--amber)}
.demo-sec[data-demo="caution"] .demo-board,
.demo-sec[data-demo="soon"] .demo-board{border-top-color:var(--amber-bar)}
.demo-sec[data-demo="closed"] .demo-board{border-top-color:var(--red)}
.demo-board .minisig{width:30px;filter:drop-shadow(0 3px 4px rgba(7,32,40,.24))}
.db-verdict{
  font-family:var(--display);font-weight:900;font-size:clamp(19px,2.2vw,24px);
  letter-spacing:-.2px;line-height:1.05;color:var(--green);min-width:9.2em;
}
.demo-sec[data-demo="caution"] .db-verdict,
.demo-sec[data-demo="soon"] .db-verdict{color:var(--amber)}
.demo-sec[data-demo="closed"] .db-verdict{color:var(--red)}
.db-say{flex:1;min-width:12em}
.db-caption{color:var(--ink-2);font-size:13.5px;line-height:1.35;margin-top:2px}
.db-caption:empty{display:none}
.demo-note{font-size:12.5px;margin-top:8px;text-align:center}
/* the two readings, written by the sky script itself through the same data-ro
   hooks the landing page card carries: one writer, no drift */
.db-read{margin-left:auto;display:flex;gap:22px}
.db-read div{min-width:0}
.db-read dt{
  font-family:var(--display);font-weight:900;font-size:11.5px;
  letter-spacing:.18em;text-transform:uppercase;color:var(--ink-2);
}
.db-read dd{
  font-weight:600;font-size:15px;color:var(--ink);
  font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;
}
.demo-sec[data-demo="closed"] .db-read dd{color:var(--red)}
.demo-sec[data-demo="soon"] .db-read dd{color:var(--amber)}

/* the scenario controls: deck signage the reader can press. Each button
   carries a lamp-coloured dot BESIDE its words, never instead of them. */
.wx-btns-row{display:flex;gap:8px;flex-wrap:wrap}
.wx-btns button{
  display:inline-flex;align-items:center;gap:8px;
  font-family:var(--body);font-weight:600;font-size:13.5px;color:var(--ink);
  background:var(--card);border:1px solid var(--edge);border-radius:999px;
  padding:8px 14px;min-height:44px;cursor:pointer;
  box-shadow:var(--cast-sm);
}
.wx-btns button:hover{background:var(--deck)}
.wx-btns button:active{transform:translate(1px,1px);box-shadow:2px 2px 0 var(--deck-shade)}

/* ---- the scenarios, standing on the edge of the picture -----------------
   A stack down the left, every one of them the same width and the same
   height so they read as one instrument, hung so that a third of each sits
   out over the open weather and two thirds over the artwork. That overhang
   is the whole trick: a control that straddles the edge of the thing it
   changes is obviously a control, where the same buttons sitting in the
   furniture above the picture were just more furniture. */
body.demo .wx-btns{
  --wxb-w:184px;
  position:absolute;z-index:20;
  /* two thirds out over the open weather, one third over the artwork: the
     stack reads as floating in front of the picture rather than sitting on
     it, and it stops covering the part of the pool worth watching */
  left:calc(var(--wxb-w) * -0.66);
  top:18px;
  display:flex;flex-direction:column;gap:9px;
}
body.demo .wx-btns-row{flex-direction:column;gap:9px;flex-wrap:nowrap}
body.demo .wx-btns button{
  width:var(--wxb-w);height:46px;min-height:0;
  justify-content:flex-start;padding:0 15px;
  box-shadow:inset 0 0 0 1px var(--edge), 4px 4px 0 rgba(7,32,40,.2);
  border-color:transparent;
}
body.demo .wx-btns button:active{box-shadow:inset 0 0 0 1px var(--edge), 2px 2px 0 rgba(7,32,40,.2)}
/* Pressed means held. The coloured dots were doing the work of saying which
   scenario was running, which put three lamp colours on the page competing
   with the one fixture actually entitled to use them. The button sits down
   into the deck instead: the cast goes, the ring turns inward, and it comes
   back up on its own when the weather moves to a part of the cycle another
   button owns. */
body.demo .wx-btns button[aria-pressed="true"]{
  background:var(--deck);
  transform:translate(3px,3px);
  box-shadow:inset 0 0 0 1px var(--edge), inset 2px 2px 5px rgba(7,32,40,.26);
}
/* the label over the stack: the same measure and the same overhang, but a
   plate rather than a control, so nothing here invites a press that does
   nothing */
.wx-btns-cap{
  width:var(--wxb-w);margin-left:-14px;border-radius:5px;
  font-family:var(--display);font-weight:900;font-size:11px;
  letter-spacing:.06em;text-transform:none;color:var(--ink-2);
  background:var(--deck);padding:8px 14px;
  box-shadow:inset 0 0 0 1px var(--edge), 4px 4px 0 rgba(7,32,40,.2);
}
.wx-dot{display:none}
.wx-dot-g{background:var(--lamp-green);box-shadow:0 0 0 3px rgba(11,122,66,.15)}
.wx-dot-a{background:var(--lamp-amber);box-shadow:0 0 0 3px rgba(138,82,0,.15)}
.wx-dot-r{background:var(--lamp-red);box-shadow:0 0 0 3px rgba(180,35,42,.15)}
/* the scene board lamps: lit by the scene's own state attribute, never by the
   sky's. Same glass, different circuit. */
.demo-board .ml{fill:var(--off-red)}
.demo-board .ml-a{fill:var(--off-amber)}
.demo-board .ml-g{fill:var(--off-green)}
.demo-sec[data-demo="open"] .demo-board .ml-g{fill:var(--lamp-green)}
.demo-sec[data-demo="caution"] .demo-board .ml-g{fill:var(--lamp-green)}
.demo-sec[data-demo="caution"] .demo-board .ml-a{fill:var(--lamp-amber);transition:fill .14s ease-out}
[data-blink="off"] .demo-sec[data-demo="caution"] .demo-board .ml-a{fill:var(--off-amber);transition:fill .18s ease}
.demo-sec[data-demo="closed"] .demo-board .ml-r{fill:var(--lamp-red)}
/* reopening soon on the scene board follows the same law as every other head:
   red stays lit and a steady amber joins it, never amber alone. */
.demo-sec[data-demo="soon"] .demo-board .ml-r{fill:var(--lamp-red)}
.demo-sec[data-demo="soon"] .demo-board .ml-a{fill:var(--lamp-amber)}

/* under reduced motion the loop never starts: the scene rests on the open
   frame, everyone in the water, and the four cards on the landing page carry
   the rest of the story without a single thing moving */

/* ---------------- motion, reduced ---------------------------------------
   Everything stops dead and the site rests on the calm sunny scene it opens
   on. The sky script never starts its clock: it draws exactly one frame of
   deep calm and leaves it standing, with the sun out, an empty sky, no drift,
   no rain, no flicker and no strike. Both signal heads rest lit on green, no
   lamp anywhere on the page blinks because the one attribute that drives every
   blink never changes, and the bolt and the wash are taken out of the box
   entirely, so there is nothing left that could flash. */
@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none !important;transition:none !important}
  html{scroll-behavior:auto}
  .wx-bolt{display:none}
  /* the two values rest on the real defaults with nothing blinking beside
     them. The script never starts its clock either. */
  .fm-caret{display:none !important}
  .fm-txt.is-sel{background-color:transparent;box-shadow:none}
  /* The caustics hold still on a good frame rather than going missing. The
     shader draws exactly one frame at a chosen moment and then stops, and the
     tiled layers park on an offset that reads as a lit pool floor. */
  .cx-tile{transform:translate3d(-232px,116px,0)}
  .cx-t1{opacity:.62}
  .cx-t2{opacity:.46}
}

/* ---------------- narrow ------------------------------------------------
   The mount is tucked tighter and scaled down as the frame narrows, and the
   column the page gives up to it comes down with it, so the measure never
   collapses and the fixture never reaches a word. */
@media (max-width:1180px){
  :root{--rig-gut:158px;--rig-w:104px;--rig-top:98px}
}
@media (max-width:1000px){
  :root{--rig-gut:146px;--rig-w:96px;--rig-top:94px}
}
@media (max-width:860px){
  :root{--rig-gut:132px;--rig-w:88px;--rig-top:88px}
  /* Below here there is no longer enough width to centre the page and keep a
     readable measure, so it goes back to giving up a column on the right. See
     the note above .plate for the two numbers that decide this. */
  body{padding-right:var(--rig-gut)}
  .plate{max-width:1148px}
  .site-head .wrap{max-width:1080px}
  .hero-pool .floor-mark{left:clamp(20px,3vw,30px);max-width:86%}
  /* the status card comes off the lip and sits squarely on the deck */
  .hero-pool{padding-bottom:0}
  .signcard{
    position:static;width:auto;margin-top:-26px;margin-inline:14px 0;
    box-shadow:9px 9px 0 var(--deck-shade),0 22px 38px -26px rgba(6,48,66,.5);
    border-color:var(--edge);
  }
}
@media (max-width:760px){
  body{font-size:16.5px}
  .wrap{padding:0 22px}
  /* the nav takes its own line under the brand rather than breaking a label */
  .site-head nav a{padding:6px 10px;font-size:13.5px}
  .site-head nav{gap:0}
  .brand svg,.brand img{width:35px;height:auto}
  .brand .name{font-size:22px}
  .brand{padding:6px 16px 6px 7px}
  html{scroll-padding-top:88px}
  .pool{border-width:9px}
  /* stack the deck: the painting near the shallow end, the sign on the concrete */
  .hero-pool .pool{min-height:320px}
  .hero-pool .floor-mark{font-size:34px}
  .signcard{padding:14px 18px 16px}
  .signcard .verdict{font-size:26px}
  .sig{width:36px}
  .waterline{height:88px;border-width:9px}
  .who b{font-size:19px}
}
@media (max-width:640px){
  :root{--rig-gut:112px;--rig-w:78px;--rig-top:82px}
  .rig-say{padding:8px 10px 10px;border-radius:10px}
}
@media (max-width:470px){
  :root{--rig-gut:96px;--rig-w:68px;--rig-top:76px}
  /* the plate is trimmed just enough that POOL OPEN holds one line down here */
  .rig-say{padding:8px 9px 10px}
  .rig-word{font-size:12.5px}
  .hero-pool .floor-mark{font-size:30px;max-width:94%}
  /* the masthead takes two rows down here, so both of them are trimmed: it is
     stuck to the top of a small screen and every pixel it holds is a pixel of
     the page the reader cannot see */
  .site-head{padding:8px 0}
  .site-head .wrap{gap:6px 12px}
  .site-head nav{padding:4px}
  .site-head nav a{padding:5px 9px;font-size:12.5px}
  html{scroll-padding-top:118px}
}
@media (max-width:420px){
  h1{letter-spacing:-1.2px}
  .brand svg,.brand img{width:31px;height:auto}
  .brand .name{font-size:20px}
  .brand{padding:6px 13px 6px 6px}
  html{scroll-padding-top:80px}
}
/* ---------------- phones ------------------------------------------------
   Below 760px the page stops giving up a column to the mount.

   The reasoning further up is sound at every width it was written for, and it
   stops being sound on a phone. The note above .plate already does the
   arithmetic and calls it: at 414px the mount's column is 96px of the screen.
   What it did not do was take the last step. Measured on a 375px phone the
   plate came out 217px wide and the measure ran at 26 characters a line,
   against the 45 to 75 that is comfortable to read. The fixture was spending a
   quarter of the screen to decorate a page that had become hard to read.

   So on a phone it is retired rather than shrunk again. Nothing a reader can
   act on is lost. The fixture is aria-hidden and always was. The status card in
   the hero draws the same signal head from the same state attribute, lit on the
   same frame off the same clock, and it sits above the fold. The four states
   are written out unmoving in the cards below it.

   Zeroing the gutter is the whole structural change: every measure on the page
   is already derived from it, so the plates, the masthead and the body padding
   all widen on their own. Hiding the mount rather than letting it collapse to
   zero width also stops the browser compositing a fixed SVG and its halos on
   every scroll frame, which is the one place this page asked a phone GPU for
   work it could not see. */
@media (max-width:760px){
  :root{--rig-gut:0px}
  .rig-mount{display:none}

  /* The weather is the product, and on a phone it was reduced to a hairline
     round the plates. The gutters and the joints both widen so the sky reads
     as a living background: a real band of it above the first plate, a clear
     channel down both sides, and a visible seam of it between every pour.
     The plates stay opaque; the sky gets room, never type. */
  :root{--gut:clamp(17px,4.8vw,24px);--joint:clamp(22px,6vw,30px)}
  /* the lift under every plate deepens a touch, because on a phone the sky in
     the gutters is the only place it has to land and it was barely reading */
  .plate{box-shadow:0 18px 42px -24px rgba(7,32,40,.66)}

  /* The status card leaves the pool edge and pins to the foot of the screen,
     so the live state is on screen for as long as the reader is, the job the
     retired mount used to do. Elevation without an upward shadow, which is
     banned: the state-coloured bar on its top edge and the hairline ring do
     the lifting, and its lift shadow falls straight down like every other. */
  /* The pinned card lives inside the hero plate, and every plate is its own
     stacking context (position:relative + z-index:1), so a fixed child can
     never paint above a LATER sibling plate no matter its own z-index. The
     hero plate itself is lifted above the other plates on phones, which
     frees the card to float over the whole page. The plates never overlap
     spatially, so nothing else changes. */
  .hero.plate{z-index:16}
  .signcard{
    position:fixed;left:12px;right:12px;bottom:12px;z-index:30;
    width:auto;margin:0;
    border:0;
    border-top:5px solid var(--green);
    border-radius:6px 6px 14px 14px;
    padding:9px 13px 11px;
    /* bar on top, ring as an inset shadow, lift falling straight down */
    box-shadow:inset 0 0 0 1px var(--edge),0 12px 26px -14px rgba(6,48,66,.6);
  }
  [data-wx="caution"] .signcard,
  [data-wx="soon"] .signcard{border-top-color:var(--amber-bar)}
  [data-wx="closed"] .signcard{border-top-color:var(--red)}
  .sc-verdict{gap:11px;padding:0}
  .signcard .sig{width:26px}
  .signcard .verdict{font-size:18px;min-height:0}
  .sc-toggle{display:inline-flex}
  .sc-sim{top:-12px;right:14px}
  /* tucked away: the verdict bar stays, the readings fold under it */
  html.sc-shut .signcard .readout{display:none}
  .readout{margin-top:9px}
  .readout>div{padding:6px 0}
  .readout dd{font-size:13.5px}
  /* the page keeps clear of the pinned card at its full height, and takes the
     room back when the card is tucked. The clearance carries a margin over the
     measured card height, because the verdict can wrap to a second line on the
     narrowest phones and the footer must stay reachable even then. */
  body{padding-bottom:236px}
  html.sc-shut body{padding-bottom:110px}

  /* The masthead stops being sticky, and stops apologising for its height.

     Sticky and opaque, it stood 106px tall on a 375px phone: 13% of the
     viewport, held for the whole of a 5000px page, to carry two links that the
     footer also carries. Its own links were 31px tall against the 44px a
     fingertip needs, because the row wrapped and the space went into a second
     line rather than into the targets.

     Brand and nav will not both fit on one row at 320px at any type size worth
     reading, so rather than shave them until they do, the nav takes its own
     full-width row and every link becomes a proper target. That costs height,
     which is exactly why the bar is no longer stuck to the top: it is read
     once, at the top, and then it scrolls away and gives the screen back. */
  .site-head{position:static;padding:9px 0 11px}
  .site-head .wrap{flex-wrap:wrap;gap:9px}
  .brand{padding:5px 15px 5px 5px;gap:9px}
  .brand svg,.brand img{width:33px;height:auto}
  .brand .name{font-size:20px}
  .site-head nav{margin-left:auto;flex-wrap:nowrap;gap:0;padding:3px}
  .site-head nav a{
    display:inline-flex;align-items:center;justify-content:center;
    min-height:44px;padding:0 11px;font-size:13.5px;
  }
  /* nothing is stuck to the top any more, so an anchor jump needs only a
     breath of room rather than the height of a bar */
  html{scroll-padding-top:14px}

  /* A real band of open sky above the first plate. It was cut to nothing when
     every pixel was contested; with the mount retired the opening screen can
     afford to show the weather the product is about. */
  .hero{margin-top:clamp(56px,9vh,84px);padding-top:26px}
  h1{font-size:clamp(40px,11vw,52px);letter-spacing:-1.4px}
  .lede{margin-top:16px}

  /* Footer links are the other place a finger has to land. */
  footer nav a{display:inline-flex;align-items:center;min-height:44px}

  /* Small type, re-floored for a screen held at arm's length.
     Both of these bottom out on a clamp floor that was set when the column was
     217px wide and every pixel was contested. With the mount's column returned
     there is room to make them legible again: the rule line under the numerals
     was rendering at 9.5px and the readout labels at 10.5px, which is decorative
     rather than readable on a phone. */
  .floor-mark small{letter-spacing:.08em}
  .readout dt{font-size:11.5px}
  .legal{font-size:13.5px}
}

/* A phone turned sideways is about 400px tall and wider than the 760px cutoff
   above, so it would otherwise get the desktop treatment on a screen with less
   vertical room than any phone held upright: a 132px column given to the
   fixture and an 80px bar stuck to the top of a 375px viewport, 21% of it.
   Height is the binding constraint in this orientation, so it is the one asked
   about. The width bound keeps real laptops out of this rule. */
@media (max-height:520px) and (max-width:1000px){
  :root{--rig-gut:0px;--gut:clamp(17px,4.8vw,24px);--joint:clamp(22px,6vw,30px)}
  .rig-mount{display:none}
  .site-head{position:static;padding:8px 0}
  html{scroll-padding-top:14px}
  .hero{margin-top:14px;padding-top:20px}
}

/* Below about 520px the brand and the nav stop fitting on one row at any type
   size worth reading, so the nav takes its own row. Given a whole row it is
   drawn as a proper bar with the three links sharing the width equally, rather
   than left as a huddle in the corner of a row it now has to itself.

   Above 520px, which includes a phone turned sideways, they fit beside each
   other and the header stays one row tall. That matters more in landscape than
   anywhere else on the site: the viewport is only 360px high, so a masthead
   that took two rows would open on a third of a screen of nothing. */
@media (max-width:520px){
  .site-head nav{width:100%;margin-left:0;justify-content:space-between}
  .site-head nav a{flex:1 1 0;padding:0 6px}
}

/* Any touch screen, whatever its width, gets a target a fingertip can hit.
   Width is the wrong question here: a tablet is 768px and still touched. */
@media (pointer:coarse){
  .site-head nav a,
  footer nav a{display:inline-flex;align-items:center;min-height:44px}
  /* and the same argument for the smallest label on the card: a screen that is
     touched is held at arm's length, not sat in front of */
  .readout dt{font-size:11.5px}
}

/* The masthead wrapped to two rows between about 861px and 950px, standing
   145px tall and sticky at 900px. The cause is the rule above .plate: below
   861px the masthead is released from the centred column and held to 1080px
   instead, so it has room again, but from 861 up it is still bound to
   (100% - 2 rig-gut - 2 gut) while the gutter is at its widest for that range.
   The contents are trimmed just enough to hold one row through the band. */
@media (min-width:861px) and (max-width:1000px){
  .brand svg,.brand img{width:35px;height:auto}
  .brand .name{font-size:22px}
  .brand{padding:6px 17px 6px 6px;gap:10px}
  .site-head nav a{padding:7px 11px;font-size:13.5px}
}

/* a short frame, usually a laptop with a lot of browser chrome. The mount
   climbs and shrinks so the word plate still has room under the head. */
@media (max-height:640px){
  :root{--rig-top:62px}
}
@media (max-height:520px){
  :root{--rig-top:46px;--rig-w:80px}
}

/* ---- the demo page on a phone -------------------------------------------
   Everything above the scene was taking the top half of the screen: a full
   masthead on two rows, a title, a stencil line, the board, and the controls
   wrapped onto two more rows. All of that is chrome around the one thing the
   page is for. It is squeezed into two short rows here, and what is left
   goes to the pool. */
@media (max-width:760px){
  body.demo .site-head{padding:5px 0}
  body.demo .site-head .wrap{flex-wrap:nowrap;gap:8px;padding:0 10px}
  body.demo .brand{padding:3px 11px 3px 3px;gap:6px}
  body.demo .brand img,body.demo .brand svg{width:26px}
  body.demo .brand .name{font-size:16px}
  /* The site-wide phone rule gives the nav a full row of its own, which is
     right everywhere else and wrong here: this header is held to one row,
     so a nav asking for 100% left the pill sitting on top of the wordmark.
     It takes only the width it needs and parks itself on the right, and the
     brand is allowed to give ground first if a narrow screen runs out. */
  body.demo .site-head nav{
    width:auto;flex:0 0 auto;margin-left:auto;padding:2px;gap:0;
    justify-content:flex-end;
  }
  body.demo .site-head nav a{font-size:12px;padding:0 8px;flex:0 0 auto}
  body.demo .brand{min-width:0}
  body.demo .brand .name{white-space:nowrap;overflow:hidden;text-overflow:clip}

  body.demo .demo-bar{padding:0 9px 6px;gap:6px}
  /* The head is not in a gutter on a phone, it is pinned over the top right
     corner of the picture, and the bar runs underneath it. So the line gives
     up the width the head actually occupies: the 104px plate, the 10px it
     sits in from the edge, and air. Without this the text runs under a
     fixture that is painted above it. */
  body.demo .demo-tier{
    font-size:10px;line-height:1.35;max-width:calc(100% - 120px);
    padding:4px 10px 5px;
  }
  body.demo .demo-board{flex:1 1 100%;padding:4px 10px 6px;gap:9px;box-shadow:inset 0 0 0 1px var(--edge),4px 4px 0 var(--deck-shade)}
  body.demo .demo-board .minisig{width:21px}
  body.demo .demo-board .sc-sim{top:-9px;right:9px;font-size:9px;padding:2px 8px}
  body.demo .db-verdict{font-size:15.5px;min-width:0}
  body.demo .db-caption{display:none}
  body.demo .db-clock{margin-left:auto}
  body.demo .db-clock b{font-size:19px}
  body.demo .db-clock span{font-size:9.5px;letter-spacing:.1em}
  body.demo .demo-sec .scene-wrap{margin-bottom:6px}
  /* A phone has no margin to overhang into, so the stack lies down across
     the foot of the picture instead, in the strip of lawn under the
     drawing rather than over anything worth seeing. */
  body.demo .wx-btns{
    left:6px;right:6px;bottom:6px;top:auto;transform:none;
    gap:5px;
  }
  body.demo .wx-btns-row{flex-direction:row;gap:5px}
  body.demo .wx-btns button{
    width:auto;flex:1 1 0;min-width:0;height:38px;
    font-size:11.5px;padding:0 7px;gap:5px;justify-content:center;white-space:nowrap;
  }
  body.demo .wx-btns-cap{display:none}
  body.demo .wx-dot{width:8px;height:8px}

  /* THE FIXTURE ON A PHONE.
     A phone has no gutter to hang a pole in, so the site hides the mount
     entirely and this page lost the one instrument it is about. It comes
     back here as just the head, small, in the corner of the picture, and it
     is the SAME markup and the same lamp rules the desktop one uses, so it
     cannot drift out of step with it: there is no second stoplight to keep
     in sync, only a second position for the one there is.
     The pole goes, since a pole needs somewhere to run to. */
  body.demo .rig-mount{
    display:block;
    position:fixed;inset:auto 10px auto auto;
    top:calc(var(--head-h, 50px) + 10px);
    width:auto;z-index:22;pointer-events:none;
  }
  body.demo .rig-pole{display:none}
  body.demo .rig{
    position:static;width:38px;height:auto;
    filter:drop-shadow(3px 4px 5px rgba(7,32,40,.34));
  }
  /* the words under it, held to the width of the head and turned down to
     match, so the corner carries a verdict as well as a colour */
  body.demo .rig-say{
    position:static;margin-top:5px;width:104px;
    padding:6px 8px 7px;border-radius:8px;
    box-shadow:inset 0 0 0 1px var(--edge),3px 3px 0 rgba(7,32,40,.2);
  }
  body.demo .rig-word{font-size:10.5px;line-height:1.15}
  body.demo .rig-note{display:none}
  body.demo .sc-sim{font-size:8.5px;letter-spacing:.1em;padding:3px 8px;margin-top:5px}
  body.demo .rig-clock{margin-top:5px}
  body.demo .rig-clock b{font-size:20px}
  body.demo .rig-clock span{font-size:9px}
  body.demo .demo-legal{font-size:9px;line-height:1.4;padding:4px 10px 5px}
}
/* the narrowest phones give the labels every pixel of the row */
@media (max-width:400px){
  body.demo .wx-btns button{font-size:10.5px;padding:0 5px;gap:4px}
}



/* ======================================================================
   THE PEOPLE, seen from directly overhead.

   The umbrella on the roof layer is the standard the rest of the scene is
   held to now. It is built out of the real parts of the real thing, it is
   domed by a highlight up and to the left and a falloff down and to the
   right, and the shadow it throws is its own outline squashed away from the
   sun. A person gets the same treatment. From straight up a body is a head,
   the line of the shoulders and two arms, and the arms are the part that
   was missing: a head on an oval is a skittle, and a skittle with arms is
   somebody.

   ARMS SWING OUT, NOT IN. This is worth saying plainly, because it was
   wrong here the first time round and it is not obvious from reading the
   rule. The rotation origin sits at the top of the arm, at the shoulder, so
   the hand is the end that travels, and a NEGATIVE angle carries the left
   hand to the RIGHT, in under the body, where the shoulders are painted
   over the top of it and almost none of it is ever seen. The mirror
   multiplier is what lets one set of numbers serve both sides; the sign of
   the number is what decides whether they open or fold. Open everywhere
   except on the chute, where they are folded on purpose.

   The sun is upper left over the whole site, so every highlight below sits
   up and left of centre and every shadow falls down and right. Nothing here
   repeats faster than about one and a half times a second, which is a long
   way inside the three a second this site holds itself to.

   THE HEADING. One custom property, --dmPplA, carries the direction a
   person is travelling in, in screen degrees, and the script writes it on
   every leg of a walk. It is measured in the DRAWING's own units and not in
   the per cent the crowd is placed in, because those are not square: one
   per cent across the crowd is 16 units of the drawing and one per cent
   down it is 7.6, so a heading read straight off the percentages is wrong
   for everything except the four square directions. Everybody carries the
   heading. Only the ones whose whole body lies along it turn to face it,
   which is why --dmPplR, the turn that actually reaches the transform, is
   zero until something switches it on.
   ====================================================================== */
.pp{
  --dmPplA:0deg;
  --dmPplR:0deg;
  transform:translate(-50%,-50%) rotate(var(--dmPplR));
}
/* A rider lies back and goes feet first, so the head trails and the body's
   head to feet axis IS the direction of travel: a quarter turn back from
   the heading. A lane swimmer is the other way round, head leading, so the
   same heading turns them a quarter turn on. */
.pp.dmPplRide,
.pp.in-w.glide{--dmPplR:calc(var(--dmPplA) - 90deg)}
.pp.in-w.dmPplLane{--dmPplR:calc(var(--dmPplA) + 90deg)}

/* THE SHOULDERS. Wider across than they are deep, which is the one
   proportion that separates a body seen from above from a ball. The sun
   lands on the near shoulder, the far one falls away, and the outline is
   thrown down and right onto the deck. */
.pp::before{
  inset:24% 6% 12% 6%;
  border-radius:44% 44% 50% 50% / 48% 48% 52% 52%;
  background:var(--suit,#4A6FA5);
  background-image:
    radial-gradient(58% 64% at 30% 22%,rgba(255,255,255,.5),rgba(255,255,255,0) 72%),
    linear-gradient(148deg,rgba(255,255,255,0) 44%,rgba(5,26,36,.34));
  box-shadow:2px 3px 2.5px rgba(7,32,40,.3);
}
/* THE HEAD. Its own tone, a little forward of the shoulders, and it throws
   its own small shadow onto them: the same move the umbrella's vent cap
   makes to sit clear of the canvas under it. */
.pp::after{
  top:-2%;width:54%;
  background:var(--skin,#E7B98F);
  background-image:radial-gradient(circle at 32% 26%,rgba(255,255,255,.52),rgba(255,255,255,0) 60%);
  box-shadow:1.6px 2.4px 2px rgba(7,32,40,.28);
}

/* THE ARMS. Bare skin, hung off the outside of the shoulders and drawn
   behind them, so what shows is the part that clears the body. One extra
   element per person carries them, and it carries the water a person pushes
   about as well, so nothing here needs a shape of its own. */
.dmPplB{position:absolute;inset:0;pointer-events:none;z-index:-1}
.dmPplB::before,
.dmPplB::after{
  content:"";position:absolute;top:30%;width:19%;height:42%;
  border-radius:48% 48% 50% 50% / 26% 26% 74% 74%;
  background:var(--skin,#E7B98F);
  background-image:linear-gradient(150deg,rgba(255,255,255,.36),rgba(255,255,255,0) 58%,rgba(5,26,36,.3));
  box-shadow:1.6px 2.2px 2px rgba(7,32,40,.24);
  transform-origin:50% 12%;
  transform:rotate(calc(var(--dmPplMir,1) * 15deg));
}
/* the far arm is the near one mirrored, which is why there is one set of
   stroke keyframes on this page and not two */
.dmPplB::before{left:1%}
.dmPplB::after{right:1%;--dmPplMir:-1}

/* IN THE WATER the body goes soft and takes the pool's colour while the
   head stays hard edged above the surface. That difference is the whole
   read of a swimmer seen from the chair at the side: one sharp thing
   sitting on one blurred one. The softness is gradients that run out to
   nothing before they reach an edge, not a filter, because twenty eight of
   these move at once and a filter would be re-rasterised on every frame. */
.pp.in-w::before{
  inset:26% 8% 8% 8%;
  border-radius:50%;
  background:rgba(12,60,86,0);
  background-image:
    radial-gradient(54% 52% at 32% 26%,rgba(206,244,255,.46),rgba(206,244,255,0) 76%),
    radial-gradient(68% 64% at 66% 74%,rgba(5,58,88,.46),rgba(5,58,88,0) 80%),
    radial-gradient(72% 70% at 50% 50%,var(--suit,#4A6FA5) 0 32%,rgba(12,60,86,0) 96%);
  box-shadow:none;
}
/* the waterline: a bright meniscus tight to the head, the shadow the head
   throws into the water down and right of it, and the last of the
   disturbance running out past that */
.pp.in-w::after{
  top:4%;width:50%;
  box-shadow:
    0 0 0 2.2px rgba(255,255,255,.58),
    1.4px 2px 0 3.4px rgba(5,52,78,.24),
    0 0 0 6.4px rgba(214,246,255,.14);
}
/* arms on the surface lie wider and take the water over them */
.pp.in-w .dmPplB::before,
.pp.in-w .dmPplB::after{
  top:34%;height:36%;
  box-shadow:none;
  background-image:linear-gradient(150deg,rgba(206,244,255,.42),rgba(255,255,255,0) 46%,rgba(5,58,88,.44));
  transform:rotate(calc(var(--dmPplMir,1) * 30deg));
}

/* THE LANE SWIMMERS. Two people swim lengths, and a body swimming a length
   is long along the lane and stroking, not a dot drifting. The torso runs
   past both ends of the person's own box: the head end pokes out ahead and
   the legs trail behind, which is what makes the shape read as travelling
   even in the instant between strokes. */
.pp.in-w.dmPplLane::before{
  inset:-2% 30% -18% 30%;
  border-radius:52% 52% 46% 46% / 26% 26% 20% 20%;
  background-image:
    radial-gradient(50% 26% at 32% 20%,rgba(206,244,255,.44),rgba(206,244,255,0) 78%),
    radial-gradient(72% 40% at 66% 76%,rgba(5,58,88,.48),rgba(5,58,88,0) 82%),
    radial-gradient(70% 46% at 50% 46%,var(--suit,#4A6FA5) 0 38%,rgba(12,60,86,0) 96%);
}
.pp.in-w.dmPplLane::after{top:-6%;width:44%}
/* the bow wave the head pushes ahead of it, and the churn the feet leave */
.pp.in-w.dmPplLane .dmPplB{
  background-image:
    radial-gradient(26% 12% at 50% 0%,rgba(255,255,255,.55),rgba(255,255,255,0) 76%),
    radial-gradient(30% 16% at 50% 100%,rgba(255,255,255,.4),rgba(255,255,255,0) 80%);
}
/* AND THE STROKE. One arm reaches while the other pulls, a full cycle every
   1.4 seconds, which is a swimmer's real cadence and nowhere near anything
   that could read as a flash. It hangs off the same class the wake does, so
   somebody stopped on the wall for a breath is not still windmilling. The
   arm goes OUT and forward on the recovery and comes back in along the ribs
   on the pull, which is the sign the mirror multiplier is carrying. */
.pp.in-w.dmPplLane .dmPplB::before,
.pp.in-w.dmPplLane .dmPplB::after{top:16%;width:17%;height:40%}
.pp.in-w.dmPplLane.swim .dmPplB::before,
.pp.in-w.dmPplLane.swim .dmPplB::after{animation:dmPplStroke 1.4s ease-in-out infinite}
.pp.in-w.dmPplLane.swim .dmPplB::after{animation-delay:-.7s}
@keyframes dmPplStroke{
  0%{transform:rotate(calc(var(--dmPplMir,1) * 10deg)) translate(0,0)}
  28%{transform:rotate(calc(var(--dmPplMir,1) * 46deg)) translate(0,-16%)}
  56%{transform:rotate(calc(var(--dmPplMir,1) * -12deg)) translate(0,14%)}
  100%{transform:rotate(calc(var(--dmPplMir,1) * 10deg)) translate(0,0)}
}
/* the kick, one beat a second, dropped off the feet end and left behind */
.pp.in-w.dmPplLane .wk{
  left:50%;top:88%;width:110%;aspect-ratio:1/1.1;
  background:radial-gradient(closest-side,rgba(255,255,255,.5),rgba(255,255,255,.18) 54%,rgba(255,255,255,0) 80%);
}
.pp.in-w.dmPplLane.swim .wk{animation:dmPplKick 1.05s ease-out infinite}
@keyframes dmPplKick{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.5,.6)}
  20%{opacity:.8}
  100%{opacity:0;transform:translate(-50%,-34%) scale(1.4,1.55)}
}

/* THE RIDER. Nobody sits up in a chute. They lie back, feet first, arms
   folded in, and the shape turns to follow the bend, which is why the body
   is long along the direction of travel instead of round. The chute tells
   the script where that is: it is the leg the rider is on. */
.pp.dmPplRide::before,
.pp.in-w.glide::before{
  inset:-4% 28% -20% 28%;
  border-radius:54% 54% 42% 42% / 28% 28% 26% 26%;
  box-shadow:none;
}
.pp.dmPplRide::after,
.pp.in-w.glide::after{top:-12%;width:46%}
/* on the chute there is no cast shadow to draw. The light comes straight
   down the tube and what shadow there is lands on the floor of it, under
   the rider and out of sight from up here. */
.pp.dmPplRide::after{box-shadow:none}
/* Arms folded across the chest. This is the one place on the page they go
   IN rather than out, so the sign here is the odd one and is meant to be. */
.pp.dmPplRide .dmPplB::before,
.pp.dmPplRide .dmPplB::after,
.pp.in-w.glide .dmPplB::before,
.pp.in-w.glide .dmPplB::after{
  top:24%;width:26%;height:20%;
  border-radius:50%;
  box-shadow:none;
  transform:rotate(calc(var(--dmPplMir,1) * -30deg));
}
.pp.dmPplRide .dmPplB::before,
.pp.in-w.glide .dmPplB::before{left:14%}
.pp.dmPplRide .dmPplB::after,
.pp.in-w.glide .dmPplB::after{right:14%}
/* and the water they are shoving along ahead of their feet */
.pp.dmPplRide .dmPplB{
  background-image:radial-gradient(32% 14% at 50% 96%,rgba(255,255,255,.55),rgba(255,255,255,0) 78%);
}
/* THE WAKE OFF THE MOUTH, now that the rider turns. It used to be pinned to
   the west of them because the glide only ever ran east; the body carries
   its own frame now, so the train sits behind the head whichever way they
   came out. */
.pp.in-w.glide .wk{
  left:50%;top:20%;width:130%;aspect-ratio:1/1.9;
  background:radial-gradient(closest-side,rgba(255,255,255,.6),rgba(255,255,255,.22) 52%,rgba(255,255,255,0) 78%);
  animation:dmPplGlide .62s ease-out infinite;
}
@keyframes dmPplGlide{
  0%{opacity:0;transform:translate(-50%,-50%) scale(.6,.5)}
  18%{opacity:.95}
  100%{opacity:0;transform:translate(-50%,-70%) scale(1.3,1.5)}
}

/* AIRBORNE, for the length of a jump. The body lifts and grows, the shadow
   it casts separates and falls away down and right of it, and the arms go
   out, which is what anybody does coming off a board. */
.pp.air{transform:translate(-50%,-64%) scale(1.3) rotate(var(--dmPplR))}
.pp.air::before{box-shadow:6px 10px 7px rgba(7,32,40,.34)}
.pp.air::after{box-shadow:5px 8px 6px rgba(7,32,40,.3)}
.pp.air .dmPplB::before,
.pp.air .dmPplB::after{
  transform:rotate(calc(var(--dmPplMir,1) * 58deg));
  box-shadow:5px 8px 6px rgba(7,32,40,.26);
}

/* THE FLOAT. An inflatable ring from above is a tube, not a hoop drawn with
   a line: brightest along its upper left where the sun catches the top of
   the tube, a seam running round the crown of it, a valve stub on one side.
   It sits ON the water, so it throws a shadow down and right INTO the water
   and pushes a little ring of ripple out around itself.

   The valve sits at eight o'clock, for two reasons that both have to hold
   at once. It has to be ON the tube, which is the band between 54 and 84
   per cent of the way out, and it has to keep off the upper left, because
   that is where the sun is: a dark stub painted over the highlight puts out
   the one thing making the ring read as round rather than flat. */
.pp.kid .fl{
  width:148%;aspect-ratio:1/.94;
  background:rgba(233,102,44,0);
  background-image:
    radial-gradient(6% 8% at 27% 75%,#A8481A 0 62%,rgba(168,72,26,0) 76%),
    radial-gradient(closest-side,rgba(120,44,12,0) 0 65%,rgba(120,44,12,.26) 67% 69%,rgba(120,44,12,0) 71%),
    radial-gradient(70% 70% at 34% 28%,rgba(255,240,224,0) 0 50%,rgba(255,240,224,.8) 60%,rgba(255,240,224,0) 76%),
    radial-gradient(closest-side,rgba(233,102,44,0) 0 54%,#E9662C 56%,#FF9152 63%,#FFB183 70%,#E4551F 79%,rgba(228,85,31,0) 84%);
  filter:drop-shadow(2px 3px 2.5px rgba(7,32,40,.32));
}
.pp.kid .fl::before{
  content:"";position:absolute;inset:-15%;border-radius:50%;
  background:radial-gradient(closest-side,rgba(236,250,255,0) 0 74%,rgba(236,250,255,.34) 82%,rgba(236,250,255,0) 93%);
}
/* a child in a ring rests their arms out along it rather than down at their
   sides, which is the giveaway that they are sitting in the thing */
.pp.kid .dmPplB::before,
.pp.kid .dmPplB::after{top:36%;height:30%;transform:rotate(calc(var(--dmPplMir,1) * 38deg))}

/* WET FEET. A print is a sole, not a dot: a broad ball where the weight
   goes, a narrow arch that barely touches, a heel behind it and five toes
   ahead. Nothing here has a hard edge, because a wet mark on a dry deck
   does not have one. */
.pr{
  aspect-ratio:1/1.62;
  border-radius:48% 52% 46% 44% / 34% 34% 62% 66%;
  background:rgba(52,84,102,0);
  background-image:
    radial-gradient(56% 26% at 52% 28%,rgba(52,84,102,.5),rgba(52,84,102,0) 82%),
    radial-gradient(44% 22% at 46% 86%,rgba(52,84,102,.46),rgba(52,84,102,0) 84%),
    radial-gradient(30% 34% at 56% 56%,rgba(52,84,102,.22),rgba(52,84,102,0) 88%);
}
.pr::before{
  content:"";position:absolute;left:12%;top:-10%;width:78%;height:24%;
  border-radius:50%;
  background-image:
    radial-gradient(20% 44% at 10% 64%,rgba(52,84,102,.46),rgba(52,84,102,0) 78%),
    radial-gradient(18% 40% at 34% 34%,rgba(52,84,102,.44),rgba(52,84,102,0) 78%),
    radial-gradient(16% 36% at 57% 30%,rgba(52,84,102,.4),rgba(52,84,102,0) 78%),
    radial-gradient(14% 32% at 77% 40%,rgba(52,84,102,.36),rgba(52,84,102,0) 78%),
    radial-gradient(12% 28% at 93% 58%,rgba(52,84,102,.32),rgba(52,84,102,0) 78%);
}



/* ---- the water, and everything that moves on it -------------------------
   A pool from directly above is never still, and the three things moving on
   it are never in step: the wind writes chop across the whole surface, the
   sun leaves a sheen on it that wanders as the surface tilts, and anybody in
   it pushes rings out of it. They are three cheap layers rather than one
   expensive one on purpose. This is the layer that covers the most pixels in
   the picture, so it is the first thing that would make the page stutter,
   and nothing here does anything but move a transform or fade an opacity. */

/* THE CHOP IS GONE, and three tilings of glints at the foot of this file
   stand in for it. One tile 120 across a pool 1044 wide is nine copies of
   the same nine crests in a row, and the dark half of it was the part that
   read as a drawn net rather than as water. Nothing points at .dmWtrChop any
   more: the rect that did is three rects now. #dmWtrCrests and
   #dmWtrChopTile in the ground defs are dead along with this rule and can go
   whenever somebody is next in demo.html with room to cut a whole block. */

/* THE SHEEN. The bright patch on real water does not hold still and it does
   not hold its shape either, so this one wanders and breathes wider and
   narrower as it goes. Under cloud there is nothing for it to be a
   reflection of, so it leaves with the sun. */
.dmWtrGleam{
  opacity:calc(.03 + .15 * var(--sun, 1));
  transition:opacity 1.6s ease;
  animation:dmWtrGleamRoam 52s ease-in-out infinite;
}
@keyframes dmWtrGleamRoam{
  0%,100%{transform:translate(0,0) scale(1,1)}
  28%{transform:translate(96px,34px) scale(1.14,.88)}
  55%{transform:translate(24px,-30px) scale(.9,1.12)}
  78%{transform:translate(-74px,22px) scale(1.06,.95)}
}

/* ---- the splash ---------------------------------------------------------
   Four things happen when a body arrives, on four clocks. The crown of
   white punches up and collapses, the droplets go out and fall back, a ring
   spreads and flattens behind them, and last of all a patch of aerated water
   sits there going flat long after the rest of it is over. The first three
   were already here. The patch that is left behind is what was missing, and
   it is the part that says the water is still full of air.

   The crown is no longer a circle. Water thrown up by a body comes up in
   fingers, so it is eleven lobes of uneven reach, the way the umbrella's rim
   is not a circle either: the shape carries the information.

   The patch is not a white disc either. A flat disc that outlives the crown
   by three seconds reads as something left switched on, so it takes the foam
   gradient, lit from the upper left and fading to nothing at its own edge,
   which is what lets it lie there that long without anybody finding an edge
   on it. */
.dmWtrAer{fill:url(#dmWtrFoamG);opacity:0}
.dmWtrLip{fill:none;stroke:#FFFFFF;opacity:0}
.splash.go .dmWtrAer{animation:dmWtrAer 1.5s cubic-bezier(.2,.6,.3,1) 1}
.splash.go .dmWtrLip{animation:dmWtrLip 1.15s cubic-bezier(.1,.7,.3,1) 1}
.splash.go .dmWtrRing3{animation:sp-ring 1.35s cubic-bezier(.15,.75,.3,1) .3s 1}
/* the aerated patch: up with the crown, then a long slow flattening */
@keyframes dmWtrAer{
  0%{opacity:0;transform:scale(.2)}
  12%{opacity:.62}
  45%{opacity:.4;transform:scale(.96)}
  100%{opacity:0;transform:scale(1.3)}
}
/* The lip of the crater, which is not a circle either. It goes further out
   than the two small rings do before it gives up, which is what keeps a
   splash off the side from reading as a set of concentric circles. Off the
   chute the rings are bigger still and outrun it, which is right: that much
   water arriving throws a ring further than it digs a crater. */
@keyframes dmWtrLip{
  0%{opacity:.8;stroke-width:6;transform:scale(.3)}
  100%{opacity:0;stroke-width:.8;transform:scale(1.95)}
}

/* THE BIG ONE, where the chute empties. A body arriving off a flume is the
   loudest thing in the picture and it should stay loud: everything about it
   is wider, and every clock on it runs between two and three times as long
   as the same clock on somebody stepping in off the side. The aerated patch
   outlives the crown that made it by three seconds. */
.splash.big{width:max(54px,15%)}
.splash.big.go .sp-crown{animation-duration:.62s}
.splash.big.go .sp-ring1{animation:dmWtrRingBig 1.5s cubic-bezier(.15,.75,.3,1) 1}
.splash.big.go .sp-ring2{animation:dmWtrRingBig 1.9s cubic-bezier(.15,.75,.3,1) .2s 1}
.splash.big.go .dmWtrRing3{animation:dmWtrRingBig 2.4s cubic-bezier(.15,.75,.3,1) .48s 1}
.splash.big.go .dmWtrLip{animation-duration:2s}
.splash.big.go .dmWtrAer{animation-duration:3.4s}
.splash.big.go .sp-drops circle{animation-duration:1.25s}
.splash.big.go .sp-drops circle:nth-child(3n){animation-duration:1.5s;animation-delay:.05s}
.splash.big.go .sp-drops circle:nth-child(4n){animation-duration:1.05s;animation-delay:.03s}
@keyframes dmWtrRingBig{
  0%{opacity:.9;stroke-width:11;transform:scale(.14)}
  100%{opacity:0;stroke-width:1.2;transform:scale(2.15)}
}

/* ---- the wake -----------------------------------------------------------
   A wake seen from above is a V opening astern, and which way astern is
   depends on which way they are going, so the walk hands the heading down as
   a custom property and the whole thing is rotated by it. Without one it
   points east, which is the way the chute sends everybody anyway.

   Three parts on one element and one animation: the churn at the body is the
   element's own fill, and the two arms of the V are its pseudo elements, so
   a swimmer costs one animation and not three. The arms open wide for
   somebody swimming and close up for somebody still travelling on a glide,
   which is the difference between the two speeds seen from above. */
.pp .wk{
  left:50%;top:56%;
  width:300%;aspect-ratio:1/.66;
  --dmWtrArm:20deg;
  border-radius:50%;
  background:radial-gradient(closest-side at 88% 50%,rgba(255,255,255,.42),rgba(255,255,255,.13) 46%,rgba(255,255,255,0) 74%);
  transform:translate(-50%,-50%) rotate(var(--dmWtrHdg,0deg)) translateX(-46%) scale(.5,.6);
}
.pp .wk::before,.pp .wk::after{
  content:"";position:absolute;right:2%;top:31%;
  width:96%;height:38%;border-radius:50%;
  transform-origin:100% 50%;
  background:radial-gradient(closest-side,rgba(255,255,255,.5),rgba(255,255,255,.15) 52%,rgba(255,255,255,0) 78%);
}
.pp .wk::before{transform:rotate(calc(-1 * var(--dmWtrArm)))}
.pp .wk::after{transform:rotate(var(--dmWtrArm))}
.pp.in-w.swim .wk{animation:dmWtrWake 1.45s ease-out infinite}
@keyframes dmWtrWake{
  0%{opacity:0;transform:translate(-50%,-50%) rotate(var(--dmWtrHdg,0deg)) translateX(-30%) scale(.42,.55)}
  22%{opacity:.8}
  100%{opacity:0;transform:translate(-50%,-50%) rotate(var(--dmWtrHdg,0deg)) translateX(-58%) scale(1.2,1.3)}
}
/* THE WAKE OFF THE CHUTE. A rider leaving the mouth is still travelling, not
   drifting: the V they cut is long and nearly closed, the water at their
   shoulders is brighter than anybody else's, and it repeats fast enough to
   read as a body carrying speed rather than working for it. */
.pp.in-w.glide .wk{
  left:50%;
  width:430%;aspect-ratio:1/.44;
  --dmWtrArm:11deg;
  background:radial-gradient(closest-side at 90% 50%,rgba(255,255,255,.62),rgba(255,255,255,.2) 42%,rgba(255,255,255,0) 72%);
  animation:dmWtrGlide .62s ease-out infinite;
}
@keyframes dmWtrGlide{
  0%{opacity:0;transform:translate(-50%,-50%) rotate(var(--dmWtrHdg,0deg)) translateX(-34%) scale(.5,.7)}
  18%{opacity:.95}
  100%{opacity:0;transform:translate(-50%,-50%) rotate(var(--dmWtrHdg,0deg)) translateX(-60%) scale(1.5,1.15)}
}

/* STANDING STILL IS NOT THE SAME AS BEING OUT OF THE WATER. Somebody up to
   their waist in the shallows is still displacing it, and what that looks
   like from above is rings leaving them slowly and going nowhere in
   particular. The wake element does this job as well, since a body that is
   not moving has no wake to draw: two rings on one element, half a cycle
   apart, and the pool is never entirely quiet.

   The period is a custom property so that the half cycle can be stated
   beside it. Every ring in the pool leaving on the same beat is the one way
   this could look mechanical, so the period varies by where somebody stands
   in the crowd and the second ring is always half of whatever their own
   period turns out to be. */
.pp.in-w:not(.swim):not(.glide):not(.air) .wk{
  width:190%;aspect-ratio:1;
  --dmWtrRing:3.4s;--dmWtrHalf:-1.7s;
  background:none;opacity:1;
  animation:none;
  transform:translate(-50%,-50%);
}
.pp:nth-of-type(2n).in-w:not(.swim):not(.glide):not(.air) .wk{--dmWtrRing:4.3s;--dmWtrHalf:-2.15s}
.pp:nth-of-type(3n).in-w:not(.swim):not(.glide):not(.air) .wk{--dmWtrRing:3.9s;--dmWtrHalf:-1.95s}
.pp.in-w:not(.swim):not(.glide):not(.air) .wk::before,
.pp.in-w:not(.swim):not(.glide):not(.air) .wk::after{
  inset:0;width:auto;height:auto;
  background:radial-gradient(closest-side,rgba(255,255,255,0) 58%,rgba(255,255,255,.5) 76%,rgba(255,255,255,0) 94%);
  opacity:0;
  transform-origin:50% 50%;
  animation:dmWtrIdle var(--dmWtrRing,3.4s) ease-out infinite;
}
.pp.in-w:not(.swim):not(.glide):not(.air) .wk::after{animation-delay:var(--dmWtrHalf,-1.7s)}
@keyframes dmWtrIdle{
  0%{opacity:0;transform:scale(.3)}
  18%{opacity:.34}
  100%{opacity:0;transform:scale(1.25)}
}

/* ---- the mouth of the flume, in more detail -----------------------------
   This is where the eye goes, so it is where the water has to be best. Four
   things happen at once and all of them are true of a hose emptying into a
   pool: the jet holds a dish open in the surface and the shaded half of that
   dish lies down and to the right of it, the white core of the arriving
   water breathes, boils of aerated water break off and are carried away
   downstream, and one wide ring leaves and keeps going long after the small
   ones have gone. Downstream here is east, which is the way the water and
   the rider on top of it are both already going. */
.dmWtrDish{opacity:.24;animation:dmWtrDish 3.7s ease-in-out infinite}
@keyframes dmWtrDish{
  0%,100%{opacity:.15;transform:scale(.9,.94)}
  50%{opacity:.3;transform:scale(1.06,1.02)}
}
.dmWtrBoil{opacity:0;animation:dmWtrBoil 3.3s linear infinite}
.dmWtrBoil2{animation-delay:-1.1s}
.dmWtrBoil3{animation-delay:-2.2s}
@keyframes dmWtrBoil{
  0%{opacity:0;transform:translate(0,0) scale(.42,.5)}
  16%{opacity:.4}
  100%{opacity:0;transform:translate(82px,8px) scale(1.5,1.08)}
}
.dmWtrCore{opacity:.8;animation:dmWtrCore 1.1s ease-in-out infinite}
@keyframes dmWtrCore{
  0%,100%{opacity:.6;transform:scale(.82,.9)}
  50%{opacity:.92;transform:scale(1.12,1.04)}
}
/* THE WIDE RING STOPS AT THE WALL, because there is a wall. The mouth sits
   452 across and 452 down and the west side of the basin is at 400, so a
   ring leaving it has 52 units of water to cross and no more. Grown past
   that it walks over the coping and paints an arc of white on dry deck,
   which is exactly what it did at four and a fifth. Two and a half puts the
   last of it against the wall, still half again as wide as the three small
   rings already leaving the same spot. */
.dmWtrWide{opacity:0;animation:dmWtrWide 3.6s cubic-bezier(.15,.7,.3,1) infinite}
@keyframes dmWtrWide{
  0%{opacity:.45;stroke-width:4;transform:scale(.4)}
  100%{opacity:0;stroke-width:.8;transform:scale(2.5)}
}
.dmWtrStreak{opacity:0;animation:dmWtrStreak 2.1s ease-out infinite}
.dmWtrStreak2{animation-duration:2.7s;animation-delay:-1.3s}
@keyframes dmWtrStreak{
  0%{opacity:0;transform:translate(-8px,0) scaleX(.7)}
  20%{opacity:.62}
  100%{opacity:0;transform:translate(34px,3px) scaleX(1.3)}
}

/* ---- the pool floor, and the light that lands on it ---------------------
   The net of caustics is two tilings of one cell drifting across each other,
   which is cheaper than it looks: two rectangles, one cached tile, and a
   transform. Each cycle travels a whole number of tiles so the loop has no
   seam, and neither layer changes opacity while it runs, so there is nothing
   here that can flash at anybody.

   Each layer carries its own opacity rather than sharing a group that carries
   it for them both. A group with an opacity of its own has to be drawn into a
   buffer of its own before it can be faded, and these two move every frame, so
   that buffer would be built again every frame to no end. Note also that these
   rules never say fill: a fill here would beat the pattern named on the rect
   itself and the whole net would go out.

   The net goes with the sun. A pool floor has no caustics under a storm
   cloud, and taking the white light off the floor only ever lets the dark
   lane lines painted on it gain contrast, never lose it. */
.dmBchCxA,.dmBchCxB{pointer-events:none;transition:opacity 1.6s ease}
.dmBchCxA{opacity:calc(.046 + .414 * var(--sun, 1));animation:dmBchDriftA 34s linear infinite}
.dmBchCxB{opacity:calc(.028 + .252 * var(--sun, 1));animation:dmBchDriftB 23s linear infinite}
/* One whole tile per cycle in each axis still: 168 for the big tiling and
   115 for the small one, which is the same cell scaled to fit a 115 tile.
   The two sizes share no factor now, so the pair of them repeat every 19320
   units instead of every 840, and 840 was shorter than the pool. The two
   periods are left exactly where they were, because what was wrong down
   here was the distance and not the speed: caustics that crawl read as
   syrup, and stretching these two out to buy a longer beat would have cost
   the floor a third of its life to fix a rhythm nobody stands at the side
   of a pool long enough to hear. */
@keyframes dmBchDriftA{from{transform:translate(0,0)}to{transform:translate(168px,168px)}}
@keyframes dmBchDriftB{from{transform:translate(0,0)}to{transform:translate(-115px,115px)}}

/* ---- THE FITTINGS: the diving board bounce ---------------------------

   The plank, its grip and both its long edges are one group in the markup
   now, and .dive-plank is on that group, so the rule at line 1415 that pins
   the transform origin to the top of the fill box is pinning it to the top
   of the WHOLE board: the deck end, where the anchor block is. Everything
   from the fulcrum south moves, the mounting does not, which is the right
   way round for a diving board.

   THE SELECTOR CARRIES BOTH CLASSES on purpose. The old rule is
   .board-bounce .dive-plank, and a rule with the same specificity wins only
   by sitting lower in the file, which is a promise about where an appended
   block lands rather than a fact about the cascade. With dmFitPlank in it
   this rule wins wherever it is put.

   Straight down on a board there is no bend to draw. A board that flexes
   down foreshortens, so the free end appears to shorten under the landing,
   throw back past flat as it returns the diver, and then ring down through
   passes that get smaller each time, because a real board settles rather
   than stopping. It crosses flat four times in 1.5s, which is 2.7 a second
   and inside the three a second the flash rule allows, and it ends on
   scaleY(1) so nothing is left standing short.

   The shadow on the water does not animate. It does not need to: the plank
   retreats about ten units at the bottom of the flex and the shadow stays
   where the tip was, so the daylight opens under the tip and closes again
   for free.

   The old .52s plank-flex above is superseded and can be deleted along with
   its keyframes, styles.css 1416 to 1422, whenever somebody is next in this
   file. It is left standing rather than cut because an append only block
   cannot reach it and nothing else reads it. */
.board-bounce .dive-plank.dmFitPlank{animation:dmFitPlankFlex 1.5s cubic-bezier(.33,0,.2,1) 1}
@keyframes dmFitPlankFlex{
  0%{transform:scaleY(1)}
  12%{transform:scaleY(.934)}
  32%{transform:scaleY(1.026)}
  56%{transform:scaleY(.976)}
  78%{transform:scaleY(1.008)}
  100%{transform:scaleY(1)}
}


/* ---------------- the water slide -------------------------------------- */
/* The entry basin at the top of the tower. Water arrives from the pump and
   leaves down the chute the whole time the place is open, so the surface up
   there is never still. The dash is 10 on and 12 off, so the pattern repeats
   every 22 and the offset has to travel exactly 22 for the loop to have no
   seam in it, which is the same arithmetic the flume flow above already does.
   Slow, because this is a pool of water being drawn into a chute and not a
   jet. The global reduced motion reset stops it dead, like everything else
   on this page that moves. */
.dmSld-basinflow > path{animation:dmSld-basinrun 4.3s linear infinite}
@keyframes dmSld-basinrun{from{stroke-dashoffset:0}to{stroke-dashoffset:-22}}



/* ======================================================================
   THE SAFETY NOTICE, PINNED TO THE PICTURE

   The rule above says it is a notice pinned in the corner. It was not: it
   was a block in the flow at the foot of the page, and between its own
   height and its margin it took 150 pixels off the artwork over it. Those
   150 pixels are where the letterboxing came from. The box they left
   behind stood at 2.14 to one; the crop the scene can honestly show is
   nearer 1.6 to one; and the difference was painted as a band of flat
   green down each side, 93 pixels of it a side at 1280 by 720 and 144 on
   the shape this was measured on. Given the height back, the box and the
   crop are the same shape, the drawing fills it edge to edge, and the
   facility itself comes out 11 per cent larger rather than smaller.

   It takes the south east corner, which is the emptiest quarter of the
   site: below the deck it is lawn, and above it there is the plant yard,
   one sunbather and nothing else. It is over the WATER at no window shape
   that has been measured, which is the line that matters, because the
   water is what the picture is of; the script hands the paragraph back to
   the flow on the two shapes where that line could not be held. What it
   does reach on a small window is the east end of the pool house roof, 36
   units of it at 1280 by 800 and 194 at 1024 by 768, and the plant yard,
   most of which goes at 1100 by 620. The roof is the one large thing
   drawn here that hides nobody, because the shelter under it is already
   out of sight, and the yard is pipework.

   The card is opaque, so the words are read against white at 7.49 to one
   whatever the deck beneath them is doing, and it is a SIBLING of the
   scene rather than a child of it, so the brightness the sun drives
   across the artwork never reaches the type.

   The class is written by the script, at the moment it actually moves the
   paragraph. With no script there is no class, and the notice stays a
   block in the flow where it has always been.
   ====================================================================== */
.demo-legal.dmFrmPinned{
  position:absolute;z-index:12;right:10px;bottom:10px;
  align-self:auto;margin:0;
  max-width:min(46ch, calc(100% - 20px));
}
/* A phone held upright has neither of those corners free. The scenario
   buttons lie across the foot of the picture and the fixture holds the top
   right, so the notice takes the top left, which on a portrait phone is a
   strip of lawn above the drawing rather than any part of the facility.
   Held sideways it is not pinned at all, and neither is any window under
   431 tall: there the strips are down the sides, too narrow for type, and
   the corners are over the water. The script leaves the paragraph in the
   flow on those, so this rule never gets a class to match. */
@media (max-width:760px){
  body.demo .demo-legal.dmFrmPinned{right:auto;bottom:auto;left:6px;top:6px}
}



/* ---- THE LIGHT ON THE WATER, in three passes that never line up ---------
   The one tiling that did this job was 120 across a pool 1044 wide, which is
   eight and a half copies of the same nine crests in a row, and it came back
   to its own starting place every 34 s. Both of those are rhythms, and
   sunlight on water has none. Half of it was drawn in deep blue as well,
   which is what made the surface read dark: a trough is a thing you see
   because the crest beside it is bright, not a line anybody draws. Measured
   over the open water, that half pulled 4.1 per cent of the surface down by
   as much as 14 in luminance, which is grey speckle and not shadow.

   So it is three tilings now, 97, 131 and 173 across. Those are primes, so
   the three together do not repeat until 2198311 units, which is two
   thousand pool lengths. The periods are 31, 47 and 67 s, primes as well, so
   the three of them beat once every 97619 s and the speeds bracket the one
   the old chop ran at.

   Each layer still travels a whole number of its OWN tiles per cycle, so
   each one on its own is seamless. They are white and only white, because
   which way the sun falls on this water is the job of the sheen above them,
   and the sheen is already doing it. The three opacities stack to about
   where the one white pass sat, and a place where two of them happen to
   cross is the bright glint the old surface never had.

   Nothing here is filtered and nothing here changes shape. Three translating
   fills is the same work the two caustic layers and the old chop were doing
   between them, and the drawn net of parallel waves that used to run over
   this comes out in the same change, so the number of things moving on this
   water is the number it always was. */
.dmRflSurfA,.dmRflSurfB,.dmRflSurfC{pointer-events:none;transition:opacity 1.6s ease}
.dmRflSurfA{opacity:calc(.045 + .11 * var(--sun, 1));animation:dmRflDriftA 31s linear infinite}
.dmRflSurfB{opacity:calc(.035 + .085 * var(--sun, 1));animation:dmRflDriftB 47s linear infinite}
.dmRflSurfC{opacity:calc(.028 + .062 * var(--sun, 1));animation:dmRflDriftC 67s linear infinite}
/* One whole tile per cycle in each axis, or a whole number of them: one east
   and one south for the first, two east and one south for the second, one
   west and one south for the third. Three angles as well as three speeds,
   and every one of them still lands back on its own lattice. */
@keyframes dmRflDriftA{from{transform:translate(0,0)}to{transform:translate(97px,97px)}}
@keyframes dmRflDriftB{from{transform:translate(0,0)}to{transform:translate(262px,131px)}}
@keyframes dmRflDriftC{from{transform:translate(0,0)}to{transform:translate(-173px,173px)}}

/* ---- HOW HARD THEY ARE PUSHING -----------------------------------------
   A wake is a picture of speed, and every wake in this pool was the same one
   whatever the body making it was doing. Somebody drifting a body length
   across the shallow end pushes a small blunt nudge of water that takes its
   time leaving. Somebody swimming the length of it pushes a long narrow V
   that reaches back behind them and repeats fast.

   The walk already knows which of those it is, because it prices every leg
   before it starts it. So it hands the speed down as one number and all the
   drawing happens here. The number is scaled so that ONE is an ordinary swim
   leg, and an ordinary swim leg is handed back exactly the 300 per cent, the
   20 degrees and the 1.45 s this wake has always had. Everything else moves
   off that: a drift lands near a fifth, a walk into the water near one and a
   half, a full length lap above two.

   The V closes as the speed rises, the way a boat wake does, and the whole
   thing grows with it. The floor of .55 is there so a body barely moving
   still ripples rather than vanishing, and the 165 per cent it lands on sits
   just inside the 190 per cent ring this same element draws when they stop.

   The glide off the chute states its own width and its own arm angle further
   up this file under four classes against this rule two, so it keeps all of
   what it was given. The lap swimmers state their own width but never stated
   an arm angle, so their kick lobes do close up as they go, which is right
   for the fastest bodies in the pool and is the one thing here that reaches
   past the general wake. */
.pp .wk{
  width:calc(300% * clamp(.55, calc(.55 + .45 * var(--dmRflSpd, 1)), 1.45));
  --dmWtrArm:calc(30deg - 10deg * clamp(.3, var(--dmRflSpd, 1), 1.6));
}
/* Slower when they are slower, and never quicker than five beats in six
   seconds even with the jitter at its shortest. The per person jitter rides
   on top of this as a multiplier rather than as a duration of its own, so
   the crowd still does not ring in unison. */
.pp.in-w.swim .wk{
  animation-duration:calc(var(--dmRflJit, 1) * (2s - .55s * clamp(.3, var(--dmRflSpd, 1), 1.6)));
}

/* ---- THE BASIN AT THE TOP OF THE TOWER ---------------------------------
   Only the strip below 744 is ever on show up there, because the chute is
   drawn on the roof layer and covers everything above it. Three dashed
   threads running fast through a window that size read as a rapid. This is a
   bowl of water waiting its turn, so one thread runs and the two beside it
   stand still as the seams they always were. */
.dmSld-basinflow > path:first-child,
.dmSld-basinflow > path:last-child{animation:none;opacity:.16}


/* ---- THE LANE ROPES RIDE THE WATER -------------------------------------
   A lane rope floats, so it lifts and settles with the surface under it. It
   is the discs that move and not the anchors, which is why only the two
   groups holding the ropes carry this and the four cups in the walls do not.
   The two ropes are on different clocks on purpose: ropes rising together
   are one wave, and a pool never has one wave in it.

   Three tenths of a cycle a second at the quicker of the two, which is a
   long way under the three a second nothing on this site may exceed, and
   the reduced motion reset further up this file takes both of them out
   along with every other animation on the page. */
@keyframes dmMrkBob{from{transform:translate(0,-1.6px)}to{transform:translate(0,1.6px)}}
.dmMrkRopeA{animation:dmMrkBob 3.2s ease-in-out infinite alternate}
.dmMrkRopeB{animation:dmMrkBob 4.1s ease-in-out infinite alternate;animation-delay:-1.3s}


/* ======================================================================
   TWO PEOPLE LYING IN THE SUN, AND A DECK WHERE NOBODY STANDS FROZEN.
   The idle clock in demo.html writes two things onto a body: a few degrees
   of turn, straight onto the property the whole crowd already turns on, and
   the class below, which is what somebody looks like once they are off
   their feet.

   BOTH SUNBATHERS LIE THE SAME WAY UP, AND THAT IS THE WHOLE REASON THEY
   ARE BOTH ON THE TOP ROW. The sun is upper left over this site: highlights
   up and left, shadows down and right, no exceptions. A gradient and a box
   shadow are written in the element own frame and a transform carries them
   round with it, so a body turned a quarter on can only keep its light
   pointing at the right corner by having every offset turned a quarter
   back, which means writing a shadow that reads as up and left in the
   source and only lands down and right after the browser has turned it. The
   chaise avoided that by baking its offsets into four upright wrappers and
   never rotating a placement; a person cannot, because the same person gets
   up and walks about. So the two chairs picked for this are both set with
   the head to the fence and the foot to the water, the body below is drawn
   the same way up as a standing one, and every offset here is down and
   right exactly as written.
   ====================================================================== */

/* LYING DOWN IS LONG, NOT ROUND. A standing body seen from straight up is a
   head on a set of shoulders and nothing else shows. A body on a lounger
   shows its whole length: the shoulders become a torso that runs the length
   of the sling, the head sits at the raised end of the chair rather than
   over the middle of the body, and the arms come down along the sides
   instead of out from them.

   IT IS NARROWER THAN A STANDER, ON PURPOSE. This crowd is drawn well over
   the scale of its own furniture: a chaise is 26 units across and a set of
   standing shoulders is 48, so a body laid out at the crowd own width would
   be twice the width of the chair under it and would read as somebody
   sprawled over four of them. 27 across the torso and 35 to the outside of
   the arms is the compromise. That is a little wider than the chair, which
   is what a person on a lounger really is. */
.pp.dmLifDown::before{
  inset:22% 25% 6% 25%;
  border-radius:44% 44% 42% 42% / 22% 22% 24% 24%;
  background-image:
    radial-gradient(46% 30% at 30% 16%,rgba(255,255,255,.44),rgba(255,255,255,0) 74%),
    linear-gradient(148deg,rgba(255,255,255,0) 48%,rgba(5,26,36,.3));
  box-shadow:2px 3px 3px rgba(7,32,40,.28);
}
/* the head, at the raised end of the chair and no longer sitting on top of
   half the body the way a standing head does */
.pp.dmLifDown::after{
  top:2%;width:28%;
  background-image:radial-gradient(circle at 32% 26%,rgba(255,255,255,.5),rgba(255,255,255,0) 60%);
  box-shadow:1.6px 2.4px 2px rgba(7,32,40,.26);
}
/* Arms down the sides, near enough straight. The 15 degrees a standing arm
   hangs at is a shoulder holding a limb clear of an upright body; lying down
   the arm is resting beside them and has nothing to swing round. They hang
   about four units past the rail on each side and throw their shadow onto
   the deck rather than onto the chair, which is what an arm on a lounger
   does. The towel in the drawing underneath does not follow them off the
   chair: a towel cut wide enough to catch an arm is wide enough to paint the
   chair out, which is argued in the def. */
.pp.dmLifDown .dmPplB::before,
.pp.dmLifDown .dmPplB::after{
  top:34%;width:10%;height:30%;
  border-radius:50% 50% 46% 46% / 30% 30% 26% 26%;
  background-image:linear-gradient(148deg,rgba(255,255,255,.34),rgba(255,255,255,0) 58%,rgba(5,26,36,.28));
  box-shadow:2px 3px 2px rgba(7,32,40,.22);
  transform:rotate(calc(var(--dmPplMir,1) * 4deg));
}
.pp.dmLifDown .dmPplB::before{left:18%}
.pp.dmLifDown .dmPplB::after{right:18%}


/* ---------------- THE FLUME AS A TUBE -----------------------------------
   The roundness of the chute is all in the drawing: eighty stations along the
   centre line, each length of wall sorted into a tone band by which way it
   faces the sun. None of that moves, so none of it is here. What is here is
   the water, and there are four clocks of it.

   THE CHUTE. The flow already running the length of the run keeps its 1.7s
   and its 2.6s. These two say where the run is quick and where it is deep.
   The fast thread lives only on the straights: 5 on and 34 off is a pattern
   39 long and it travels exactly 39, so the loop has no seam in it, and 39
   units in .95s is about two and a half times the speed of the flow it is
   laid over. That is one dash a second past any given point, a third of the
   three a second nothing on this site may exceed.

   THE BENDS. The slow thread rides the outside wall, where the water piles
   up. 18 on and 24 off is 42 long and travels 42, in 5.2s, so the same water
   crawls round the turn at a fifth of the speed it left the straight at. */
.dmTubRush{animation:dmTubRush .95s linear infinite}
.dmTubPile{animation:dmTubPile 5.2s linear infinite}
@keyframes dmTubRush{from{stroke-dashoffset:0}to{stroke-dashoffset:-39}}
@keyframes dmTubPile{from{stroke-dashoffset:0}to{stroke-dashoffset:-42}}

/* THE ENTRY BASIN. A boil is a ring of water spreading and dying, so the ring
   grows and fades and there is nothing left of it by the end of the cycle.
   Three of them on one 3.6s clock a third of a cycle apart, so one leaves the
   middle every 1.2 seconds. fill-box for the same reason the rain pocks up
   the file have it: without it a shape inside the scene scales about the
   middle of the whole drawing and goes travelling across the pool as it
   grows, and this one would leave the tower altogether. */
.dmTubBoil > ellipse{
  transform-box:fill-box;transform-origin:center;
  animation:dmTubBoil 3.6s linear infinite;
}
.dmTubBoil > ellipse:nth-child(2){animation-delay:-1.2s}
.dmTubBoil > ellipse:nth-child(3){animation-delay:-2.4s}
@keyframes dmTubBoil{
  0%{transform:scale(.5);opacity:0}
  18%{opacity:.5}
  100%{transform:scale(2.8);opacity:0}
}
/* The slow turn of the bowl. Eleven seconds to the revolution is a tenth of a
   turn a second, which is water being drawn round rather than stirred. It
   turns about the middle of the two arcs, which is the middle of the bowl,
   for the same reason the rings scale about themselves. */
.dmTubEddy{
  transform-box:fill-box;transform-origin:center;
  animation:dmTubEddy 11s linear infinite;
}
@keyframes dmTubEddy{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
/* The pull into the throat. 7 on and 13 off is a pattern 20 long travelling
   exactly 20 in 2.4s, half again the speed of the bowl flow beside it, which
   reads as the water gathering itself where the basin necks in. */
.dmTubDraw{animation:dmTubDraw 2.4s linear infinite}
@keyframes dmTubDraw{from{stroke-dashoffset:0}to{stroke-dashoffset:-20}}



/* ======================================================================
   THE RIDE: the chute, and what the water does to a body that leaves it.
   ====================================================================== */

/* WHILE THEY ARE ON THE CHUTE, left and top belong to the flume's own line.
   The script hands the descent to one animation along the drawn path rather
   than walking the corners between waypoints, and a transition on the same
   property fights it. Taking those two out of the list settles that, and it
   settles something else on the way: with only two properties left to match
   the durations against, the transform picks up the LEG's own duration
   instead of a flat quarter second, so the body finishes turning into a bend
   inside the leg that bends rather than a beat behind it. The list goes back
   to what it was one leg short of the mouth, on a straight, where the swap
   cannot show. */
.pp.dmRidChute{transition-property:opacity,transform}

/* COMING UPRIGHT IN THE WATER. Every part of it is stated here as a
   transition rather than left to a class going off, so the long body
   shortens, the head slides back onto the shoulders, the folded arms open
   out and the whole frame turns off its side together, over the same second
   and a half the glide spends slowing down.

   The duration is a custom property because the script owns it: it is read
   off how much speed is left rather than written into a stylesheet. It is
   nought until the script says otherwise, so nobody who is not coming off a
   slide is touched by any of this. */
.pp.in-w.glide::before,
.pp.in-w.glide::after,
.pp.in-w.glide .dmPplB::before,
.pp.in-w.glide .dmPplB::after{
  transition:
    top var(--dmRidMs,0ms) cubic-bezier(.24,.66,.32,1),
    right var(--dmRidMs,0ms) cubic-bezier(.24,.66,.32,1),
    bottom var(--dmRidMs,0ms) cubic-bezier(.24,.66,.32,1),
    left var(--dmRidMs,0ms) cubic-bezier(.24,.66,.32,1),
    width var(--dmRidMs,0ms) cubic-bezier(.24,.66,.32,1),
    height var(--dmRidMs,0ms) cubic-bezier(.24,.66,.32,1),
    border-radius var(--dmRidMs,0ms) cubic-bezier(.24,.66,.32,1),
    transform var(--dmRidMs,0ms) cubic-bezier(.24,.66,.32,1);
}
/* and these are the swimmer's own numbers restated on a body that is still
   gliding, which is what gives the transitions above somewhere to go. The
   turn is the same move: a rider lies along their travel and a swimmer does
   not, so the quarter turn comes off as the shape that needed it goes. */
.pp.in-w.glide.dmRidRise{--dmPplR:0deg}
.pp.in-w.glide.dmRidRise::before{inset:26% 8% 8% 8%;border-radius:50%}
.pp.in-w.glide.dmRidRise::after{top:4%;width:50%}
.pp.in-w.glide.dmRidRise .dmPplB::before,
.pp.in-w.glide.dmRidRise .dmPplB::after{
  top:34%;width:19%;height:36%;
  border-radius:48% 48% 50% 50% / 26% 26% 74% 74%;
  transform:rotate(calc(var(--dmPplMir,1) * 30deg));
}
.pp.in-w.glide.dmRidRise .dmPplB::before{left:1%}
.pp.in-w.glide.dmRidRise .dmPplB::after{right:1%}



/* ---------------- the gate leaf's shadow --------------------------------

   The leaf swings, so its shadow has to swing too, and it has to stay down
   and to the right of the leaf the whole way round. Those are two different
   jobs, so they live on two different groups in the markup: the outer one
   carries the offset as a plain translate in scene coordinates that nothing
   ever turns, and this rule turns the inner one.

   It is a copy of the .gate-leaf rule above and it has to stay a copy. Same
   pin, because it is the same fill box and the same top centre origin. Same
   three verdicts, because a shadow of an open gate on a closed pool is worse
   than no shadow at all. Same duration and same curve, because a shadow that
   arrives after the thing casting it is a shadow the eye follows instead of
   the gate. If the angles above are ever changed, change these with them. */
.dmTexGtLeafSh{
  transform-box:fill-box;transform-origin:top center;
  transform:rotate(-82deg);          /* swung out of the opening: open */
  transition:transform 1.1s cubic-bezier(.3,.8,.4,1);
}
[data-wx="caution"] .dmTexGtLeafSh,
[data-wx="closed"] .dmTexGtLeafSh,
[data-wx="soon"] .dmTexGtLeafSh{transform:rotate(0deg)}   /* across the gap: shut */
