/* ══ products.css — THE RANGE PAGE ═════════════════════════════════════════════
   Loaded only by products.html and ms/produk.html. Every class is prefixed .pr-
   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).

   WHY THIS FILE EXISTS. Measured on the shipped page at 1440 before it was written:
   3774px tall, 550 words inside <main>, ZERO images, ZERO SVG, ZERO animated
   elements, four sections. A wall of type — and nearly half the hero, its whole
   right-hand column, was empty navy from the headline down to the section rule,
   roughly 400px of nothing below the buttons. A page whose whole job is "here is
   everything we make" was the one page on the site that showed none of it.

   (That column is described in words rather than as a percentage on purpose. The
   literal string is a banned pattern in claims.json — it is a conference labour
   figure that contradicts the site-specific one — and deploy-check.py scans the raw
   text of every shipping file, comments included. It cannot tell a layout proportion
   from a labour claim and should not try; the cure is to not write the token.)

   THE THREE THINGS IT ADDS, and the reasoning behind each:

   1. THE HERO CARRIES A REAL SCREEN. The strongest thing this page owns is that
      MyPalm is a shipping app with real product pixels in this repo. The hero's
      empty column takes the ramp composite — a photograph of a phone in a hand at
      a mill ramp with the real MyPalm grading form on it. It satisfies DESIGN.md
      §6 ("one real mill photograph with one real product screen, never two
      renders, never two screenshots") in a single frame, which is exactly what
      mypalm.css §.mp-scene argues a composite is.

   2. THE CHAIN IS A HAIRLINE GRID, NOT A LIST. The reader's first question is
      "which of these is mine". Six positions, each with the products built for it,
      set in the site's existing hairline-gap grid idiom (`.figures` / `.apps`:
      gap:1px over a hairline background, so the gutters ARE the rule). That reads
      as one continuous rail rather than six floating cards, and it dodges
      DESIGN.md §8's named ban on "the three-icon rounded-square feature grid" —
      there is no card, no radius and no fill, only ruled cells.

   3. THE ICONS ARE THE SITE'S OWN. The six position icons are the SAME PATHS the
      chain figure on index.html already draws (home.css §.cf-icon), lifted
      verbatim rather than redrawn, so the site has one icon language and not two.
      The product and service icons are new, drawn to the house geometry declared
      in assets/brand/icons/*.svg: 24x24 box, fill:none, stroke-width 1.75,
      linecap butt, linejoin miter. currentColor throughout, inline, no request.

   MOTION. Nothing new is invented. Reveal is base.css `.rev` + `data-d`, which
   already has its reduced-motion arm AND — load-bearing — its `@media
   (scripting:none)` arm in shell.css:256, so every element here is visible with
   JavaScript off. The only additions are hover transitions, all on properties in
   qa/motion-allow.json's allowlist (transform, border-color, color, background),
   each with an exact-selector final-state rule in the reduce block at the foot of
   this file. qa/motion-lint.py matches those selectors EXACTLY, so the pairs below
   travel together and must not be renamed one at a time. */

/* ── 1. THE HERO ────────────────────────────────────────────────────────────────
   Two columns from 1000px. Below that it is one column and the DOM order does the
   work: the whole copy block (which contains the buttons) comes first and the
   figure second, so a phone reader reaches the call to action before the
   photograph. That is the same conclusion base.css §"ON A PHONE, THE ACTION COMES
   BEFORE THE PICTURE" reached with a flex reorder; here it needs no reorder
   because the source order is already right, which is the more durable version of
   the same fix.

   0.86fr, not 1fr. The photograph is 4:5 and the copy column holds a 16ch headline
   at clamp(38px,6.2vw,78px); at an even split the picture column ran 604px wide and
   755px tall at 1440 and pushed the hero past a screen and a half. At 0.86 the
   figure settles at 512x640 and the whole hero closes at 812px — inside one 900px
   viewport, with the headline still breaking where it broke before. */
.pr-herogrid{display:grid;gap:clamp(28px,4vw,56px);grid-template-columns:minmax(0,1fr)}
@media (min-width:1000px){
  .pr-herogrid{grid-template-columns:minmax(0,1fr) minmax(0,0.86fr);align-items:start}
}
.pr-herofig{margin:0;display:grid;gap:14px}
.pr-herofig>img{width:100%;height:auto;display:block;
  border:1px solid var(--hairline);background:var(--panel-navy)}
/* SQUARE ON DESKTOP, 4:5 ON A PHONE — and the reason is the column beside it.
   Every photograph in this set is 4:5. At 488px of picture column that is a 610px
   frame, and with the source line and caption the figure measured 739px against a
   543px copy column: 196px of empty navy under "Product names and descriptions are
   AIREI's own", which — added to the hero's 129px bottom padding and the next
   section's 129px top padding — is 454px of nothing below the copy. That is the same
   hole this pass was opened to close, moved one column to the left.
   1:1 takes the figure to 620px, the gap to 77px and the whole hero to 850px, inside
   one 900px viewport. The crop keeps the full width and 80% of the height, and the
   phone occupies y 0.19-0.66 of the frame, so at object-position 46% it sits whole
   and larger relative to the frame than it did before. base.css §.pband reached the
   same ratio for the same reason and shot all three states before choosing.
   Below 1000px the picture is the only column and the tall frame is right, so the
   square applies only where there is a second column to balance against. */
@media (min-width:1000px){
  .pr-herofig>img{aspect-ratio:1/1;object-fit:cover;object-position:50% 46%}
}
/* The source line sits ABOVE the figure, per DESIGN.md §7. */
.pr-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)}
.pr-src b{color:var(--ink-subtle);font-weight:500}
.pr-herofig figcaption{font-size:15px;line-height:1.55;color:var(--ink-muted);max-width:44ch}
.pr-herofig figcaption b{color:var(--ink);font-weight:600}

/* ── 2. THE HAIRLINE GRID, shared by the chain and the two card runs ───────────
   gap:1px over a hairline ground is the site's existing table-of-facts idiom
   (base.css `.figures`, `.apps`). One background paints every rule, so the cells
   cannot drift out of alignment and there is no per-cell border to double up. */
/* list-style + padding:0 are LOAD-BEARING, not tidying. The chain rail is a <ul>
   because six positions are a list, and the UA stylesheet gives a <ul> 40px of
   padding-left — which here painted a 40px strip of the container's own hairline
   ground down the left of the whole rail, outside the first cell. It reads as a
   seventh, empty, darker column. Caught by looking at the render, not the rules. */
.pr-grid{display:grid;gap:1px;background:var(--hairline);
  border:1px solid var(--hairline);margin:clamp(30px,4vw,48px) 0 0;
  padding:0;list-style:none}
.pr-grid>*{background:var(--mill-navy);min-width:0}

/* THREE ACROSS IS THE MAXIMUM, AND THAT IS A MEASUREMENT, NOT A PREFERENCE.
   `--wrap` is 1240px and `--gut` clamps at 64px, and .wrap is border-box — so the
   content column never exceeds 1112px NO MATTER HOW WIDE THE VIEWPORT IS. Six cells
   in 1112px measured 178px each in the browser at 1440, which leaves 130px of text
   after padding: "MyPalm Kernel Crushing Plant" took three lines in it and the label
   "KERNEL PLANT" took two. A six-across breakpoint was tried at 1360 and removed after
   looking at it — widening the viewport cannot rescue it, because the wrap is capped.
   Three across is 370px per cell, every product name on one or two lines, and the two
   rows still sit inside one screen, which is what "find your position" needs. */
.pr-chain{grid-template-columns:minmax(0,1fr)}
@media (min-width:600px){ .pr-chain{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media (min-width:900px){ .pr-chain{grid-template-columns:repeat(3,minmax(0,1fr))} }

.pr-node{padding:clamp(18px,2vw,24px);display:flex;flex-direction:column;gap:12px;
  border-top:2px solid transparent;
  transition:border-color var(--d2) var(--e-quad),background var(--d2) var(--e-quad)}
@media (hover:hover) and (pointer:fine){
  .pr-node:hover{border-top-color:var(--crude-amber);background:var(--panel-navy)}
}
.pr-node:focus-within{border-top-color:var(--signal-gold);background:var(--panel-navy)}

/* THE DOT IS THE NODE ON THE RAIL. It sits on the cell's own top edge, which is
   the hairline the grid gap already draws, so the six dots read as six stations on
   one continuous rule rather than six decorations. --crude-amber matches
   home.css §.cf-node, the same token the chain figure on index.html uses for the
   same object. */
/* flex-START, not centre, and the reason is base.css §"THE EYEBROW MUST SURVIVE
   WRAPPING" measured for its own dash: centring parks the marker at the vertical middle
   of the whole block, which is half a line BELOW the text it introduces the moment the
   label runs to two lines. "Kernel plant" and "Tapak semaian" both wrap in a 205px cell
   at the six-across breakpoint, so this is the two-line case, not the hypothetical one.
   The margin-tops are measured against the 1.2 line-height of the label beside them. */
.pr-nodetop{display:flex;align-items:flex-start;gap:12px;margin:0}
.pr-dot{width:7px;height:7px;flex:none;background:var(--crude-amber);margin-top:.34em}
.pr-ico{width:24px;height:24px;flex:none;color:var(--airei-blue-lift);display:block}
.pr-nodetop .pr-ico{margin-top:-.24em}
.pr-pos{font:600 12.5px/1.2 var(--f-mono);letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink);margin:0;min-width:0}
/* Every list on this page is a set of named things, not an enumeration, so none of
   them carries a marker. .pr-svc was left out of the first version and shipped four
   cells of UA bullet discs beside three cells that had none. */
.pr-node ul,.pr-card ul,.pr-svc ul{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.pr-node li{font-size:14.5px;line-height:1.45;color:var(--ink)}
.pr-node li span{color:var(--ink-muted);display:block;font-size:13.5px;margin-top:2px}
.pr-node a,.pr-end a{border-bottom:1px solid var(--hairline-strong)}

/* The end of the chain is not a position on it — a brand that buys the oil stands
   past the refinery — so it is one full-width cell under the rail rather than a
   seventh node, and it is the only cell with a left rule instead of a top dot. */
/* 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. */
/* Was a 2px left stripe. Every other cell in this family already carries its accent on the
   TOP edge (.pr-node and .pr-card both move border-top-color on hover and focus), so the
   end-of-chain cell takes the same edge and the family reads as one system. */
.pr-end{margin:clamp(18px,2vw,26px) 0 0;padding:clamp(18px,2vw,24px);
  background:var(--panel-navy);border:1px solid var(--hairline);
  border-top:2px solid var(--airei-blue-lift);
  display:grid;gap:14px}
@media (min-width:820px){ .pr-end{grid-template-columns:minmax(0,.5fr) minmax(0,1fr);
  align-items:start;gap:clamp(20px,3vw,40px)} }
.pr-end ul{list-style:none;margin:0;padding:0;display:grid;gap:10px}
.pr-end li{font-size:14.5px;line-height:1.45;color:var(--ink)}
.pr-end li span{color:var(--ink-muted)}

/* ── 3. THE PRODUCT RUN ────────────────────────────────────────────────────────
   MyPalm spans the row because it is the one product with six variants and a
   phone to show; the other four are equal cells beneath it. Five cells in a
   two-column grid would otherwise leave an orphan on row three — the same
   raggedness base.css §.apps names and fixes by choosing the column count rather
   than letting auto-fill choose it. */
/* 700px, not 820. At 768 — iPad portrait, a real device and not an edge case — a
   single column of five cards each carrying a full-width square photograph measured
   8,788px of page. Two columns at 768 give 310px cards, which is wider than the phone
   layout the copy was written for, and take the page to about 6,600px. The `sizes`
   attribute on every .pr-shot is written against this same 699/700 boundary, so the
   browser stops fetching the 1024px rung for a 322px slot at the same width the layout
   changes: a breakpoint and its sizes attribute are one decision, not two. */
/* THREE COLUMNS, NOT TWO — AND THE REASON IS A COUNT, NOT A TASTE.
   The paragraph above chose TWO for a five-card grid: MyPalm spanning the row, then
   four equal cells as 2x2. At FOUR cards that leaves THREE narrow cards in a two-column
   grid, and the orphan the paragraph above says it exists to prevent came back on row
   three.
   It was not a polite gap. .pr-grid paints --hairline and lets each child paint over it,
   so an EMPTY cell paints hairline across its whole area: measured at every width from
   700 to 1440, the hole was exactly as wide as a card (343px at 760, 557px at 1440) and
   read as a broken panel rather than as space.
   Two other fixes were built and rejected on the render. Spanning the orphan across the
   row makes .pr-shot a 1110px square, and gives Supply Chain & Traceability a banner
   larger than MyPalm's — a hierarchy invented by a card count. Three columns only above
   1100px leaves the same slab intact through the whole 700-1099 band.
   ON RESTORING ROBOVESTER, PUT THIS BACK TO repeat(2,...) IN THE SAME COMMIT — four
   narrow cards in three columns orphans the fourth and re-opens the identical hole. */
.pr-prods{grid-template-columns:minmax(0,1fr)}
@media (min-width:700px){
  .pr-prods{grid-template-columns:repeat(3,minmax(0,1fr))}
  .pr-prods>.pr-wide{grid-column:1 / -1}
}
.pr-card{padding:clamp(20px,2.2vw,28px);display:flex;flex-direction:column;gap:14px;
  border-top:2px solid transparent;
  transition:border-color var(--d2) var(--e-quad),background var(--d2) var(--e-quad)}
@media (hover:hover) and (pointer:fine){
  .pr-card:hover{border-top-color:var(--crude-amber);background:var(--panel-navy)}
}
.pr-card:focus-within{border-top-color:var(--signal-gold);background:var(--panel-navy)}
/* Same flex-start reasoning as .pr-nodetop. "A.I. Based Smart Palm Oil Mill" and
   "Penggredan BTS AI Tanpa Manusia" are three lines in a two-column card at 820px; an
   icon centred against three lines of display serif floats a line and a half down. */
.pr-cardhead{display:flex;align-items:flex-start;gap:12px}
.pr-cardhead .pr-ico{margin-top:2px}
.pr-card h3{font-size:clamp(21px,2vw,27px);margin:0}
.pr-card h3 a{border-bottom:1px solid var(--hairline-strong)}
.pr-card li{font-size:15px;line-height:1.5;color:var(--ink-muted)}
.pr-card li b{color:var(--ink);font-weight:600}
.pr-ask{margin-top:auto;padding-top:4px;font-size:14.5px}
.pr-ask a{border-bottom:1px solid var(--hairline-strong)}

/* THE PICTURE IS 1:1 IN A CARD, NOT 4:5. Every photograph in assets/img/45/ is
   4:5 (1024x1280, all 204 files, one ratio). Dropped into a two-column card at
   1440 that is a 522x653 frame — taller than the copy beside it and taller than a
   phone screen shows — so the card becomes a picture with a footnote. base.css
   §.pband reached the same conclusion for the same reason and settled on the
   square: the crop goes from 34.2% to 20.0% of the frame and the block stays
   inside one screen. object-position keeps the top of the frame, which is where
   the subject sits in all four of the photographs used here. */
.pr-shot{width:100%;height:auto;aspect-ratio:1/1;object-fit:cover;object-position:50% 42%;
  display:block;background:var(--panel-navy);border:1px solid var(--hairline)}
/* 11.5px -> 12.5px, third pass (see `.stat .src`, base.css). This is a 74-character
   sentence — "Where it goes — the field. The photograph shows the work, not the machine." —
   which is the note that tells a reader what they are looking at. Tracking .05em -> .02em
   for the same reason: loose tracking is a device for short uppercase labels, and on a
   sentence in sentence case it only pulls the words apart. */
.pr-shotnote{margin:0;font:500 12.5px/1.5 var(--f-mono);letter-spacing:.02em;
  color:var(--ink-ghost)}

/* THE WIDE CELL: a real phone screen beside the copy. The bezel is DOM, in navy,
   for the reason mypalm.css states — the source decks ship these screens already
   inside a drawn blue device frame, that frame is cropped off at build time
   (assets/img/mypalm/phone-*.jpg), and a photograph of a phone would be a second
   render stacked on the first. The radius is the only radius on this page: a phone
   has one, and "uniform 16px radius on everything" is on the DESIGN.md §8
   blacklist. */
/* TWO CHILDREN, NOT FOUR. The first version made .pr-wide a grid of `auto 1fr` and
   spanned the phone down three rows — so the heading, the list and the link each got a
   third of the phone's ~500px height and, with align-items:center, floated in the
   middle of it. The render showed roughly 200px of navy between "MyPalm" and its first
   bullet. A row-span is not a column: the copy is ONE stack that centres against the
   phone as a whole, which is what .pr-widebody is for. */
@media (min-width:700px){
  .pr-wide{display:grid;grid-template-columns:auto minmax(0,1fr);
    gap:clamp(24px,3.4vw,52px);align-items:center}
}
.pr-widebody{display:flex;flex-direction:column;gap:14px;min-width:0}
/* MEASURE, AND IT ONLY SHOWED UP IN MALAY. The wide card's body takes the whole 1fr
   track with no cap, which the English copy never revealed because every line fits on
   ONE line and a line that never wraps has no measure at all. The Malay translation of
   the same sentence is longer, so it wraps — at 88.9ch, 98 characters to a line, on
   ms/produk at 1280, 1440 and 1920 alike, while products.html measured clean.
   That is the second instance of this shape found in this pass (`.plate figcaption>*`
   is the other), and the lesson is the same: an uncapped box is a latent defect that
   the SHORTER language hides. 65ch is 596px here, 71-72 characters at 15px — inside the
   band in both languages, and it leaves the English lines exactly where they are. */
.pr-widebody li{max-width:65ch}
.pr-device{display:grid;gap:12px;justify-items:center;margin:0}
/* THE BEZEL WAS PRESENT AND INVISIBLE, WHICH IS WORSE THAN ABSENT. It was
   --mill-navy #1A2C44, drawn on a --panel-navy #1B273A card, around a screenshot whose
   own ground is dark navy: three navies within one value step of each other, so the
   12px of "phone body" never resolved and the card read as a rounded-corner crop of a
   dark rectangle. Screenshot, 2026-08-27, before and after side by side — the geometry
   did not change at all, only the material. --device-bezel (tokens.css) is the same
   moulding base.css §.scr draws the desktop captures into, so the phone on this page
   and the monitors on mypalm are made of one substance rather than two. */
.pr-phone{--bez:clamp(8px,1vw,12px);width:min(100%,188px);padding:var(--bez);
  border-radius:28px;background:var(--device-bezel);border:1px solid var(--hairline-strong)}
.pr-phone>img{width:100%;height:auto;display:block;border-radius:18px;
  background:var(--deep-water)}
.pr-device figcaption{font:500 11.5px/1.5 var(--f-mono);letter-spacing:.05em;
  color:var(--ink-ghost);text-align:center;max-width:22ch}

/* ── 4. THE SERVICES ──────────────────────────────────────────────────────────── */
.pr-svcs{grid-template-columns:minmax(0,1fr)}
@media (min-width:640px){ .pr-svcs{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media (min-width:1100px){ .pr-svcs{grid-template-columns:repeat(4,minmax(0,1fr))} }
.pr-svc{padding:clamp(18px,2vw,24px);display:flex;flex-direction:column;gap:12px}
.pr-svchead{display:flex;align-items:flex-start;gap:12px}
.pr-svchead .pr-ico{margin-top:-.1em}
.pr-svc h3{font:600 12.5px/1.3 var(--f-mono);letter-spacing:.16em;text-transform:uppercase;
  color:var(--ink);margin:0;min-width:0}
.pr-svc li{font-size:14.5px;line-height:1.5;color:var(--ink-muted)}

/* ── 5. REDUCED MOTION ─────────────────────────────────────────────────────────
   qa/motion-lint.py rule REDUCED matches these selectors EXACTLY against the ones
   that declare a transition above. Renaming one without the other is a build
   failure, which is the point. */
@media (prefers-reduced-motion:reduce){
  .pr-node{transition:none}
  .pr-card{transition:none}
}

/* ── 6. PRINT ──────────────────────────────────────────────────────────────────
   base.css §PRINT inverts the palette to ink-on-paper and lets long sections break
   while protecting the atoms inside them. These cells are atoms: a position and
   its products, or a product and its description, should not split across a page
   boundary. The hairline ground does not print (Chromium omits background colours
   by default), so each cell states its own rule. */
@media print{
  .pr-grid{background:none;border-color:rgba(16,24,32,.22)}
  .pr-grid>*{background:none;border-top:1px solid rgba(16,24,32,.22)}
  .pr-node,.pr-card,.pr-svc,.pr-end{break-inside:avoid;page-break-inside:avoid}
  .pr-end{background:none;border-color:rgba(16,24,32,.22)}
  .pr-dot{background:#7A5310}
  .pr-ico{color:#7A5310}
  .pr-phone{background:none;border-color:rgba(16,24,32,.42)}
  .pr-herogrid{grid-template-columns:1fr}
}

/* TAP TARGETS ON TOUCH. Measured at 390 in a real mobile context (is_mobile+has_touch):
   the card-title link was 27px tall and the "See the screens →" ask 19px — the ask under
   the 24px WCAG 2.5.8 floor, and both the primary way into a product from this page.
   padding-block, not min-height: these are display:inline, and min-height does not apply
   to a non-replaced inline box. Vertical padding DOES expand an inline element's hit box
   (and its getBoundingClientRect) while leaving the line box — and therefore the layout
   and the wrapping — byte-identical. Making them inline-flex instead would stop them
   wrapping, and "Supply Chain & Traceability" already runs 183px at 390. */
@media (pointer:coarse){
  .pr-ask a{padding-block:13px}
  .pr-cardhead h3 a{padding-block:9px}
}

/* ── .pr-screen — the software, at the width the card already had ────────────────
   Added 2026-08-27 to the MyPalm wide card.

   WHAT WAS WRONG. `.pr-wide` spans `grid-column:1 / -1` — the full grid row, 1180px
   at 1440 — and spent it on `auto minmax(0,1fr)`: a 164px phone and a column of
   text. The phone carried the QR Code Scanner, whose whole content is the sentence
   "scan a QR code". Two cards to its right, each a THIRD of that width, carried a
   499px photograph of a mill at dusk and a 499px photograph of a camera rig. So on
   a page whose h1 is "The software that runs the chain. The machines that feed it.",
   the machines were shown at 3x the size of the software, and the software's own
   frame was the least informative capture in a library of twenty-two.

   THE FIX IS NOT A NEW COMPONENT. The screen goes in as a third grid child spanning
   the row, above the existing phone+copy pair, drawn into base.css's `.scr` moulding
   — the same bezel mypalm.html uses, so a capture on this page and a capture on that
   one are made of one substance. 1180px against a 1600px source puts UI text at
   ~8.1px, which is read, not decoration; verified at 1:1 and at 2x, not inferred
   from the ratio.

   AND THE PHONE GREW, 188 -> 248. The QR scanner was replaced by the grading form,
   which is a form: seven labelled fields and two buttons. That is only worth showing
   if the labels resolve. The capture is 348px wide, so 188 rendered it at 0.54 and
   248 renders it at 0.71 — the difference between a shape of a form and a form you
   can read the fields of. Scoped to `--lg` so the `.pr-phone` default is untouched
   for any other card that later wants the small one. */
.pr-screen{margin:0;display:grid;gap:10px;justify-items:stretch}
/* Below the 700px break the card is one column and its own padding is the only thing
   between the capture and the viewport edge. A 1600px source at 308px is a texture;
   at 348 it is a slightly larger texture, so this is not sold as making it readable —
   it is the free 13%, and the card's existing "See the screens" link is the honest
   route to a screen you can actually read. Pull by the card's own padding, not a
   guess, so the two stay in step if that clamp is ever retuned. */
@media (max-width:699px){
  .pr-screen{margin-inline:calc(-1 * clamp(20px,2.2vw,28px))}
  .pr-screen figcaption{padding-inline:clamp(20px,2.2vw,28px)}
}
.pr-screen>img{width:100%;height:auto;display:block}
.pr-screen figcaption{font:500 11.5px/1.5 var(--f-mono);letter-spacing:.05em;
  color:var(--ink-ghost)}
@media (min-width:700px){
  /* Third child of a two-column grid: without this it becomes a row of its own in
     column 1 and the card's whole layout shifts. */
  .pr-wide>.pr-screen{grid-column:1 / -1}
}
.pr-device--lg .pr-phone{width:min(100%,248px)}

/* ── THE VOICES BLOCK — two portrait delegate clips, then one customer ───────────
   Added 2026-08-27 with #voices and #customer on products.html / ms/produk.html.

   WHY THESE ARE `pr-` AND NOT THE SITE-WIDE NAMES. `.filmplayer` and `.filmnote` are
   base.css's and are reused verbatim. `.pull` and `.note`, which read like site-wide
   components, are NOT: they are defined only in smart-mill.css, mypalm.css and
   proof.css, none of which this page loads. Writing `class="pull"` here would have
   rendered a browser-default blockquote with a 40px inline margin and no rule at all —
   a component that LOOKS shared and is not. So the two are re-declared under the
   file's own `pr-` prefix, matching proof.css's design so the two pages read as one
   site, and `.filmplayer.portrait` is likewise `pr-portrait` rather than a second,
   divergent definition of a smart-mill.css selector.

   THE PORTRAIT CASE. Both delegate clips are 9:16, shot on a phone held upright.
   base.css hard-sets `.filmplayer{aspect-ratio:16/9}` with `object-fit:cover`, and a
   9:16 source in that box crops a standing man to a band across his chest — losing the
   badge that names him and the board that places him at the mill. The width is CAPPED
   at 340px, not fluid: rendered 700px wide, handheld phone footage reads as a
   production mistake instead of as what it is.

   THE CUSTOMER CASE. That master is 816x464 = 51:29, which is NEAR 16:9 but not it.
   Its own ratio plus `contain` means nothing is cropped and no pillarbox appears.

   AND IT IS `pr-filmwide`, NOT `pr-wide`, FOR A MEASURED REASON. `.pr-wide` is ALREADY
   taken in this file (line ~264) by the MyPalm product card, where it means
   `display:grid; grid-template-columns:auto minmax(0,1fr)` — and that declaration sits
   inside `@media (min-width:700px)`. Naming the player `.filmplayer.pr-wide` turned the
   <figure> into a two-column grid and dropped the <video> into the `auto` track, so it
   rendered 509px inside a 560px frame at 1440 and CORRECTLY at 390. A collision visible
   only above 700px, in a file where every class starts `pr-`, is the shape that ships.

   Spacing is a literal clamp. There is no spacing scale in tokens.css — --d1..--d6 are
   DURATIONS, so `gap:var(--d3)` parses as `gap:320ms`, which is invalid, and an invalid
   value drops the whole declaration and butts the two players together. */
.pr-voices{display:grid;gap:clamp(24px,3.4vw,44px);
  grid-template-columns:repeat(auto-fit,minmax(260px,320px));
  justify-content:start;margin-top:clamp(26px,3vw,40px)}
.filmplayer.pr-portrait{aspect-ratio:9/16;width:100%;max-width:340px;margin-top:0}
.filmplayer.pr-portrait video{object-fit:contain}
.filmplayer.pr-filmwide{aspect-ratio:51/29;width:100%;max-width:560px;margin-top:0}
.filmplayer.pr-filmwide video{object-fit:contain}

/* The sentence that says who is speaking and what they say. A <video> takes no alt, so
   this paragraph IS the text alternative — it is prose for a reader, not a hidden
   string, and it sits above the mono metadata row rather than inside it. */
.pr-said{margin:14px 0 0;max-width:44ch;font-size:15.5px;line-height:1.55;
  color:var(--ink-muted)}
.pr-said b{color:var(--ink);font-weight:600}

/* The trailing note is the grid's THIRD cell, not a paragraph under it. Two 320px cards
   in a 1240px wrap left 556px of nothing to their right at 1440 while the note sat below
   in the gap — the same hole smart-mill.css's .witness has and the reason its own comment
   caps the card width. auto-fit needs no media query for this: 3 x 320 + 2 x 44 = 1048,
   inside the wrap, and on a phone the note simply stacks last, exactly where it was. */
.pr-aside{align-self:end;padding-top:14px;border-top:1px solid var(--hairline)}

.pr-note{margin:0;font-family:var(--f-mono);font-size:13px;line-height:1.7;
  letter-spacing:.04em;color:var(--ink-subtle);max-width:620px}

.pr-customer{display:grid;gap:clamp(24px,3.2vw,40px);margin-top:clamp(26px,3vw,40px);
  align-items:start}
@media (min-width:900px){
  /* The player is capped at 560px, so a 1fr/1fr split would leave it floating in a
     column half again its own width. Give the quote the remainder instead. */
  .pr-customer{grid-template-columns:minmax(0,560px) minmax(0,1fr)}
}
.pr-pull{margin:0;border-left:2px solid var(--crude-amber);
  padding:4px 0 4px clamp(20px,3vw,34px)}
.pr-pull blockquote{margin:0;font-family:var(--f-display);font-weight:420;
  font-size:clamp(21px,2.4vw,30px);line-height:1.32;letter-spacing:-0.0068em;color:var(--ink)}
.pr-pull figcaption{margin-top:20px;font-family:var(--f-mono);font-size:13px;line-height:1.7;
  letter-spacing:.02em;color:var(--ink-subtle)}
.pr-pull figcaption b{color:var(--ink);font-weight:500;display:block}

/* Cross-origin captions land as a ::cue on a dark video. Chromium's default cue box is
   already high-contrast, but the font is left to the UA — pin the family so the caption
   does not arrive in Times on a page that owns three typefaces. */
.filmplayer video::cue{font-family:var(--f-body);background:rgba(5,8,11,.78)}
