/* arecibo.css - the landing's transmission. Owner: the Arecibo piece.
   Loads after landing.css, so it wins on equal specificity by cascade order.

   ================= THE ONE DECLARED PALETTE EXCEPTION =================

   The site's identity is four colours - ground, ink, gray, red - plus the
   lattice's unlit cell (spec 3). THIS CANVAS IS THE ONE EXCEPTION, and it is a
   decision, not a leak. It is written into the spec (section 3) and into
   tools/beefydan/RUBRIC.md; it is not re-litigated here.

     Developer  face (white): the 1974 Arecibo message in the SIX inks measured
                       off Dan's own picture (white, purple, green, blue,
                       yellow, red), each cell moving only in lightness.
     Storyteller face (black): the same message with the chroma removed - six
                       neutral greys on the dark ground, nothing else.

   SWAPPED 2026-09-05 (Dan: "the colored version of the Arecibo Message looks
   so much better on the white canvas. Put the colored version on white and
   the gray version on the black background"). Which face is coloured is the
   --msg-chroma knob on each face, so the exception's scope is unchanged: one
   canvas, six inks, lightness only.

   Dan's brief, 2026-09-04, in his words: "Use consistent color palette with the
   picture and rather than introducing new colors just make each section of the
   message behave as if the pixels are changing shades... then when the user
   toggles the switch, the entire message changes to black and white, to feel
   more inline with the aesthetic of the developer side."

   So the exception has an exact SCOPE and an exact REASON. Scope: the lit cells
   of one canvas, in the picture's own six inks, at lightnesses of those inks
   only - no renderer here may move a hue or introduce a seventh (arecibo.js
   enforces that by scaling LINEAR rgb, where OKLab L scales as cbrt and a hue
   cannot move). Reason: the artwork Dan handed over IS six colours, and the
   toggle's whole payoff is that the Developer face is the SAME picture in grey.
   Draining the Storyteller face to --ink would satisfy the palette table and
   delete both. Everything else on this page still obeys the four.

   It is readable at run time as `window.beefydanArecibo.palette`, so a craft
   check reads the decision instead of inferring one from the pixels.

   THE WHOLE PLACEMENT IS THE BLOCK BELOW. Everything about where the message
   sits and how it behaves is a custom property on the canvas, so it can be
   moved in one line. arecibo.js reads these and draws what they say; it
   decides nothing about placement.

   ================= 2026-09-05: THE MESSAGE IS THE PAGE =================

   Dan's brief, in his words: "remove the bear. The page should only have the
   Arecibo Message. Leave the background as is. Let's put a toggle on the
   bottom of the page... Make the Arecibo message the center attraction.
   Clean, just the message, 1 toggle that changes 1 text that changes back and
   forth."

   So the corridor beside the word, the rule through row 25, the sub-tile nudge
   and the per-breakpoint columns that rounds 6 to 12 solved are gone with the
   composition they solved for; that history is in git (this file before this
   date) and in .tmp/shots/arecibo*. What is left is a plain centring:

     THE BOX is the middle of the width and the whole height above the foot's
     band (landing.css --foot-h, where the word and the switch live), so the
     artwork and the control own their space by construction and never meet.
     THE MESSAGE is centred in that box - --msg-cx 0.5, --msg-cy 0.5, the
     anchor row the middle one - at 90% of the box's height.
     THE PITCH FILLS THE BOX. --msg-snap 0 lets a cell be any whole CSS pixel
     rather than a whole ground tile, because with the message as the
     centrepiece its SIZE is the composition. Snapping stepped it down a whole
     tile at a time: 584px tall at 900, 1080 and 1300 tall frames alike, and
     92 x 292 at a 4px cell on a phone. A whole-pixel fit holds it at ~90% of
     the box at every height instead. Cells still land on whole pixels, so
     nothing is resampled and no edge is softened; what the snap bought - the
     ends' dissolve landing on the ground's own dots - has not been needed
     since round 13 made the dissolve a corner radius.

   MEASURED on the rendered page (Playwright, the lit cells' bounding box
   against the frame, 2026-09-05), message width x height, cell pitch, and how
   far its centre sits from the frame's:

     1440 x 900         207 x 657   9px   0.5px     (was 184 x 584 at 8, left)
     2560 x 1300        322 x 1022  14px  0.5px     (was 184 x 584 at 8, left)
      768 x 1024        230 x 730   10px  1.5px     (was 138 x 438 at 6, left)
      390 x 844 (phone) 184 x 584   8px   0         (was  92 x 292 at 4, left)
      375 x 667 (phone) 138 x 438   6px   2px       (snapped: 6 is the ground's
                                                     own tile, so the centre can
                                                     only land on a tile edge)

   The foot's ink starts 76px (desktop) / 88px (phone) under the message's
   last row; the page's peak backing store mid-sweep is 1.3M px at 1440 x 900
   and 2.2M px on the phone at dpr 2, two canvases, against the 8M ceiling.

   THE KNOB NOTES BELOW STILL QUOTE THE OLD PICTURE'S NUMBERS. Every
   measurement in them (the 6px desktop cell, the 4px phone cell, "beside a
   bear of dense black") was taken on the composition this replaced. They
   describe how the knobs BEHAVE, which has not changed; they do not describe
   where or how large the message is, which has. The envelope, the ladder, the
   halo, the wave and the rest state are exactly as round 13 left them. */

.land__msg {
  position: absolute;
  left: 20%;
  width: 60%;
  top: 0;
  /* the whole height above the foot: landing.css reserves --foot-h for the
     word and the switch, and this box ends where that band begins */
  height: calc(100% - var(--foot-h));
  /* under the foot, which is the page's only control; nothing else is here */
  z-index: 0;
  pointer-events: none;

  /* ---- placement ---- */
  --msg-cx: 0.5;         /* the message's horizontal centre, as a fraction of the box */
  --msg-shift-x: 0;      /* the sub-tile nudge evened the old corridor; a centred box
                            has nothing to even */
  --msg-cy: 0.5;         /* the box line the anchor row lands on: the middle */
  --msg-anchor-row: 36;  /* the middle row of 73, so this is a plain centring */
  --msg-w: 0.9;          /* width it may take, as a fraction of the box */
  --msg-h: 0.9;          /* height it may take - and so the number that chooses the
                            pitch, since the fit is floored to whole pixels. 0.9
                            leaves 5% of the box as air above and below. */
  --msg-snap: 0;         /* whole pixels, not whole ground tiles: see the head of
                            this file */
  --msg-scale: 4;        /* the ceiling in either mode: a cell is at most this many
                            ground tiles (32px on the desktop lattice) */
  --msg-gutter: 0.25;    /* of the pitch left as gap, so cells read as cells */

  /* ---- colour or print ----
     The black face is the PRINT since 2026-09-05: six greys ordered by the
     light the inks carry on the colour side, on the same ladder of light. */
  --msg-chroma: 0;

  /* ---- the ends: A STATIC SHAPE, AND NOTHING THE WAVE TOUCHES ----
     Rows over which the message becomes the ground's own lattice, top and
     bottom, so it enters and leaves the picture by becoming ground rather than
     by being cut off.

     ROUND 13 TOOK THE SIZE OUT OF IT. Until now these rows walked a cell down
     to the size of the ground's dot, and an end cell drawn at a fifth of a
     cell's area breathes at a fifth of the depth: measured on the last build,
     rows 0 and 72 - the binary numbers 1 to 10 at the top, the dish's diameter
     at the foot - swung 53.1 and 47.5 in tone against the body's 119.5-213.2,
     and 30.2 and 18.1 in L* against the body's 33-67.5. Two of the message's
     most legible lines, nearly still.

     SO THE DISSOLVE IS A CORNER RADIUS. arecibo.js (buildRound) walks a cell
     from a hard square in the body toward a round dot - the ground's own form,
     at the message's own size - over these rows. It is a function of the ROW
     alone, resolved with the geometry, and no phase of the wave can reach it.
     At two rows the ramp reaches 2.53px of a possible 3 on row 0 and 0.47px on
     row 1 (desktop, 6px cell), so the outermost row is very nearly a circle and
     the next one is a square with the corners just off it.

     WHAT IT COSTS is the 15.3% of area between that shape and its square, and
     nothing else - no light, no rung, no phase. Rows 0 and 72 now swing 206.2
     and 173.9 in tone and 70.9 and 43.5 in L*, against a body of 113.3-218 and
     35.2-73.6: inside the body's own range on both measures, where the old
     shrinking dissolve had them at a fifth of it.

     Two rows, not three: at three the outermost row was down at 7% under the
     old shrinking dissolve, and two is what the shape ramp inherited. */
  --msg-fade-top: 2;
  --msg-fade-bot: 2;

  /* ---- the envelope: ONE WAVE OF LIGHT, AND NOW IT CARRIES THE WHOLE SWING ----
     (3:1 floor withdrawn round 10; measured in light round 11; the contraction
      that used to carry half the depth deleted round 13)

     --msg-floor is the round-3 sentence, read the same way on both faces:

       at the bottom of the wave a cell has travelled this fraction of the way
       from the PAGE'S OWN GROUND to its crest, measured in LIGHT.

     Round 8 made that fraction the SAME for all six inks, because round 6 had
     bolted a per-ink contrast floor on top of it and the message came apart
     into two breathing depths. Round 11 kept the one fraction and moved the
     space it is measured in from OKLab L to LIGHT, which is what makes one
     fraction one dimming on six different inks. Both still hold.

     WHAT ROUND 13 CHANGED IS HOW MUCH OF THE SWING THIS NUMBER HAS TO CARRY,
     and the answer is now ALL of it. Until now the wave moved two things: tone,
     and MASS - a cell gave up whole pixels at the bottom rungs, so a crest cell
     filled its 6x6 body and the same cell at the trough was a 4x4 square inside
     the same tile. A critic reading Dan's own reference frames named that as
     the last deviation from the brief, and it is: every frame of the reference
     holds each cell at a constant full square and carries the entire wave in
     tone, which is why its dim cells read as a dark version of their own hue
     and not as a receding dot. Measured off those frames - a 26px pitch, a 22px
     cell - the reference's dimmest cells sit at 21-30 against a ground of 19
     and its brightest at 221-244, all at the same size.

     SO THE CONTRACTION IS GONE AND THE LADDER WAS WIDENED TO REPLACE IT. The
     old trough put out 0.031-0.037 of its crest's light only because 0.444 of
     that was area; with a full square the same depth has to come out of tone,
     which takes the floor from 0.05 to 0.015 of the journey. Measured at
     1440x900 on the rendered page, over 48 phases of a whole cycle, per row,
     as min / median / max across rows, in two units: the mean TONE of the drawn
     cell, and the L* of the mean LIGHT of its tile.

                              old (size + tone)      new (tone alone)
       body rows, tone         119.5 / 208.4 / 213.2  113.3 / 205.0 / 218.0
       body rows, L*            33.0 /  45.0 /  67.5   35.2 /  48.7 /  73.6
       row 0                    53.1 tone,  30.2 L*   206.2 tone,  70.9 L*
       row 72                   47.5 tone,  18.1 L*   173.9 tone,  43.5 L*

     THE TWO MEASURES DISAGREE BY ABOUT 2% AND THE REASON IS WORTH STATING. In
     L* - the light the tile actually puts out, read on a perceptual scale - the
     new build is deeper at every quantile: +6.7% at the weakest row, +8.2% at
     the median, +9.0% at the strongest. In mean TONE it is +2.3% at the top and
     -1.6% at the median. That second number is an artefact of the OLD build,
     not a loss in the new one: averaging gamma-encoded values over a tile only
     means something when the tile is one colour, and the old trough's tile was
     a MOSAIC - a bright 4x4 square in a dark surround. The eye integrates
     light, not 8-bit codes. Both numbers are here because both were measured;
     the L* one is the one that describes a picture.

     The two end rows are the headline: they were a fifth of the message's depth
     and are now inside the body's own range.

     THE LEGIBILITY FLOOR IS OFF, AND THAT IS THE CONTROLLER'S 2026-09-05
     RULING APPLIED (RUBRIC.md: the ratio floor is withdrawn for this decorative
     aria-hidden canvas; ratios are information, not a threshold). It could not
     stay: --msg-min-contrast 1.25 pins the trough at 0.067 of the journey
     whatever --msg-floor says, which is where the old build sat, and the whole
     of this round is that the trough has to go deeper now that it is the only
     thing moving. One knob sets the trough now, and it is --msg-floor.

     WHAT THE TROUGH ACTUALLY LOOKS LIKE, judged against the reference and not
     against a table. MEASURED at 1440x900, bottom rung .. top rung, with the
     WCAG ratio of the bottom rung against this page's own ground:

       W  rgb(37,37,37)   .. rgb(255,255,255)   1.28:1 .. 19.67:1   step 218
       Y  rgb(38,40,1)    .. rgb(246,255,27)    1.30:1 .. 18.02:1   step 215
       G  rgb(1,38,1)     .. rgb(28,255,28)     1.20:1 .. 14.40:1   step 217
       M  rgb(40,1,47)    .. rgb(221,27,252)    1.06:1 ..  5.28:1   step 205
       R  rgb(49,3,3)     .. rgb(255,13,31)     1.07:1 ..  4.99:1   step 206
       B  rgb(1,17,49)    .. rgb(21,117,255)    1.05:1 ..  4.70:1   step 206

     Those are close to the ground and they are MEANT to be: Dan's reference
     frames put their dim cells at 1.02-1.11 against their own ground, so this
     is inside the picture we were handed rather than under it. 0.015 is also
     where the eight-bit grid stops being able to hold a hue: below it the
     ladder's OKLab hue starts to wander (0.59 degrees at 0.012, 0.83 at 0.008,
     against 0.465 here and 0.497 on the old build), because one integer step is
     a large fraction of a small channel. See `quantize` in arecibo.js. What keeps a
     trough cell a MARK is no longer its tone alone - it is that it is a full
     6x6 square of its own hue, 9 times the area of the 2.2px lattice dot it
     stands beside, at every phase of the wave. That is the trade this round
     made: the cell gave up the right to shrink and got the right to go dark.

     THE BREATH IS STILL EVEN, and it needs three numbers now instead of one,
     because a single 8-bit RATIO stops describing a wave once its trough is
     deep. arecibo.js publishes all three on `ladder`:

       spread      1.324  crest/trough in the dominant channel (was 1.080).
                          The check asks under 1.6. It rose because sRGB's curve
                          near black turns equal light into unequal 8-bit steps,
                          not because the inks came apart.
       spreadStep  1.063  crest MINUS trough in the dominant channel (was 1.041)
                          - the swing a screen actually shows, and what a
                          per-row measurement reduces to. Unchanged in kind.
       trough      one fraction of one journey in light for all six inks, by
                          construction, exactly as round 11 left it.

     --msg-dyn: 1 uses the whole travel. Below 1 it pulls the bottom rung back
     toward the crest for every ink at once, which only ever raises contrast.
     Every breakpoint is at 1. */
  --msg-min-contrast: 1;
  --msg-floor: 0.015;
  /* --msg-ink-floor and --msg-ink-pow shape the WHITE face's ladder and are not
     read on this one, so they are declared once, in its own block below, rather
     than defaulted here and overridden there. */
  --msg-dyn: 1;
  --msg-gamma: 1;      /* shapes the crest: > 1 broadens the trough, < 1 the band */

  /* ---- the crest ----
     RAISING A TROUGH WITHOUT RAISING A CREST IS FLATTENING, and the reference
     clip's whole character is a wide swing. --msg-lift is how far past the
     value measured off Dan's picture an ink may brighten, as a LINEAR gain -
     which moves OKLab L by its cube root and cannot move a hue. arecibo.js
     clamps it per ink to the gain at which that ink's largest channel would
     clip, because clipping is exactly where a hue WOULD move, so the ceiling is
     arithmetic and different for every ink: green takes 1.67x, magenta 1.80x,
     red 1.36x, blue only 1.05x, and white and yellow are already at 255 and
     cannot lift at all. The crest each one reaches is in the ladder table
     above; the bottom of every ladder is --msg-floor, full stop - the
     legibility rule that used to be able to raise it is off (see above).

     Blue's range is the artwork's own arithmetic and no renderer can widen it -
     250 of its 255 blue is already spent, so a saturated blue simply cannot be
     bright on a near-black ground without becoming another colour. The picture's
     own measured inks now sit in the MIDDLE of each ladder rather than at the
     top, which is what "changing shades" of them means.

     --msg-crest belongs to the white face only (see the block below): it is the
     OKLab L the darkest mark reaches there. On the black face the top of the
     ladder is --msg-lift and this value is not read. */
  --msg-lift: 1.8;
  --msg-crest: 1;

  /* ---- the rungs ----
     The wave does not glide, it STEPS. A continuous ramp measured
     198,196,194,191,189,187,185,182,180,179,175 along one lit row - under 1%
     between neighbours, a gradient wearing a grid - where the reference frames
     put two squares side by side in one row at obviously different shades. So a
     cell's wave value is snapped to SIX rungs, and each cell carries a stable
     offset of one rung either way, dealt three at a time along each row so
     three rungs are present in any row of three lit cells or more, by
     construction, and never a seventh shade.

     --msg-scatter is that offset in RUNGS, and it moves the quantizer's
     threshold rather than the rung it lands on, so a cell can differ from its
     neighbour by a rung everywhere on the slope of the wave and still travel
     the WHOLE ladder over a cycle. */
  --msg-levels: 6;
  --msg-scatter: 1;

  /* ---- and there is no third channel ----
     Rounds 9 to 12 had one: the cell gave up whole CSS pixels at the bottom
     rungs (--msg-trough-px / --msg-trough-rungs), so the wave moved MASS as
     well as light. Round 13 deleted it, knobs and all, because it was size
     doing tone's job and the reference does not do it - see the envelope above
     for the measurement and for the ladder that replaced it. A cell is `cell`
     wide at every rung, every phase and every row; the only shape the wave
     never touches is the dissolve's corner radius at the two ends. */

  /* ---- the halo ----
     A bright cell blooms. --msg-bloom-blur is a shadowBlur in CSS pixels and
     means exactly that: arecibo.js bakes the gaussian a canvas shadow would
     draw (sigma = blur/2) into one sprite per ink, because 794 shadowed fills a
     frame costs the whole budget and a drawImage costs nothing.
     --msg-bloom-tight is how much of that halo is a hard rim (sigma 0.85px,
     enough to fill the gutter) rather than the broad field; at this cell size
     one sigma can only be one of the two.

     --msg-bloom-min / --msg-bloom-gain are the ramp: alpha = (b - min) * gain,
     so with six rungs only the top two bloom at all - the top 40% of the wave -
     and every other cell in the message stays a hard-edged square.

     THE BLUR TRACKS THE CELL, and the cell is 6px again at 1440. 5px puts the
     visible halo (about 2 sigma) at 5px around a 6px cell: it fills the 2px
     gutter and spills most of a cell's width beyond it.

     THE GUTTER RULE STILL HOLDS. Measured on the render, on the cells that
     stand alone in the message (row 16, columns 3 and 21) so the reading is one
     cell's own halo and not its neighbours': the gutter pixel nearest a crest
     cell reads 0.30 of that cell's own value at 1440, and the gutter's centre
     reads 0.13. Where the gutter is one pixel wide - the tablet's 5px pitch and
     the phone's 4px - those are the same pixel and it reads 0.26 and 0.25.
     Every one of them is under 0.35, and the closed-form kernel agrees with the
     render to three decimal places. */
  --msg-bloom: 1;
  --msg-bloom-blur: 5;
  --msg-bloom-min: 0.6;
  --msg-bloom-gain: 2.5;
  --msg-bloom-tight: 0.45;

  /* ---- the wave ---- */
  --msg-wave-rows: 22;  /* grid rows in one wavelength. Three and a third crests
                           are inside the 73-row message at any moment, so you
                           can see where the light has been and where it is
                           going, and the ramp INSIDE a section is steep enough
                           that a band cannot dim as one plate. */
  --msg-speed: 0.3;     /* periods per second: 6.6 rows a second, four rows
                           between frames 600ms apart, one crest-to-crest in
                           3.3s. */
  --msg-bow: 0.18;      /* periods across its width, so the crest is not a flat rule */

  /* ---- reaching the ends of the ladder (round 7) ----
     The field is TWO waves added, 0.76 of the first and 0.24 of the second, and
     a sum like that only touches +-1 when the two phases coincide - which they
     do on their own beat, once every 8.8s, not once a cycle. So a cell could
     spend three whole crests inside the middle of its own ladder: measured on
     the last build, only 66% of the message travelled from its bottom rung to
     its top one within any single 3.3s cycle, and the inks whose ladder is
     shortest by arithmetic (magenta, blue, red - see --msg-lift) were the ones
     that cost the most, because half a short ladder is nothing at all.

     --msg-gain drives the wave into its own clamp: the sum is scaled and then
     limited to +-1, which broadens the crest and the trough into short plateaus
     - a lamp that is fully on for a moment and fully off for a moment, which is
     what the reference clip does, rather than a sine that grazes its peak.

     1.25 is MEASURED, not chosen. Over one 3.3s cycle, the fraction of cells
     that travel their whole ladder goes 0.66 (gain 1) -> 0.82 (1.15) -> 0.90
     (1.25) -> 0.92 (1.3) -> 0.97 (1.45); the fraction of cell-samples parked at
     an end of the ladder goes 0.41 -> 0.50 -> 0.56 -> 0.58 -> 0.63. Past about
     1.3 the field starts to read as two tones taking turns instead of a wave
     with a slope, and 1.25 is the last value where the slope still holds more
     than half the message at any moment. */
  --msg-gain: 1.25;

  /* ---- the twelve sections ----
     Dan's sentence has two halves and only the first was built: "each section
     of the message behave as if the pixels are changing shades WHILE SHOWING
     ASPECTS OF A WAVE THAT FLOWS DOWNWARDS". A critic measured the old field as
     one global plane wave - phase a straight line in row, residual std 12
     degrees across 44 row-bins - so the message read as a single striped sheet.

     THREE KNOBS, AND EACH ONE ANSWERS A DIFFERENT PART OF THAT SENTENCE.

     --msg-spread is the CONSTANT lag between the sections' phases, and on its
     own it is not enough: shifting a sine by a constant leaves the phase a
     straight line with twelve small steps in it.

     --msg-warp is the one that BENDS the line: each section's row term is
     scaled by (1 +/- warp), so its own wavelength - and therefore the speed its
     crest crosses it, omega/k - is up to 28% off the message's. The temporal
     term stays the message's own for every section, so this varies the speed
     WITHOUT letting any two sections walk away from each other over time, and
     the scaling is measured from each section's own first row so the seam at a
     band edge cannot grow with the row number.

     --msg-swell is the breath: each section also runs this many radians ahead
     of the message and then behind it, at --msg-swell-rate cycles a second
     (0.04 is one breath every 25s), each section starting somewhere else in
     that cycle - so which sections are early and which are late CHANGES, rather
     than being a fixed pattern that only scrolls. --msg-amp-var gives each one
     its own energy on top.

     ALL THREE ARE BOUNDED AND NONE OF THEM TOUCHES THE SIGN OF THE ROW TERM,
     which is what keeps twelve bodies of water inside one weather: every
     section still travels the same way, always. A per-section SPEED added to
     the TIME term would have been the obvious way to do this and it is the
     trap: those offsets accumulate, and a minute in, the twelve parts sit at
     unrelated phases and the picture is noise.

     MEASURED on the field, over 44 row-bins, against the critic's own metric:
     the phase's residual about a straight line in row is 24 degrees where the
     last build measured 12 (its own noise floor, from the bow and the second
     wave, is 10.5). Direction is unchanged and was checked at 24 wave-times on
     both faces: the row profile 600ms later correlates best at +3 to +5 rows on
     Storyteller and -3 to -5 on Developer, never once at zero or the wrong
     sign, against an expected 4.0 rows. */
  --msg-spread: 1.2;
  --msg-warp: 0.28;
  --msg-swell: 0.2;
  --msg-swell-rate: 0.04;
  --msg-amp-var: 0.2;

  /* ---- the rest state ----
     The motion contract says canvases repaint on state change, not forever, so
     the wave RUNS and then STOPS. --msg-run is how many crest-to-crest cycles
     it runs from each wake - in cycles and not seconds, so it stays right if
     --msg-speed moves: 3 cycles at 0.3 periods/s is 10s - and --msg-glide is
     how long it takes to decelerate to a standstill, after which there is no
     rAF at all. A pointer moving over the page, a focus, a flip, a resize or a
     return to the tab wakes it again: someone is there, it moves; the room is
     empty, it is a still. arecibo.js chooses the phase it lands on rather than
     accepting one - over the window it can still reach while slowing down, it
     takes the phase with the fewest cells in the trough ON EITHER FACE. */
  --msg-run: 3;
  --msg-glide: 2.2;

  --msg-still: 1.85;   /* seconds into the wave: the phase drawn under reduced
                          motion. Searched over 400 phases of one cycle, and
                          RE-searched in round 7 because the gain and the dither
                          changed the field under it - a knob that quietly stops
                          being the answer to its own question is worse than one
                          that was never solved. It is the phase where the
                          fraction of lit cells standing at rung 2 or higher is
                          largest ON BOTH FACES at once (0.705; the old 1.842
                          now scores 0.700) - the two run in opposite
                          directions, so a phase that suits one can gut the
                          other, and the still has to show the whole message on
                          whichever side the visitor lands.

                          ROUND 8 DID NOT MOVE IT AND DID NOT NEED TO: the score
                          counts RUNGS, and the rungs are a function of the wave
                          alone. The envelope decides what a rung looks like,
                          never which rung a cell is on, so the phase that shows
                          the most of the message is the same phase it was. */
}

/* The Developer face: the same message with the chroma taken out, and every
   number above meaning the same thing.

   ROUND 10: IT IS INK ON PAPER, NOT A GHOST. Looked at beside the black face,
   this one read as a pale grey shadow next to a bear of dense black: the six
   inks' crests landed at greys 11, 14, 38, 56, 58 and 63 on 242 paper, so four
   of the six never reached an ink tone at all, and the helix and the telescope
   barely separated from the ground AT THEIR OWN BRIGHTEST. Dan asked for
   "shades of gray"; grey on this page means ink, at the weight the type and the
   figure beside it carry.

   THREE NUMBERS SAY IT, and each one says a different thing.

   --msg-crest is the OKLab L the darkest mark reaches. 0.15 is the page's own
   --ink (#0B0B0C) exactly, so the message never goes darker than the type
   beside it. Unchanged through both corrections, and it never was the defect:
   only the ink carrying the MOST light on the colour side ever reached it - the
   floor below is what decides whether the other five are ink at all.

   --msg-ink-floor is the weight the ink carrying the LEAST light gets, and it
   is the whole fix. The six greys are spread between it and 1 across the inks'
   own lightness range, so the ORDER survives - white and yellow are still the
   darkest marks, a black-and-white print of the picture rather than a
   flattening of it - while the palest mark is chosen instead of inherited from
   how saturated Dan's picture happened to be. sqrt(L) is the same SHAPE with
   its floor at 0.734, which is where 56, 58 and 63 came from; there is no
   branch to switch here, only a floor to raise. Round 10 raised it to 0.84 and
   round 11 raised it to 0.90, so that the palest of the six is ink beyond
   argument rather than only just. The crests measure

       W 11   Y 13   G 21   M 29   R 28   B 27      (on 242 paper)

   - six distinct tones, every one of them ink, the palest at 15.04:1, and the
   three saturated inks a full ten greys darker than round 10's 41 / 39 / 37.
   The order is untouched: white and yellow are still the darkest marks on the
   page, so the face is still a print of the picture and not a flattening.

   --msg-floor is 0.38 HERE and 0.015 on the black face, and that asymmetry is
   real rather than a taste. Both numbers say the same sentence - the fraction
   of the journey from the page's ground to the crest, IN LIGHT - and the two
   grounds are at opposite ends of the light: black paper carries 0.003 of the
   page's light and white paper carries 0.887, so the same journey read from
   each end gives numbers that cannot be compared to one another. Adding light
   to nothing and taking ink off paper are not the same act.

   ROUND 13 MOVED IT FROM 0.76, and for the same reason it moved the black
   face's: this face's cells used to shrink at the bottom rungs too, and on
   paper a shrinking cell shows MORE paper, so the contraction was carrying part
   of the swing here exactly as it was there. With the cell held at full size
   the trough has to be paler to reach the same depth. At 0.38 the troughs
   measure

       W 196  Y 196  G 196  B 196  R 196  M 196

   so every cell of this face, at every phase of the wave, sits between grey 11
   and grey 196 on 242 paper: 1.56:1 at the very worst. The lattice dot is 210
   (1.352:1), so the palest trough this face can show is still fourteen greys
   darker than the ground's own dot AND nine times its area - which is what
   keeps it a mark. The wave swings DOWN from ink, which is what the controller
   asked for and what the render shows.

   MEASURED on the rendered page, 48 phases, per row: the body rows' tone swing
   runs 137 / 167 / 185 against the old build's 136.1 / 161.4 / 178.6, and in L*
   47.6 / 59.0 / 67.1 against 52.0 / 60.3 / 68.2. Deeper on the tone measure at
   every quantile, 2.2% shallower at the median on the L* one - which is the
   honest read and the reason the floor stopped at 0.38 rather than going on to
   0.34, where L* would have matched but the trough would have closed to within
   eight greys of the lattice dot. Rows 0 and 72 went from 31.8 and 29.7 to
   169.3 and 161.0 in tone, and from 6.5 and 6.1 to 50.3 and 48.6 in L*: on this
   face the shrinking dissolve had them at a TENTH of the message's depth.

   AND SINCE ROUND 11 THE SIX TROUGHS ARE WITHIN A GREY OF EACH OTHER, because
   one fraction measured in light lands them all on the same luminance by
   construction. Round 10's were 134..150, a five-grey spread that read as the
   pale end of the wave breaking up by ink.

   THE SWING CHECK DOES NOT APPLY TO THIS FACE, and that is arithmetic, not an
   excuse. It reduces an ink to crest-over-trough in its dominant channel; here
   the crests are six DIFFERENT greys by design (that is the "shades of gray"
   Dan asked for) and the troughs are one, so the ratio runs 17.8 for the white
   ink down to 6.8 for magenta. Equalising it would mean troughs proportional to
   crests - the darkest ink fading only to grey 78 - which is a mark that never
   fades at all. The even-breath number that DOES apply here is the one this
   face is built on: one trough fraction, all six inks, 0.38, by construction.

   --msg-min-contrast is inherited and is 1 since round 13, so nothing but this
   floor sets the trough on either face.

   ROUND 8 SHIPPED 176-193 HERE and it was 2.39:1 at the worst, which the solver
   did not catch: its contrast test carried a black-face assumption (`sReq < 1`)
   that is false whenever the crest is the DARK end, so this whole face went
   unchecked. Round 11's expression has no such branch at all - one fraction of
   one journey, and the numerator and denominator change sign together.

   The halo shrinks to a whisper, because on paper a bloom is a smudge. */
.land__face[data-pol="white"] .land__msg {
  /* THE COLOUR IS HERE NOW (2026-09-05). Each ink is darkened, hue-locked,
     only as far as it needs to clear --msg-paper-contrast against the paper:
     blue and red pass untouched, green and yellow come down to it. The
     picture's white ink has no hue to keep and takes --msg-crest as the page's
     own ink. The wave fades each cell toward the paper (less ink), from the
     crest down to --msg-floor of the journey. The numbers below that describe
     the grey print (--msg-ink-floor, --msg-ink-pow) still govern the white ink
     and would govern the whole face if --msg-chroma were set back to 0. */
  --msg-chroma: 1;
  --msg-paper-contrast: 2.5;
  --msg-crest: 0.15;
  --msg-ink-floor: 0.90;
  --msg-ink-pow: 1;
  --msg-floor: 0.38;
  --msg-bloom: 0.34;
  --msg-bloom-blur: 4;
  --msg-bloom-tight: 0.7;
}

/* ---- narrower frames: the same centring, the box widened ----
   The middle 60% of a 1024px frame would still hold the message (it is never
   wider than about a third of the frame), but there is no reason to keep the
   box narrow where there is nothing beside it. The halo knobs do not change:
   the cell is the desktop's 6-8px on these frames too. */
@media (max-width: 1024px) {
  .land__msg {
    left: 10%;
    width: 80%;
  }
}

@media (max-width: 640px) {
  .land__msg {
    left: 0;
    width: 100%;
  }
}
