/* ══ mypalm.css — THE MYPALM SHOWCASE ══════════════════════════════════════════
   Loaded only by mypalm.html and ms/mypalm.html. Everything here is prefixed .mp-
   so it can collide with nothing: qa/css-collision.py exists because `.plate` was
   introduced twice in two files and the later one silently restyled 24 figures.

   THE COMPOSITION LAW (brands/airei/DESIGN.md §6): "Every product page pairs ONE
   REAL MILL PHOTOGRAPH with ONE REAL PRODUCT SCREEN — never two renders, never two
   screenshots." So the unit here is .mp-pair: a photograph and a screen, side by
   side, and .mp-pair--rev flips the sides so the run alternates down the page and
   never reads as a table.

   WHY THE SCREEN SITS IN A FRAME. A photograph is 4:5 (1024x1280, every one of them).
   A dashboard capture is ~2:1. Put them in equal columns and the screen becomes a
   letterbox slot a third the height of the picture beside it. .mp-frame is a navy
   panel that stretches to the photograph's height with the capture centred inside it
   at its own true shape — no cover-crop, which on a dashboard would cut off the left
   navigation, the half of the frame that proves it is a working screen and not a mock.

   THE PHONE BEZEL IS DOM, NOT A PICTURE. The source decks ship their phone screens
   already inside a drawn blue device frame. Those frames are cropped OFF at build
   time (see assets/img/mypalm/phone-*.jpg) and redrawn here in navy, because a
   photograph of a phone is a render and the law above forbids stacking two of them.
   The radius on .mp-phone is the only radius on this page: a phone has one, and
   "uniform 16px radius on everything" is on the DESIGN.md §8 blacklist. */

/* ── the lockup: the mark, once, where the product is named ─────────────────── */
.mp-lock{display:grid;gap:clamp(20px,3vw,40px);align-items:start;
  grid-template-columns:1fr;margin-bottom:clamp(34px,4.6vw,60px)}
@media (min-width:760px){.mp-lock{grid-template-columns:auto minmax(0,1fr)}}
.mp-lock>img{width:clamp(58px,7vw,84px);height:auto}

/* ── the pair ───────────────────────────────────────────────────────────────── */
.mp-pair{display:grid;margin:0 0 clamp(48px,6.2vw,96px);
  gap:clamp(16px,2.2vw,30px) clamp(20px,2.8vw,40px);
  grid-template-columns:1fr;
  grid-template-areas:"src" "place" "screen" "cap"}
@media (min-width:900px){
  /* 0.52, not 0.5-ish by feel. The photograph is 4:5 and the screen is ~2:1, so the ROW
     height is set by the photograph's width, and the screen then floats in whatever is left.
     Measured on this page at 1440: at 0.66fr the capture filled 57% of its frame's height;
     at 0.52fr it fills 76%, and the photograph is still 367x459. */
  .mp-pair{grid-template-columns:minmax(0,0.52fr) minmax(0,1fr);
    grid-template-areas:"src src" "place screen" "cap cap"}
  .mp-pair--rev{grid-template-columns:minmax(0,1fr) minmax(0,0.52fr);
    grid-template-areas:"src src" "screen place" "cap cap"}
}
.mp-pair>.mp-src{grid-area:src}
.mp-pair>.mp-place{grid-area:place}
.mp-pair>.mp-frame{grid-area:screen}
.mp-pair>figcaption{grid-area:cap}

/* The source line sits ABOVE the figure, per DESIGN.md §7. */
/* NOT CAPPED, and that is deliberate — checked 2026-08-27 and reverted. A measure probe read
   this at 98-148ch and called it the widest running prose on the site; it is not prose. It is
   a ONE-LINE separator whose hairline is meant to span the figure it introduces, and the
   probe's line count came from the border box, where the 12px padding-bottom rounds a single
   18.75px line up to two. Capping it to a reading measure would have shortened the RULE under
   a 1074px figure to 620px, which is the visible defect, not the cure. */
.mp-src{font:500 12.5px/1.5 var(--f-mono);letter-spacing:.05em;color:var(--ink-ghost);
  margin:0;padding-bottom:12px;border-bottom:1px solid var(--hairline)}
.mp-src b{color:var(--ink-subtle);font-weight:500}

.mp-place{width:100%;height:100%;min-height:0;aspect-ratio:4/5;object-fit:cover;
  display:block;background:var(--panel-navy);border:1px solid var(--hairline)}

.mp-frame{min-width:0;min-height:0;display:grid;place-items:center;
  background:var(--panel-navy);border:1px solid var(--hairline);
  padding:clamp(10px,1.5vw,22px)}
/* width:100%, NOT width:auto. With srcset+sizes the browser gives an auto-width image its
   density-corrected intrinsic size, which is the `sizes` value — so the capture was laid out
   at 46vw and left a third of its own frame empty regardless of how much room the frame had.
   max-height + object-fit:contain keep it inside the frame when the frame is the short one. */
/* `:not(.scr)` IS NOT DECORATION — IT IS THE ONLY REASON THE DRAWN DISPLAY APPEARS.
   base.css §.scr is a bare class (0,1,0). `.mp-frame>img` is (0,1,1) and beats it, so
   the first build of this shipped a capture wearing .scr and a 1px hairline: the padding
   and recess applied, the BEZEL did not, and it looked like nothing had changed. Caught
   by screenshot, not by any linter — there is no gate that can see a losing declaration.
   Same fix on .mp-gal img below. Anything that adds `border` to a capture must do this. */
.mp-frame>img{width:100%;height:auto;max-width:100%;max-height:100%;object-fit:contain;
  display:block}
.mp-frame>img:not(.scr){border:1px solid var(--hairline)}

.mp-pair>figcaption .mp-k,.mp-scene>figcaption .mp-k{font:600 12px/1 var(--f-mono);letter-spacing:.17em;
  text-transform:uppercase;color:var(--airei-blue-lift);margin:0 0 10px}
.mp-pair>figcaption p:not(.mp-k),.mp-scene>figcaption p:not(.mp-k){font-size:clamp(15.5px,1.25vw,18px);line-height:1.55;
  color:var(--ink-muted);margin:0;max-width:var(--measure)}
.mp-pair>figcaption p:not(.mp-k) b,.mp-scene>figcaption p:not(.mp-k) b{color:var(--ink);font-weight:600}

/* ── .mp-scene — the composite: ONE frame that already holds both halves ───────
   .mp-pair sets a photograph beside a screen and asks the reader to join them; the
   navy frame and the DOM bezel are the join, made honestly and visible as a join.
   A composite has already been joined — the capture is mapped onto the monitor or
   the phone that is actually in the room, at that surface's own angle and light —
   so a frame around it would be a second, contradictory device. The unit is
   therefore ONE image at the full width of the column, caption under it.

   It does not break DESIGN.md §6. The law is one real mill photograph and one real
   product screen, never two renders and never two screenshots; a composite is that
   pair, in one frame, and putting the flat capture back beside it would be the
   violation.

   WHY THE TALL ONE IS CAPPED. The landscape scenes are 16:9 and take the column
   whole. The phone scenes are 4:5, and 4:5 at the full column is 1390px tall on a
   1440 desktop — a screenful and a half for one picture — while the phone inside
   stops repaying the height long before that: it is 22% of the frame on the ramp
   scene, so the gain from 940px to the full column is 207 CSS px of phone against
   245. 940 keeps the phone larger than the DOM bezel it replaces (190px) and the
   block inside one screen. */
.mp-scene{display:grid;margin:0 0 clamp(48px,6.2vw,96px);
  gap:clamp(14px,1.9vw,26px);grid-template-columns:1fr}
.mp-scene>img{width:100%;height:auto;display:block;
  border:1px solid var(--hairline);background:var(--panel-navy)}
.mp-scene--tall>img,.mp-scene--tall>figcaption,.mp-scene--tall>.mp-src{
  width:100%;max-width:940px;margin-inline:auto}

/* ── the DOM phone ──────────────────────────────────────────────────────────── */
.mp-phone{--bez:clamp(8px,1vw,13px);position:relative;
  width:min(100%,238px);padding:var(--bez);padding-top:calc(var(--bez)*2.1);
  border-radius:30px;background:var(--mill-navy);
  border:1px solid var(--hairline-strong);box-shadow:none}
.mp-phone::before{content:"";position:absolute;top:calc(var(--bez)*0.85);left:50%;
  width:40px;height:4px;margin-left:-20px;border-radius:2px;
  background:var(--mill-steel)}
.mp-phone>img{width:100%;height:auto;display:block;border-radius:16px;border:0;
  background:var(--deep-water)}
.mp-frame>.mp-phone{max-height:100%}
/* The phone is the ONE thing in a pair that can be taller than the photograph beside it, and
   when it is, the grid row grows and `object-fit:cover` crops the photograph to fill the extra.
   Measured at 900px with a flat 208px phone: 438px of phone against 390px of photograph, a 16.5%
   side-crop. The clamp keeps the phone under the photograph's height at every desktop width. */
@media (min-width:900px){.mp-frame>.mp-phone{width:min(100%,clamp(140px,15vw,190px))}}

/* ── the rebuilt verdict cards ──────────────────────────────────────────────────
   DESIGN.md §6, hazard 2: "rebuild the GREEN-FILLED verdict cards as DOM in
   navy/gold". The product paints these three panels solid green and the WORDS are
   re-set here, unchanged.

   THE REASON CHANGED ON 2026-08-26 AND THE COMMENT IS CORRECTED WITH IT. It used to
   read: the green is cut off the capture, because the crop the pair carried
   (assets/img/mypalm/oil-loss-prediction-chart.jpg) ends at y=608 and the green band
   starts at y=616. That crop is no longer on the page. Scene 03 now carries the FULL
   capture — oil-loss-prediction-recommendation.jpg, chart and all three green panels
   — mapped onto the control-room monitor, so the green IS visible, at the size and
   angle a monitor across a desk gives it. These cards are no longer a substitute for
   something missing; they are the same three verdicts set large enough to read, in
   this site's palette rather than the product's. */
.mp-verdict{display:grid;gap:clamp(12px,1.5vw,20px);margin-top:clamp(16px,2vw,26px);
  grid-template-columns:1fr}
@media (min-width:720px){.mp-verdict{grid-template-columns:repeat(3,minmax(0,1fr))}}
/* ABSOLUTE BAN — side-stripe borders. A `border-left` thicker than 1px used as a coloured
   accent on a card, a list item, a callout or an alert is the first item on the DON'T list
   in the impeccable skill, and it is the one AI tell this site was still carrying in six
   places. The prescribed rewrites are full borders, background tints, leading
   numbers/icons, or nothing, and each of the six below takes whichever of those the
   surrounding component already uses. */
/* Three verdict cards, each with a 2px amber left stripe — the textbook instance. The
   accent moves to the top edge, which is also the edge the .mp-vk key sits against. */
.mp-verdict>div{background:var(--panel-navy);border:1px solid var(--hairline);
  border-top:2px solid var(--crude-amber);padding:clamp(14px,1.6vw,20px)}
/* THE LINE ABOVE THIS BLOCK PROMISES "its wording, our palette", AND THIS RULE WAS BREAKING
   THE FIRST HALF OF THAT. `.mp-vk` carries three strings printed by MyPalm — "Predicted oil
   losses is lower than the baseline value", "Reasons for oil losses", "Recommendation" —
   and the first is 53 painted characters, the longest quoted string on the page. Rendering
   a quotation in caps re-cases it: `.mp-src` says the wording is the product's, then the
   stylesheet changes the wording's case. (The tell is still in the string: "losses is",
   verbatim from the screen. Uppercase hides that it is verbatim.)
   It was also three readability faults stacked on one element — under 12px, over 30 caps
   characters, and .14em on running text — the only element on the site that trips all three.
   Case and tracking now come from the source; the panel keeps its gold, its mono and its
   amber top edge, so it still reads as a machine verdict. 11.5px -> 12.5px because a
   sentence in sentence case needs the x-height a label did not. */
.mp-vk{font:600 12.5px/1.45 var(--f-mono);letter-spacing:.01em;
  color:var(--signal-gold);margin:0 0 10px}
.mp-verdict p:not(.mp-vk){margin:0;font-size:15px;line-height:1.5;color:var(--ink-muted)}
.mp-verdict dl{margin:0;display:grid;grid-template-columns:auto 1fr;gap:4px 14px}
.mp-verdict dt{font:500 12px/1.5 var(--f-mono);letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-ghost)}
.mp-verdict dd{margin:0;font-family:var(--f-mono);font-variant-numeric:tabular-nums;
  font-size:14px;color:var(--ink)}

/* ── the hero's three figures, on a phone ──────────────────────────────────────
   base.css §"ON A PHONE, THE ACTION COMES BEFORE THE PICTURE" reorders the hero at
   <=820px: opening copy (0), .cta-row (1), the hero figure (2), everything else (3).
   The result strip is a `div`, so it fell into that "everything else" bucket and
   landed BELOW the photograph — measured at 390: figures at y=1727, under a plate
   that ends at y=1662. The whole point of these three numbers is that they arrive
   before the apparatus, so they are named up to order:2, which ties with the plate
   and therefore resolves by DOM order — the numbers, then the picture. The CTA stays
   above both, which is the rule base.css was written to protect.
   Scoped here rather than in base.css because these two pages are the only ones on
   the site with a .figures block inside a hero wrap (checked across all 21 pages). */
@media (max-width:820px){
  main>section.portrait>.wrap>.figures{order:2}
}

/* ── .mp-gal — the screen wall ────────────────────────────────────────────────
   NOT a second pair component. A .mp-pair is the DESIGN.md §6 unit: one real mill
   photograph beside one real product screen. This is the OTHER half of "show me the
   software" — product screens on their own, one line of copy each, no photograph in
   any cell, so §6's "never two renders, never two screenshots" is not in play here:
   nothing is paired with anything.

   TWO WIDTHS, AND THE REASON IS LEGIBILITY, NOT RHYTHM. A 1600px dashboard rendered
   into a 552px column is readable as a shape; its table text is not. Three captures
   carry their meaning IN detail a half column throws away — a block map with pins, a
   supplier map with its category totals, and a limits table whose whole point is the
   remark written beside each red reading. Those three span both columns (~1120px, near
   native), as does the weighbridge composite — a room, not a capture. Everything else
   takes a column. (This said "those three" while four cells carry .wide, which it has
   done for as long as the composite has been there.) */
.mp-gal{display:grid;gap:1px;background:var(--hairline);
  border:1px solid var(--hairline);grid-template-columns:1fr;
  margin:clamp(26px,3.4vw,44px) 0 0}
@media (min-width:900px){.mp-gal{grid-template-columns:repeat(2,minmax(0,1fr))}}
.mp-gal>figure{margin:0;background:var(--panel-navy);
  padding:clamp(12px,1.5vw,18px);display:flex;flex-direction:column;gap:12px}
@media (min-width:900px){.mp-gal>figure.wide{grid-column:1/-1}}
/* height:auto and no aspect-ratio: a capture is its own shape. object-fit:cover here
   would crop away the left navigation, which is the half of a frame that proves it is
   a working screen and not a mock — the same reasoning as .pband--screen in base.css. */
.mp-gal img{width:100%;height:auto;display:block}
/* Two of the twelve cells are NOT desktop captures and must not be given a drawn
   monitor: both carry a composite — a phone on a nursery bench, a monitor on a weighbridge
   desk — already a device, in a room. See .scr in base.css for why the split is declared
   here. (This read "three of the fourteen"; the third was the pair of cells carrying the
   app decks, excused because "a monitor moulding around three phones would be a lie about
   what the picture is". True, and the wrong conclusion: the PICTURE was the lie — a
   rendered device shipped as a photograph. They are .mp-stage now.) */
.mp-gal img:not(.scr){border:1px solid var(--hairline)}
.mp-gal figcaption{margin:0}
.mp-gal .gk{font:600 11px/1.35 var(--f-mono);letter-spacing:.17em;text-transform:uppercase;
  color:var(--airei-blue-lift);margin:0 0 8px}
.mp-gal figcaption p:not(.gk){margin:0;font-size:15px;line-height:1.55;
  color:var(--ink-muted);max-width:var(--measure)}
.mp-gal figcaption p:not(.gk) b{color:var(--ink);font-weight:600}

/* ── .mp-stage — ONE phone, three real screens, chosen by scroll ───────────────
   Replaces two .mp-gal cells that shipped the source decks WHOLE: three phones inside
   three drawn blue device frames on a white ground. That broke this file's opening rule
   — THE PHONE BEZEL IS DOM, NOT A PICTURE — and each phone drew at ~107 CSS px, screen
   ~97: real, sharp, unreadable. The six screens are cut OUT of the decks (windows in
   ASSETS.md) and set in the bezel this page already draws, at 224-236 CSS px. Delivered/
   drawn is now ~1.27; the capture is 274-279px wide and anything larger would be invented.

   WHY IT IS NOT IN THE WALL. Sticky holds for (area − element); the states are spread
   over (2 × step); both hold only when a step is at least as tall as the device. Built
   compact first at a 916px cell, and walked at 80px increments: the third screen came up
   while the device was already un-pinning, and the phone had slid off the top before the
   third caption left the reading band. A stage that works is ~1550px; a .mp-gal cell
   measures 405-767.

   THE STATIC LAYOUT IS THE DEFAULT, AND THAT IS THE SAFETY ARGUMENT. Everything before
   the .is-live block is what a reader gets with JS off, with reduced motion asked for, or
   on a phone: three bezels in a row, each screen under its own line. `is-live` is added by
   assets/js/mp-stage.js and nothing else, so a script that never runs cannot hide a
   screen. This site shipped a fail-CLOSED reveal once and it hid a section; this fails
   OPEN, because the swap enhances a layout that already says everything.

   AND IT FALLS BACK TO THE ROW ON A PHONE. A device pinned in a 390px viewport is ~430px
   of a ~730px window: it spends the reader's screen showing a picture of a screen, to
   someone holding the object depicted. Below 900px nothing sticks and nothing swaps. */
.mp-stage{display:grid;margin:clamp(30px,4vw,56px) 0 clamp(48px,6.2vw,96px);
  gap:clamp(16px,2vw,26px) clamp(20px,2.6vw,38px);
  grid-template-columns:repeat(3,minmax(0,1fr));align-items:end}
/* display:contents so the phones and step blocks land in the FIGURE's grid as siblings,
   each phone above its own copy. The wrappers exist for the live layout, which needs the
   phones under one sticky parent. <div>s not a list: display:contents drops list
   semantics in older engines. */
.mp-stage-dev,.mp-steps{display:contents}
.mp-stage .mp-phone{width:min(100%,236px);justify-self:center;align-self:end;grid-row:2}
.mp-stage .mp-phone[data-s="0"]{grid-column:1}
.mp-stage .mp-phone[data-s="1"]{grid-column:2}
.mp-stage .mp-phone[data-s="2"]{grid-column:3}
/* align-self:start against the figure's align-items:end, which is there to sit three
   bezels of unequal height on one baseline and was reaching the copy row too: at 760 a
   two-line step started 26px below a three-line one and the row read as a stagger. */
.mp-stage .mp-step{grid-row:3;margin:0;align-self:start}
.mp-stage .mp-step[data-s="0"]{grid-column:1}
.mp-stage .mp-step[data-s="1"]{grid-column:2}
.mp-stage .mp-step[data-s="2"]{grid-column:3}
/* The caption sits ABOVE the figure: it names the app and says in one line what the app
   is for, which is what a reader needs BEFORE three screens go past — read last it only
   repeated the steps it was meant to introduce. */
.mp-stage>figcaption{grid-column:1/-1;grid-row:1;margin:0 0 clamp(6px,1vw,14px);
  border-bottom:1px solid var(--hairline);padding-bottom:clamp(14px,1.8vw,22px)}
.mp-stage>figcaption .gk,.mp-stage .mp-sn{font:600 11px/1.35 var(--f-mono);
  letter-spacing:.17em;text-transform:uppercase;margin:0 0 8px}
.mp-stage>figcaption .gk{color:var(--airei-blue-lift)}
.mp-stage .mp-sn{letter-spacing:.14em;color:var(--ink-subtle)}
.mp-stage>figcaption p:not(.gk),.mp-stage .mp-step p:not(.mp-sn){margin:0;font-size:15px;
  line-height:1.55;color:var(--ink-muted);max-width:var(--measure)}
.mp-stage>figcaption p:not(.gk) b,.mp-stage .mp-step p:not(.mp-sn) b{color:var(--ink);
  font-weight:600}

/* ONE COLUMN below the width where three bezels stop being readable side by side.
   EVERY RULE HERE CARRIES ITS [data-s], and that is not tidiness: the column each phone
   sits in is set by `.mp-stage .mp-phone[data-s="1"]`, (0,3,0). A bare
   `.mp-stage .mp-phone{grid-column:1}` in here is (0,2,0) and LOSES to it however late it
   appears — so the three phones stayed in columns 1-3 of a one-column grid, creating two
   implicit columns and dividing 390px by three: measured, a 79px bezel and body copy at
   one word per line. A media query that cannot outrank the rule it overrides is not one. */
@media (max-width:640px){
  .mp-stage{grid-template-columns:minmax(0,1fr);gap:clamp(12px,3vw,18px)}
  .mp-stage>figcaption{grid-row:1}
  .mp-stage .mp-phone[data-s="0"]{grid-column:1;grid-row:2}
  .mp-stage .mp-step[data-s="0"]{grid-column:1;grid-row:3}
  .mp-stage .mp-phone[data-s="1"]{grid-column:1;grid-row:4}
  .mp-stage .mp-step[data-s="1"]{grid-column:1;grid-row:5}
  .mp-stage .mp-phone[data-s="2"]{grid-column:1;grid-row:6}
  .mp-stage .mp-step[data-s="2"]{grid-column:1;grid-row:7}
  .mp-stage .mp-phone{justify-self:start}
  .mp-stage .mp-step{margin-bottom:clamp(18px,5vw,30px)}
}

/* ── live: the bezel moves up to the wrapper, the screens stack inside it ────── */
@media (min-width:900px){
  .mp-stage.is-live{grid-template-columns:minmax(0,236px) minmax(0,1fr);
    align-items:start;column-gap:clamp(30px,4vw,72px)}
  /* the bezel .mp-phone draws, one level up, so the three screens share ONE. The width
     is a vh and not flat px because the step height is: a phone fixed in px on a 620px
     window is taller than its own step, un-pins early, and the last screen is never seen. */
  .mp-stage.is-live .mp-stage-dev{--bez:clamp(8px,1vw,13px);display:grid;
    grid-template-columns:minmax(0,1fr);grid-column:1;grid-row:2/span 3;
    position:sticky;top:calc(var(--hdr) + 90px);align-self:start;justify-self:center;
    width:min(100%,224px,24vh);padding:var(--bez);padding-top:calc(var(--bez)*2.1);
    border-radius:30px;background:var(--mill-navy);
    border:1px solid var(--hairline-strong)}
  .mp-stage.is-live .mp-stage-dev::before{content:"";position:absolute;
    top:calc(var(--bez)*0.85);left:50%;width:40px;height:4px;margin-left:-20px;
    border-radius:2px;background:var(--mill-steel)}
  /* [data-s] for the specificity reason the max-640 block explains. THE OUTGOING SCREEN
     HOLDS AT FULL OPACITY UNTIL THE INCOMING ARRIVES: two screens cross-fading at 0.5
     cover only 75% between them, so the navy bezel shows THROUGH the swap — a dark flash
     under the white sign-in screen, measured on the first build. The incoming (z-index 2)
     fades in over a still-solid outgoing, which then cuts to 0 after the fade: a 0s
     duration on a .26s delay, which is what the first rule is. */
  .mp-stage.is-live .mp-phone[data-s]{grid-area:1/1;width:100%;padding:0;border:0;
    background:none;border-radius:0;opacity:0;z-index:1;
    transition:opacity 0s linear .26s}
  .mp-stage.is-live .mp-phone[data-s]::before{display:none}
  .mp-stage.is-live .mp-phone[data-s].on{opacity:1;z-index:2;
    transition:opacity .26s var(--e-cubic)}
  /* 58vh against a device of ~52vh: a step must be taller than the device (see the head
     of this block), with enough slack that the LAST screen still gets real pinned scroll —
     walked at 1440x900, step 522 / device 475, the third screen holds pinned for 380px
     (scrollY 14457-14817) instead of arriving as the phone leaves. */
  .mp-stage.is-live .mp-step{grid-column:2;grid-row:auto;display:flex;
    flex-direction:column;justify-content:center;min-height:clamp(430px,58vh,580px);
    padding:clamp(20px,2.4vw,34px) 0;border-top:1px solid var(--hairline)}
  .mp-stage.is-live .mp-step[data-s="0"]{grid-row:2;border-top:0}
  .mp-stage.is-live .mp-step[data-s="1"]{grid-row:3}
  .mp-stage.is-live .mp-step[data-s="2"]{grid-row:4}
  .mp-stage.is-live .mp-step p:not(.mp-sn){font-size:clamp(16px,1.25vw,18.5px);
    max-width:52ch}
  /* a COLOUR change, not a dim: fading inactive copy to 40% is how a scrolling block
     ships body text at 2.4:1. Every step stays at full contrast; only the eyebrow moves,
     between two colours that both pass on this ground. */
  .mp-stage.is-live .mp-sn{transition:color .26s var(--e-cubic)}
  .mp-stage.is-live .mp-step.on .mp-sn{color:var(--airei-blue-lift)}
  .mp-stage.is-live>figcaption{grid-column:1/-1;grid-row:1}
}

/* ── reduced motion: the live layout reverts to the row, whole ─────────────────
   mp-stage.js refuses to add .is-live under reduce, so on a normal load these match
   nothing. They are here for the load that already happened — the reader who turns the
   OS setting on with the page open — and because a selector declaring a transition with
   no final state here is a motion-lint failure by design. Proven red: deleting the
   .mp-phone[data-s].on arm below fails motion-lint on that exact selector. */
@media (min-width:900px) and (prefers-reduced-motion: reduce){
  .mp-stage.is-live{grid-template-columns:repeat(3,minmax(0,1fr));align-items:end}
  .mp-stage.is-live .mp-stage-dev{display:contents}
  .mp-stage.is-live .mp-phone[data-s]{--bez:clamp(8px,1vw,13px);grid-area:auto;grid-row:2;
    width:min(100%,236px);justify-self:center;align-self:end;opacity:1;transition:none;
    padding:var(--bez);padding-top:calc(var(--bez)*2.1);border-radius:30px;
    background:var(--mill-navy);border:1px solid var(--hairline-strong)}
  .mp-stage.is-live .mp-phone[data-s="0"]{grid-column:1}
  .mp-stage.is-live .mp-phone[data-s="1"]{grid-column:2}
  .mp-stage.is-live .mp-phone[data-s="2"]{grid-column:3}
  .mp-stage.is-live .mp-phone[data-s]::before{display:block}
  .mp-stage.is-live .mp-phone[data-s].on{opacity:1;transition:none}
  .mp-stage.is-live .mp-step{grid-column:auto;grid-row:3;min-height:0;display:block;
    padding:0;border-top:0}
  .mp-stage.is-live .mp-step[data-s="0"]{grid-column:1;grid-row:3}
  .mp-stage.is-live .mp-step[data-s="1"]{grid-column:2;grid-row:3}
  .mp-stage.is-live .mp-step[data-s="2"]{grid-column:3;grid-row:3}
  .mp-stage.is-live .mp-sn{transition:none}
  .mp-stage.is-live .mp-step.on .mp-sn{color:var(--ink-subtle)}
  .mp-stage.is-live>figcaption{grid-column:1/-1;grid-row:1}
}

/* ── .appc .use — one line saying what an app is for ─────────────────────────── */
.appc .use{margin:0;font-size:14.5px;line-height:1.5;color:var(--ink-muted)}

/* ── .mp-fold — the package identifiers, folded ───────────────────────────────
   FOLD-STANDARD.md says do not invent a parallel .fold implementation. The base it
   protects lives in assets/css/home.css, and this page does not load home.css (base,
   shell, proof, mypalm). So this is the base's declaration copied literally — same
   13px mono, .2em tracking, "+"/"–" marker, gold focus ring — under a .mp- name so it
   cannot collide with the real one if home.css is ever added here. If it is, delete
   this block and use .fold.

   WHY IT EXISTS AT ALL. Nine store cards used to print their package id in full, and
   nine repeats of com.airei.milltracking.* is the noise the operator flagged. A
   package id is what an Android build is CALLED; it is not a fact a mill owner reads.
   It is not deleted, because someone verifying the listings needs it — it is folded. */
.mp-fold{margin-top:clamp(22px,2.8vw,34px);
  border-top:1px solid var(--hairline-strong);border-bottom:1px solid var(--hairline)}
.mp-fold > summary{display:flex;align-items:baseline;gap:12px;list-style:none;cursor:pointer;
  padding:16px 2px;font-family:var(--f-mono);font-size:13px;font-weight:600;
  letter-spacing:.2em;text-transform:uppercase;color:var(--ink-subtle)}
.mp-fold > summary::-webkit-details-marker{display:none}
.mp-fold > summary::before{content:"+";font-family:var(--f-mono);font-size:15px;font-weight:600;
  line-height:1;color:var(--crude-amber);width:12px;flex:none}
.mp-fold[open] > summary::before{content:"–"}
@media (hover:hover) and (pointer:fine){.mp-fold > summary:hover{color:var(--ink)}}
.mp-fold > summary:focus-visible{outline:2px solid var(--signal-gold);outline-offset:3px}
.mp-fold-body{padding:2px 2px 26px}
.mp-fold-body dl{margin:0;display:grid;grid-template-columns:1fr;gap:2px 24px}
@media (min-width:720px){.mp-fold-body dl{grid-template-columns:auto minmax(0,1fr)}}
.mp-fold-body dt{font:500 12.5px/1.7 var(--f-mono);letter-spacing:.05em;color:var(--ink-subtle)}
.mp-fold-body dd{margin:0 0 8px;font:500 12.5px/1.7 var(--f-mono);color:var(--ink-ghost);
  word-break:break-all}
@media (min-width:720px){.mp-fold-body dd{margin-bottom:0}}

/* ── print ──────────────────────────────────────────────────────────────────── */
@media print{
  .mp-pair{break-inside:avoid;grid-template-columns:minmax(0,0.66fr) minmax(0,1fr)}
  .mp-scene{break-inside:avoid}
  .mp-frame,.mp-verdict>div{background:#fff;border-color:#B9C2CB}
  .mp-phone{background:#E7EAEE;border-color:#B9C2CB}
  .mp-vk{color:#7A520F}
  .mp-verdict dd,.mp-pair>figcaption p:not(.mp-k) b,
  .mp-scene>figcaption p:not(.mp-k) b{color:#0B1218}
  .mp-verdict p:not(.mp-vk),.mp-pair>figcaption p:not(.mp-k),
  .mp-scene>figcaption p:not(.mp-k){color:#2C3A47}
  .mp-src{color:#55636F;border-bottom-color:#B9C2CB}
  .mp-src b{color:#41505E}
  .mp-pair>figcaption .mp-k,.mp-scene>figcaption .mp-k{color:#0B4F86}
  .mp-verdict dt{color:#55636F}
  .mp-gal>figure{background:#fff;break-inside:avoid}
  /* `:not(.scr)` again, and for the opposite reason to the screen rule: here the container
     selector WINS, so a blanket border-color would repaint the drawn display's moulding in
     the hairline grey and flatten the chin back into a mount. base.css's print :root already
     swaps --device-bezel/--device-recess to paper values; leave the framed captures to it. */
  .mp-gal,.mp-gal img:not(.scr),.mp-scene>img{border-color:#B9C2CB}
  .mp-gal .gk{color:#0B4F86}
  .mp-gal figcaption p:not(.gk){color:#2C3A47}
  .mp-gal figcaption p:not(.gk) b{color:#0B1218}
  .appc .use{color:#2C3A47}
  .mp-fold > summary{color:#41505E}
  .mp-fold-body dt{color:#41505E}
  .mp-fold-body dd{color:#55636F}
}
