/* 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 0.8 flashes a second, 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);
}
.badge{
  display:inline-flex;align-items:center;gap:10px;margin-top:30px;
  background:var(--card);border:1px solid var(--edge);
  color:var(--ink);font-weight:600;font-size:14.5px;
  padding:11px 20px;border-radius:999px;box-shadow:var(--cast-sm);
}
.badge .dot{
  width:10px;height:10px;border-radius:50%;background:var(--lamp-green);
  box-shadow:0 0 0 4px rgba(11,122,66,.16);
}

/* ---------------- 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}
[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 unhurried cadence. 1.25 seconds is 0.8
   blinks a second, comfortably under the 3 a second ceiling for flashing
   content, and 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 switch is hard rather than faded, which is what a filament lamp on a
   flasher relay does, so the transition above is taken off the two things that
   blink. It is never applied to the hood. */
[data-wx="caution"][data-blink="off"] .lamp-a .lens{fill:var(--off-amber)}
[data-wx="caution"][data-blink="off"] .halo-a{opacity:0}
[data-wx="caution"] .lamp-a .lens,
[data-wx="caution"] .halo-a{transition:none}

/* 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 lock screen 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)}
[data-blink="off"] .state.caution .ml-a{fill:var(--off-amber)}
.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)}
.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)}
footer .mark{font-family:var(--display);font-weight:900;font-size:17px;letter-spacing:-.2px}
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 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(1148px, calc(100% - 2 * 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%)}
.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:.95;transform:scale(.2)}
  45%{opacity:.9}
  100%{opacity:0;transform:scale(1.2)}
}
/* 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}
@keyframes flume-run{from{stroke-dashoffset:0}to{stroke-dashoffset:-46}}

/* 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. */
.cx-net{fill:none;stroke:#FFFFFF;stroke-linecap:round}
/* 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 7s linear}
[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 */

.dive-plank{transform-box:fill-box;transform-origin:bottom 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}
/* Above even the roof layer, for the one stretch where a person is on top of
   a structure rather than under it: the rider crossing the ramp out of the
   slide tower. Everywhere else they sit between the ground and the roof, so
   the same ramp passes over them when the loop brings them back beneath it. */
.pp.on-top{z-index:9}

/* 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)}
[data-blink="off"] .demo-sec[data-demo="caution"] .demo-board .ml-a{fill:var(--off-amber)}
.demo-sec[data-demo="closed"] .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}
  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}
  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}
}
