/* ==========================================================================
   PEMBEKAL PERALATAN TADIKA / PPT EDU SUPPLIES (002550066-P)
   (c) 2026 PPT EDU SUPPLIES

   002550066-P is an ENTERPRISE registration, read at 700dpi off their own
   microtext. It is not the other suffix and this site must never say so.
   The banned string is not written here either: the browser downloads this
   file, so a comment ships as surely as a heading does.

   THE COMPOSITION, and why it is this and not the obvious one.
   The best designed thing this client owns is their spec sheet: 22 consecutive
   pages holding one fixed grid without deviation, where a product is stated as
   a drawing, four dimensioned elevations and a row of printed attributes. This
   page is that artefact turned on the company itself. It opens by stating the
   company the way their sheets state a product, indexes itself numerically,
   and presents the catalogue as a price list rather than as a grid of cards,
   because a price list is what a kindergarten buyer is actually reading.

   Nothing here is centred in a fixed column, no section repeats the heading,
   paragraph, chips, button order, no two sections share a density, and the
   largest object on the page is roughly forty times the height of the smallest
   type. That is deliberate: their equipment is physically large, so scale is
   the subject rather than a flourish.

   COLOUR. Every value is an exact-hit flat-fill pixel count from their own
   rendered catalogue pages, or a tint of one computed to a stated contrast
   ratio. Nothing outside this list is on the page.

   DASHES. No em dash, no en dash, no hyphen joining two ideas. The only
   hyphens are inside phone numbers, the registration number, the street
   address, and captions quoted verbatim from their catalogue.

   NO LINES. No rule, divider, underline or hairline anywhere. Sections are
   separated by colour and space.
   ========================================================================== */
@view-transition{navigation:auto}
/* THE TWO TYPEFACES ARE INLINED AS DATA URIs, and that is not a style
   choice. @font-face requests are CORS-checked, and on a file:// page
   Chrome refuses them: opening this site by double-clicking a page lost
   BOTH typefaces and fell back to the system stack. Same root cause as
   the search failing, found in the same console. 15,256 bytes of woff2,
   20,344 as base64, and two fewer requests. */
@font-face{font-family:"Gabarito";src:url("data:font/woff2;base64,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") format("woff2");
  font-weight:700;font-style:normal;font-display:swap}
@font-face{font-family:"Poppins";src:url("data:font/woff2;base64,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") format("woff2");
  font-weight:400;font-style:normal;font-display:swap}

:root{
  --beige:#F1E7DD;  /* 10.8M px, their signature surface   */
  --white:#FFFFFF;  /* 63.0M px, the ground of every page  */
  --cream:#EEE9E2;  /*  2.0M px, the spec sheet panel      */
  --play:#ECE3C7;   /*   451k px, Playground Catalogue      */
  --ink:#373636;    /*  1.13M px, product names and body   */
  --mute:#5A5857;   /* ink lightened. 4.94:1 on the palest ground we print on,
                       #C3DCE3. Was #605E5D, whose comment claimed 4.94 but
                       measured 4.50 on that ground, 4.51 on #E1D8B6 and 4.53
                       on #D2D8E9: three hairline passes on one page. Session G
                       already ruled on this exact case when it rejected 4.51
                       as "not a margin worth having" and cut #5C4A38 for the
                       projects band. Computed, not eyedropped, on every ground
                       this token actually lands on. Never used on a dark
                       ground; those carry rgba(255,255,255,...) instead.
                       paper 6.90 · white 7.07 · #DCE4E5 5.48                */
  --price:#B50526;  /* every Normal Price line. PRICES ONLY */
  --rule:#74A1D0;   /*   605k px, the spec sheet top rule   */
  --taupe:#A79A89;  /*   443k px, their band fill           */
  --forest:#003C20; /* Playground Catalogue headings        */
  --zest:#FF741F;   /* the script word Collection           */
  --zest-t:#AA4D15; /* zest darkened to 4.55:1 on beige     */
  /* ==================================================================
     SESSION AC. FOUR GROUNDS, NOT FIFTEEN.

     Every value below was correctly measured off a real product, and
     every one of those measurements is kept in the comments because it
     is the record of where the colour came from. What was wrong was not
     the sampling, it was the lightening. Each ground was walked up until
     --mute and --price cleared 4.5:1 on it, which put all fifteen of
     them between 79.8% and 91.0% lightness. Measured as contrast ratios
     against each other they came out at 1.01 to 1.4, and 1.01 is not a
     difference, it is the same colour twice. On the two long pages the
     effect was literal: learning.html ran one unbroken 26,359px band and
     storage.html one of 14,639px, out of 34,008 and 23,011.

     So the fifteen collapse onto four, and the four are chosen so the
     step between them is measurable rather than nominal:

       --g-paper  #FFFFFF  their page white, 63.0M px across four books
       --g-warm   #F1E7DD  beige, their signature surface, 10.8M px
       --g-cool   #C3DCE3  from #5CBBD8, the globe. The darkest ground
                           this type system allows: --mute lands at 4.94
                           and --price at 4.87 on it, so going one step
                           deeper would break body text.
       --g-deep   #003C20  forest, PLY p5, the word PLAYGROUND

     And the honest limit, computed rather than hoped for: white/warm is
     1.22, white/cool 1.43, warm/cool 1.17. Three light grounds cannot
     give a page arrival, only rhythm, because anything light enough to
     carry this body text sits within 1.4 of everything else that is.
     Forest against the other three is 8.80 to 12.60. The dark ground is
     therefore not decoration, it is the only real step available, and
     it is not new here: index.html has carried two mid-page forest bands
     and contact.html a forest masthead since session G.

     Nothing was invented. Four of the fifteen sampled values survive as
     the four grounds; the other eleven now point at whichever of the
     four their own hue already belonged to.
     ================================================================== */
  --g-paper:#FFFFFF;
  --g-warm:#F1E7DD;
  --g-cool:#C3DCE3;
  --g-deep:#003C20;

  /* filled fields, each sampled from the products in its own section.
     The measured source stays on the left of every comment; the token
     now resolves to whichever of the four grounds that hue belongs to. */
  --g-play:var(--g-warm);   /* from #C8A93F, the frames' plywood and yellow */
  --g-spec:var(--g-cool);   /* from #4059A4, their elevation drawing blue   */
  --g-class:var(--g-cool);  /* from #5CBBD8, the globe, 8194 px cluster     */
  --g-proj:var(--g-warm);   /* from #DEBC98, the warm interior light        */
  --g-carp:var(--g-cool);   /* from #3FCE52, the green on the Counting Bee and
                         Country Side Roadmap carpets, edu p32. Was #D6E6D8 */
  --g-mat:var(--g-warm);    /* from #F75A53, the red Eva Mat, edu p16.
                         Was #EDD7D6                                     */
  /* SESSION K. Grounds measured off the images each page actually ships, by
     tools/k_ground.py, which reads the built HTML, takes every <img> in <main>,
     discards the studio sweep, the shadow and the steel, and returns the
     largest cluster of real product colour with the files it came from. The
     lightness is then walked down until --mute AND --price both clear 4.5:1 on
     that exact ground, so the value is set by the contrast requirement.
     Every figure below is computed, not eyedropped, and traceable to a
     photograph a visitor can see on the same page.

     It also corrected a colour that was simply wrong: floors.html carried a
     GREEN carpet band, sampled from the green inside two carpets. Measured
     across all thirteen, the carpets are BLUE: #3D94D1 at 12.2% and #2A60AB at
     11.5%, from Counting Bee, Colours, Universe, The Galaxy and Hopscoth. The
     page is now the colour its own products are.                            */
  --g-learn:var(--g-warm);  /* from #CC3325, 10.3% of saturated pixels across
                         the 55 photographs on learning.html: the red plastic
                         of Bolt & Nuts, Bullets Blocks, Lacing Numbers, Big
                         Flowers. Was #F5DBD8                              */
  --g-learn-2:var(--g-cool);/* from #DED03A, 7.8%, the yellow of the same range.
                         Was #F5F3D8. It follows --g-learn directly down the
                         page, so it takes the cool ground: two warm tints in
                         sequence is where the corridor came from.         */
  --g-shelf:var(--g-warm);  /* from #BF9965, 36.3% across the 30 photographs on
                         storage.html: the birch every shelf is made of.
                         Was #F5E9D8                                       */
  --g-tray:var(--g-cool);   /* from #334B92, 6.0%, the deep blue of the plastic
                         trays in the Nature Series shelves, edu p2.
                         Was #D8DFF5                                       */
  --g-fl-carp:var(--g-cool);/* from #3D94D1, 12.2%, the blue across the 13
                         carpets. Was #D8E9F5                              */
  --g-fl-mat:var(--g-warm); /* from #D62836, 8.3%, the red on Alphabet Animals,
                         Rainbow Alphabet, Hopscoth and Sunshine.
                         Was #F5D8DB                                       */
  --g-post:var(--g-warm);   /* from #C3A64C, 7.4% across the 25 photographs on
                         play.html: the yellow of the seesaw seats, the swing
                         frames, the hoops and the smiling trampoline.
                         Was #F5EED8                                       */
  --g-stor:var(--g-warm);   /* from #E0F102, the lime on the Multi-coloured
                         Series shelves, edu p8. Was #E9EAD1               */
  --g-inst:var(--g-cool);   /* from #1CA8FF, the blue on the metal playgrounds,
                         edu p25. Was #D3E2EB. The note that used to sit here
                         said this was the third pale blue on the site and
                         that this was accepted. There is now one pale blue,
                         which is the better answer to the same problem.   */

  --s1:8px; --s2:16px; --s3:24px; --s4:40px; --s5:64px; --s6:104px; --s7:168px;
  --settle:cubic-bezier(0.65,0.05,0,1);
  --fast:0.3s; --open:0.45s; --slow:0.75s;
  --r-ui:3px;
  /* Type. Two genuinely large moments, everything else small. The gap between
     28px and 64px is the design, not an oversight. */
  --t-huge:clamp(2.5rem,6vw,5rem);
  --t-big:clamp(2rem,4.4vw,3.5rem);
  --t-mid:1.75rem;
  /* --t-sub is 20px and it is not new, it is named. Session AF counted every
     rendered character on all fourteen pages at 390 and found seventeen
     distinct sizes, of which five carried 98.1% of the text: 14, 17, 20, 28
     and 32. 14, 17, 28 and 32 already had names here. 20 did not, so it was
     written as a literal 1.25rem in nine rules and, having no name, it
     collected neighbours: 18px product names on therapy, 22px on boards,
     index and projects, 24px prices on therapy and floors. Those are 6%,
     10% and 20% from a size that was already there doing the same job.
     A step nobody can name is a step nobody can keep. */
  --t-sub:1.25rem;
  --body:1.0625rem; --meta:0.875rem; --fig:0.875rem;
  /* --fig WAS 0.75rem, which is 12px, and 72 elements on the two live pages
     were rendering at it: every code, every label, every caption, the menu
     descriptions and the enquiry-list items. The footer alone was raised to
     14px in session 7 and was reported as "nothing on the site is 12px any
     more", which was not true anywhere else. 14px is the floor now and it is
     set once here so no page can drop below it. --fig and --meta are the same
     size on purpose: what separates a label from body text is the uppercase
     and the tracking, not the size. */
  --room:clamp(20px,4.6vw,72px);
  --bar:0px;   /* set from the measured dock height by site.js, never guessed */
}
*,*::before,*::after{box-sizing:border-box}
*{-webkit-tap-highlight-color:transparent}
/* scroll-behavior was `smooth` until session AB. Measured at 390px with a
   4x CPU throttle, an in-page jump took 1,550ms to arrive on index.html
   and on learning.html, because smooth animates the whole distance and
   these pages are 17,048px and 33,779px tall. With it off the same jump
   lands in about 100ms. That one word was the single largest source of
   the "very laggy" complaint: frame rate was already a locked 16.7ms, so
   nothing was dropping frames, the page was simply taking a second and a
   half to go anywhere. Section jumps and back-to-top are now instant.
   Reduced-motion users already had `auto`, so they were the only people
   getting the fast version. */
html{-webkit-text-size-adjust:100%;scroll-behavior:auto;overflow-x:clip}
/* overflow-x:hidden on body alone did not stop the document scrolling sideways
   at 1024 and 1440 once page two put an object off the right edge. clip on the
   root does, and unlike hidden it does not turn the root into a scroll port.

   AND THE BODY MUST NOT CARRY overflow-x AT ALL. This is the regression that
   broke the landing page and it cost the client a working page for a day, so
   it is written down rather than merely fixed.

   overflow-x:hidden forces overflow-y to compute to auto. That makes <body> a
   scroll container. Every position:sticky descendant then resolves against a
   scrollport that never itself scrolls, so it silently stops sticking. The
   four step sequence was pinned inside a 3600px block: the pin died, the panel
   scrolled past in one screen, its animation played to an empty room, and it
   left 2700px of blank beige behind it. One declaration, three symptoms, none
   of them visible in the source. Measured, not guessed: the sticky element's
   top read -395, -995, -1595 and -2195 at four scroll offsets with the line
   in, and 0 at all four with it out.

   If sideways scroll ever returns, fix it on the element that overflows or on
   the root. Never on body. */
body{margin:0;background:var(--beige);color:var(--ink);
  font-family:"Poppins",ui-sans-serif,system-ui,sans-serif;font-size:var(--body);
  line-height:1.55;-webkit-font-smoothing:antialiased;padding-bottom:var(--bar)}
h1,h2,h3,b,strong,.btn,.rm,.code,.n{font-family:"Gabarito","Poppins",sans-serif;font-weight:700}
h1,h2,h3{margin:0;line-height:.98;letter-spacing:-.03em;color:var(--ink);text-wrap:balance}
p{margin:0}
img{display:block;max-width:100%;height:auto}
a{color:inherit}
button{font:inherit;color:inherit;border:0;background:none}
a:focus:not(:focus-visible),button:focus:not(:focus-visible){outline:none}
::selection{background:var(--zest);color:var(--forest)}  /* white on zest is 2.70:1 and genuinely hard to read; forest is 4.66:1 */
:focus-visible{outline:2px solid var(--forest);outline-offset:3px;border-radius:var(--r-ui);
  box-shadow:0 0 0 5px rgba(0,60,32,.16)}
.on-dark :focus-visible{outline-color:#FFF;box-shadow:0 0 0 5px rgba(255,255,255,.3)}
.vh{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.wrap{padding-inline:var(--room)}
.rm{color:var(--price);font-variant-numeric:tabular-nums}
.code{font-size:var(--fig);letter-spacing:.09em;color:var(--mute);font-variant-numeric:tabular-nums}

/* ===================== HEADER =====================
   THE HEADER IS IN NORMAL FLOW AND RESERVES ITS OWN SPACE. It used to be
   position:absolute over the top of whatever came first, which meant every
   page had to remember to leave a hole for it. Page one did not: the white
   WhatsApp control sat on top of the words "Supermarket Playhouse" and cut
   them off mid-word, with "Playground Collection" running underneath it. That
   was reported twice, on two different pages, which is the signature of a
   shared component rather than a page bug.

   In flow it cannot overlap anything, on any page, at any width, ever. It
   carries the ground colour of the field it opens onto so the page still
   reads as one uninterrupted band of colour from the very top edge. Nothing
   downstream needs to compensate for it and no page needs a magic number. */
/* THE BAR IS A GRID, NOT A FLEX ROW, so the search field can be given a whole
   row of its own on a narrow screen instead of being squeezed between the logo
   and the menu button. In a flex row at 390px the field collapsed to about
   110px and its prompt text truncated to "Search pro...", which is why it kept
   being reported as missing: it was present and it did not look like a search
   field. Measured at 390 before the change: 110px wide. After: 350px, on its
   own row, full width. */
.hdr{position:relative;z-index:70;display:grid;align-items:center;
  grid-template-columns:auto minmax(0,1fr) auto auto auto;
  grid-template-areas:"logo find wa list menu";
  gap:var(--s2);min-height:88px;padding-inline:var(--room);padding-block:14px}
.hdr .logo{grid-area:logo} .hdr .find-field{grid-area:find}
.hdr .wa{grid-area:wa} .hdr .menuBtn{grid-area:menu}
.hdr .listBtn{grid-area:list}
@media(max-width:900px){
  .hdr{grid-template-columns:minmax(0,1fr) auto auto;
       grid-template-areas:"logo list menu" "find find find";row-gap:12px}
}

/* THE ROUTE TO THE ENQUIRY LIST, IN THE HEADER OF EVERY PAGE.
   The list itself is position:fixed at the foot of the window and these pages
   run to 33,000px, so the moment somebody scrolled it was gone. Ashyun added
   products and then asked out loud where his list was, and he commissioned the
   site. This is in normal flow at the top, so it is one tap away at 320 and at
   1440 alike, and it always carries the count.
   It is never hidden: an empty state that shows 0 also teaches that the feature
   is there, and a hidden grid item leaves a track whose gaps still render, so
   the header would have jumped 16px the first time anything was added. */
.listBtn{display:inline-flex;align-items:center;gap:10px;min-height:52px;
  text-decoration:none;
  padding:0 var(--s2);border:0;border-radius:var(--r-ui);cursor:pointer;
  background:rgba(255,255,255,.18);color:#FFF;white-space:nowrap;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--meta);
  transition:background var(--fast) var(--settle),color var(--fast) var(--settle)}
.on-light .listBtn{background:rgba(55,54,54,.09);color:var(--ink)}
.listBtn .lb-n{font-family:"Poppins",sans-serif;font-weight:700;
  font-variant-numeric:tabular-nums;min-width:1.4em;text-align:center;
  border-radius:var(--r-ui);padding:2px 6px;font-size:var(--meta)}
.listBtn[data-n="0"] .lb-n{color:rgba(255,255,255,.66);background:none;padding:2px 0}
.on-light .listBtn[data-n="0"] .lb-n{color:var(--mute)}
/* full it is their own price red, which is the one saturated colour on the
   site and is already what a count means here */
.listBtn[data-n="1"] .lb-n{background:var(--price);color:#FFF}
@media (hover:hover){
  .listBtn:hover{background:#FFF;color:var(--forest)}
  .on-light .listBtn:hover{background:var(--ink);color:#FFF}
  .on-light .listBtn:hover[data-n="0"] .lb-n{color:rgba(255,255,255,.72)}
}
@media (hover:none){
  .listBtn:hover{background:rgba(255,255,255,.18);color:#FFF}
  .on-light .listBtn:hover{background:rgba(55,54,54,.09);color:var(--ink)}
}
/* below 560 the words go and the count stays: the count is the information */
@media(max-width:560px){
  .listBtn .lb-t{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}
  .listBtn{padding:0 12px;gap:0}
  .listBtn::before{content:"";width:19px;height:19px;flex:none;background:currentColor;
    -webkit-mask:var(--lb-ico) center/contain no-repeat;mask:var(--lb-ico) center/contain no-repeat}
}
:root{--lb-ico:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6h16M4 12h16M4 18h10'/%3E%3C/svg%3E")}
.hdr.on-dark{background:var(--forest)}
.hdr.on-light{background:var(--beige)}
/* the logo no longer pushes everything else to the right edge: the search
   field takes that space, because search belongs next to the identity and not
   at the end of a row of controls */
.logo{display:flex;align-items:center;text-decoration:none;min-height:48px;flex:none;
  border-radius:var(--r-ui);transition:opacity var(--fast) var(--settle)}
.logo img{height:44px;width:auto}
/* The logo is the way home from every page, so it has to read as a control
   and not as a printed letterhead. It carries the word Home beside it. */
.logo .home{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--fig);
  letter-spacing:.14em;text-transform:uppercase;margin-left:var(--s2);
  color:rgba(255,255,255,.72);white-space:nowrap}
.hdr.on-light .logo .home{color:var(--mute)}
@media (hover:hover){
  .logo:hover .home{color:#FFF}
  .hdr.on-light .logo:hover .home{color:var(--ink)}
}
@media(max-width:760px){.logo .home{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0)}}
.menuBtn{display:inline-flex;align-items:center;gap:10px;min-height:52px;padding:0 var(--s2);
  border:0;border-radius:var(--r-ui);background:rgba(255,255,255,.18);color:#FFF;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--meta);cursor:pointer;
  transition:background var(--fast) var(--settle),color var(--fast) var(--settle)}
@media (hover:hover){
  .menuBtn:hover{background:#FFF;color:var(--forest)}
  .on-light .menuBtn:hover{background:var(--ink);color:#FFF}
}
@media (hover:none){
  .menuBtn:hover{background:rgba(255,255,255,.18);color:#FFF}
  .on-light .menuBtn:hover{background:rgba(55,54,54,.09);color:var(--ink)}
}
/* The compact bar's menu control draws the same three bars off the same
   rule rather than a second set six pixels different. One glyph, one
   stroke, two places. */
.menuBtn .bars,.secnav-menu .bars{display:block;width:18px;height:2px;background:currentColor;position:relative}
.menuBtn .bars::before,.menuBtn .bars::after,
.secnav-menu .bars::before,.secnav-menu .bars::after{content:"";position:absolute;left:0;width:18px;height:2px;background:currentColor}
.menuBtn .bars::before,.secnav-menu .bars::before{top:-6px}
.menuBtn .bars::after,.secnav-menu .bars::after{top:6px}
.hdr .wa{border:0;background:#FFF;color:var(--forest)}
@media (hover:hover){.hdr .wa:hover{background:var(--zest);color:var(--ink)}}
@media (hover:none){.hdr .wa:hover{background:#FFF;color:var(--forest)}}
@media(max-width:900px){.hdr .wa{display:none}.logo img{height:38px}}

/* ===================== 07 THE PRICE LIST =====================
   Their Education Catalogue is a price list, so this is a price list. A row
   per item carrying the thumbnail, the code, the name, the size and the price,
   which is the four things a kindergarten buyer is actually reading. A grid of
   equal cards with a button under each is the shape this replaces. */
.list-sec{background:var(--g-class);padding-block:var(--s5) var(--s6)}
.list-head{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,18em);gap:var(--s4);
  align-items:end;margin-bottom:var(--s4)}
@media(max-width:860px){.list-head{grid-template-columns:1fr;gap:var(--s2)}}
.list-head h2{font-size:var(--t-big);max-width:13em}
.list-head p{font-size:var(--meta);color:var(--mute)}
.plist{display:grid;gap:2px}
.prow{display:grid;grid-template-columns:96px minmax(0,4.75rem) minmax(0,1fr) minmax(0,9.5rem) minmax(0,6.5rem) auto;
  gap:var(--s2);align-items:center;background:rgba(255,255,255,.5);padding:var(--s2);
  transition:background var(--fast) var(--settle)}
@media (hover:hover){.prow:hover{background:#FFF}}
.prow .th{width:96px;height:72px;display:grid;place-items:center}
.prow .th img{max-width:100%;max-height:72px;width:auto;height:auto}
.prow .nm{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--body);
  line-height:1.15;letter-spacing:-.018em}
.prow .sz{font-size:var(--meta);color:var(--mute);font-variant-numeric:tabular-nums}
.prow .rm{font-size:var(--t-mid);letter-spacing:-.026em;line-height:1}
@media(max-width:900px){
  .prow{grid-template-columns:76px minmax(0,1fr) auto;grid-template-areas:
    "th nm rm" "th sz rm" "th add add";row-gap:6px}
  .prow .nm{min-width:0;overflow-wrap:anywhere}
  .prow .th{grid-area:th;width:76px;height:76px}
  .prow .code{display:none}
  .prow .nm{grid-area:nm}.prow .sz{grid-area:sz}.prow .rm{grid-area:rm;text-align:right}
  .prow .add{grid-area:add;justify-self:start}
}
@media(max-width:400px){
  .prow{grid-template-columns:64px minmax(0,1fr);grid-template-areas:
    "th nm" "th sz" "th rm" "add add";gap:var(--s1);padding:var(--s2) var(--s1)}
  .prow .th{width:64px;height:64px}
  .prow .rm{grid-area:rm;text-align:left;font-size:var(--t-mid)}
  .prow .add{grid-area:add;justify-self:stretch;margin-top:6px}
}
.list-note{margin-top:var(--s3);font-size:var(--meta);color:var(--mute);max-width:34em} /* 39em was 83ch at meta size */
.list-note b{color:var(--ink)}

/* A SEARCH RESULT CARRIES THE PRODUCT, NOT A REFERENCE TO IT.

   It used to be one thin line: a code, a name, a size, a price, a button. That
   is a row out of an index, and it was rebuilt because a buyer reading it
   cannot tell a bookshelf from a bag rack. A result now carries the same four
   things the printed catalogue puts against a product -- the photograph, the
   code, the size and the price -- plus the material on the 22 rows that print
   one, and it says so in words where the book prints nothing.

   It still names which page the product lives on, which the catalogue row does
   not need to. It inherits nothing from .prow's areas: those name a thumbnail
   cell the old result did not have, and at 390px the add button landed left of
   the product name. Every width below was opened and looked at. */
.find-res .prow{
  grid-template-columns:96px minmax(0,1fr) minmax(0,10.5rem) auto;
  grid-template-areas:"th meat rm add" "th where where where";
  row-gap:10px;column-gap:var(--s2);align-items:start;padding-block:var(--s2)}
.find-res .prow .th{grid-area:th;width:96px;min-height:76px;display:grid;
  place-items:center;align-self:start}
.find-res .prow .th img{max-width:96px;max-height:96px;width:auto;height:auto;
  border-radius:var(--r-ui)}
/* Where there is no photograph the cell SAYS SO. A grey box reads as a broken
   image, and 46% of the range has no photograph in any of the four books. */
.find-res .prow .th.nopic i{font-style:normal;font-family:"Poppins",sans-serif;
  font-size:var(--meta);line-height:1.3;color:var(--mute);text-align:center;
  display:block}
.find-res .prow .meat{grid-area:meat;min-width:0;display:grid;gap:6px;align-content:start}
/* grid-area:auto IS LOAD-BEARING, and it was found by measuring.
   The catalogue row sets `.prow .nm{grid-area:nm}`, and .nm is now a child of
   .meat rather than of .prow. .meat has no area called "nm", so the name
   claimed an implicit column of its own: the card laid out as picture, facts,
   and then the product name wrapping down a 70px gutter, one or two letters to
   a line. Measured, not guessed -- .meat computed to `182px 70px`. Any element
   moved inside a new grid container has to have its inherited area released. */
.find-res .prow .nm,.find-res .prow .facts{grid-area:auto}
.find-res .prow .nm{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--body);letter-spacing:-.02em;line-height:1.18;
  overflow-wrap:anywhere;color:var(--ink)}
.find-res .prow .facts{display:flex;flex-wrap:wrap;gap:4px var(--s2)}
.find-res .prow .f{font-family:"Poppins",sans-serif;font-size:var(--meta);
  color:var(--ink);font-variant-numeric:tabular-nums;white-space:nowrap}
.find-res .prow .f i{font-style:normal;color:var(--mute);margin-right:6px}
.find-res .prow .f.none{color:var(--mute)}
.find-res .prow .f.f-size{white-space:normal}
.find-res .prow .rm{grid-area:rm;text-align:right;display:grid;gap:2px;
  justify-items:end;align-content:start}
.find-res .prow .rm b{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-mid);letter-spacing:-.026em;line-height:1;color:var(--price)}
.find-res .prow .rm i,.find-res .noprice i{font-style:normal;display:block;
  font-family:"Poppins",sans-serif;font-size:var(--meta);color:var(--mute)}
.find-res .noprice{color:var(--ink);font-family:"Poppins",sans-serif;font-weight:400;
  font-size:var(--meta);letter-spacing:0;text-align:right}
.find-res .prow .add-wrap{grid-area:add;justify-self:end}
.find-res .prow .where{grid-area:where;font-size:var(--meta);color:var(--mute);
  text-decoration:none;align-self:center}
@media (hover:hover){.find-res .prow .where:hover{color:var(--ink)}}
@media (hover:none){.find-res .prow .where:hover{color:var(--mute)}}
.find-more{margin-top:var(--s3);font-size:var(--meta);color:var(--mute)}

@media(max-width:900px){
  .find-res .prow{grid-template-columns:84px minmax(0,1fr) auto;
    grid-template-areas:"th meat meat" "th rm add" "where where where";
    row-gap:10px;align-items:start}
  .find-res .prow .th{width:84px}
  .find-res .prow .th img{max-width:84px;max-height:84px}
  .find-res .prow .rm{text-align:left;justify-items:start;align-self:center}
  .find-res .noprice{text-align:left}
}
/* 320 IS THE FLOOR AND IT IS WHERE A CARD WITH A PICTURE, FOUR FACTS AND A
   CONTROL BREAKS FIRST. The picture drops to 64px, the price and the button
   take a row of their own, and the facts wrap instead of forcing the row wide. */
@media(max-width:560px){
  /* THE BOX MOVED, NOT THE TYPE. This cell used to be 64px wide with the "no
     photograph in their catalogue" note set at 12px inside it. 12px is below
     the 14px floor, and measuring the note at 12px in Poppins showed the word
     "photograph" alone is 74px, so the note was ALREADY being clipped by 10px
     at the size that broke the rule. Raising the note to the floor makes that
     word 83px, so the column is 84px and the note fits on its own terms.
     Photographs are untouched: max-width stays 64px and the cell centres them,
     so the picture a reader sees is exactly the size it was. */
  .find-res .prow{grid-template-columns:84px minmax(0,1fr);
    grid-template-areas:"th meat" "rm rm" "add add" "where where";
    row-gap:8px;column-gap:12px}
  .find-res .prow .th{width:84px;min-height:64px}
  .find-res .prow .th img{max-width:64px;max-height:64px}
  .find-res .prow .facts{gap:2px 12px}
  .find-res .prow .add-wrap{justify-self:stretch}
  .find-res .prow .add{width:100%;justify-content:center}
}

/* THE WAY IN FOR SOMEBODY WHO HAS NOT TYPED ANYTHING.
   Four rooms this company has actually finished, then the categories with the
   number of products really in each. No borders: each control is told apart by
   having a ground of its own, which is the rule this project works to. */
.browse{padding-block:var(--s2) var(--s4)}
.browse h3{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--t-mid);
  letter-spacing:-.02em;margin-top:var(--s4)}
.browse h3:first-child{margin-top:0}
.br-rooms{margin-top:var(--s3);display:grid;gap:var(--s2);
  grid-template-columns:repeat(auto-fit,minmax(min(250px,100%),1fr))}
.br-room{text-align:left;border:0;border-radius:var(--r-ui);cursor:pointer;
  background:rgba(55,54,54,.07);color:var(--ink);padding:var(--s2);
  display:grid;gap:6px;align-content:start;min-height:48px;
  transition:background var(--fast) var(--settle)}
.br-room b{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--body);
  letter-spacing:-.02em}
.br-room .br-n{font-family:"Poppins",sans-serif;font-size:var(--meta);color:var(--price)}
.br-room .br-why{font-family:"Poppins",sans-serif;font-size:var(--meta);
  color:var(--mute);line-height:1.4}
.br-cats{margin-top:var(--s3);display:flex;flex-wrap:wrap;gap:10px}
.br-cat{border:0;border-radius:var(--r-ui);cursor:pointer;min-height:48px;
  background:rgba(55,54,54,.07);color:var(--ink);padding:0 var(--s2);
  display:inline-flex;align-items:center;gap:10px;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--meta);
  transition:background var(--fast) var(--settle)}
.br-cat .br-n{font-family:"Poppins",sans-serif;font-weight:400;color:var(--mute);
  font-variant-numeric:tabular-nums}
@media (hover:hover){
  .br-room:hover,.br-cat:hover{background:rgba(55,54,54,.13)}
}
@media (hover:none){
  .br-room:hover,.br-cat:hover{background:rgba(55,54,54,.07)}
}
.br-note{margin-top:var(--s3);font-size:var(--meta);color:var(--mute);max-width:44em;
  line-height:1.5}
/* every chip carries the number of products it would leave you with */
.find-chips .cn{margin-left:8px;font-family:"Poppins",sans-serif;font-weight:400;
  font-variant-numeric:tabular-nums;opacity:.72}

/* ===================== 11 CONTACT =====================
   CONTRAST, MEASURED IN THE BROWSER RATHER THAN ASSERTED. Ashyun said he could
   barely see the type here. Every value in this block was passing AA on paper:
   the lowest was 12px at 60% white over #003C20, which composites to #66857A
   and computes 5.51:1 against the ground, where AA asks 4.5. So the ratio was
   never the whole problem. The problem was 12px of 400 weight at 60% opacity
   on a dark field, which is legible to a measuring script and tiring to a
   person. Passing a threshold is not the same as being readable.

   What changed: nothing on this footer is 12px any more, nothing is below 75%
   white, and the labels that were 60% are now 82%. New floor is 8.05:1 at
   14px. The one deliberately quiet line, the copyright, is 14px at 75%, 7.4:1.
   These are computed, not estimated, and the harness re-reads them. */
.ft{background:var(--forest);color:rgba(255,255,255,.9);padding-block:var(--s6) var(--s4)}
.ft h2{color:#FFF;font-size:var(--t-big);max-width:11em}
.ft-lede{margin-top:var(--s3);max-width:25em;font-size:var(--body);color:rgba(255,255,255,.9)}
.ft-acts{display:flex;flex-wrap:wrap;gap:var(--s2);margin-top:var(--s3)}
.ft .btn{background:#FFF;border-color:#FFF;color:var(--forest)}
@media (hover:hover){.ft .btn:hover{background:var(--zest);border-color:var(--zest);color:var(--ink)}}
.ft .btn-2{background:transparent;border-color:rgba(255,255,255,.55);color:#FFF}
@media (hover:hover){.ft .btn-2:hover{background:#FFF;border-color:#FFF;color:var(--forest)}}
.ft-grid{display:grid;gap:var(--s4);grid-template-columns:repeat(3,minmax(0,1fr));margin-top:var(--s5)}
@media(max-width:860px){.ft-grid{grid-template-columns:1fr}}
/* Footer column headings were h4 directly under the section's h2, which skips
   h3 and gives a screen reader reading by headings a hierarchy with a hole in
   it. They are h3 now on all 13 pages; the h4 selector stays so nothing that
   still ships an h4 loses its styling. Purely structural, nothing moves. */
.ft h3.ft-col,.ft h4{margin:0 0 var(--s2);font-size:var(--meta);letter-spacing:.12em;text-transform:uppercase;
  color:rgba(255,255,255,.82);font-family:"Gabarito",sans-serif;font-weight:700}
.ft ul{list-style:none;margin:0;padding:0;font-size:var(--body)}
.ft li{display:flex;flex-wrap:wrap;align-items:baseline;gap:8px;min-height:44px}
.ft ul a{display:inline-flex;align-items:center;min-height:44px;color:#FFF}
.ft .who{color:rgba(255,255,255,.82);font-size:var(--meta)}
.ft p{color:rgba(255,255,255,.9)}
.ft-base{margin-top:var(--s5);display:flex;flex-wrap:wrap;gap:var(--s2);
  justify-content:space-between;font-size:var(--meta);color:rgba(255,255,255,.75)}

/* ===================== BUTTONS, DOCK, OVERLAY ===================== */
/* EVERY CONTROL IS A FILLED FIELD. No control on this site is drawn with a
   line. The secondary button and the add button were 2px outlines, 48 drawn
   borders between them on the landing page alone, and an outline is a line
   whatever it is doing. A quieter fill carries the same "this is secondary"
   signal, gives the colour some area, and removes the last rules on the page. */
.btn{display:inline-flex;align-items:center;justify-content:center;gap:var(--s1);min-height:52px;
  padding:0 var(--s3);border-radius:var(--r-ui);border:0;background:var(--ink);
  color:var(--beige);text-decoration:none;font-size:var(--meta);cursor:pointer;
  transition:background var(--fast) var(--settle),color var(--fast) var(--settle)}
.btn-2{background:rgba(55,54,54,.10);color:var(--ink)}
/* A primary button on the forest ground is white with forest text. That was
   only ever written per page — .pg-home .mast, .gym, .range, .ft, .ovl-foot —
   so 404.html's on-dark band kept the base charcoal fill and put #373636 on
   #003C20, a button you could barely find. This is the rule those five were
   each re-stating; they now agree with it rather than carrying it alone. */
.on-dark .btn{background:#FFF;border-color:#FFF;color:var(--forest)}
.on-dark .btn-2,.ft .btn-2{background:rgba(255,255,255,.18);color:#FFF}
.btn svg{width:17px;height:17px;flex:none}
.add{min-height:44px;padding:0 var(--s2);border-radius:var(--r-ui);border:0;
  background:rgba(55,54,54,.10);color:var(--ink);font-size:var(--fig);font-family:"Gabarito",sans-serif;
  font-weight:700;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;
  transition:background var(--fast) var(--settle),color var(--fast) var(--settle)}
.add[aria-pressed="true"]{background:var(--forest);color:#FFF}
@media (hover:hover){
  .btn:hover{background:var(--forest);color:#FFF}
  .btn-2:hover{background:var(--ink);color:var(--beige)}
  .on-dark .btn-2:hover,.ft .btn-2:hover{background:#FFF;color:var(--forest)}
  .add:hover{background:var(--ink);color:#FFF}
  .add[aria-pressed="true"]:hover{background:var(--forest);color:#FFF}
}
@media (hover:none){
  .btn:hover,.btn-2:hover,.add:hover{background:inherit}
  .btn:hover{background:var(--ink);color:var(--beige)}
  .btn-2:hover{background:rgba(55,54,54,.10);color:var(--ink)}
  .add:hover{background:rgba(55,54,54,.10);color:var(--ink)}
  .add[aria-pressed="true"]:hover{background:var(--forest);color:#FFF}
}
.add .tick2{display:none;width:13px;height:13px}
.add[aria-pressed="true"] .tick2{display:block}
.add[aria-pressed="true"] .plus{display:none}
.add .plus{width:13px;height:13px}
.dock{position:fixed;left:0;right:0;bottom:0;z-index:75;background:var(--forest);color:#FFF;
  transform:translateY(110%);transition:transform var(--slow) var(--settle)}
.dock[data-open="1"]{transform:none}
.dock-in{display:flex;align-items:center;gap:var(--s2);padding:var(--s2) 0;flex-wrap:wrap}
.dock .count{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:30px;
  border-radius:var(--r-ui);background:var(--zest);color:var(--forest);font-size:var(--meta);
  /* was --ink on --zest, computed 4.46:1, which misses AA at 14px bold by
     0.04. --forest on --zest computes 4.66:1 and passes, and it is a colour
     already in the palette rather than a new one mixed to clear a threshold. */
  font-family:"Gabarito",sans-serif;font-weight:700;padding:0 9px}
.dock .lbl{font-size:var(--fig);font-family:"Gabarito",sans-serif;font-weight:700;letter-spacing:.1em;
  text-transform:uppercase;color:rgba(255,255,255,.74);margin-right:auto}
/* The panel, Clear, Print and the send button all moved to enquiry.html in
   session AE, so their rules are gone rather than left here matching
   nothing. What the bar still needs is under THE BAR IS A ROUTE TO THE
   LIST, further down this file. */
/* ===================== THE MENU =====================
   THE COMPOSITION PROBLEM, WHICH IS THE REAL ONE.
   Two destinations in a full-screen green field left roughly ninety percent of
   it empty, so it read as broken rather than as spare. Centring the list only
   moved the hole. What fixes it is not centring but MEASURE: the list is held
   in a column the width of its own longest line, sat against the left edge of
   the type area, and the field is closed up around it. Empty space that has an
   edge reads as margin. Empty space with the list floating in the middle of it
   reads as a loading failure.

   At two entries the rows are large and the column is short, and the panel
   ends just below the last row rather than running to the bottom of the screen.
   At twelve, the same column keeps its width, the rows step down one size, and
   it splits into two columns above 900px. Nothing has to be re-laid out when
   pages ship: the list grows into the room already reserved for it. */
/* THIRD ATTEMPT, AND THE PREVIOUS TWO WERE BOTH FIGHTING THE WRONG THING.
   Attempt one put two entries in the corner of a full screen green field.
   Attempt two centred them in the same field. Attempt three set them in a
   column the width of their own longest line. All three still read as mostly
   empty, because THE FULL SCREEN FIELD IS WHAT CREATES THE EMPTINESS. Two
   destinations do not need a whole viewport and no arrangement of two things
   inside 1440x900 will ever fill it.

   So the surface is gone. The menu is a panel anchored to the top right,
   as wide as it needs and only as tall as its contents, and it slides in from
   the right edge it belongs to. At two entries it is about 560x480 and has no
   void in it at all. At twelve it grows downward and scrolls past 100vh, and
   the two column setting past six entries keeps it from becoming a long strip.
   Below 700px it takes the full width, which is what a sheet does on a phone. */
.ovl{position:fixed;top:0;right:0;z-index:95;background:var(--forest);color:#FFF;
  width:min(27rem,100vw);max-height:100vh;
  transform:translate3d(100%,0,0);
  transition:transform var(--fast) var(--settle),visibility var(--fast);
  overflow-y:auto;display:flex;flex-direction:column;visibility:hidden}
.ovl-scrim{position:fixed;inset:0;z-index:94;background:rgba(0,18,10,.82);
  opacity:0;visibility:hidden;transition:opacity var(--fast) var(--settle),visibility var(--fast)}
.ovl-scrim[data-open="1"]{opacity:1;visibility:visible}
@media(max-width:700px){.ovl{width:100vw}}
.ovl[data-open="1"]{transform:translate3d(0,0,0);visibility:visible;
  transition:transform var(--open) var(--settle),visibility 0s}
.ovl-top{display:flex;align-items:center;justify-content:flex-end;gap:var(--s2);
  min-height:88px;padding-block:14px;flex:none}
.closeX{min-width:52px;height:52px;border-radius:var(--r-ui);background:rgba(255,255,255,.18);
  cursor:pointer;display:inline-flex;align-items:center;justify-content:center;
  gap:10px;padding:0 var(--s2);color:#FFF;line-height:1;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--meta)}
.closeX .x{font-size:22px;line-height:1}
@media (hover:hover){.closeX:hover{background:#FFF;color:var(--forest)}}
@media (hover:none){.closeX:hover{background:rgba(255,255,255,.18);color:#FFF}}

/* the list is as wide as its longest line and no wider, and the panel stops
   under it. This is what makes two entries look deliberate. */
.ovl-body{flex:0 1 auto;display:block;padding-block:var(--s2) var(--s5)}
.ovl ul{list-style:none;margin:0;padding:0;width:max-content;max-width:100%;
  display:grid;gap:var(--s1)}
.ovl-count{display:block;font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--fig);letter-spacing:.14em;text-transform:uppercase;
  color:rgba(255,255,255,.75);margin-bottom:var(--s3)}
.ovl li{opacity:0;transform:translate3d(26px,0,0);
  transition:opacity var(--fast) var(--settle),transform var(--fast) var(--settle)}
.ovl[data-open="1"] li{opacity:1;transform:none;transition-delay:calc(var(--i) * 60ms + 120ms)}
.ovl li a,.ovl li .here{display:grid;grid-template-columns:2.75rem 1fr;gap:var(--s2);
  align-items:baseline;padding:10px 0;text-decoration:none;color:#FFF;
  font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:clamp(1.75rem,4.4vw,3.25rem);line-height:1.02;letter-spacing:-.028em;
  transition:color var(--fast) var(--settle)}
.ovl li .no{font-size:var(--meta);letter-spacing:.06em;color:rgba(255,255,255,.55);
  font-variant-numeric:tabular-nums}
.ovl li .here{color:var(--zest)}
.ovl li em{display:block;font-style:normal;font-size:var(--meta);color:rgba(255,255,255,.78);
  font-family:"Poppins",sans-serif;font-weight:400;letter-spacing:0;margin-top:4px;
  line-height:1.5;max-width:26em}
@media (hover:hover){.ovl li a:hover{color:var(--zest)}}
@media (hover:none){.ovl li a:hover{color:#FFF}}
/* at twelve entries: two columns of six, and the rows step down one size */

.ovl-foot{margin-top:auto;padding-block:var(--s3) var(--s4);display:flex;flex-wrap:wrap;
  gap:var(--s3);align-items:center;justify-content:space-between;flex:none}
.ovl-foot span{font-size:var(--meta);color:rgba(255,255,255,.78)}
.ovl-foot .btn{background:#FFF;color:var(--forest)}
/* REDUCED MOTION: the panel still arrives from the right, it just arrives
   instantly rather than travelling. A fade is not a substitute for direction,
   and turning motion off should not change what the thing is. */
@media(prefers-reduced-motion:reduce){
  .ovl{transition:visibility 0s}
  .ovl,.ovl[data-open="1"]{transition-duration:0s}
  .ovl li{opacity:1;transform:none;transition:none}
  .ovl[data-open="1"] li{transition-delay:0s}
}

/* ===================== MOTION ===================== */
@keyframes settleUp{from{opacity:0;transform:translate3d(0,26px,0)}to{opacity:1;transform:none}}
@keyframes riseIn{from{opacity:0;transform:translate3d(0,58px,0)}to{opacity:1;transform:none}}
@keyframes chipIn{from{opacity:0;transform:translate3d(0,14px,0)}to{opacity:1;transform:none}}
@keyframes fromLeft{from{opacity:0;transform:translate3d(-30px,0,0)}to{opacity:1;transform:none}}
@keyframes photoIn{from{opacity:0;transform:scale(1.06)}to{opacity:1;transform:none}}
/* buildUp animated clip-path until session AB. clip-path cannot be handed
   to the compositor, so all nine tiles using it repainted every frame for
   as long as they were on screen, on a scroll timeline that never ends.
   Rewritten onto transform and opacity, which composite. 34px because the
   ladder already runs 14 (chip) / 16 (row) / 26 (settle) / 58 (hero) and
   these blocks sit between a row and the hero. */
@keyframes buildUp{from{opacity:0;transform:translate3d(0,34px,0)}to{opacity:1;transform:none}}
/* driftPan replaced stripPan in session AA. The band is now a track of two
   identical sequences, so -50% to 0 is one seamless period and the strip
   TRAVELS LEFT TO RIGHT forever instead of panning 26% once per scroll.
   Time-based rather than scroll-driven on Ashyun's direct instruction, and
   it also ends the Firefox stillness session Z measured and left open. */
@keyframes driftPan{from{transform:translate3d(-50%,0,0)}to{transform:translate3d(0,0,0)}}
@keyframes rowIn{from{opacity:0;transform:translate3d(0,16px,0)}to{opacity:1;transform:none}}
/* ==========================================================================
   SHARED COMPONENTS ADDED IN SESSION 6, for the twelve page site.
   Everything above this line is lifted unchanged from the approved page one so
   that no page drifts from it. Everything below is new and shared by all pages.
   ========================================================================== */

/* ---------- THE MENU ----------
   It is a list of destinations. That is its whole job and nothing else lives
   in it. Earlier versions carried group headings over groups of one, a second
   Home that duplicated the one already in the bar, and a block of search,
   WhatsApp and office details, which turned an overlay into a page. Opened on
   a phone it read as empty, because two links floated in a large dark field
   with no mass anywhere. Ashyun has raised this five times.

   Rules it is built to, and they do not change as pages ship:
   - every page that exists is in here, and nothing that does not exist is
   - one tap from any page to any other page
   - no headings, no copy, no controls that are not a destination
   - the page you are on is marked and is not a link to itself
   - it groups only once there are enough entries that grouping helps someone
     find one faster. Two entries do not need a taxonomy over them.  */
/* THE OPENING. A menu comes from somewhere, so it arrives from the right and
   the entries follow it in. A cross fade is the default and reads as nothing
   happening. One curve, one direction, 0.45s in and 0.3s out, because leaving
   should be quicker than arriving. The green field wipes across first and the
   destinations rise into it 60ms apart, so the list assembles rather than
   appearing complete. prefers-reduced-motion drops all of it to opacity. */
.ovl{will-change:transform}
.ovl[data-open="1"] .ovl-top{animation:ovlTop var(--open) var(--settle) .06s both}
@keyframes ovlTop{from{opacity:0;transform:translate3d(0,-14px,0)}to{opacity:1;transform:none}}

/* THE COMPOSITION, second attempt, and the reasoning is different this time.
   The first attempt centred the list in the field. That moved the hole from
   the bottom to the top and the bottom, and it still read as ninety percent
   empty, because a block floating in the middle of a large colour has no edge
   to belong to.

   What it needs is not centring but a MEASURE. The list is set in a column as
   wide as its own longest line, aligned to the same left edge as every other
   page on the site, and the panel closes up under the last row instead of
   running to the foot of the screen. Empty space with an edge is margin.
   The same column takes twelve entries without any re-layout: the rows step
   down one size and split into two columns above 900px. */
.ovl-nav{width:100%}
.ovl-nav ul{list-style:none;margin:0;padding:0;columns:auto;
  width:max-content;max-width:100%;display:grid;gap:2px}
.ovl-nav li{border:0}
.ovl-nav li a,.ovl-nav li .here{display:grid;
  grid-template-columns:2.75rem minmax(0,1fr);gap:0 var(--s2);align-items:baseline;
  padding:var(--s2) 0;text-decoration:none;color:#FFF;
  font-family:"Gabarito",sans-serif;font-weight:700;letter-spacing:-.03em;
  font-size:clamp(2rem,5.6vw,3.5rem);line-height:1.02;
  transition:color var(--fast) var(--settle)}
.ovl-nav .num{font-size:var(--meta);letter-spacing:.1em;color:rgba(255,255,255,.55);
  font-variant-numeric:tabular-nums;grid-column:1}
.ovl-nav em{grid-column:2;display:block;font-style:normal;font-size:var(--meta);
  color:rgba(255,255,255,.78);font-family:"Poppins",sans-serif;font-weight:400;
  letter-spacing:0;margin-top:6px;line-height:1.5;max-width:26em}
/* the page you are standing on is stated, and is not a link back to itself */
.ovl-nav .here{color:var(--zest);cursor:default}
.ovl-nav .here .num{color:var(--zest)}
.ovl-nav .here .tag{grid-column:2;justify-self:start;font-family:"Poppins",sans-serif;
  font-weight:400;font-size:var(--meta);letter-spacing:0;color:#FFF;
  padding:3px 10px;border-radius:var(--r-ui);background:rgba(255,255,255,.2);margin-top:8px}
@media (hover:hover){.ovl-nav li a:hover{color:var(--zest)}}
@media (hover:none){.ovl-nav li a:hover{color:#FFF}}
/* twelve entries: two columns of six, one size down. No re-layout needed. */
/* twelve entries in a 34rem panel: one column, one size down, and the panel
   scrolls. Two columns inside a panel this wide would crowd, which is the
   fault the twelve-entry setting exists to avoid. */
/* AND THE ROWS WERE 36px TALL. Measured at 390 and at 1440, every row in
   this list rendered a 36px tap target on a 38px pitch, with 90px between
   groups. 36px is under every comfortable minimum on a phone and this is
   the list somebody uses to leave the page they are lost on, so it is the
   worst place on the site to be asking for a precise tap. The type did not
   need to change and the pitch barely does: 1.5rem at line-height 1.02 is
   24.5px of text, so the padding went from 6px to 11px and the row is
   46px on a 48px pitch. Twelve rows is 120px more, on a panel that already
   scrolled at 390 before this change, so nothing is newly hidden. */
.ovl-nav ul.many li a,.ovl-nav ul.many li .here{font-size:1.5rem;padding:11px 0}
.ovl-nav ul.many em{display:none}

/* ---------- search ----------
   The one thing the site does that the printed catalogue cannot. It is in the
   header of every page and it opens over the page rather than navigating away,
   so a buyer never loses their place. */
/* THE SEARCH CONTROL LOOKS LIKE A SEARCH FIELD, because it is the one thing
   this site does that the printed catalogue cannot, and Ashyun went looking
   for it and could not find it. It used to be a bordered ghost button reading
   "Search the range", third in a row of three identical bordered controls, and
   at 520px and below its label was hidden so it became an unlabelled circle.
   A person looking for search looks for a box with a magnifier and some grey
   prompt text in it. So that is what it is now: white field, dark magnifier,
   prompt text naming what can be typed into it. It never loses its label. */
/* IT IS AN INPUT NOW, NOT A BUTTON THAT LOOKS LIKE ONE.
   It was a <button> styled as a field. Two consequences, both of which the
   client felt: it could not filter as you type, because you had to click it
   first to reveal the real input inside a full-screen panel; and at 390px it
   was squeezed to 110px between the logo and the menu button with its prompt
   truncated to "Search pro...". Reported as not findable three times.
   There is one input on the page, it lives in the bar, it is always visible,
   and typing into it filters immediately. Nothing has to be opened first. */
.find-field{display:flex;align-items:center;gap:10px;min-height:52px;
  padding:0 14px;border-radius:var(--r-ui);background:#FFF;
  width:100%;max-width:560px;position:relative;z-index:97;
  transition:box-shadow var(--fast) var(--settle)}
.find-field .mag{width:19px;height:19px;flex:none;color:var(--ink)}
.find-field input{flex:1;min-width:0;border:0;background:none;outline:none;
  font-family:"Poppins",sans-serif;font-weight:400;font-size:var(--meta);
  color:var(--ink);min-height:52px;padding:0;-webkit-appearance:none;appearance:none}
.find-field input::-webkit-search-cancel-button{-webkit-appearance:none;appearance:none}
.find-field input::placeholder{color:var(--mute);opacity:1}
.find-field .k{flex:none;font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--fig);color:var(--mute);background:rgba(55,54,54,.08);
  padding:2px 8px;border-radius:var(--r-ui);line-height:1.5}
.find-field:focus-within{box-shadow:0 0 0 3px var(--zest)}
/* THE GREY LINE ROUND THE SEARCH FIELD IS GONE, and it was a line.
   `.on-light .find-field` carried box-shadow:0 0 0 2px rgba(55,54,54,.18),
   which draws a 2px grey ring in the RESTING state on every light-headed
   page. The standing rule on this site is that no drawn rule survives and
   that only focus rings may be strokes; a resting ring is a border with
   another name. The field is now told apart from its ground by having a
   ground of its own, which is the rule this project actually works to:
   colour needs area. The hover ring went with it for the same reason. */
/* SESSION AA. The hover rule is gone and it was a bug wearing a style's
   clothes. It survived from the era when this was a translucent button:
   hovering the now-solid-white field swapped it to rgba(255,255,255,.16),
   which over a dark masthead turns the input DARK the moment the pointer
   arrives. Click and the field went dark green under an orange ring; that
   state, plus the sheet arriving unasked, was the "weird white layer"
   report. The field now holds one ground per header and focus alone
   changes it, with the zest ring. */
/* The 7% black tint read as a muddy grey slab on the warm header rather than
   as a field you can type in. A white ground with a real edge says "input"
   at a glance, which is the whole job of the control. */
.on-light .find-field{background:#FFF;box-shadow:inset 0 0 0 2px rgba(55,54,54,.22)}
.on-light .find-field:hover{box-shadow:inset 0 0 0 2px rgba(55,54,54,.42)}
.on-light .find-field:focus-within{box-shadow:0 0 0 3px var(--zest)}
.on-light .menuBtn{background:rgba(55,54,54,.09);color:var(--ink)}
.on-light .logo img{filter:none}
@media(max-width:900px){.find-field{max-width:none}.find-field .k{display:none}}

/* The results sheet opens BELOW the bar, never over it, so the field you are
   typing into stays on screen and keeps focus. --hdr is measured from the bar
   by site.js on open and on resize, so no page carries a magic offset. While
   it is open the bar is pinned and the page behind is scroll locked, so
   nothing can travel underneath either one. */
.find{position:fixed;left:0;right:0;bottom:0;top:var(--hdr,88px);z-index:96;
  background:var(--beige);color:var(--ink);
  display:flex;flex-direction:column;visibility:hidden;opacity:0;
  transition:opacity var(--fast) var(--settle),visibility var(--fast)}
.find[data-open="1"]{visibility:visible;opacity:1}
body.searching .hdr{position:fixed;top:0;left:0;right:0}
body.searching{overflow:hidden}
/* SESSION AA. The sheet's header is the stat line now. The find-echo heading
   is gone from all thirteen pages: it was static text ("Everything they
   sell") doing the same job the stat line does with live numbers, and the
   open sheet was carrying four navigation systems at once. One header row,
   stat left, Close right, at every width. */
.find-top{display:flex;align-items:center;justify-content:space-between;gap:var(--s2);
  padding-top:var(--s3);flex:none}
.find-top .closeX{background:rgba(55,54,54,.1);color:var(--ink);flex:none}
.find-top .find-stat{flex:1;min-width:0;padding:0}
/* One row that scrolls sideways, at EVERY width now, not only under 900.
   MEASURED on the old sheet: twelve wrapped chips were 440px tall on an
   820px phone screen, so the first product sat at y=706. On desktop the two
   wrapped rows were the wall Ashyun called cluttered. The overflow is on
   this element, never on body or html. */
.find-chips{display:flex;gap:6px;flex-wrap:nowrap;padding-block:var(--s2);flex:none;
  overflow-x:auto;overflow-y:hidden;
  -webkit-overflow-scrolling:touch;scrollbar-width:none}
.find-chips::-webkit-scrollbar{display:none}
/* No scroll-snap here. Snap aligned the first chip's edge to the scrollport
   edge, which sits INSIDE the wrap padding, so the row rode 20 to 66px left
   of every other line on the sheet. Measured on both widths; the padding
   holds without it and a chips row does not need detents. */
.find-chips button{flex:none}
/* 40px was 4px under the 44px floor the footer already keeps. Same floor here. */
.find-chips button{min-height:44px;padding:0 14px;border-radius:var(--r-ui);
  background:rgba(255,255,255,.66);color:var(--mute);font-size:var(--fig);
  font-family:"Gabarito",sans-serif;font-weight:700;letter-spacing:.05em;cursor:pointer;
  transition:background var(--fast) var(--settle),color var(--fast) var(--settle)}
.find-chips button[aria-pressed="true"]{background:var(--ink);color:#FFF}
.find-stat{font-size:var(--meta);color:var(--mute);flex:none}
.find-stat b{color:var(--ink)}
.find-res{flex:1;overflow-y:auto;overscroll-behavior:contain;padding-bottom:var(--s5);
  display:grid;gap:2px;align-content:start}
/* SESSION AD. THE ROW TAKES A GROUND, NOT A VEIL, AND THAT WAS THE WHITE BLOCK.
   This was rgba(255,255,255,.62), which is the only white veil left on the site
   that lands on a warm ground. Over the sheet's own #F1E7DD it composites to
   #FAF6F2: four units off white, 1.13:1 against the ground it sits on. That is
   neither the ground nor a card, and on a phone it reads as one pale slab
   running the length of the sheet.

   The alpha was never wrong, it was measured against a different ground. The
   same .prow in the catalogue carries rgba(255,255,255,.5) over --g-cool and
   composites to #E1EEF1 at 1.21:1, which is a card. Run .62 over --g-cool and
   you get #E8F2F4 at 1.25:1. That is the pairing this number was built for, and
   the sheet is warm, so it never arrived.

   Warm cannot carry an upward step: white on warm is 1.22 and that is the
   ceiling the four grounds document. So the row stops improvising and takes one
   of the four. --g-paper on --g-warm is 1.22:1, the largest step available and
   the one the system already names. */
.find-res .prow{background:var(--g-paper)}
.find-empty{padding-block:var(--s4);max-width:27em}
.find-empty b{display:block;font-size:var(--t-mid);letter-spacing:-.026em}
.find-empty p{margin-top:var(--s2);font-size:var(--meta);color:var(--mute)}
@media(prefers-reduced-motion:reduce){.find{transition:none}}

/* ---------- the enquiry list, extended ---------- */
.dock .sum{font-size:var(--fig);color:rgba(255,255,255,.82);font-variant-numeric:tabular-nums;
  white-space:nowrap}
.dock .sum b{color:#FFF;font-size:var(--meta)}


/* ---------- shared page furniture ----------
   Every page after the first opens on a light field, so its header sits on
   light. Section rhythm is deliberately unequal: no two consecutive sections
   on any page use the same vertical padding. */
.pg-lede{font-size:var(--meta);color:var(--mute);max-width:24em}
/* THE CRUMB IS A BREADCRUMB NOW, WHICH IS WHAT IT ALWAYS LOOKED LIKE.
   It was a <p> holding "PPT EDU SUPPLIES . Storage and shelving": tracked
   uppercase, a middot between two levels, sitting directly above the h1.
   That is a breadcrumb in every respect except that neither half went
   anywhere. On a 23,011px page the one thing above the fold that offered
   the way out did nothing when it was tapped.

   The company segment is the link home. The page segment is not a link,
   because a breadcrumb never links to the page you are standing on, and
   it is marked aria-current so that is stated rather than implied.

   The separator moved from the markup into CSS. It is decoration, so it
   should not be in the content layer and should not be read out loud.
   The look is unchanged to the pixel: same size, same tracking, same
   caps, same weight, same colour. Only the link now carries the ink
   value and the page you are on keeps the muted one, so the half that
   does something is the half that looks live. No underline, because
   there are no lines anywhere on this site. */
.crumb{font-size:var(--fig);letter-spacing:.14em;text-transform:uppercase;color:var(--mute);
  font-family:"Gabarito",sans-serif;font-weight:700}
.crumb ol{list-style:none;margin:0;padding:0;display:flex;flex-wrap:wrap;align-items:baseline}
.crumb li{display:inline}
/* THE SEPARATOR TRAILS THE LEVEL IT FOLLOWS, IT DOES NOT LEAD THE NEXT ONE.
   As ::before on the second item it became the first thing on the wrapped
   line at 390, where "PPT EDU SUPPLIES" fills the width and the page name
   drops below it, and a lone middot at the start of a line reads as a bullet
   in a list. As ::after on the first it stays with the words it belongs to
   and the second line starts flush. */
.crumb li:not(:last-child)::after{content:"\00B7";margin-inline:.5em;opacity:.75}
/* The link is 17px of tracked 14px capitals, which is a small thing to hit
   on a phone, so the box it answers to is bigger than the box it draws.
   The extra room is taken upward, not evenly: measured at 390, the gap
   between the crumb and the h1 under it is 24px on storage and 34px on
   projects but exactly 0 on contact, tables and 404, so a symmetrical
   hit area would put the top edge of those headings inside a link and
   send somebody home for touching a word. 13px up and 5px down gives a
   35px target with nothing underneath it, and the negative margins hand
   the layout back unchanged. It is above the 24px floor rather than at
   the 44px comfortable size, and it is deliberately the fourth route
   home rather than the first: the bar, the header mark and the menu all
   carry a 44px one. Closing the gap under the crumb would buy the other
   9px and it would move type on three pages, which is not this pass. */
.crumb a{color:var(--ink);text-decoration:none;display:inline-block;
  padding-block:13px 9px;margin-block:-13px -9px; /* was 13/5 = 40px tall; 13/9 reaches 44 without moving a pixel */
  transition:color var(--fast) var(--settle)}
@media (hover:hover){.crumb a:hover{color:var(--zest-t)}}
@media (hover:none){.crumb a:hover{color:var(--ink)}}
.on-dark .crumb{color:rgba(255,255,255,.62)}
.on-dark .crumb a{color:#FFF}
@media (hover:hover){.on-dark .crumb a:hover{color:var(--zest)}}
@media (hover:none){.on-dark .crumb a:hover{color:#FFF}}
.qbox{background:var(--cream);padding:var(--s3);border-radius:var(--r-ui);max-width:31em}
.qbox b{display:block;font-size:var(--body);letter-spacing:-.02em}
.qbox p{margin-top:var(--s1);font-size:var(--meta);color:var(--mute)}

/* ---------- print ----------
   Procurement staff have to show a committee. This is the whole feature. */
@media print{
  .hdr,.ovl,.find,.dock,.btn,.add,.rot-ctl,.bctl,.menuBtn,.findBtn{display:none !important}
  body{background:#FFF;color:#000;font-size:11pt}
  *{animation:none !important;transition:none !important}
  /* TWO THINGS GET PRINTED AND IT DEPENDS ON WHICH ONE EXISTS.
     `main{display:none}` was unconditional, so Ctrl+P on a product page with
     an empty enquiry list produced a blank sheet with a footer on it. That is
     the commonest case: somebody is still choosing. With items on the list
     the list is the document; with none, the page is. site.js writes the
     count onto <body data-list>. */
  body[data-list="1"] .print-list{display:block !important}
  body[data-list="1"] main{display:none}
  body:not([data-list="1"]) .print-list{display:none !important}
  /* The page itself, printed. The footer is a forest green field on screen,
     which on paper is a solid dark block across the foot of every sheet, so
     it prints as text. Photographs are kept: a committee is being shown a
     product. Nothing is allowed to break across a page in the middle of a
     priced row. */
  .ft,.ft.on-dark{background:#FFF !important;color:#000 !important}
  .ft h2,.ft a,.ft p,.ft div,.ft span,.ft li{color:#000 !important}
  main section{break-inside:auto}
  .prow,.fr,.tc,.tr,.pc,.sp,.b-plate,.sr,li{break-inside:avoid}
  h1,h2,h3{break-after:avoid}
  img{max-width:100% !important}
}
.print-list{display:none;padding:24px}
.print-list h1{font-size:20pt;margin-bottom:4pt}
.print-list .pl-meta{font-size:9pt;color:#333;margin-bottom:16pt}
.print-list table{width:100%;border-collapse:collapse;font-size:10pt}
.print-list td{padding:6pt 8pt 6pt 0;vertical-align:top}
.print-list tr{page-break-inside:avoid}
.print-list .pl-tot{margin-top:14pt;font-size:12pt;font-weight:700}
.print-list .pl-note{margin-top:8pt;font-size:9pt;color:#333;max-width:36em}

/* ==========================================================================
   SESSION 6, ROUND 2. Three defects found by clicking and tapping the page
   rather than by reading it.

   1. THE WHATSAPP CONTROL WAS A WHITE PILL ON A BEIGE FIELD. Its rule is
      background #FFF with a #FFF border, which was written for the dark green
      masthead of page one. On page two the header sits on beige, so the border
      vanished and the fill barely separated from the page: it read as a pale
      rectangle laid over the page rather than as a button. It is the single
      most important control on the site.
   2. ITS HOVER STATE FAILED CONTRAST. Zest #FF741F carrying #373636 text
      computes about 3.4:1, and it is 14px bold, which needs 4.5:1. Zest is a
      background wash and a display colour in their catalogue, never small
      text, and it was being used as both here.
   3. THE HOVER STATE STUCK AFTER A TAP. No hover media guard anywhere, so on a
      phone the button stayed orange after being touched, which is what an
      overlay that will not clear looks like.
   ========================================================================== */
.hdr.on-light .wa{background:var(--forest);border-color:var(--forest);color:#FFF}
.hdr .wa:focus-visible{outline-offset:4px}

/* Hover is a pointer capability, not a screen width. Everything that changes on
   hover is now inside this guard, so nothing can stick to a touch. */
@media (hover:none){
  .btn:hover,.btn-2:hover,.add:hover,.menuBtn:hover,.findBtn:hover,.hdr .wa:hover,
  .prow:hover,.index a:hover,.ix-btn:hover,.mosaic figure:hover img,.ovl li a:hover,
  .rot-ctl button:hover,.find-chips button:hover{
    background:revert;border-color:revert;color:revert;transform:none}
  .hdr.on-light .wa:hover{background:var(--forest);border-color:var(--forest);color:#FFF}
  .hdr.on-dark .wa:hover{background:#FFF;border-color:#FFF;color:var(--forest)}
}
/* Zest carries display type and fields, never a label. The hover on the
   WhatsApp control now goes to the forest green with white on it, 12.60:1. */
@media (hover:hover){
  .hdr.on-dark .wa:hover{background:var(--forest);border-color:#FFF;color:#FFF}
  .hdr.on-light .wa:hover{background:var(--ink);border-color:var(--ink);color:#FFF}
}

/* The overlay bar carries the close control and nothing else. There is no
   second Home in it: the list below holds Home like every other destination,
   and the logo in the page header goes home from anywhere. */
/* search sits inside the menu as well as in the header, because the menu is
   one of the two places a person looks for it */
.ovl-top{justify-content:flex-end}
.ovl-top .ovl-title{margin-right:auto;font-size:var(--fig);letter-spacing:.14em;
  text-transform:uppercase;color:rgba(255,255,255,.5);
  font-family:"Gabarito",sans-serif;font-weight:700}

/* ---------- menu group headings ----------
   The menu reads as empty three times running, and each fix rearranged the
   contents. The recorded lesson is that the problem is the SIZE OF THE
   SURFACE, not the arrangement, so this time the panel got smaller: 34rem
   to 27rem. At 34rem the entries ended around x=390 in a 544px panel and the
   remaining third was bare green, which is what "empty" meant.

   The headings are not links and are not counted as destinations. They are
   marked role="presentation" so a screen reader reads a flat list of real
   destinations rather than announcing four list items that go nowhere. */
.ovl-nav .mg{margin-top:var(--s3)}
.ovl-nav .mg:first-child{margin-top:0}
.ovl-nav .mg span{display:block;font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--fig);letter-spacing:.14em;text-transform:uppercase;
  color:rgba(255,255,255,.62);padding-bottom:4px}
.ovl-nav .mi a,.ovl-nav .mi .here{padding:7px 0}

/* The compact list gets a tighter number column. At 27rem the default
   2.75rem column plus the gutter left 240px for the label and "Therapy and
   special needs" wrapped at 1440 while fitting at 390, because the page
   gutter is wider on desktop. Measured across 320/360/390/440/768/1024/1440
   after the change: no entry wraps at any width. */
.ovl-nav ul.many li a,.ovl-nav ul.many li .here{grid-template-columns:2.1rem minmax(0,1fr);
  gap:0 12px}
.ovl .wrap{padding-inline:clamp(20px,4.4vw,30px)}


/* ##################################################################
   MERGED PAGE STYLES
   Every page-local <style> block, folded in here in page order and
   each one scoped to its own body class. Source order inside each
   block is unchanged, so the cascade is exactly what it was when the
   block sat in the page head after the link to this file.
   ################################################################## */

/* ==================================================================
   index.html  ->  body.pg-home
   Was a <style> block in the page. Scoped to its own page class so a
   rule written for this page cannot reach another page's markup.
   ================================================================== */
/* Page one only. Tokens, header, product row, footer, buttons, enquiry list,
   menu, search and print all live in css/ppt.css and are shared by twelve pages. */

/* ===================== 01 MASTHEAD =====================
   The company stated the way their spec sheets state a product. The widest
   thing they make, at 420 cm, rendered as the largest object on the page and
   allowed off the right edge, with its figures placed around it rather than
   stacked beneath it. Nothing is centred. */
/* No padding-top compensating for a floating header any more. The header is
   in normal flow above this section and reserves its own space, so this
   section starts where it starts. */
.pg-home .mast{position:relative;background:var(--forest);color:#FFF;overflow:hidden;
  min-height:min(80svh,700px);display:grid;align-items:center}
.pg-home .mast-in{display:grid;grid-template-columns:minmax(0,46fr) minmax(0,54fr);gap:var(--s4);
  align-items:end;padding-bottom:var(--s5)}
@media(max-width:900px){.pg-home .mast{min-height:auto}
  .pg-home .mast-in{grid-template-columns:1fr;gap:var(--s3);padding-bottom:var(--s4);padding-top:var(--s3)}}
.pg-home .mast h1{font-size:clamp(2.5rem,4.5vw,4rem);color:#FFF;max-width:13em}
.pg-home .mast .lede{margin-top:var(--s3);max-width:38ch;color:rgba(255,255,255,.82);font-size:var(--meta)}
.pg-home .mast .acts{display:flex;flex-wrap:wrap;gap:var(--s2);margin-top:var(--s3)}
.pg-home .mast .btn{background:#FFF;border-color:#FFF;color:var(--forest)}
@media (hover:hover){.pg-home .mast .btn:hover{background:var(--zest);border-color:var(--zest);color:var(--ink)}}
.pg-home .mast .btn-2{background:transparent;border-color:rgba(255,255,255,.6);color:#FFF}
@media (hover:hover){.pg-home .mast .btn-2:hover{background:#FFF;border-color:#FFF;color:var(--forest)}}
/* the object, off the right edge */
.pg-home .mast-obj{display:flex;justify-content:flex-end;margin-right:calc(var(--room) * -1)}
.pg-home .mast-fr{position:relative;display:inline-block;max-width:515px;width:100%}
.pg-home .mast-obj img{width:100%;height:auto;display:block}
@media(max-width:900px){.pg-home .mast-obj img{max-height:44svh}}
/* THE FLOATING DIMENSION FIGURES ARE GONE.
   "230 cm high" and "195 cm across" were set beside the object, hanging in the
   green field with nothing tying them to the edge they described. Ashyun read
   them twice and could not tell what they were: "what's the thing of the 230
   centimetre and 195 centimetre, I don't know what that is." A device that
   fails a reader twice has failed, and a third variation of the same idea is
   not an answer. The size now sits in the caption, in a sentence, where it
   cannot be mistaken for anything else. Sizes are the subject of page two,
   where they are drawn to scale and the page says so. */
.pg-home .fig-n{display:block;margin-top:var(--s2);font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--meta);letter-spacing:-.01em;color:#FFF}
.pg-home .fig-n i{display:block;font-style:normal;font-weight:400;font-family:"Poppins",sans-serif;
  font-size:var(--fig);color:rgba(255,255,255,.72);letter-spacing:0;margin-top:2px}
@media(max-width:900px){.pg-home .mast-obj{margin-right:0}}

/* ===================== 02 THE INDEX =====================
   REBUILT. The previous version put six figures on a twelve column grid with
   deliberately unequal spans, to stop it reading as a metrics dashboard. It
   stopped reading as a dashboard and started reading as a fault: six numerals
   marooned in about 490px of empty beige, three quarters of it dead, which is
   what Ashyun saw as "a massive dent". Breaking the even row was right.
   Replacing it with sprawl was not, and the fix for sprawl is not a different
   sprawl. Stated target for this rebuild, so it can be checked afterwards:
   taken in at one glance, no void larger than a line of type, the largest
   figure clearly dominant, every figure a route to the thing that proves it.

   What it is now: one dominant figure, and beside it a contents list. Their
   Education Catalogue is a price list, a dense column of code, name, size and
   figure with nothing floating; this is that column, indexing the site instead
   of the range. The five entries are a list, not a row, so they carry no
   implication of being six equal metrics. */
.pg-home .index{background:var(--beige);padding-block:var(--s4) var(--s5)}
.pg-home .ix{display:grid;grid-template-columns:minmax(0,42fr) minmax(0,58fr);
  gap:var(--s4) var(--s6);align-items:start}
@media(max-width:900px){.pg-home .ix{grid-template-columns:1fr;gap:var(--s3)}}

/* the lead figure. It is the count of priced products and it opens search,
   which is the only thing on the site the printed book cannot do. */
.pg-home .ix-lead{display:block;text-align:left;cursor:pointer;padding:0;width:100%;
  animation:chipIn var(--fast) var(--settle) both;animation-delay:.44s}
.pg-home .ix-lead b{display:block;font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:clamp(4.5rem,11vw,9rem);line-height:.82;letter-spacing:-.055em;color:var(--ink);
  font-variant-numeric:tabular-nums;transition:color var(--fast) var(--settle)}
/* the caption is set at body size, not at label size. It carries the one
   sentence that tells a buyer what this site is for, and it also brings the
   left column up to roughly the height of the list beside it, which is what
   closes the hole that was under it. */
.pg-home .ix-lead span{display:block;margin-top:var(--s2);font-size:var(--body);color:var(--mute);
  max-width:22em;line-height:1.5}
.pg-home .ix-lead span i{font-style:normal;color:var(--ink);font-family:"Gabarito",sans-serif;
  font-weight:700}
@media (hover:hover){.pg-home .ix-lead:hover b{color:var(--zest-t)}
  .pg-home .ix-lead:hover span i{color:var(--zest-t)}}

/* the contents list. Fixed numeral column, so the five figures line up as a
   column of numbers the way a price list does, and the labels start on one
   axis. Nothing here can leave a hole, because it is a list. */
.pg-home .ix-list{list-style:none;margin:0;padding:0}
.pg-home .ix-list li{animation:chipIn var(--fast) var(--settle) both}
.pg-home .ix-list li:nth-child(1){animation-delay:.52s}
.pg-home .ix-list li:nth-child(2){animation-delay:.58s}
.pg-home .ix-list li:nth-child(3){animation-delay:.64s}
.pg-home .ix-list li:nth-child(4){animation-delay:.70s}
.pg-home .ix-list li:nth-child(5){animation-delay:.76s}
/* The only entrance animation on the site that had no reduced-motion reset,
   found by comparing against the fourteen blocks that do have one. It matters
   more here than most: chipIn starts at opacity 0 with backwards fill, so
   until its delay elapses these five figures are not dimmed, they are absent.
   They are the page's whole credibility claim and the fifth one names two
   hospitals. Anyone who has asked the OS to stop moving things was being made
   to wait 0.76s plus 0.3s to read them, and any hitch that postpones the
   animation postpones the text with it. Same shape as the .pcard-f reset. */
@media(prefers-reduced-motion:reduce){
  .pg-home .ix-list li{animation:none;opacity:1;transform:none}
}
.pg-home .ix-list a{display:grid;grid-template-columns:2.7em minmax(0,1fr);gap:var(--s3);
  align-items:baseline;text-decoration:none;color:var(--ink);
  padding-block:var(--s2);min-height:52px;
  transition:color var(--fast) var(--settle)}
.pg-home .ix-list b{font-family:"Gabarito",sans-serif;font-weight:700;font-size:2rem;line-height:1;
  letter-spacing:-.04em;font-variant-numeric:tabular-nums;text-align:right;
  transition:color var(--fast) var(--settle)}
.pg-home .ix-list span{font-size:var(--meta);color:var(--mute);line-height:1.4;
  transition:transform var(--fast) var(--settle),color var(--fast) var(--settle)}
/* no scale on anything, and nothing moves except the label, which shifts the
   width of one word toward its figure */
@media (hover:hover){
  .pg-home .ix-list a:hover b{color:var(--zest-t)}
  .pg-home .ix-list a:hover span{color:var(--ink);transform:translateX(6px)}
}
@media(max-width:560px){
  .pg-home .ix-list a{grid-template-columns:2.2em minmax(0,1fr);gap:var(--s2);padding-block:10px}
  .pg-home .ix-list b{font-size:var(--t-mid)}
}
.pg-home .index span{display:block;margin-top:6px;font-size:var(--fig);color:var(--mute);line-height:1.35}
@media (hover:hover){.pg-home .index a:hover b, .pg-home .ix-btn:hover b{color:var(--zest-t)}}

/* ===================== 03 WHAT IS ACTUALLY PRINTED ON THE SHEETS ==========
   This replaced an icon row reading Child safe / Eco friendly / Durable / Easy
   clean. Every one of those four is printed in their catalogue and every one of
   them could sit on any manufacturer's website, which is the whole problem: an
   icon with a two word benefit under it is the feature strip shape and the eye
   knows it instantly. What sits here instead is the specific fact behind each
   claim, in the catalogue's own spelling, counted across all 22 spec sheets.
   Deliberately not four equal cells: the age is the line a kindergarten
   actually buys on, so it is set large and the other three are small beside it. */
.pg-home .spec-say{background:var(--white);padding-block:var(--s4) var(--s7)}
.pg-home .spec-in{display:grid;grid-template-columns:minmax(0,53fr) minmax(0,47fr);gap:var(--s5) var(--s4);
  align-items:start}
.pg-home .spec-in>div:last-child{padding-top:var(--s5)}
@media(max-width:900px){.pg-home .spec-in{grid-template-columns:1fr;gap:var(--s3)}
  .pg-home .spec-in>div:last-child{padding-top:0}}
.pg-home .spec-say h2{font-size:var(--t-big);max-width:12em}
.pg-home .spec-say .n22{display:block;font-size:clamp(4rem,8.5vw,7.5rem);line-height:.82;letter-spacing:-.05em;
  color:var(--forest);font-family:"Gabarito",sans-serif;font-weight:700;font-variant-numeric:tabular-nums}
.pg-home .spec-say .n22 + p{margin-top:var(--s2);font-size:var(--meta);color:var(--mute);max-width:34ch}
.pg-home .printed{list-style:none;margin:var(--s3) 0 0;padding:0;display:grid;gap:var(--s3)}
.pg-home .printed li{display:grid;grid-template-columns:minmax(0,1fr);gap:2px}
.pg-home .printed q{quotes:none;font-family:"Gabarito",sans-serif;font-weight:700;letter-spacing:-.02em;
  font-size:var(--t-mid);line-height:1;color:var(--ink)}
.pg-home .printed i{font-style:normal;font-size:var(--fig);color:var(--mute)}
/* the age is what a kindergarten buys on, so it is not the same size as the rest */
.pg-home .printed li.lead{margin-bottom:var(--s2)}
.pg-home .printed li.lead q{font-size:clamp(2.5rem,5.4vw,4rem);letter-spacing:-.035em;color:var(--forest)}
.pg-home .printed li.lead i{font-size:var(--meta)}
.pg-home .spec-note{margin-top:var(--s4);font-size:var(--meta);color:var(--mute);max-width:44ch}
.pg-home .spec-note b{color:var(--ink)}

/* ===================== 04 WHAT THEY BUILD =====================
   Not a four across grid. One frame at full size with its figures placed
   around it, and the rest small and unequal beside it. */
.pg-home .build-sec{background:var(--g-play);padding-block:var(--s6) var(--s7);position:relative}
.pg-home .build-head{display:flex;flex-wrap:wrap;align-items:flex-end;justify-content:space-between;
  gap:var(--s3);margin-bottom:var(--s4)}
.pg-home .build-head h2{font-size:var(--t-big);max-width:11em}
.pg-home .build-head p{max-width:40ch;font-size:var(--meta);color:var(--mute)}
.pg-home .lead-prod{display:grid;grid-template-columns:minmax(0,62fr) minmax(0,38fr);
  gap:var(--s4);align-items:end}
@media(max-width:860px){.pg-home .lead-prod{grid-template-columns:1fr}}
.pg-home .lead-prod .shot{position:relative}
.pg-home .lead-prod .shot img{width:100%;max-width:678px;height:auto}
.pg-home .lead-prod .meta{display:flex;flex-wrap:wrap;gap:var(--s3);align-items:baseline;margin-top:var(--s2)}
.pg-home .lead-prod .meta b{font-size:var(--t-mid);letter-spacing:-.026em}
/* THE CHILD COMBINATOR IS THE WHOLE POINT OF THIS RULE, session AF.
   It was a descendant selector, and the Add button's label is a <span>
   sitting in this same box, so the dimension colour reached inside a
   shared component and repainted it. --mute on the button's --forest
   ground measured 1.78:1, against 12.6:1 for the same control in every
   other section of the site. --mute already carries the instruction
   "never used on a dark ground"; a descendant selector broke it without
   anyone writing --mute on a dark ground anywhere.
   Same fault, same fix, in .trio below. */
.pg-home .lead-prod .meta > span{font-size:var(--meta);color:var(--mute);font-variant-numeric:tabular-nums}
/* the three others, deliberately unequal */
.pg-home .trio{display:grid;gap:var(--s3);align-content:end}
.pg-home .trio article{display:grid;grid-template-columns:auto 1fr;gap:var(--s2);align-items:end}
.pg-home .trio img{width:100%;height:auto}
.pg-home .trio article:nth-child(1) img{max-width:150px}
.pg-home .trio article:nth-child(2) img{max-width:110px}
.pg-home .trio article:nth-child(3) img{max-width:128px}
.pg-home .trio b{display:block;font-size:var(--body);line-height:1.15;letter-spacing:-.018em}
.pg-home .trio div > span{display:block;font-size:var(--fig);color:var(--mute);font-variant-numeric:tabular-nums;margin-top:3px}
.pg-home .trio .add, .pg-home .lead-prod .add{margin-top:var(--s2)}

/* ===================== 05 FOUR VIEWS ===================== */
.pg-home .views{background:var(--g-spec);padding-block:var(--s5) var(--s4);position:relative}
.pg-home .views-in{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,34ch);gap:var(--s5);align-items:center}
@media(max-width:900px){.pg-home .views-in{grid-template-columns:1fr;gap:var(--s3)}}
.pg-home .views h2{font-size:var(--t-big);max-width:10em}
.pg-home .views p{margin-top:var(--s3);font-size:var(--meta);color:var(--mute)}
.pg-home .rot-stage{position:relative;display:grid;place-items:center;min-height:min(52vh,420px);
  touch-action:pan-y;cursor:ew-resize;background:var(--white);
  padding:var(--s4) var(--s3);border-radius:var(--r-ui)}
.pg-home .rot-stage img{max-height:min(50vh,400px);width:auto;grid-area:1/1;opacity:0;
  transition:opacity var(--fast) var(--settle)}
.pg-home .rot-stage img[data-on="1"]{opacity:1}
.pg-home .rot-meta{display:flex;flex-wrap:wrap;gap:var(--s3);align-items:baseline;margin-top:var(--s2)}
.pg-home .rot-meta b{font-size:var(--fig);letter-spacing:.13em;text-transform:uppercase;color:var(--mute)}
.pg-home .rot-meta span{font-size:var(--t-mid);letter-spacing:-.026em;font-variant-numeric:tabular-nums;
  font-family:"Gabarito",sans-serif;font-weight:700}
.pg-home .rot-ctl{display:flex;gap:6px;margin-top:var(--s3);flex-wrap:wrap}
.pg-home .rot-ctl button{min-height:44px;padding:0 var(--s2);border-radius:var(--r-ui);background:rgba(255,255,255,.55);
  font-size:var(--fig);font-family:"Gabarito",sans-serif;font-weight:700;letter-spacing:.06em;
  text-transform:uppercase;color:var(--mute);cursor:pointer;
  transition:background var(--fast) var(--settle),color var(--fast) var(--settle)}
.pg-home .rot-ctl button[aria-pressed="true"]{background:var(--ink);color:#FFF}

/* ===================== 06 GYM, the band that works =====================
   Kept, because it is the one section the client has praised and never
   criticised. What it does that the rest did not: the whole field is one
   colour so the colour has area, the photograph is a large real room rather
   than a cut-out, the type sits directly on the colour with no panel under it,
   and it carries exactly one idea. The rest of this page is built to that. */
.pg-home .gym{background:var(--forest);color:rgba(255,255,255,.88);padding-block:var(--s6);overflow:hidden}
.pg-home .gym-in{display:grid;gap:var(--s5);grid-template-columns:minmax(0,1.45fr) minmax(0,1fr);align-items:center}
@media(max-width:980px){.pg-home .gym-in{grid-template-columns:1fr;gap:var(--s3)}.pg-home .gym figure{order:-1}}
.pg-home .gym figure{margin:0}
.pg-home .gym figure img{width:100%;height:auto}
.pg-home .gym h2{color:#FFF;font-size:var(--t-big);max-width:12em}
.pg-home .gym p{color:rgba(255,255,255,.8);margin-top:var(--s3);font-size:var(--meta)}
.pg-home .conds{list-style:none;margin:var(--s3) 0 0;padding:0;display:flex;flex-wrap:wrap;gap:0 var(--s2);
  font-size:var(--meta);color:#FFF}
/* A 1px vertical hairline used to sit between these conditions. Same rule,
   same removal: the gap does the separating. */
.pg-home .conds{gap:0 var(--s3)}
.pg-home .conds li{position:relative}
.pg-home .gym .btn{background:#FFF;border-color:#FFF;color:var(--forest);margin-top:var(--s3)}
@media (hover:hover){.pg-home .gym .btn:hover{background:var(--zest);border-color:var(--zest);color:var(--ink)}}

/* ===================== 08 THE RANGE ===================== */
.pg-home .range{background:var(--forest);color:#FFF;overflow:hidden;padding-block:var(--s5) 0}
.pg-home .range-in{display:grid;grid-template-columns:minmax(0,44ch) minmax(0,1fr);gap:var(--s4);align-items:end}
@media(max-width:860px){.pg-home .range-in{grid-template-columns:1fr}}
.pg-home .range h2{color:#FFF;font-size:var(--t-big);max-width:11em}
.pg-home .range p{color:rgba(255,255,255,.82);margin-top:var(--s3);font-size:var(--meta)}
.pg-home .range .btn{background:#FFF;border-color:#FFF;color:var(--forest);margin-top:var(--s3)}
@media (hover:hover){.pg-home .range .btn:hover{background:var(--zest);border-color:var(--zest);color:var(--ink)}}
/* THE DRIFT, session AA. 54 tappable products on one shared ground line,
   travelling left to right at a walking pace. One duration variable,
   --drift-d, set by the page from the measured track width so the SPEED is
   constant (about 28px a second) at every viewport, rather than the loop
   time. Pauses under a pointer, under a held finger (data-paused, set by
   the page script), and while any tile holds keyboard focus, because a
   moving tap target is a missed tap target. */
.pg-home .drift{margin-top:var(--s4);overflow:hidden}
/* The seam gap lives INSIDE each sequence as padding-right, never on the
   track: with a track gap, half the track is one period PLUS half a gap,
   and the loop visibly hops that half gap every cycle. Measured before it
   shipped, for once. */
.pg-home .drift-track{display:flex;align-items:flex-end;gap:0;
  width:max-content;will-change:transform;transform:translate3d(-50%,0,0);
  animation:driftPan var(--drift-d,240s) linear infinite}
.pg-home .drift-seq{display:flex;align-items:flex-end;gap:clamp(28px,3.2vw,46px);
  padding-right:clamp(28px,3.2vw,46px)}
/* The hover pause sits inside the hover guard: on touch, tapping a tile
   would otherwise stick the hover state and leave the parade standing
   after the visitor came back. Touch pauses through data-paused instead. */
@media (hover:hover){
  .pg-home .drift:hover .drift-track{animation-play-state:paused}
}
.pg-home .drift:focus-within .drift-track,
.pg-home .drift[data-paused="1"] .drift-track{animation-play-state:paused}
.pg-home .drift-i{flex:none;display:block;text-decoration:none;
  padding-inline:6px;margin-inline:-6px; /* the thinnest cutout tapped at 36px wide; +12 invisible hit width, the 28px seq gap absorbs it */
  transition:transform var(--fast) var(--settle)}
.pg-home .drift-i img{height:clamp(96px,11.5vw,150px);width:auto;display:block}
@media (hover:hover){.pg-home .drift-i:hover{transform:translateY(-7px)}}
.pg-home .drift-i:focus-visible{transform:translateY(-7px);outline:3px solid var(--zest);outline-offset:4px}
/* Reduced motion: the parade stands still and becomes a hand scroller, and
   the duplicate sequence leaves so nothing exists twice. */
@media(prefers-reduced-motion:reduce){
  .pg-home .drift{overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
  .pg-home .drift::-webkit-scrollbar{display:none}
  .pg-home .drift-track{animation:none;transform:none}
  .pg-home .drift-seq[aria-hidden="true"]{display:none}
}
.pg-home .strip-note{padding-block:var(--s2) var(--s5);font-size:var(--fig);color:rgba(255,255,255,.74)}

/* ===================== 09 THE PROCESS ===================== */
.pg-home .proc{background:var(--g-paper);padding-block:var(--s5) var(--s3)}
.pg-home .proc-head{max-width:34rem;margin-bottom:var(--s3)}
.pg-home .proc-head h2{font-size:var(--t-big)}
.pg-home .proc-head p{margin-top:var(--s3);font-size:var(--meta);color:var(--mute)}
.pg-home .build{position:relative}
.pg-home [data-js="1"] .build{height:400vh}
.pg-home .stick{position:relative}
/* top:0 parked this panel under the persistent bar, so the four step numbers
   and the orange progress track were sliced in half by it the whole way down
   the section. Sticking below the bar instead of behind it is the fix. */
.pg-home [data-js="1"] .stick{position:sticky;top:var(--secnav-h,56px);
  min-height:100vh;display:flex;align-items:center;padding-block:var(--s4)}
.pg-home .tl{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.05fr);gap:var(--s5);
  align-items:center;width:100%}
@media(max-width:940px){.pg-home .tl{grid-template-columns:1fr;gap:var(--s3)}
  .pg-home [data-js="1"] .stick{min-height:100vh;padding-block:var(--s3)}
  .pg-home [data-js="1"] .tl-steps{min-height:280px}}
.pg-home .tl-track{display:flex;gap:4px;margin-bottom:var(--s4)}
.pg-home .tl-track div{flex:1;height:4px;background:rgba(55,54,54,.14);position:relative}
.pg-home .tl-track div i{position:absolute;inset:0;background:var(--zest);transform:scaleX(0);
  transform-origin:left;transition:transform var(--fast) linear}
.pg-home .tl-track div b{position:absolute;top:10px;left:0;font-size:var(--fig);color:var(--mute);
  font-variant-numeric:tabular-nums}
.pg-home .tl-track div[data-on="1"] b{color:var(--zest-t)}
.pg-home .tl-steps{position:relative;margin-top:var(--s3)}
.pg-home [data-js="1"] .tl-steps{min-height:330px}
.pg-home [data-js="1"] .tl-steps article{position:absolute;inset:0;opacity:0;pointer-events:none;
  transform:translate3d(0,14px,0);
  transition:opacity var(--fast) var(--settle),transform var(--fast) var(--settle)}
.pg-home [data-js="1"] .tl-steps article[data-on="1"]{opacity:1;transform:none;pointer-events:auto}
.pg-home .tl-steps article+article{margin-top:var(--s4)}
.pg-home [data-js="1"] .tl-steps article+article{margin-top:0}
.pg-home .tl-steps .n{display:block;font-size:var(--fig);letter-spacing:.14em;text-transform:uppercase;
  color:var(--zest-t);font-family:"Gabarito",sans-serif;font-weight:700}
.pg-home .tl-steps h3{margin-top:var(--s1);font-size:clamp(1.25rem,2vw,1.75rem);line-height:1.08;letter-spacing:-.022em}
.pg-home .tl-steps p{margin-top:var(--s3);color:var(--mute);max-width:48ch;font-size:var(--meta)}
.pg-home .tl-steps p b{color:var(--ink)}
.pg-home .bstage{position:relative;width:100%;aspect-ratio:1.5;background:var(--white);overflow:hidden}
.pg-home .bstage svg.plan{position:absolute;inset:0;width:100%;height:100%;overflow:visible;display:block}
.pg-home .bstage img{position:absolute;left:50%;top:47%;max-width:62%;max-height:72%;width:auto}
.pg-home .bl{animation-duration:1s;animation-timing-function:linear;animation-fill-mode:both;
    animation-play-state:paused;animation-delay:calc(var(--t,0) * -1s)}
.pg-home .k-room{fill:none;stroke:var(--taupe);stroke-width:2;stroke-dasharray:1810;animation-name:kRoom}
@keyframes kRoom{0%{stroke-dashoffset:1810}7%{stroke-dashoffset:0}100%{stroke-dashoffset:0}}
.pg-home .k-dim{stroke:var(--ink);stroke-width:1.5;fill:none;transform-origin:310px 358px;animation-name:kDim}
@keyframes kDim{0%,5%{transform:scaleX(0)}13%{transform:scaleX(1)}100%{transform:scaleX(1)}}
.pg-home .k-tick{stroke:var(--ink);stroke-width:1.5;animation-name:kFig}
.pg-home .k-fig{font-family:"Gabarito",sans-serif;font-weight:700;font-size:17px;fill:var(--ink);
  text-anchor:middle;animation-name:kFig}
@keyframes kFig{0%,11%{opacity:0}16%{opacity:1}100%{opacity:1}}
.pg-home .k-foot{fill:none;stroke:var(--zest);stroke-width:2;stroke-dasharray:9 7;animation-name:kFoot}
@keyframes kFoot{0%,23%{opacity:0}29%{opacity:1}100%{opacity:1}}
.pg-home .k-part{fill:var(--play);stroke:var(--ink);stroke-width:1.4}
.pg-home .k-a{animation-name:kA}.pg-home .k-b{animation-name:kB}.pg-home .k-c{animation-name:kC}.pg-home .k-d{animation-name:kD}
@keyframes kA{0%,36%{opacity:0;transform:translate(-300px,-110px)}42%{opacity:1}
  50%{opacity:1;transform:translate(0,0)}100%{opacity:1;transform:translate(0,0)}}
@keyframes kB{0%,39%{opacity:0;transform:translate(320px,-90px)}45%{opacity:1}
  53%{opacity:1;transform:translate(0,0)}100%{opacity:1;transform:translate(0,0)}}
@keyframes kC{0%,42%{opacity:0;transform:translate(-280px,140px)}48%{opacity:1}
  56%{opacity:1;transform:translate(0,0)}100%{opacity:1;transform:translate(0,0)}}
@keyframes kD{0%,45%{opacity:0;transform:translate(300px,150px)}51%{opacity:1}
  59%{opacity:1;transform:translate(0,0)}100%{opacity:1;transform:translate(0,0)}}
.pg-home .k-plan{animation-name:kPlan}
@keyframes kPlan{0%,78%{opacity:1}90%{opacity:0}100%{opacity:0}}
.pg-home .k-built{animation-name:kBuilt}
@keyframes kBuilt{0%,78%{opacity:0;transform:translate(-50%,-44%)}
  90%{opacity:1;transform:translate(-50%,-50%)}100%{opacity:1;transform:translate(-50%,-50%)}}
.pg-home .k-ok{fill:none;stroke:var(--forest);stroke-width:3.6;stroke-linecap:butt;stroke-linejoin:round;
  stroke-dasharray:70;animation-name:kOk}
/* kOk drew a tick with stroke-dashoffset, which cannot composite. The tick
   is 3.6px of stroke in a 40px box, so the draw was never legible at that
   size anyway. It now arrives on opacity and scale, which do composite. */
@keyframes kOk{0%,88%{opacity:0;transform:scale(.72)}97%{opacity:1;transform:none}100%{opacity:1;transform:none}}
.pg-home .bctl{margin-top:var(--s3);display:flex;align-items:center;gap:var(--s2)}
.pg-home .bctl input{flex:1;-webkit-appearance:none;appearance:none;height:46px;background:transparent;cursor:grab}
.pg-home .bctl input:active{cursor:grabbing}
.pg-home .bctl input::-webkit-slider-runnable-track{height:4px;background:rgba(55,54,54,.16)}
.pg-home .bctl input::-moz-range-track{height:4px;background:rgba(55,54,54,.16)}
.pg-home .bctl input::-webkit-slider-thumb{-webkit-appearance:none;width:28px;height:28px;margin-top:-12px;
  background:var(--ink);border:0;border-radius:var(--r-ui)}
.pg-home .bctl input::-moz-range-thumb{width:28px;height:28px;background:var(--ink);border:0;border-radius:var(--r-ui)}
.pg-home .bctl input:active::-webkit-slider-thumb{background:var(--zest)}
.pg-home .bhint{margin-top:var(--s2);font-size:var(--fig);color:var(--mute)}
.pg-home .bctl .st{font-size:var(--fig);font-family:"Gabarito",sans-serif;font-weight:700;color:var(--mute);
  font-variant-numeric:tabular-nums;white-space:nowrap}
.pg-home [data-js="0"] .bctl{display:none}

/* ===================== 10 NINE JOBS =====================
   Nine photographs for nine captioned jobs. Not a regular grid: the two
   hospitals run wide, the rest are unequal, and the mosaic is deliberately
   uneven so no row repeats the one above it. */
.pg-home .jobs{background:var(--g-proj);padding-block:var(--s7) var(--s5)}
.pg-home .jobs-head{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,38ch);gap:var(--s4);
  align-items:end;margin-bottom:var(--s4)}
@media(max-width:860px){.pg-home .jobs-head{grid-template-columns:1fr;gap:var(--s2)}}
.pg-home .jobs-head h2{font-size:var(--t-big);max-width:11em}
.pg-home .jobs-head p{font-size:var(--meta);color:var(--mute)}
.pg-home .mosaic{display:grid;grid-template-columns:repeat(6,1fr);gap:var(--s3)}
@media(max-width:980px){.pg-home .mosaic{grid-template-columns:repeat(4,1fr)}}
@media(max-width:620px){.pg-home .mosaic{grid-template-columns:repeat(2,1fr)}}
.pg-home .mosaic figure{margin:0;grid-column:span 2}
.pg-home .mosaic figure.w3{grid-column:span 3}
.pg-home .mosaic figure.w4{grid-column:span 4}
@media(max-width:980px){.pg-home .mosaic figure.w4, .pg-home .mosaic figure.w3{grid-column:span 4}}
@media(max-width:620px){.pg-home .mosaic figure, .pg-home .mosaic figure.w3, .pg-home .mosaic figure.w4{grid-column:span 2}}
.pg-home .mosaic .ph{overflow:hidden}
/* No scale on hover. A photograph of a real installed room is the content, and
   nudging it 3 percent bigger says nothing about it. What changes on hover is
   the caption, which is where the information is. */
.pg-home .mosaic img{width:100%;height:auto;aspect-ratio:4/3;object-fit:cover}
.pg-home .mosaic figcaption{transition:transform var(--fast) var(--settle)}
@media (hover:hover){.pg-home .mosaic figure:hover figcaption{transform:translateX(6px)}
  .pg-home .mosaic figure:hover h3{color:var(--zest-t)}}
.pg-home .mosaic figcaption{margin-top:var(--s2)}
.pg-home .mosaic .job{display:block;font-size:var(--fig);letter-spacing:.1em;text-transform:uppercase;color:var(--mute)}
.pg-home .mosaic h3{margin-top:3px;font-size:var(--body);letter-spacing:-.018em}
.pg-home .mosaic .where{display:block;margin-top:1px;font-size:var(--meta);color:var(--mute)}

/* ===================== MOTION ===================== */
.pg-home .mast h1, .pg-home .mast .lede, .pg-home .mast .acts{animation:settleUp var(--slow) var(--settle) both}
.pg-home .mast .lede{animation-delay:.10s}.pg-home .mast .acts{animation-delay:.18s}
.pg-home .mast-obj{animation:riseIn var(--slow) var(--settle) .26s both}
.pg-home .mast .fig{animation:chipIn var(--fast) var(--settle) .9s both}
@supports (animation-timeline:view()){
  @media(prefers-reduced-motion:no-preference){
    .pg-home .printed li, .pg-home .index li~li{animation:chipIn linear both;animation-timeline:view();animation-range:entry 14% cover 34%}
    .pg-home .build-head, .pg-home .list-head, .pg-home .jobs-head, .pg-home .proc-head, .pg-home .range-in>div:first-child{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 5% cover 24%}
    .pg-home .lead-prod .shot{animation:riseIn linear both;animation-timeline:view();animation-range:entry 2% cover 22%}
    .pg-home .trio article{animation:chipIn linear both;animation-timeline:view();animation-range:entry 8% cover 30%}
    .pg-home .rot-stage{animation:fromLeft linear both;animation-timeline:view();animation-range:entry 4% cover 26%}
    .pg-home .views-in>div:last-child{animation:settleUp linear both;animation-timeline:view();animation-range:entry 6% cover 28%}
    .pg-home .gym figure img{animation:photoIn linear both;animation-timeline:view();animation-range:entry 0% cover 30%}
    .pg-home .gym-in>div{animation:settleUp linear both;animation-timeline:view();animation-range:entry 6% cover 28%}
    .pg-home .prow{animation:rowIn linear both;animation-timeline:view();animation-range:entry 4% cover 22%}
    /* The nine job photographs and their nine captions used to animate in,
       eighteen animations for one section, all on the same curve and the
       same stagger. That is the "fade up on every element" tell, and it was
       also nine permanent clip-path repaints. They are finished rooms with
       named clients: they are more convincing sitting still and already
       there than arriving one at a time. Section heading still moves. */
    .pg-home .ft-grid>div{animation:settleUp linear both;animation-timeline:view();animation-range:entry 6% cover 30%}
  }
}
@media(prefers-reduced-motion:reduce){
  .pg-home *:not(.bl){animation:none !important}
  .pg-home .mast h1, .pg-home .mast .lede, .pg-home .mast .acts, .pg-home .mast-obj, .pg-home .mast .fig, .pg-home .index li, .pg-home .promise li, .pg-home .build-head, .pg-home .list-head, .pg-home .jobs-head, .pg-home .proc-head, .pg-home .spec-in>div, .pg-home .lead-prod .shot, .pg-home .trio article, .pg-home .rot-stage, .pg-home .gym-in>div, .pg-home .gym figure img, .pg-home .prow, .pg-home .mosaic figcaption, .pg-home .ft-grid>div, .pg-home .range-in>div{opacity:1;transform:none}
  html{scroll-behavior:auto}
}

/* ==================================================================
   playground.html  ->  body.pg-playground
   Was a <style> block in the page. Scoped to its own page class so a
   rule written for this page cannot reach another page's markup.
   ================================================================== */
/* ==========================================================================
   PAGE TWO. THE PLAYGROUND AND TRANSPORT COLLECTIONS.

   THE COMPOSITION IDEA: the range walked from the smallest to the largest.
   The previous version of this page was about the catalogue rather than about
   the products. Its headline counted the pages in a book. A kindergarten
   operator does not care how many pages the book has, and everything below the
   headline inherited that error: two enormous numerals were the subject and
   the products were thumbnails beside them.

   THIS PAGE HAS ONE REGISTER AND NO MEASURING DEVICE, and that is the whole
   decision. Four have now been tried on this client: a drawn dimension rule, a
   pair of floating hero figures, two proportional numerals, and during this
   session a stack of products drawn to one scale. Every one of them had to be
   explained, and the fourth had a real accuracy fault on top: the renders are
   three quarter views, so forcing each image to a width proportional to its
   printed centimetres gives it a height that is not proportional to anything.
   It was built, looked at, and cut in the same pass.

   What replaces it is the thing a buyer does anyway. THE FOURTEEN ARE ORDERED
   BY HOW MUCH FLOOR THEY TAKE, smallest first, numbered 01 to 14, each drawn
   large with the size our catalogue prints set beside it in one format. The
   order is the comparison. Nobody has to be taught how to read it, and the
   figure they need for their tape measure is right next to the picture.
   ========================================================================== */
.pg-playground .m2{background:var(--g-cool);padding-block:var(--s5) var(--s6)}
.pg-playground .m2 h1{font-size:var(--t-huge);max-width:15em}
.pg-playground .m2-lede{margin-top:var(--s3);max-width:34em;font-size:var(--body)}
.pg-playground .m2-lede b{color:var(--ink)}

/* ---------- the fourteen, large ----------
   Not a card grid. Each product gets a full band, the image is the biggest
   thing in it, and the text column sits under the image on a phone and beside
   it above 900px. The bands alternate ground colour rather than being
   separated by a rule, and the alternation is by collection, not by parity,
   so the change of colour tells you something. */
.pg-playground .range{padding-block:var(--s6) 0}
.pg-playground .pc{padding-block:var(--s5);display:grid;gap:var(--s4);
  grid-template-columns:minmax(0,1.15fr) minmax(0,1fr);align-items:center}
.pg-playground .pc:nth-child(odd){background:var(--white)}
.pg-playground .pc:nth-child(even){background:var(--g-warm)}
.pg-playground .pc-fig{padding-inline:var(--room)}
.pg-playground .pc-fig img{width:auto;max-width:100%;height:auto;max-height:min(66vh,600px);margin-inline:auto;
  /* capped by height, not by width. Uncapped, a tall render made the band over
     900px deep and left roughly 450px of empty ground under the text column
     beside it. Found by looking at Little Hideout at 1440. */
  animation:pcIn var(--slow) var(--settle) both;
  animation-timeline:view();animation-range:entry 4% cover 30%}
@keyframes pcIn{from{opacity:0;transform:translate3d(0,42px,0)}to{opacity:1;transform:none}}
.pg-playground .pc-fig{display:grid;justify-items:end}
.pg-playground .pc-txt{padding-inline:var(--room);display:grid;justify-items:start;gap:var(--s2)}

/* THE FIVE VEHICLES GET A DIFFERENT BAND, and the reason is in the products.
   A play frame is 230 cm tall and about 127 cm across, so it is a portrait
   object and it sits happily beside its text. A school bus is 462 cm long and
   168 cm high, so it is a landscape object roughly three times as wide as it
   is tall, and forcing it into the same half-width column drew it small in the
   middle of a lot of nothing. Long objects get the full width and their text
   underneath in a row. It also means the change from frames to vehicles is
   visible without a heading announcing it. */
.pg-playground .pc.long{grid-template-columns:minmax(0,1fr);gap:var(--s3)}
.pg-playground .pc.long .pc-fig{justify-items:center}
.pg-playground .pc.long .pc-fig img{max-height:min(52vh,460px)}
.pg-playground .pc.long .pc-txt{display:grid;grid-template-columns:auto minmax(0,1fr) auto;
  align-items:end;gap:var(--s2) var(--s4);justify-items:start}
.pg-playground .pc.long .pc-no{grid-row:1}
.pg-playground .pc.long .pc-n{grid-row:2;grid-column:1}
.pg-playground .pc.long .pc-d{grid-row:2;grid-column:2;align-self:end}
.pg-playground .pc.long .pc-s{grid-row:2;grid-column:3;white-space:nowrap}
.pg-playground .pc.long .p-note{grid-row:3;grid-column:1/-1}
.pg-playground .pc.long .add{grid-row:4;grid-column:1}
@media(max-width:900px){
  .pg-playground .pc.long .pc-txt{grid-template-columns:minmax(0,1fr)}
  .pg-playground .pc.long .pc-n, .pg-playground .pc.long .pc-d, .pg-playground .pc.long .pc-s, .pg-playground .pc.long .p-note, .pg-playground .pc.long .add{
    grid-row:auto;grid-column:1}
  /* nowrap is right when the size sits in its own column beside the name and
     wrong the moment it does not: "242.4 × 175.5 × 167.6 cm" at 28px is 310px
     wide and the type area at 320px is 280px, so it pushed the document 10px
     sideways. Caught by the width sweep, not by reading the rule. */
  .pg-playground .pc.long .pc-s{white-space:normal}
}
.pg-playground .pc-no{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--meta);
  letter-spacing:.14em;color:var(--mute);font-variant-numeric:tabular-nums}
.pg-playground .pc-n{font-size:var(--t-big);letter-spacing:-.03em;line-height:1}
.pg-playground .pc-d{font-size:var(--body);max-width:26em}
.pg-playground .pc-s{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--t-mid);
  letter-spacing:-.02em;font-variant-numeric:tabular-nums;color:var(--forest)}
.pg-playground .p-note{font-size:var(--meta);color:var(--mute);max-width:28em}
.pg-playground .p-note b{color:var(--ink)}
@media(max-width:900px){
  .pg-playground .pc{grid-template-columns:minmax(0,1fr);gap:var(--s3);padding-block:var(--s4)}
  .pg-playground .pc-n{font-size:1.75rem}
}

/* ---------- the price footnote, and it is a footnote ---------- */
.pg-playground .foot2{background:var(--g-cool);padding-block:var(--s6)}
.pg-playground .foot2 h2{font-size:var(--t-mid);max-width:20em}
.pg-playground .foot2 p{margin-top:var(--s2);max-width:38em;font-size:var(--meta);color:var(--mute)}
.pg-playground .foot2 p b{color:var(--ink)}
.pg-playground .foot2 .btn{margin-top:var(--s3)}
@media(prefers-reduced-motion:reduce){
  .pg-playground .sc li, .pg-playground .pc-fig img{animation:none;opacity:1;transform:none}
}

/* ==================================================================
   tables.html  ->  body.pg-tables
   Was a <style> block in the page. Scoped to its own page class so a
   rule written for this page cannot reach another page's markup.
   ================================================================== */
/* ==========================================================================
   PAGE FIVE. TABLES, CHAIRS AND SOFT SEATING.

   THE COMPOSITION IDEA: THE HEIGHT QUESTION, ANSWERED.

   Two other ideas were tried on this data first and both died on it, which is
   why this one is here.

   The first was the plan's own "organised by shape", because their names are
   shapes: Bean, Moon, Flower, Trapezoid, Semi-round. It fails because shape is
   not a decision. A kindergarten does not walk in wanting a trapezoid. Shape is
   how the catalogue already sorts itself, so a page organised that way would be
   the printed book with a scrollbar.

   The second was "a capacity index", the idea recorded for the storage page.
   It was tested against the names and 16 of 104 rows carry a capacity figure.
   Written down as failed rather than forced.

   What the data actually says is this. Their catalogue prints one instruction,
   in a box, in the top right of pages 9, 10 and 11: FOR CHILDREN AGED 3-5, THE
   IDEAL TABLE HEIGHT IS 53CM. It is the ONLY age printed against any of the 458
   products in that book, and every previous pass on this project concluded there
   was no age data at all. They then sell the same table at 30, 53, 66 and 76 cm,
   each height with its own code and its own price. So height is the decision the
   buyer has to make, they have already told the buyer which one to make, and the
   page is that instruction carried out: the twelve tables that come at 53 cm
   first, then the other heights, then the seven that adjust.

   NO MEASURING DEVICE, AGAIN. Four have been rejected on this client and a
   fifth was built and cut. The heights are headings and sentences. There is no
   drawn scale, no axis, no ruler, no annotation line, and nothing on this page
   is proportional to anything.

   COLOUR. --g-tabs is the pale grey blue of the metal legs, sampled off the
   product cutouts (#D0D8D8 and #D8E0E0 were the two commonest non-white leg
   pixels). It is the one surface every table in this range shares, it is the
   only cool ground on the site, and it is nowhere near the four banned values.
   ========================================================================== */
:root{ --g-tabs:var(--g-cool) }  /* was #DCE4E5, a fourth pale blue */

.pg-tables .m5{background:var(--g-tabs);padding-block:var(--s5) var(--s5)}
.pg-tables .m5 h1{font-size:var(--t-huge);max-width:11em}
.pg-tables .m5-lede{margin-top:var(--s3);max-width:34em}
.pg-tables .m5-quote{margin-top:var(--s4);font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-mid);line-height:1.14;letter-spacing:-.02em;max-width:15em;color:var(--forest)}
.pg-tables .m5-src{margin-top:var(--s1);font-size:var(--meta);color:var(--mute)}
.pg-tables .m5-facts{margin-top:var(--s4);display:flex;flex-wrap:wrap;gap:var(--s3) var(--s5);
  list-style:none;padding:0}
/* AT 1024 AND UP THE MASTHEAD IS TWO COLUMNS. At one column it left the right
   700px of a 1440 screen completely empty under a 5rem headline, which is the
   failure this project has already learned once: a surface too big for what is
   on it does not get fixed by rearranging what is on it. The right column is
   the printed instruction the whole page is built on, so it earns the space,
   and it is type rather than a decorative photograph. */
@media(min-width:1024px){
  .pg-tables .m5{display:grid;grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);
    column-gap:var(--s6);row-gap:var(--s3);align-items:start}
  .pg-tables .m5 .crumb{grid-column:1/-1}
  .pg-tables .m5 h1, .pg-tables .m5-lede{grid-column:1}
  /* the quote and its source are ONE block. Spanning the quote across two grid
     rows drove its source line 300px below it, so they were bound together
     instead. */
  .pg-tables .m5-say{grid-column:2;grid-row:2/span 2;margin-top:0}
  .pg-tables .m5-quote{margin-top:0;font-size:var(--t-big);max-width:11em}
  .pg-tables .m5-facts{grid-column:1/-1;margin-top:var(--s3)}
}
.pg-tables .m5-facts b{display:block;font-size:var(--t-mid);letter-spacing:-.02em;line-height:1}
.pg-tables .m5-facts span{font-size:var(--meta);color:var(--mute);max-width:15em;display:block}

/* ---------- a height band ----------
   The band is the heading. Its ground changes, nothing is ruled off. */
.pg-tables .band{padding-block:var(--s6)}
.pg-tables .band-53{background:var(--white)}
.pg-tables .band-other{background:var(--g-warm)}
.pg-tables .band-adj{background:var(--g-cool)}
.pg-tables .band-rest{background:var(--white)}
.pg-tables .band h2{font-size:var(--t-big);max-width:16em}
.pg-tables .band-say{margin-top:var(--s2);max-width:34em;color:var(--mute)}
.pg-tables .band-say b{color:var(--ink)}

/* the twelve at 53, as photographed products */
.pg-tables .pgrid{margin-top:var(--s5);display:grid;gap:var(--s4) var(--s3);
  grid-template-columns:repeat(auto-fill,minmax(216px,1fr))}
.pg-tables .pcard{display:flex;flex-direction:column;gap:var(--s2)}
.pg-tables .pcard-t{display:flex;flex-direction:column;gap:var(--s1);flex:1}
.pg-tables .pcard-t .add{margin-top:auto;align-self:start}
.pg-tables .pcard-f{background:var(--cream);border-radius:var(--r-ui);padding:var(--s2);
  display:grid;place-items:center;min-height:150px}
.pg-tables .band-53 .pcard-f{background:var(--g-tabs)}
.pg-tables .pcard-f img{width:auto;max-width:100%;height:auto;max-height:170px;
  animation:tbIn var(--slow) var(--settle) both;
  animation-timeline:view();animation-range:entry 2% cover 22%}
@keyframes tbIn{from{opacity:0;transform:translate3d(0,26px,0)}to{opacity:1;transform:none}}
.pg-tables .pcard-nf{min-height:110px;color:var(--mute);font-size:var(--meta);text-align:center;
  padding:var(--s3)}
.pg-tables .pcard-n{font-size:1.0625rem;line-height:1.2;letter-spacing:-.02em}
.pg-tables .pcard-m{display:flex;flex-wrap:wrap;gap:var(--s1) var(--s2);align-items:baseline}
.pg-tables .pcard-s{font-size:var(--fig);color:var(--mute);font-variant-numeric:tabular-nums}
.pg-tables .pcard-p .rm{font-size:1.0625rem}
.pg-tables .pcard-np{font-size:var(--fig);color:var(--mute)}

/* the priced lines. A list, not a table element, so it reflows on a phone
   without a horizontal scroller. */
.pg-tables .trs{margin-top:var(--s4);list-style:none;padding:0;display:grid;gap:var(--s1)}
.pg-tables .tr{display:grid;gap:var(--s1) var(--s3);align-items:baseline;padding:var(--s2) var(--s2);
  background:var(--white);border-radius:var(--r-ui);
  grid-template-columns:minmax(0,1fr) auto}
.pg-tables .band-53 .tr, .pg-tables .band-rest .tr{background:var(--cream)}
.pg-tables .tr-n{font-family:"Gabarito",sans-serif;font-weight:700;letter-spacing:-.02em;
  grid-column:1;min-width:0;overflow-wrap:anywhere}
.pg-tables .tr-c{grid-column:2;justify-self:end;white-space:nowrap}
.pg-tables .tr-h{grid-column:1;font-family:"Gabarito",sans-serif;font-weight:700;color:var(--forest);
  font-variant-numeric:tabular-nums}
.pg-tables .tr-s{grid-column:1;font-size:var(--fig);color:var(--mute);font-variant-numeric:tabular-nums;
  overflow-wrap:anywhere}
.pg-tables .tr-p{grid-column:2;justify-self:end;white-space:nowrap;font-family:"Gabarito",sans-serif;
  font-weight:700}
.pg-tables .tr-np{font-family:"Poppins",sans-serif;font-weight:400;font-size:var(--fig);color:var(--mute)}
.pg-tables .tr-f{grid-column:1/-1;font-size:var(--fig);color:var(--mute)}
.pg-tables .tr .add{grid-column:1/-1;justify-self:start;margin-top:var(--s1)}
@media(min-width:760px){
  .pg-tables .tr.tr-noh{grid-template-columns:minmax(0,22em) minmax(0,1fr) auto auto}
  .pg-tables .tr.tr-noh .tr-s{grid-column:2}
  .pg-tables .tr.tr-noh .tr-p{grid-column:3}
  .pg-tables .tr.tr-noh .add{grid-column:4}
  .pg-tables .tr.tr-noh .tr-f{grid-column:2/-1}
  .pg-tables .tr{grid-template-columns:minmax(0,22em) 8em minmax(0,1fr) auto auto;
    gap:var(--s2) var(--s3);align-items:center}
  .pg-tables .tr-n{grid-column:1}
  .pg-tables .tr-h{grid-column:2}
  .pg-tables .tr-s{grid-column:3}
  .pg-tables .tr-p{grid-column:4}
  .pg-tables .tr .add{grid-column:5;justify-self:end;margin-top:0}
  .pg-tables .tr-c{grid-column:1;justify-self:start;grid-row:2}
  .pg-tables .tr-f{grid-column:2/-1;grid-row:2}
}
/* ---- A PHOTOGRAPH INSIDE A PRICED LINE -------------------------------------
   Two of these lists price a product the card grids above never show, and the
   book does print a photograph of that one row. Ten of them were cut, proved
   and looked at in session N and then could not be published, because a list
   row had nowhere to put a picture. This is that slot.

   ON A DESKTOP the column is on the LIST, not on the row, so every name in a
   list that has a photograph starts on the same edge. A row with no photograph
   leaves the column empty rather than drawing a box: an empty box on seven of
   the ten office lines reads as ten broken images, which is worse than a gap.

   Same measurements as the therapy hand-tool list, which is the same object:
   76 px on a desktop, 56 px on a phone, no background, no radius, no frame.

   ON A PHONE THE PICTURE TAKES ITS OWN BAND ABOVE THE LINE and the row keeps
   the two columns it already had. A leading column was tried first and shot at
   320: it left the name about 90 px wide and broke "Rectangular" across three
   lines. There is no alignment to protect at that width, because every line is
   already a stacked block whose name starts at the left edge. */
.pg-tables .trs-fig .tr-fig{grid-column:1/-1;justify-self:start;
  width:56px;height:auto;background:transparent}
.pg-tables .trs-fig .tr-nofig{display:none}
@media(min-width:760px){
  .pg-tables .trs-fig .tr-nofig{display:block}
  .pg-tables .trs-fig .tr-fig, .pg-tables .trs-fig .tr-nofig{
    grid-column:1;grid-row:1/span 2;align-self:center;justify-self:center}
  .pg-tables .trs-fig .tr.tr-noh{
    grid-template-columns:76px minmax(0,16em) minmax(0,1fr) auto auto}
  .pg-tables .trs-fig .tr.tr-noh .tr-s{grid-column:3}
  .pg-tables .trs-fig .tr.tr-noh .tr-p{grid-column:4}
  .pg-tables .trs-fig .tr.tr-noh .add{grid-column:5}
  .pg-tables .trs-fig .tr.tr-noh .tr-f{grid-column:3/-1}
  .pg-tables .trs-fig .tr{
    grid-template-columns:76px minmax(0,16em) 8em minmax(0,1fr) auto auto}
  .pg-tables .trs-fig .tr-fig, .pg-tables .trs-fig .tr-nofig{width:76px}
  .pg-tables .trs-fig .tr-n{grid-column:2}
  .pg-tables .trs-fig .tr-h{grid-column:3}
  .pg-tables .trs-fig .tr-s{grid-column:4}
  .pg-tables .trs-fig .tr-p{grid-column:5}
  .pg-tables .trs-fig .tr .add{grid-column:6;justify-self:end}
  .pg-tables .trs-fig .tr-c{grid-column:2;justify-self:start;grid-row:2}
  .pg-tables .trs-fig .tr-f{grid-column:3/-1;grid-row:2}
}
@media(hover:hover){
  .pg-tables .trs-fig .tr-fig{transition:opacity var(--fast) var(--settle)}
  .pg-tables .trs-fig .tr:hover .tr-fig{opacity:.86}
}
@media(hover:none){
  .pg-tables .trs-fig .tr:hover .tr-fig{opacity:1}
}
@media(prefers-reduced-motion:reduce){
  .pg-tables .trs-fig .tr-fig{transition:none}
}
.pg-tables .verb::after{content:" as printed";font-size:var(--fig);color:var(--mute);opacity:.85}

/* the seven that adjust */
.pg-tables .adjs{margin-top:var(--s5);display:grid;gap:var(--s5) var(--s4);
  grid-template-columns:repeat(auto-fill,minmax(260px,1fr))}
.pg-tables .adjs .pcard-f{background:var(--white);min-height:210px}
.pg-tables .adjs .pcard-f img{max-height:230px}
.pg-tables .stops{margin-top:var(--s3);display:flex;flex-wrap:wrap;gap:var(--s1) var(--s2);
  list-style:none;padding:0}
.pg-tables .stops li{background:var(--white);border-radius:var(--r-ui);padding:6px 12px;
  font-family:"Gabarito",sans-serif;font-weight:700;font-variant-numeric:tabular-nums;
  font-size:var(--meta)}

/* closing note */
.pg-tables .f5{background:var(--forest);color:#FFF;padding-block:var(--s6)}
.pg-tables .f5 h2{color:#FFF;font-size:var(--t-mid);max-width:20em}
.pg-tables .f5 p{margin-top:var(--s2);max-width:40em;color:rgba(255,255,255,.92);font-size:var(--meta)}
.pg-tables .f5 p b{color:#FFF}
.pg-tables .f5 .btn{margin-top:var(--s3)}
@media(max-width:900px){ .pg-tables .m5 h1{max-width:none} }
@media(prefers-reduced-motion:reduce){ .pg-tables .pcard-f img{animation:none;opacity:1;transform:none} }

.pg-tables .band-h3{margin-top:var(--s5);font-size:var(--t-mid);letter-spacing:-.02em}
.pg-tables .band-h3:first-of-type{margin-top:var(--s4)}

/* ==================================================================
   therapy.html  ->  body.pg-therapy
   Was a <style> block in the page. Scoped to its own page class so a
   rule written for this page cannot reach another page's markup.
   ================================================================== */
/* ==========================================================================
   PAGE FOUR. THERAPY AND SPECIAL NEEDS.  (c) 2026 PPT EDU SUPPLIES

   SESSION G. Everything in this block is page-local and is listed in
   PROGRESS.md so it can be merged into css/ppt.css centrally. Nothing here
   overrides a shared rule; the four ground colours are the only new values
   and every one is a pixel sampled off the source page its band comes from.

   THE COMPOSITION. A room that exists, then four bands at four densities,
   because the four groups are four different physical sizes. See
   tools/g_build_therapy.py for the full note.

   NO RULES, NO BORDERS, NO SHADOWS. Bands are separated by a change of
   ground and by space. Every hover sits inside a hover query.
   ========================================================================== */
.pg-therapy .t-open{background:var(--white);padding-block:var(--s5) var(--s5);
  display:grid;gap:var(--s3) var(--s4);
  grid-template-columns:minmax(0,1.35fr) minmax(0,1fr);align-items:end}
.pg-therapy .t-open .crumb{grid-column:1/-1}
.pg-therapy .t-open h1{font-size:var(--t-huge);max-width:11em}
/* THE LEDE SITS BESIDE THE HEADLINE, NOT UNDER IT. Stacked, the headline ran
   three lines across 60% of the width and the lede ran four under it, which
   left the right hand 40% of an eight hundred pixel band empty at 1440. That
   is the defect where the surface is the wrong size for what is on it, not
   the arrangement, so the surface was made to carry two columns. */
.pg-therapy .t-lede{max-width:26em;font-size:var(--body);padding-bottom:6px}
.pg-therapy .t-lede b{color:var(--ink)}
@media(max-width:900px){
  .pg-therapy .t-open{grid-template-columns:minmax(0,1fr);align-items:start}
  .pg-therapy .t-lede{max-width:33em}
}

/* ---------- the room. Two photographs, unequal, and the caption sits in the
   gap the shorter one leaves rather than in a box of its own. ---------- */
.pg-therapy .room{background:var(--white);padding-block:var(--s5) var(--s6)}
.pg-therapy .room-g{display:grid;gap:var(--s3);grid-template-columns:minmax(0,1.06fr) minmax(0,1fr);
  align-items:end}   /* the floor is the datum in both photographs */
.pg-therapy .room-g img{width:100%;border-radius:var(--r-ui)}
.pg-therapy .room-b{grid-column:1/-1;display:grid;gap:var(--s3) var(--s4);
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);margin-top:var(--s2)}
.pg-therapy .room-b h2{font-size:var(--t-mid);max-width:14em}
.pg-therapy .room-b p{font-size:var(--body);max-width:31em}
.pg-therapy .room-q{font-size:1.375rem;line-height:1.35;letter-spacing:-.02em;max-width:20em;
  color:var(--forest);font-family:"Gabarito",sans-serif;font-weight:700}
.pg-therapy .room-w{margin-top:var(--s2);font-size:var(--meta);color:var(--mute);max-width:30em}
.pg-therapy .room-w b{color:var(--ink)}
@media(max-width:820px){
  .pg-therapy .room-g, .pg-therapy .room-b{grid-template-columns:minmax(0,1fr)}
}

/* ---------- band heads. One shape, four grounds. ----------
   SESSION T. THESE FOUR WERE THE ONLY INVENTED COLOURS ON THE SITE, and they
   were also four whites. #F7F5F1, #FFFFFF, #FFF5EF and #EFEEEE carried no
   provenance comment, appear in no measurement, and are within about six
   points of lightness of each other, so "four grounds" was one ground written
   four ways. Measured across all 68 photographs this page ships, by
   tools/k_ground.py, therapy is a blue and yellow page and always was:

     --g-th-blue  #D8E4F5  from #296AC4, 11.4% -- the blue mats and wedges on
                  th-p116-r1c1, r1c4, r1c2 and the climbing wall on th-kpj-a
                  ink 9.37 · mute 5.50 · price 5.42 · forest 9.80
     --g-th-wood  #F5EED8  from #DFBB51, 11.1% -- the timber frame on th-kpj-b
                  and the wooden equipment on th-e30-d, th-e31-f, th-e30-a
                  ink 10.38 · mute 6.10 · price 6.01 · forest 10.86
     --g-th-deep  #D8DFF5  from #3152B5, 10.6% -- the deeper blue on th-e30-b,
                  th-e31-e, th-e30-d, th-e30-a
                  ink 9.06 · mute 5.32 · price 5.24 · forest 9.47
     --g-th-sun   #F5F3D8  from #E9DA34, 9.9% -- the yellow on th-e31-b,
                  th-p116-r4c2, th-e31-a, th-e30-c
                  ink 10.72 · mute 6.30 · price 6.20 · forest 11.21

   HONESTLY: two of these are close to grounds other pages already carry.
   #D8E4F5 sits five points of green from floors' #D8E9F5, #F5EED8 is play's
   posted band and #D8DFF5 is storage's tray blue. That is not a mistake in
   the sampling, it is what the client's range is made of: therapy equipment
   and nursery carpets are both mostly blue. The alternative was to invent a
   difference, which is the rule this site does not break. Recorded as a
   client question rather than papered over.                                */
:root{
  /* The four therapy bands ran #D8E4F5 #F5EED8 #D8DFF5 #F5F3D8: two pale
     blues and two pale yellows, all four at 90.4% lightness, over 22,400px.
     They now alternate cool, warm, cool, warm in page order, which is the
     same four bands doing the job the four tints were meant to do. */
  --g-th-blue:var(--g-cool); --g-th-wood:var(--g-warm);
  --g-th-deep:var(--g-cool); --g-th-sun:var(--g-warm);
}
.pg-therapy .band{padding-block:var(--s6)}
.pg-therapy .b-swing{background:var(--g-th-blue)}
.pg-therapy .b-soft{background:var(--g-th-wood)}
.pg-therapy .b-floor{background:var(--g-th-deep)}
.pg-therapy .b-hand{background:var(--g-th-sun)}
.pg-therapy .bhead{max-width:64rem}
.pg-therapy .bno{display:block;font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--fig);letter-spacing:.16em;color:var(--mute);
  font-variant-numeric:tabular-nums;margin-bottom:var(--s2)}
.pg-therapy .bhead h2{font-size:var(--t-big);max-width:11em}
.pg-therapy .bcount{margin-top:var(--s2);font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-mid);letter-spacing:-.02em;color:var(--forest);
  font-variant-numeric:tabular-nums}
.pg-therapy .bcount .bspan{color:var(--price)}
.pg-therapy .blede{margin-top:var(--s2);max-width:36em;font-size:var(--body);color:var(--mute)}

/* ---------- the grids. Four densities, one card. ---------- */
.pg-therapy .grid{display:grid;gap:var(--s4) var(--s3);margin-top:var(--s5)}
.pg-therapy .grid-swing{grid-template-columns:repeat(4,minmax(0,1fr))}
.pg-therapy .grid-soft{grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--s5) var(--s4)}
.pg-therapy .grid-floor{grid-template-columns:repeat(3,minmax(0,1fr))}
@media(max-width:1000px){.pg-therapy .grid-swing{grid-template-columns:repeat(3,minmax(0,1fr))}}
@media(max-width:760px){
  .pg-therapy .grid-swing, .pg-therapy .grid-floor{grid-template-columns:repeat(2,minmax(0,1fr))}
  .pg-therapy .grid-soft{grid-template-columns:minmax(0,1fr)}
}
@media(max-width:400px){.pg-therapy .grid-floor{grid-template-columns:minmax(0,1fr)}}
.pg-therapy .tc{display:grid;align-content:start;gap:var(--s2)}
.pg-therapy .tf{background:var(--white);border-radius:var(--r-ui);padding:var(--s2);
  display:grid;place-items:center;min-height:150px}
.pg-therapy .b-soft .tf{background:var(--white)}
.pg-therapy .tf img{width:auto;max-width:100%;max-height:210px;object-fit:contain}
.pg-therapy .grid-soft .tf img{max-height:300px}
/* priced, not pictured. One block per band, not one placeholder per product. */
.pg-therapy .nopic{margin-top:var(--s5);max-width:52rem}
.pg-therapy .npl{list-style:none;margin:var(--s3) 0 0;padding:0;display:grid;gap:var(--s1)}
.pg-therapy .npl li{display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s1) var(--s2);
  background:var(--white);border-radius:var(--r-ui);padding:var(--s2)}
.pg-therapy .b-soft .npl li{background:var(--white)}
.pg-therapy .npn{font-family:"Gabarito",sans-serif;font-weight:700;font-size:1.0625rem;
  letter-spacing:-.02em;flex:1 1 12rem}
.pg-therapy .nps{font-size:var(--meta);color:var(--mute);font-variant-numeric:tabular-nums}
.pg-therapy .npl .rm{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--t-sub);
  font-variant-numeric:tabular-nums}
.pg-therapy .npw{font-size:var(--meta);color:var(--mute)}
.pg-therapy .tt{display:grid;gap:6px;justify-items:start}
.pg-therapy .tn{font-size:var(--t-sub);letter-spacing:-.02em;line-height:1.12}
.pg-therapy .grid-soft .tn{font-size:var(--t-mid)}
.pg-therapy .ts{font-size:var(--meta);color:var(--mute);font-variant-numeric:tabular-nums}
.pg-therapy .tp{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--t-sub);
  letter-spacing:-.02em;font-variant-numeric:tabular-nums}
.pg-therapy .tp .tu{font-family:"Poppins",sans-serif;font-weight:400;font-size:var(--meta);
  color:var(--mute);margin-left:4px}
.pg-therapy .tp-none{color:var(--mute);font-family:"Poppins",sans-serif;font-weight:400;
  font-size:var(--meta)}
.pg-therapy .tnote{font-size:var(--meta);color:var(--mute);max-width:26em;line-height:1.45}
.pg-therapy .tc .add{margin-top:6px}

/* ---------- the hand tools. A list, and the thumbnail is the smallest
   picture on the page because the objects are the smallest things on it. */
.pg-therapy .list{margin-top:var(--s5);display:grid;gap:var(--s1)}
.pg-therapy .tr{display:grid;align-items:center;gap:var(--s2) var(--s3);
  grid-template-columns:76px minmax(0,1fr) minmax(0,10rem) auto;
  background:var(--white);border-radius:var(--r-ui);padding:var(--s2)}
.pg-therapy .tr .th{width:76px;height:76px;object-fit:contain;background:transparent}
.pg-therapy .tr .th-none{display:block;width:76px;height:76px;border-radius:var(--r-ui);
  background:rgba(55,54,54,.06)}
.pg-therapy .trt{display:grid;gap:4px}
.pg-therapy .tr .tp{text-align:right}
@media(max-width:760px){
  .pg-therapy .tr{grid-template-columns:56px minmax(0,1fr);row-gap:var(--s2)}
  .pg-therapy .tr .th, .pg-therapy .tr .th-none{width:56px;height:56px}
  .pg-therapy .tr .tp{grid-column:2;text-align:left}
  .pg-therapy .tr .add{grid-column:2;justify-self:start}
}

/* ---------- the ball packs, which are a sub list on their own page ---------- */
.pg-therapy .packs{margin-top:var(--s5);display:grid;gap:var(--s3);
  grid-template-columns:170px minmax(0,1fr);align-items:center}
.pg-therapy .packs img{width:170px;height:auto}
.pg-therapy .pkl{list-style:none;margin:var(--s2) 0 0;padding:0;display:flex;flex-wrap:wrap;
  gap:var(--s1) var(--s3)}
.pg-therapy .pkl li{display:flex;gap:var(--s1);align-items:baseline}
.pg-therapy .pkl .pk{font-size:var(--meta);color:var(--mute)}
.pg-therapy .pkl .rm{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--t-sub)}
@media(max-width:600px){.pg-therapy .packs{grid-template-columns:minmax(0,1fr)}}

/* ---------- closing ---------- */


/* ---------- THE SEARCH AS A PAGE, NOT A SHEET ----------
   The sheet is fixed to the viewport and sits under the header, which on a
   phone leaves it about 500px of usable height no matter how tight the rows
   are. That is why it felt cramped, and no amount of trimming a result row
   was going to fix a container that short. On find.html the same markup and
   the same JS render as ordinary page flow instead: full document height, the
   filters get room, and the whole thing is a destination with a URL rather
   than something covering the page you were reading. */
.pg-find .find.find-page,
.pg-find .find.find-page[data-open="0"]{position:static;inset:auto;z-index:auto;height:auto;
  visibility:visible;opacity:1;background:transparent;padding-bottom:var(--s6)}
.pg-find .find.find-page .find-top{display:block;padding-top:var(--s5)}
.pg-find .find.find-page .find-res{overflow:visible;max-height:none;padding-bottom:0}
.pg-find .find.find-page .closeX{display:none}
.pg-find body.searching{overflow:auto}


/* the page variant must not inherit the sheet's fixed-viewport sizing */
.pg-find .find.find-page{max-width:100%;overflow-x:hidden}
.pg-find .find.find-page .find-chips{max-width:100%}
.pg-find main{overflow-x:hidden}
/* The header is fixed and 144px tall, so a hero that starts at the top of the
   document sits under it: the eyebrow and the first line of the heading were
   both behind the search field. Clear the header, and take the forest ground
   the contact and enquiry heroes already use so the white breadcrumb has
   something to sit on. It was white on beige and effectively invisible. */

/* the sort row: same shape as the filter chips so it reads as one control set */
/* Long labels are the default; the short <b> forms only appear on phones. */
/* THE BOX INSIDE A BOX ON HIS PHONE. The field measures clean in Chrome at
   every width, transparent background, no border, and the orange ring lives on
   the wrapper. The doubled outline he photographed is Samsung Internet drawing
   its own highlight on the focused input, which these four lines suppress.
   Harmless everywhere else, because all four are already the values Chrome
   computes. Keyboard focus is unaffected, the visible ring is on the wrapper. */
.find-field input,
.pg-find .fd-field input,
#findQ{
  -webkit-tap-highlight-color:transparent;
  -webkit-appearance:none;appearance:none;
  background:transparent;border:0;box-shadow:none;border-radius:0}
#findQ:focus,#findQ:focus-visible{outline:none;box-shadow:none;background:transparent}
/* THE PRICE RAIL IS THE SORT RAIL. Same shape, same behaviour, same short
   forms on a phone, deliberately: it sits directly above the sort row and a
   second control that looked like a new idea would have made the page feel
   like it was growing rather than answering. One family of controls. */
/* THE CATEGORY CHIPS GET THE SAME TREATMENT AS THE SORT AND PRICE RAILS.
   Ten chips ran 1,624px in a 390px scrollport, so a phone showed one chip
   and half of the next. The edge fade said "there is more this way", but
   one and a half filters is not a filter row, it is a rumour of one. Short
   forms on phones, the full wording on desktop where there is room. */
.find-chips button b{display:none}
.find-chips button i{font-style:normal}
.pg-find .find-sort button b,
.pg-find .find-bands button b{display:none}
.pg-find .find-sort button i,
.pg-find .find-bands button i{font-style:normal}
.pg-find .find-sort,
.pg-find .find-bands{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s1) 6px;
  padding-block:var(--s2)}
/* FOUR SORT BUTTONS WRAPPED TO THREE ROWS AT 390 and stood 168px tall — more
   than the height of a result — so the thing you sort was pushed off screen by
   the controls that sort it. One row that scrolls sideways, the same mechanism
   the category chips directly above already use, so it reads as one family of
   controls rather than a new idea. Desktop has the width and keeps wrapping. */
@media (max-width:719px){
  .pg-find .find-sort,
  .pg-find .find-bands{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;
    -webkit-overflow-scrolling:touch;scrollbar-width:none}
  .pg-find .find-sort::-webkit-scrollbar,
  .pg-find .find-bands::-webkit-scrollbar{display:none}
  .pg-find .find-sort button,
  .pg-find .find-sort .fs-lab,
  .pg-find .find-bands button,
  .pg-find .find-bands .fs-lab{flex:none;white-space:nowrap}
}
/* With a query running the status line does not need a fifth of a screen of
   air above it; the introduction it used to be separating it from is gone. */
.pg-find.q-on .find.find-page .find-top{padding-top:var(--s2)}
.pg-find.q-on .fd-top{padding-bottom:0}

/* PHONE FIRST, BECAUSE THE BUYERS ARE ON PHONES. Desktop has 800px of height
   to spend on a status line, a chip rail and a sort rail before it costs
   anyone a result. A phone does not. These four bands were carrying desktop
   spacing at 390 and stood 74px taller than they needed to, which is half a
   result. Same controls, same order, tighter air. First result moved from
   382px to 308px and the screen went from 2.4 results to 3.1. */
@media (max-width:560px){
  .pg-find.q-on .find.find-page .find-top{padding-top:6px}
  .pg-find .find-stat{padding-top:8px}
  .pg-find .find-chips,
  .find-chips{padding-block:8px}
  .pg-find .find-sort,
  .pg-find .find-bands{padding-block:8px}

  /* THE THREE ROWS INSIDE A RESULT WERE STRETCHING TO EQUAL HEIGHT. Measured
     at 390: three tracks of 48.67px holding 39px of name, 22px of code and
     size, and a 44px button. The middle one was carrying 26px of nothing and
     every row paid for it. Sized to content the row goes 184px to about 143,
     which is most of a fourth result on the screen. */
  .find-res .prow{align-content:start}

  /* AND THE RAILS LOOKED BROKEN RATHER THAN SCROLLABLE. The chips run 1,237px
     wider than the screen and simply stopped mid-word at the edge, which reads
     as a bug, not as "there is more this way". A fade off the right edge is
     the whole fix. */
  /* THE SORT EXISTED AND HE COULD NOT FIND IT ON HIS OWN SITE. At 390 the four
     buttons ran 1,200px wide, so only "As the book prints" and half of "Price,
     low to high" were on screen and the rest was off the right edge behind a
     fade. If the person who built it misses it, a customer has no chance.
     Short forms fit all four at once, so the whole control is visible without
     scrolling and the word Price is finally on screen. Desktop keeps the long
     labels, since it has the room and they read better. */
  /* Both rails drop their label on a phone. The price rail can afford to
     because its short forms carry RM in them, so the numbers cannot be
     misread as sizes or product codes, which is the only reason the label
     was there. Keeping it cost 33px and pushed the fourth band off the
     edge, and a band you cannot see is a band nobody uses. */
  .find-chips button i{display:none}
  .find-chips button b{display:inline;font-style:normal;font-weight:inherit}
  .pg-find .find-sort .fs-lab,
  .pg-find .find-bands .fs-lab{display:none}
  .pg-find .find-sort button i,
  .pg-find .find-bands button i{display:none}
  .pg-find .find-sort button b,
  .pg-find .find-bands button b{display:inline;font-style:normal;font-weight:inherit}
  .pg-find .find-sort,
  .pg-find .find-bands{gap:6px}
  .pg-find .find-sort button,
  .pg-find .find-bands button{padding:0 12px}
  .pg-find .find-chips,
  .pg-find .find-sort,
  .pg-find .find-bands{
    -webkit-mask-image:linear-gradient(to right,#000 calc(100% - 34px),transparent);
    mask-image:linear-gradient(to right,#000 calc(100% - 34px),transparent)}
  .pg-find .find-chips.at-end,
  .pg-find .find-sort.at-end,
  .pg-find .find-bands.at-end{-webkit-mask-image:none;mask-image:none}
}
.pg-find .find-sort .fs-lab,
.pg-find .find-bands .fs-lab{font-size:var(--meta);letter-spacing:.12em;
  text-transform:uppercase;color:var(--mute);margin-right:var(--s1)}
.pg-find .find-sort button,
.pg-find .find-bands button{min-height:44px;padding:0 14px;border:0;cursor:pointer; /* same 44px floor as the chips row above */
  border-radius:var(--r-ui);background:rgba(55,54,54,.08);color:var(--ink);
  font-family:"Poppins",sans-serif;font-size:var(--meta)}
.pg-find .find-sort button[aria-pressed="true"],
.pg-find .find-bands button[aria-pressed="true"]{background:var(--ink);color:#FFF}
.pg-find .fd-top{background:var(--g-deep);color:#FFF;
  padding-block:calc(var(--hdr,144px) + var(--s4)) var(--s5)}
/* WITH A QUERY RUNNING, THE INTRODUCTION IS IN THE WAY. It keeps its ground
   colour so the page does not flinch, and gives back the height its heading,
   lede and breadcrumb were holding — the first result then lands directly
   under the field instead of 802px below it. Cleared field, copy returns. */
/* THE COLLAPSED INTRO WAS STILL PAINTING A BAND OF GREEN. Its children are
   hidden while a query runs, but it kept a step of padding under the header,
   so a dark stripe about 33px tall sat between the search field and the
   results with nothing in it. On a phone that reads as a rendering fault,
   not as spacing. The header clearance has to stay, because the header is
   fixed and this block is what reserves room for it. Everything past that
   goes. */
.pg-find.q-on .fd-top{padding-block:var(--hdr,144px) 0}
.pg-find.q-on .fd-top .eyebrow,
.pg-find.q-on .fd-top .fd-h,
.pg-find.q-on .fd-top .fd-lede,
.pg-find.q-on .fd-top .crumb{display:none}
.pg-find .fd-top .eyebrow{color:rgba(255,255,255,.72)}
.pg-find .fd-top .crumb{color:rgba(255,255,255,.72)}
.pg-find .fd-top .crumb a{color:#FFF}
.pg-find .fd-h{color:#FFF}
.pg-find .fd-lede{color:rgba(255,255,255,.88)}
.pg-find .fd-h{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-big);letter-spacing:-.03em;line-height:1;margin:0 0 var(--s3)}
.pg-find .fd-lede{max-width:40rem;margin:0 0 var(--s4)} /* 44rem measured 76ch; 40rem is 69 */
.pg-find .fd-lab{display:block;font-size:var(--meta);letter-spacing:.12em;
  text-transform:uppercase;color:var(--mute);margin-bottom:var(--s2)}
.pg-find .fd-field{display:flex;align-items:center;gap:var(--s2);background:var(--g-paper);
  border:2px solid var(--ink);border-radius:var(--r-ui);padding:0 var(--s3);min-height:56px}
.pg-find .fd-field svg{width:22px;height:22px;flex:none;color:var(--mute)}
.pg-find .fd-field input{flex:1;border:0;background:transparent;min-height:52px;
  font-family:"Poppins",sans-serif;font-size:var(--body);color:var(--ink);outline:0}
.pg-find .find-stat{padding-top:var(--s3)}

/* ---------- band 05, the Weplay range ----------
   Added on the client's instruction after the section was found missing: page
   47 of the education book is a Weplay page and forty priced products sat on
   it, kept off the site because Weplay is somebody else's brand. The answer to
   that is attribution, not omission — the band says whose range it is, prints
   only the figure our own book prints, and reproduces none of their artwork.
   It reuses the .npl list the sensory band already uses, because these forty
   have no code, no printed size and no photograph we are entitled to show. */
.pg-therapy .b-weplay{background:var(--g-th-blue)}
.pg-therapy .b-weplay .npl{grid-template-columns:1fr}
@media(min-width:760px){.pg-therapy .b-weplay .npl{grid-template-columns:1fr 1fr}}
@media(min-width:1180px){.pg-therapy .b-weplay .npl{grid-template-columns:1fr 1fr 1fr}}
.pg-therapy .b-weplay .npl li{background:var(--white)}

.pg-therapy .t-end{background:var(--g-paper);padding-block:var(--s6)}
.pg-therapy .t-end h2{font-size:var(--t-big);max-width:12em}
.pg-therapy .t-end p{margin-top:var(--s3);max-width:36em;font-size:var(--body);color:var(--mute)}
.pg-therapy .t-end p b{color:var(--ink)}
.pg-therapy .t-end .btn{margin-top:var(--s4)}

@media(hover:hover){
  .pg-therapy .tc .tf img, .pg-therapy .tr .th{transition:opacity var(--fast) var(--settle)}
  .pg-therapy .tc:hover .tf img, .pg-therapy .tr:hover .th{opacity:.86}
}
@media(prefers-reduced-motion:reduce){
  .pg-therapy .tc, .pg-therapy .tr, .pg-therapy .room-g img{animation:none;opacity:1;transform:none}
}

/* ==================================================================
   projects.html  ->  body.pg-projects
   Was a <style> block in the page. Scoped to its own page class so a
   rule written for this page cannot reach another page's markup.
   ================================================================== */
/* ==========================================================================
   PAGE FIVE. ROOMS WE HAVE FINISHED.  (c) 2026 PPT EDU SUPPLIES

   SESSION G, page local. Listed in PROGRESS.md for the central merge into
   css/ppt.css. No new colour: the two grounds are --g-proj, which was
   sampled off this client's own interior photography in session 4 for
   exactly this purpose, and --play from the Playground Catalogue, used on
   the one job that is a playground.

   --mute is NOT used on --g-proj. Measured: #605E5D on #E6D5C4 is 4.51:1,
   which passes AA by one hundredth and is not a margin worth having. Body
   text on that ground is --ink, at 8.42:1.
   ========================================================================== */
.pg-projects .pj-open{background:var(--beige);padding-block:var(--s5) var(--s6);
  display:grid;gap:var(--s3) var(--s4);
  grid-template-columns:minmax(0,1.3fr) minmax(0,1fr);align-items:end}
.pg-projects .pj-open .crumb{grid-column:1/-1}
.pg-projects .pj-open h1{font-size:var(--t-huge);max-width:11em}
.pg-projects .pj-open .lede{max-width:27em;font-size:var(--body);padding-bottom:6px}
.pg-projects .pj-open .lede b{color:var(--ink)}
@media(max-width:900px){
  .pg-projects .pj-open{grid-template-columns:minmax(0,1fr);align-items:start}
  .pg-projects .pj-open .lede{max-width:34em}
}

.pg-projects .gband{padding-block:var(--s6)}
.pg-projects .g-setup{background:var(--g-cool)}
.pg-projects .g-makeover{background:var(--g-proj)}
.pg-projects .g-outdoor{background:var(--g-cool)}
.pg-projects .ghead{max-width:60rem}
.pg-projects .ghead h2{font-size:var(--t-big);max-width:12em}
.pg-projects .gsub{margin-top:var(--s2);max-width:38em;font-size:var(--body)}
.pg-projects .g-setup .gsub, .pg-projects .g-outdoor .gsub{color:var(--mute)}

.pg-projects .pj-grid{margin-top:var(--s5);display:grid;gap:var(--s6) var(--s4);
  grid-template-columns:repeat(2,minmax(0,1fr))}
/* the ninth job is the only one outdoors and the only one that is a single
   object rather than a room, so it is the only band that runs full width */
.pg-projects .g-outdoor .pj-grid{grid-template-columns:minmax(0,1fr)}
.pg-projects .g-outdoor .pj{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr);
  gap:var(--s4);align-items:center}
@media(max-width:820px){
  .pg-projects .pj-grid{grid-template-columns:minmax(0,1fr);gap:var(--s5)}
  .pg-projects .g-outdoor .pj{grid-template-columns:minmax(0,1fr)}
}
.pg-projects .pj{display:grid;gap:var(--s3);align-content:start}
.pg-projects .pj-f{display:grid}
.pg-projects .pj-f img{width:100%;height:auto;max-height:min(74vh,660px);object-fit:cover;
  object-position:center;border-radius:var(--r-ui)}
.pg-projects .g-outdoor .pj-f img{max-height:min(60vh,520px)}
.pg-projects .pj-t{display:grid;gap:6px;justify-items:start}
.pg-projects .pj-k{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--fig);
  letter-spacing:.14em;text-transform:uppercase;color:var(--mute)}
.pg-projects .g-makeover .pj-k{color:#5C4A38}   /* 5.02:1 on --g-proj, measured */
.pg-projects .pj-c{font-size:var(--t-mid);letter-spacing:-.025em;line-height:1.05}
.pg-projects .pj-anon{color:var(--mute)}
.pg-projects .g-makeover .pj-anon{color:#5C4A38}
.pg-projects .pj-p{font-size:var(--body);color:var(--forest);font-family:"Gabarito",sans-serif;
  font-weight:700;letter-spacing:-.01em}
.pg-projects .pj-d{margin-top:6px;font-size:var(--body);max-width:30em;color:var(--mute)}
.pg-projects .g-makeover .pj-d{color:var(--ink)}
.pg-projects .pj-note{font-size:var(--meta);color:var(--mute);max-width:28em}
.pg-projects .g-makeover .pj-note{color:#5C4A38}

.pg-projects .pj-end{background:var(--white);padding-block:var(--s6)}
.pg-projects .pj-end h2{font-size:var(--t-big);max-width:12em}
.pg-projects .pj-end p{margin-top:var(--s3);max-width:36em;font-size:var(--body);color:var(--mute)}
.pg-projects .pj-end p b{color:var(--ink)}
.pg-projects .pj-end .btn{margin-top:var(--s4)}

@media(hover:hover){
  .pg-projects .pj-f img{transition:opacity var(--fast) var(--settle)}
  .pg-projects .pj:hover .pj-f img{opacity:.9}
}
@media(prefers-reduced-motion:reduce){.pg-projects .pj-f img{transition:none}}

/* ==================================================================
   contact.html  ->  body.pg-contact
   Was a <style> block in the page. Scoped to its own page class so a
   rule written for this page cannot reach another page's markup.
   ================================================================== */
/* ==========================================================================
   PAGE SIX. CONTACT.  (c) 2026 PPT EDU SUPPLIES

   SESSION G, page local. Listed in PROGRESS.md for the central merge.
   No new colour anywhere on this page and no new easing. The header carries
   --forest because this section's ground is --forest; it is in normal flow
   and is not fixed, not sticky.

   MEASURED CONTRAST ON THE DARK GROUND. #FFFFFF on #003C20 is 15.1:1.
   --zest #FF741F on #003C20 is 4.66:1, which is why the numbers are white
   and only the small label above them is zest. rgba(255,255,255,.86) on
   #003C20 is 12.0:1, comfortably past the 75 percent floor.
   ========================================================================== */
.pg-contact .ct-top{background:var(--forest);color:#FFF;padding-block:var(--s5) var(--s7)}
.pg-contact .ct-top h1{color:#FFF;font-size:var(--t-huge);max-width:9em}
.pg-contact .ct-top .crumb{color:rgba(255,255,255,.72)}
.pg-contact .ct-lede{margin-top:var(--s3);max-width:31em;font-size:var(--body);
  color:rgba(255,255,255,.86)}

/* THE NUMBERS ARE THE PAGE. Set at display size, one per row, each with the
   person and the job beside it. Not a list of equals: a visitor who cannot
   tell which of five numbers to ring will ring none of them. */
/* THE BAND CARRIES THE WHOLE DIRECTORY, not just the numbers.
   Built first with the numbers alone it filled the left 58 percent of a 1440
   viewport and left the right 42 percent empty for 900px of scroll, and the
   address, the email and the social accounts sat in a separate band below
   doing the same thing. Two half empty bands became one full one. The page
   is shorter for it, which is the point of this page. */
.pg-contact .nums{background:var(--beige);padding-block:var(--s6)}
.pg-contact .nums h2{font-size:var(--t-big);max-width:12em}
.pg-contact .nums .sub{margin-top:var(--s2);max-width:36em;font-size:var(--body);color:var(--mute)}
.pg-contact .ncols{display:grid;gap:var(--s5) var(--s6);
  grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);margin-top:var(--s5)}
@media(max-width:1000px){.pg-contact .ncols{grid-template-columns:minmax(0,1fr);gap:var(--s5)}}
.pg-contact .nside{display:grid;gap:var(--s4);align-content:start}
.pg-contact .nside h3{font-size:var(--t-mid);letter-spacing:-.02em}
.pg-contact .nside p, .pg-contact .nside li{font-size:var(--body);color:var(--mute)}
.pg-contact .nside p{margin-top:var(--s2)}
.pg-contact .nside b{color:var(--ink)}
.pg-contact .nside ul{list-style:none;margin:0;padding:0}
.pg-contact .nside li a{display:inline-flex;align-items:center;min-height:44px}
.pg-contact .nlist{list-style:none;margin:0;padding:0;display:grid;gap:var(--s4)}
.pg-contact .nrow{display:grid;gap:var(--s1) var(--s4);align-items:baseline;
  grid-template-columns:minmax(0,auto) minmax(0,1fr)}
.pg-contact .nnum{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:clamp(1.75rem,4.4vw,3rem);letter-spacing:-.03em;line-height:1;
  font-variant-numeric:tabular-nums;text-decoration:none;
  display:inline-flex;align-items:center;min-height:56px}
.pg-contact .nwho{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--t-mid);
  letter-spacing:-.02em;line-height:1.1}
/* A GRID, NOT A FLEX WRAP. As a flex row the label and the sentence sat on
   one line where the sentence was short and on two where it was long, so the
   sentence started at x=265 on one row and x=66 on the next. Two columns fix
   the label width whatever the sentence does. */
.pg-contact .nmeta{grid-column:1/-1;display:grid;gap:var(--s1) var(--s2);
  grid-template-columns:11.5rem minmax(0,1fr);align-items:baseline}
@media(max-width:560px){.pg-contact .nmeta{grid-template-columns:minmax(0,1fr)}}
/* min-width so the purpose sentence starts on the same x for every row.
   Without it "CALLS ONLY" and "WHATSAPP AND CALLS" set the following text at
   178px and 261px and the column read as ragged. */
.pg-contact .nkind{font-size:var(--fig);letter-spacing:.12em;text-transform:uppercase;
  font-family:"Gabarito",sans-serif;font-weight:700;color:var(--forest)}
.pg-contact .nkind.tel{color:var(--mute)}
.pg-contact .nfor{font-size:var(--body);color:var(--mute);max-width:34em}
@media(max-width:700px){
  .pg-contact .nrow{grid-template-columns:minmax(0,1fr)}
}

/* address, registration and the two things that are easy to get wrong */
.pg-contact .big-addr{font-family:"Gabarito",sans-serif;font-weight:700;font-size:1.375rem;
  letter-spacing:-.02em;line-height:1.25;color:var(--ink);margin-top:var(--s2)}
.pg-contact .reg{margin-top:var(--s3);font-size:var(--body);color:var(--mute)}
.pg-contact .reg b{font-family:"Gabarito",sans-serif}

.pg-contact .send{background:var(--g-class);padding-block:var(--s6)}
.pg-contact .send h2{font-size:var(--t-big);max-width:11em}
.pg-contact .send ol{margin:var(--s4) 0 0;padding:0;list-style:none;display:grid;gap:var(--s3);
  counter-reset:s}
.pg-contact .send li{counter-increment:s;display:grid;grid-template-columns:2.5rem minmax(0,1fr);
  gap:var(--s2);max-width:42em}
.pg-contact .send li::before{content:counter(s,decimal-leading-zero);
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--fig);
  letter-spacing:.1em;color:var(--forest);padding-top:5px}
.pg-contact .send li p{font-size:var(--body);color:var(--ink);max-width:34em} /* ran 78 to 84ch beside the counter column */
.pg-contact .send li p b{color:var(--ink)}
.pg-contact .send .btn{margin-top:var(--s4)}
.pg-contact .send .cost{margin-top:var(--s4);max-width:34em;font-size:var(--meta);color:var(--mute)}
.pg-contact .send .cost b{color:var(--ink)}

/* ==========================================================================
   floors.html  ->  body.pg-floors

   COMPOSITION: "two numbers or three". Verified on all 27 rows: every mat
   prints three dimensions, no carpet prints more than two, and two carpets
   print one, a diameter. So the page has two halves ordered by two different
   quantities: carpets by the floor one of them covers, mats by how thick they
   are. The measure leads each row, at a size that makes it the thing you read
   first, because it is the thing the half is sorted on.

   The two halves are deliberately different densities. Carpets are large flat
   single objects and get air, one long row each with the coverage set big.
   Mats stack, physically, so their rows are tighter and their figure is
   smaller. Drawing them at one density would say they are the same kind of
   purchase, and they are not.

   Two grounds, both sampled from the products in their own half, both
   computed against every colour that lands on them. No lines anywhere; the
   halves are separated by colour and by the change of measure.
   ========================================================================== */
.pg-floors .fl-top{background:var(--white);padding-block:var(--s5) var(--s6)}
.pg-floors .fl-top h1{font-size:var(--t-huge);max-width:13em;margin-top:var(--s3)}
.pg-floors .fl-lede{margin-top:var(--s4);display:grid;gap:var(--s3);
  grid-template-columns:minmax(0,26em) minmax(0,30em);align-items:start;
  font-size:var(--body);color:var(--mute)}
.pg-floors .fl-lede b{color:var(--ink)}
.pg-floors .fl-lede p:last-child{padding-top:var(--s4)}   /* the deliberate misalignment */
@media(max-width:900px){
  .pg-floors .fl-lede{grid-template-columns:minmax(0,1fr);gap:var(--s2)}
  .pg-floors .fl-lede p:last-child{padding-top:0}
}

/* SESSION K. The carpet band was GREEN, --g-carp, sampled from the green
   INSIDE two carpets. Measured across all thirteen photographs now on the page
   by tools/k_ground.py, the carpets are BLUE: #3D94D1 at 12.2% of saturated
   pixels and #2A60AB at 11.5%, from Counting Bee, Colours, Universe, The
   Galaxy and Hopscoth, against #D62836 red at 8.3%. A page whose whole
   argument is "a carpet is its pattern" was sitting on a colour its carpets
   mostly are not. It is now the colour they are, and the mats band takes the
   red, which is the second thing the same measurement found. */
.pg-floors .fl-carpets{background:var(--g-fl-carp);padding-block:var(--s6) var(--s7)}
.pg-floors .fl-mats{background:var(--g-fl-mat);padding-block:var(--s7) var(--s6)}
.pg-floors .fl-carpets h2,.pg-floors .fl-mats h2{font-size:var(--t-big);max-width:14em}
/* floors ran four prose widths, 36/38/40/40em, on one page; fl-say-2 and the
   note measured 79ch. One value for all four, same 34em as the rest of the site. */
.pg-floors .fl-say{margin-top:var(--s3);max-width:34em;font-size:var(--body);color:var(--mute)}
.pg-floors .fl-say b{color:var(--ink)}
.pg-floors .fl-say-2{margin-top:var(--s2);max-width:34em}
.pg-floors .fl-foot{margin-top:var(--s4);max-width:34em;font-size:var(--meta);color:var(--mute)}

.pg-floors .fl-list{list-style:none;margin:var(--s5) 0 0;padding:0;display:grid}
.pg-floors .fl-list-m{margin-top:var(--s4)}
/* The text column is a FIXED measure, not 1fr, and the row is packed from the
   left. 1fr stretched the row to the full 1308px available at 1440 and left
   roughly 400px of nothing between the last word and the price: the same hole
   that was found and fixed inside every row of page two. A fixed measure keeps
   the price beside the text it belongs to, and because the measure is fixed
   the prices still line up in a column, which is the one thing a buyer
   scanning a price list actually needs. */
.pg-floors .fr{display:grid;align-items:start;justify-content:start;
  grid-template-columns:168px 9.5rem minmax(0,34em) 6.5rem auto;
  gap:var(--s2) var(--s3);padding-block:var(--s3)}
.pg-floors .fl-list-m .fr{grid-template-columns:104px 7rem minmax(0,34em) 6.5rem auto;
  padding-block:var(--s2)}
@media(max-width:1180px){
  .pg-floors .fr,.pg-floors .fl-list-m .fr{justify-content:stretch;
    grid-template-columns:168px 9.5rem minmax(0,1fr) 6.5rem auto}
  .pg-floors .fl-list-m .fr{grid-template-columns:104px 7rem minmax(0,1fr) 6.5rem auto}
}

/* THE PICTURE. A carpet is its pattern, so a carpet row without one is a name
   and not a product; that was the largest single gap in this build. The two
   halves are drawn at two sizes for the same reason their figures are: a
   carpet is chosen by eye and gets 168px, a mat is chosen by its depth and
   gets 104px. Drawing a RM33 rubber tile the size of a RM250 printed rug
   would misdescribe both.
   No border, no shadow, no radius on the image itself: these are cut-outs on
   white and the ground already separates them. */
.pg-floors .fr-fig{display:block;align-self:start}
.pg-floors .fr-fig img{display:block;width:100%;height:auto;
  background:var(--white);border-radius:var(--r-ui)}
/* The three mats with no proved photograph keep the column so the rows either
   side do not step sideways. It is empty on purpose and says so in the copy
   above rather than drawing a placeholder box that means nothing. */
.pg-floors .fr-fig-none{min-height:1px}

/* the measure each half is sorted on */
.pg-floors .fr-cov{font-family:"Gabarito",sans-serif;font-weight:700;
  /* WAS 1.85rem = 29.6px, the only non-integer rendered size anywhere on the
     twelve pages, measured across all of them at 390px. 1.75rem = 28px is a
     step the site already uses on ten of the twelve. A scale with one value
     off it is not a scale. */
  font-size:1.75rem;line-height:.95;letter-spacing:-.03em;color:var(--forest);
  font-variant-numeric:tabular-nums}
.pg-floors .fr-cov[data-none]{font-size:var(--fig);letter-spacing:.09em;
  text-transform:uppercase;color:var(--mute);font-weight:700;padding-top:6px}
.pg-floors .fr-th{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-sub);line-height:.95;letter-spacing:-.03em;color:var(--forest);
  font-variant-numeric:tabular-nums;display:grid;justify-items:start}
.pg-floors .fr-th-u{font-size:var(--fig);letter-spacing:.06em;color:var(--mute);
  font-weight:700;margin-top:4px}

.pg-floors .fr-txt{display:grid;justify-items:start;gap:4px;min-width:0}
.pg-floors .fr-n{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:1.0625rem;line-height:1.2;letter-spacing:-.01em}
.pg-floors .fr-s{font-size:var(--body);color:var(--ink);font-variant-numeric:tabular-nums}
.pg-floors .fr-x{font-size:var(--meta);color:var(--mute);max-width:34em}
.pg-floors .fr-alt{font-size:var(--meta);color:var(--ink);max-width:34em}
.pg-floors .fr-flag{font-size:var(--meta);color:var(--ink);max-width:34em;
  background:var(--white);padding:var(--s2);border-radius:var(--r-ui);margin-top:4px}
.pg-floors .fr-p{justify-self:end;font-size:1.0625rem;padding-top:2px}
.pg-floors [data-verbatim]{font-style:italic}

.pg-floors .fl-note{background:var(--g-paper);padding-block:var(--s6) var(--s5)}
.pg-floors .fl-note h2{font-size:var(--t-mid);max-width:16em}
.pg-floors .fl-note p{margin-top:var(--s3);max-width:34em;font-size:var(--body);color:var(--mute)}
.pg-floors .fl-note p b{color:var(--ink)}
.pg-floors .fl-note .btn{margin-top:var(--s4)}

@media(max-width:820px){
  /* The picture takes a left column and everything else stacks beside it, so
     a row stays one object on a phone instead of becoming four stacked ones.
     132px is the width at which these patterns are still readable: measured
     at 320, where it leaves 176px for the text beside it. */
  .pg-floors .fr,.pg-floors .fl-list-m .fr{
    grid-template-columns:132px minmax(0,1fr) auto;
    grid-template-areas:"img fig fig" "img txt txt" "img p add";
    gap:var(--s1) var(--s2);padding-block:var(--s3)}
  .pg-floors .fl-list-m .fr{grid-template-columns:88px minmax(0,1fr) auto}
  .pg-floors .fr-fig{grid-area:img}
  .pg-floors .fr-cov,.pg-floors .fr-th{grid-area:fig}
  .pg-floors .fr-txt{grid-area:txt}
  .pg-floors .fr-p{grid-area:p;justify-self:start;align-self:center}
  .pg-floors .fr .add{grid-area:add;justify-self:end}
  .pg-floors .fr-th{grid-auto-flow:column;align-items:baseline;gap:8px}
  .pg-floors .fr-th-u{margin-top:0}
  /* a row with no proved photograph gives the space back rather than
     holding open a column of nothing on a 320px screen */
  .pg-floors .fr[data-nophoto]{grid-template-columns:0 minmax(0,1fr) auto;
    column-gap:0}
}
@media(max-width:400px){
  .pg-floors .fr{grid-template-columns:112px minmax(0,1fr) auto}
  .pg-floors .fl-list-m .fr{grid-template-columns:76px minmax(0,1fr) auto}
}

/* ==========================================================================
   storage.html  ->  body.pg-storage

   COMPOSITION: "what are you putting in it". 47 of the 64 sort themselves on
   the word in their own printed name. The page is therefore a set of small
   answers to one question, and the group heading carries the answer while the
   count beside it carries the evidence.

   DENSITY. This page is deliberately tighter than floors.html. 64 utilitarian
   units bought by a bursar are not 27 floor coverings chosen by eye, and
   drawing them at the same rhythm would say they are the same kind of
   decision. Rows are close, groups are far apart, and the space between two
   groups is the only thing separating them: there is no rule anywhere.

   The footnote under each price is set as a real line rather than a
   superscript because it is price-affecting and differs row to row.
   ========================================================================== */
/* SESSION K. THIS PAGE OPENS ON AN OBJECT, NOT ON A SENTENCE.
   Ten of the twelve pages began masthead / headline / paragraph, which is a
   shape rather than a design once you see them in a row. Here the tallest
   shelf in the range stands beside the headline at 470px, taller than the
   headline itself and running past the bottom of its own section, so the first
   thing on the page is the thing the page sells. It is also the only page that
   opens on a single object at full height: learning opens on a size
   comparison, play opens on two fields, contact opens on type alone. */
.pg-storage .st-top{background:var(--white);padding-block:var(--s5) 0;position:relative}
.pg-storage .st-top-in{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,26rem);
  gap:var(--s4);align-items:start}
.pg-storage .st-top h1{font-size:var(--t-huge);max-width:12em;margin-top:var(--s3)}
.pg-storage .st-hero{justify-self:end;align-self:start;margin-bottom:calc(var(--s5) * -1);
  position:relative;z-index:1}
.pg-storage .st-hero img{display:block;width:min(330px,100%);height:auto}
.pg-storage .st-hero i{display:block;font-style:normal;margin-top:var(--s2);
  font-size:var(--fig);letter-spacing:.08em;text-transform:uppercase;color:var(--mute)}
.pg-storage .st-lede{margin-top:var(--s4);padding-bottom:var(--s6);display:grid;gap:var(--s3);
  grid-template-columns:minmax(0,27em) minmax(0,27em);align-items:start;
  font-size:var(--body);color:var(--mute)}
.pg-storage .st-lede b{color:var(--ink)}
@media(max-width:900px){
  .pg-storage .st-lede{grid-template-columns:minmax(0,1fr)}
  .pg-storage .st-top-in{grid-template-columns:minmax(0,1fr)}
  .pg-storage .st-hero{justify-self:start;margin-bottom:0;margin-top:var(--s4)}
  .pg-storage .st-hero img{width:min(300px,72%)}
}

.pg-storage .st-named{background:var(--g-shelf);padding-block:var(--s6) var(--s7)}
.pg-storage .st-open{background:var(--g-tray);padding-block:var(--s6) var(--s6)}

/* The picture column. Fixed width so the name, price and Add stay aligned. */
.pg-storage .sr-fig{align-self:center;justify-self:center}
.pg-storage .sr-fig img{display:block;width:auto;height:auto;max-width:100%;
  max-height:96px}
/* The lead object for each group: what that group looks like, at four times
   the row size, so "somewhere for shoes" is answered by a picture of the
   pigeon hole rack rather than by the phrase. */
.pg-storage .stg-lead{float:right;width:min(268px,38%);margin:0 0 var(--s3) var(--s4);
  shape-outside:margin-box}
.pg-storage .stg-lead img{display:block;width:100%;height:auto}
.pg-storage .stg-lead i{display:block;font-style:normal;margin-top:6px;
  font-size:var(--fig);color:var(--mute)}
@media(max-width:760px){
  /* The float becomes a block on a phone and landed ABOVE the group heading,
     so the picture and its caption arrived before the reader knew what group
     they were in. Re-ordered so the heading still leads. */
  .pg-storage .stg{display:flex;flex-direction:column}
  .pg-storage .stg-h{order:1}
  .pg-storage .stg-say{order:2}
  .pg-storage .stg-lead{order:3;float:none;width:min(240px,60%);margin:var(--s3) 0 0}
  .pg-storage .st-list{order:4}
}
.pg-storage .st-named h2,.pg-storage .st-open h2{font-size:var(--t-big);max-width:13em}
.pg-storage .st-say{margin-top:var(--s3);max-width:37em;font-size:var(--body);color:var(--mute)}
.pg-storage .st-say b{color:var(--ink)}

.pg-storage .stg{margin-top:var(--s6)}
.pg-storage .stg:first-of-type{margin-top:var(--s5)}
.pg-storage .stg-h{font-size:var(--t-mid);max-width:16em;line-height:1.02;
  display:flex;align-items:baseline;gap:var(--s2);flex-wrap:wrap}
.pg-storage .stg-n{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--fig);letter-spacing:.1em;color:var(--forest);
  background:var(--white);padding:4px 10px;border-radius:var(--r-ui);
  font-variant-numeric:tabular-nums}
.pg-storage .stg-say{margin-top:var(--s2);max-width:34em;font-size:var(--meta);color:var(--mute)}
.pg-storage .stg-say b{color:var(--ink)}

.pg-storage .st-list{list-style:none;margin:var(--s3) 0 0;padding:0;display:grid}
.pg-storage .st-open .st-list{margin-top:var(--s5)}
.pg-storage .sr{display:grid;align-items:start;justify-content:start;
  grid-template-columns:7.5rem minmax(0,32em) 7rem auto;
  gap:var(--s1) var(--s3);padding-block:14px}
.pg-storage .sr-txt{display:grid;justify-items:start;gap:3px;min-width:0}
.pg-storage .sr-n{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:1.0625rem;line-height:1.2;letter-spacing:-.01em}
.pg-storage .sr-s{font-size:var(--body);font-variant-numeric:tabular-nums}
.pg-storage .sr-fn{font-size:var(--meta);color:var(--mute);font-style:italic}
.pg-storage .sr-fn-none{font-style:normal}
.pg-storage .sr-note{font-size:var(--meta);color:var(--ink);max-width:33em;
  background:var(--white);padding:10px var(--s2);border-radius:var(--r-ui);margin-block:4px}
.pg-storage .st-open .sr-note{background:var(--white)}
.pg-storage .sr-p{justify-self:end;font-size:1.0625rem;padding-top:2px}
.pg-storage [data-verbatim]{font-style:italic}
.pg-storage .sr-s[data-verbatim]{font-style:normal;letter-spacing:.01em}

.pg-storage .st-note{background:var(--g-paper);padding-block:var(--s6) var(--s5)}
.pg-storage .st-note h2{font-size:var(--t-mid);max-width:17em}
.pg-storage .st-note p{margin-top:var(--s3);max-width:34em;font-size:var(--body);color:var(--mute)}
.pg-storage .st-note p b{color:var(--ink)}
.pg-storage .st-note .btn{margin-top:var(--s4)}

@media(max-width:1120px){
  .pg-storage .sr{justify-content:stretch;
    grid-template-columns:6rem minmax(0,1fr) 7rem auto}
}
@media(max-width:720px){
  .pg-storage .sr{grid-template-columns:4.5rem minmax(0,1fr) auto;
    grid-template-areas:"fig txt txt" "fig p add";gap:var(--s1) var(--s2);padding-block:var(--s2)}
  .pg-storage .sr-fig{grid-area:fig;align-self:start;justify-self:start}
  .pg-storage .sr-fig img{max-height:64px}
  .pg-storage .sr-txt{grid-area:txt}
  .pg-storage .sr-p{grid-area:p;justify-self:start;align-self:center}
  .pg-storage .sr .add{grid-area:add;justify-self:end}
  /* THE FIVE ROWS THAT CARRY A NOTE, session AF.
     Session M put the shared-photograph line inside the figure, under the
     image, because it is a fact about the picture and not about the product.
     That is still where it is. What was wrong was the width. The figure track
     is 4.5rem and .sr-fig is justify-self:start, so a 65 character sentence
     sized the cell to 83px, the width of its own longest word, and ran seven
     and eight lines deep next to a 262px column of product text.
     A sentence cannot be read in 83px. At phone width these five rows put the
     figure across the row and the rest of the row underneath, so the note has
     350px and reads in two lines. The note has not moved: it is still under
     the image, still inside the figure, still smaller and quieter. */
  .pg-storage .sr:has(.sh-note){grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:"fig fig" "txt txt" "p add"}
}

/* ==========================================================================
   play.html  ->  body.pg-play

   COMPOSITION: "does it come in the post, or does a van come and build it?"
   The catalogue prints that answer under every price and never explains it.
   56 of 58 rows carry one of the two notes; the other two print something else
   and get their own short section rather than being forced into a field.

   THE TWO FIELDS ARE DRAWN AT DIFFERENT DENSITIES ON PURPOSE, and it is the
   one place the recorded rule about a RM1.50 fidget and a RM4,000 flying fox
   can actually be obeyed, because this page splits exactly along that line.
   19 installed structures: few, wide, one per line, open. 37 posted objects:
   many, tight, two columns from 900px up. The installed rows carry the price
   note under the price because it is the point of the field; the posted rows
   do not repeat it 37 times, it is stated once in the section.
   ========================================================================== */
.pg-play .pl-top{background:var(--white);padding-block:var(--s5) var(--s6)}
.pg-play .pl-top h1{font-size:var(--t-big);max-width:15em;margin-top:var(--s3)}
.pg-play .pl-lede{margin-top:var(--s4);display:grid;gap:var(--s3);
  grid-template-columns:minmax(0,26em) minmax(0,29em);align-items:start;
  font-size:var(--body);color:var(--mute)}
.pg-play .pl-lede b{color:var(--ink)}
.pg-play .pl-lede p:first-child{padding-top:var(--s3)}   /* deliberate: the columns do not align */
@media(max-width:900px){
  .pg-play .pl-lede{grid-template-columns:minmax(0,1fr);gap:var(--s2)}
  .pg-play .pl-lede p:first-child{padding-top:0}
}

.pg-play .pl-inst{background:var(--g-inst);padding-block:var(--s6) var(--s7)}
/* The posted field takes its own ground, sampled from its own objects: #C3A64C,
   7.4% of the saturated pixels across the 25 photographs on this page, the
   yellow of the seesaw seats, the swing frames, the hoops and the smiling
   trampoline. The installed field keeps the blue of the metal playgrounds.
   Two purchases, two grounds, and both measured off this page's own pictures. */
.pg-play .pl-post{background:var(--g-post);padding-block:var(--s7) var(--s6)}
.pg-play .pl-two{background:var(--g-cool);padding-block:var(--s5) var(--s5)}
.pg-play .pl-note{background:var(--g-paper);padding-block:var(--s6) var(--s5)}
.pg-play .pl-inst h2,.pg-play .pl-post h2{font-size:var(--t-big);max-width:12em}
.pg-play .pl-two h2,.pg-play .pl-note h2{font-size:var(--t-mid);max-width:17em}
/* MEASURE, SESSION 15 AUG. The say/note prose family was capped at 38 to 42em,
   which this face renders at 76 to 88 characters a line at 1440: over the 75ch
   ceiling every measured line. 34em puts the worst line at 73ch and the family
   on one value. Measured with a Range against the rendered first line, not
   estimated. floors .pl-say never measured over and is left as authored. */
.pg-play .pl-say{margin-top:var(--s3);max-width:34em;font-size:var(--body);color:var(--mute)}

.pg-play .pl-list{list-style:none;margin:var(--s5) 0 0;padding:0;display:grid}
.pg-play .pl-two .pl-list{margin-top:var(--s4);gap:var(--s3)}

/* few, wide, open */
/* SESSION K. The two fields now differ by PICTURE SIZE as well as by column
   count, which is the one thing that was missing: the installed field was 19
   wide rows carrying nothing but type, so the top third of this page rendered
   as an empty band. A flying fox arriving should not look like a skipping rope
   arriving, and now it does not: installed structures draw at 260px, posted
   objects at 92px, a ratio of nearly three. Both are cut at 320px, so nothing
   is upscaled. */
.pg-play .pl-fig{align-self:center;justify-self:start}
.pg-play .pl-fig img{display:block;height:auto}
.pg-play .pl-list-i .pl-fig img{width:min(260px,100%)}
.pg-play .pl-list-p .pl-fig img{width:min(92px,100%)}
.pg-play .pl-list-i{gap:var(--s3)}
.pg-play .pl-list-i .pr{grid-template-columns:17rem minmax(0,26em) 13rem auto;
  padding-block:var(--s3)}
.pg-play .pl-list-i .pr-n{font-size:var(--t-sub)}

/* Many, tight, two columns where there is room.

   COLUMNS, NOT A TWO-COLUMN GRID. The first attempt was
   grid-template-columns:repeat(2,1fr), and a two-column grid row-aligns: every
   row is as tall as its tallest cell, so the moment one row carried a note box
   the cell beside it opened a 250px hole. Measured at 1440 and visible in
   _look-i/play-1440.png. CSS columns flow instead of aligning, so nothing is
   pushed down by its neighbour, and as a bonus the reading order becomes top
   to bottom down column one and then column two, which is the right way to
   read a list sorted cheapest first. */
.pg-play .pl-list-p{display:block;margin-top:var(--s4)}
@media(min-width:900px){.pg-play .pl-list-p{columns:2;column-gap:var(--s5)}}
.pg-play .pl-list-p .pr{grid-template-columns:6.5rem minmax(0,1fr) 6.5rem auto;
  padding-block:12px;break-inside:avoid;-webkit-column-break-inside:avoid}

.pg-play .pr{display:grid;align-items:start;justify-content:start;
  grid-template-columns:minmax(0,34em) 8rem auto;gap:var(--s1) var(--s3)}
.pg-play .pr-txt{display:grid;justify-items:start;gap:3px;min-width:0}
.pg-play .pr-n{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:1.0625rem;line-height:1.2;letter-spacing:-.01em}
.pg-play .pr-s{font-size:var(--body);font-variant-numeric:tabular-nums}
.pg-play .pr-note{font-size:var(--meta);color:var(--ink);max-width:31em;
  background:var(--white);padding:10px var(--s2);border-radius:var(--r-ui);margin-block:4px}
.pg-play .pl-post .pr-note,.pg-play .pl-two .pr-note{background:var(--beige)}
.pg-play .pl-two .pr-note{background:var(--white)}
.pg-play .pr-p{justify-self:end;text-align:right;font-size:1.0625rem;padding-top:2px;
  display:grid;justify-items:end;gap:4px}
.pg-play .pr-fn{font-size:var(--meta);color:var(--mute);font-style:italic;max-width:12em}
.pg-play [data-verbatim]{font-style:italic}
.pg-play .pr-s[data-verbatim]{font-style:normal;letter-spacing:.01em}

.pg-play .pl-note p{margin-top:var(--s3);max-width:34em;font-size:var(--body);color:var(--mute)}
.pg-play .pl-note p b{color:var(--ink)}
.pg-play .pl-note .btn{margin-top:var(--s4)}

@media(max-width:1160px){
  .pg-play .pr{justify-content:stretch;grid-template-columns:minmax(0,1fr) 10rem auto}
  .pg-play .pl-list-i .pr{justify-content:stretch;
    grid-template-columns:13rem minmax(0,1fr) 10rem auto}
  .pg-play .pl-list-i .pl-fig img{width:min(200px,100%)}
  .pg-play .pl-list-p .pr{grid-template-columns:5rem minmax(0,1fr) 6.5rem auto}
}
@media(max-width:720px){
  .pg-play .pr,.pg-play .pl-list-i .pr,.pg-play .pl-list-p .pr{
    grid-template-columns:5.5rem minmax(0,1fr) auto;
    grid-template-areas:"fig txt txt" "fig p add";gap:var(--s1) var(--s2);padding-block:var(--s2)}
  .pg-play .pl-fig{grid-area:fig;align-self:start}
  .pg-play .pl-list-i .pl-fig img,.pg-play .pl-list-p .pl-fig img{width:100%}
  .pg-play .pr-txt{grid-area:txt}
  .pg-play .pr-p{grid-area:p;justify-self:start;text-align:left;justify-items:start;align-self:center}
  .pg-play .pr-fn{max-width:22em}
  .pg-play .pr .add{grid-area:add;justify-self:end;align-self:center}
}

/* ==========================================================================
   learning.html  ->  body.pg-learning

   COMPOSITION: a price ladder. 140 of 156 print a price and they span
   RM0.60 to RM2,699.00, 4,498x, the widest range on the site. The price
   therefore LEADS every row, set tabular so the column of figures can be
   scanned straight down, and the list runs cheapest first with no invented
   bands in it. No other page here puts money in the leading column.

   ONE COLUMN, deliberately, where play.html's posted field is two. That field
   is 37 small objects and reads as a bin to rummage in; this is 140 rows whose
   whole point is a single continuous order, and breaking it into two columns
   would mean the ladder restarts halfway down the page. Long is correct here.

   The unpriced 16 get "ask us" or "in a set" in the same leading column, at
   label size rather than figure size, so the ladder's shape stays readable and
   a missing price never looks like a cheap one.
   ========================================================================== */
/* SESSION K. THE LADDER IS NOW VISIBLE ON SCREEN AND NOT ONLY TRUE IN THE DATA.
   Ashyun: "All the pages just look like a template." At 1440 this page was
   20,966px of one pale column carrying 140 rows drawn identically, and a
   reader cannot see a 4,498x price range in a list where every line is the
   same height. So the PICTURE IS SIZED BY THE PRICE, on a log scale because
   the range is four and a half thousand fold: --pw is written per row by the
   builder from that row's own printed figure. The column it sits in is FIXED
   at 13rem so the price, name and Add button stay on the same verticals all
   the way down; only the image inside it grows. Distinctive must not mean
   harder to scan.
   Nothing is upscaled: the assets are cut at 260px and the largest draw is
   200px. */
.pg-learning .lr-top{background:var(--white);padding-block:var(--s5) var(--s4)}
.pg-learning .lr-top h1{font-size:var(--t-huge);max-width:11em;margin-top:var(--s3)}

/* The opening move: the two ends of the ladder, at the true relative size the
   ladder gives them, before a word of body copy. No other page here opens on a
   size comparison. */
.pg-learning .lr-ends{background:var(--white);padding-block:var(--s3) var(--s6)}
.pg-learning .lr-ends-in{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:var(--s4);align-items:end}
.pg-learning .lr-end{display:grid;justify-items:start;gap:var(--s2)}
.pg-learning .lr-end img{display:block;height:auto;width:var(--pw)}
.pg-learning .lr-end b{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-big);line-height:1;letter-spacing:-.028em;color:var(--price);
  font-variant-numeric:tabular-nums}
.pg-learning .lr-end i{font-style:normal;font-size:var(--meta);color:var(--mute);max-width:26em}
.pg-learning .lr-end i b{font-size:var(--body);color:var(--ink);display:block;letter-spacing:-.01em}
@media(max-width:700px){
  .pg-learning .lr-ends-in{grid-template-columns:minmax(0,1fr);gap:var(--s5)}
}
.pg-learning .lr-lede{margin-top:var(--s4);display:grid;gap:var(--s3);
  grid-template-columns:minmax(0,25em) minmax(0,29em);align-items:start;
  font-size:var(--body);color:var(--mute)}
.pg-learning .lr-lede b{color:var(--ink)}
.pg-learning .lr-lede p:last-child{padding-top:var(--s3)}
@media(max-width:900px){
  .pg-learning .lr-lede{grid-template-columns:minmax(0,1fr);gap:var(--s2)}
  .pg-learning .lr-lede p:last-child{padding-top:0}
}

.pg-learning .lr-ladder{background:var(--g-learn);padding-block:var(--s6) var(--s7)}
.pg-learning .lr-none{background:var(--g-learn-2);padding-block:var(--s6) var(--s6)}
.pg-learning .lr-note{background:var(--g-paper);padding-block:var(--s6) var(--s5)}
.pg-learning .lr-ladder h2,.pg-learning .lr-none h2{font-size:var(--t-big);max-width:13em}
.pg-learning .lr-note h2{font-size:var(--t-mid);max-width:17em}
.pg-learning .lr-say{margin-top:var(--s3);max-width:34em;font-size:var(--body);color:var(--mute)}
.pg-learning .lr-say b{color:var(--ink)}
.pg-learning .list-note{max-width:32em} /* the shared 34em still measured 76ch in this section's size; 32em is 71 */
.pg-learning .lr-say-2{margin-top:var(--s2);max-width:36em;font-size:var(--meta)}
.pg-learning .lr-h3{margin-top:var(--s6);font-size:var(--t-mid);max-width:16em}

.pg-learning .lr-list{list-style:none;margin:var(--s5) 0 0;padding:0;display:grid}
.pg-learning .lr-list-n{margin-top:var(--s3)}
.pg-learning .lr{display:grid;align-items:start;justify-content:start;
  grid-template-columns:8.5rem 13rem minmax(0,28em) auto;
  gap:var(--s1) var(--s3);padding-block:11px}
/* The picture is set against the RIGHT edge of its column, so every image's
   right edge lands on the same vertical as the name column beside it and the
   growth reads leftwards as a bar. Left-aligned it read as a ragged gutter and
   the ladder was invisible again. */
.pg-learning .lr-fig{align-self:center;justify-self:end;min-height:1px}
.pg-learning .lr-fig img{display:block;width:var(--pw);height:auto}
.pg-learning .lr-p{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-sub);line-height:1.15;letter-spacing:-.02em;color:var(--price);
  font-variant-numeric:tabular-nums;text-align:right}
.pg-learning .lr-p-none{font-size:var(--fig);letter-spacing:.09em;
  text-transform:uppercase;color:var(--mute);padding-top:5px}
.pg-learning .lr-txt{display:grid;justify-items:start;gap:3px;min-width:0}
.pg-learning .lr-n{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:1.0625rem;line-height:1.2;letter-spacing:-.01em}
.pg-learning .lr-s{font-size:var(--body);font-variant-numeric:tabular-nums}
.pg-learning .lr-s.lr-nosize{color:var(--mute);font-size:var(--meta)}
/* NOT .lr-none. That class is the unpriced SECTION, which carries
   padding-block:104px and a background. Using the same name on an inline span
   put a 230px pale box round the words "no size printed" on 85 rows. The gate
   passed it: nothing overlapped and nothing overflowed the viewport. It was
   found by looking at the page. */
.pg-learning .lr-cat{font-size:var(--fig);letter-spacing:.08em;text-transform:uppercase;
  color:var(--mute)}
.pg-learning .lr-note-x{display:none}
.pg-learning .lr-txt .lr-note{font-size:var(--meta);color:var(--ink);max-width:33em;
  background:var(--white);padding:10px var(--s2);border-radius:var(--r-ui);margin-block:4px}
.pg-learning .lr-none .lr-txt .lr-note{background:var(--white)}
.pg-learning [data-verbatim]{font-style:italic}
.pg-learning .lr-s[data-verbatim]{font-style:normal;letter-spacing:.01em}

.pg-learning .lr-note p{margin-top:var(--s3);max-width:34em;font-size:var(--body);color:var(--mute)}
.pg-learning .lr-note p b{color:var(--ink)}
.pg-learning .lr-note .btn{margin-top:var(--s4)}

@media(max-width:1080px){
  .pg-learning .lr{justify-content:stretch;
    grid-template-columns:8.5rem 9rem minmax(0,1fr) auto}
}
@media(max-width:700px){
  .pg-learning .lr{grid-template-columns:5.5rem minmax(0,1fr) auto;
    grid-template-areas:"fig p add" "fig txt txt";gap:var(--s1) var(--s2);
    padding-block:var(--s2)!important}
  .pg-learning .lr-fig{grid-area:fig;align-self:start;justify-self:start;padding-top:2px}
  /* On a phone the column is fixed at 5.5rem and the picture fills it, because
     a 26px thumbnail next to a 200px one is unreadable at that width. The
     ladder is carried by the price figure there, which is where it started. */
  .pg-learning .lr-fig img{width:100%}
  .pg-learning .lr-fig-none{display:none}
  .pg-learning .lr-p{grid-area:p;text-align:left;align-self:center}
  .pg-learning .lr-txt{grid-area:txt}
  .pg-learning .lr .add{grid-area:add;justify-self:end;align-self:center}
}

/* ==========================================================================
   specsheets.html  ->  body.pg-spec

   COMPOSITION: "four numbers wide, one number tall". These 22 are the only
   things this client documents as a full orthographic set, so the page gives
   each structure its four views as four labelled cells, side by side, and
   leads the row with the FRONT figure because that is the only one printed on
   all 22 and the only one that always spans the whole face.

   Ground --g-spec, which was sampled from #4059A4, their own elevation drawing
   blue, and has been waiting in the tokens for the page it was taken for.

   The four view cells are the only place on this site where a row contains a
   small table of its own. That is deliberate and it is what an orthographic
   set is: the same object measured four times.
   ========================================================================== */
.pg-spec .sp-top{background:var(--white);padding-block:var(--s5) var(--s6)}
/* THE DRAWINGS OPEN THIS PAGE NOW, NOT THE SENTENCE ABOUT THEM. The page used
   to begin crumb, headline, lede, with nothing to look at until the reader had
   scrolled past three paragraphs -- the same shape a template gives every page,
   and the one shape this page had no excuse for. It owns 88 elevations at one
   true scale and they were sitting below the fold. `.sp-open` is now the first
   section and carries the h1; this heading follows the drawings and is an h2. */
.pg-spec .sp-top h2{font-size:var(--t-big);max-width:14em;margin-top:var(--s3)}
.pg-spec .sp-lede{margin-top:var(--s4);display:grid;gap:var(--s3);
  grid-template-columns:minmax(0,25em) minmax(0,30em);align-items:start;
  font-size:var(--body);color:var(--mute)}
.pg-spec .sp-lede b{color:var(--ink)}
@media(max-width:900px){.pg-spec .sp-lede{grid-template-columns:minmax(0,1fr)}}

.pg-spec .sp-name{background:var(--beige);padding-block:var(--s6) var(--s6)}
.pg-spec .sp-list-sec{background:var(--g-spec);padding-block:var(--s6) var(--s7)}
.pg-spec .sp-note{background:var(--g-paper);padding-block:var(--s6) var(--s5)}
.pg-spec .sp-name h2,.pg-spec .sp-list-sec h2{font-size:var(--t-big);max-width:13em}
.pg-spec .sp-note h2{font-size:var(--t-mid);max-width:17em}
.pg-spec .sp-say{margin-top:var(--s3);max-width:34em;font-size:var(--body);color:var(--mute)}
.pg-spec .sp-say b{color:var(--ink)}
.pg-spec .sp-say-2{margin-top:var(--s3);max-width:34em}

.pg-spec .sp-list{list-style:none;margin:var(--s5) 0 0;padding:0;display:grid;gap:var(--s5)}
.pg-spec .sp{display:grid;align-items:start;justify-content:start;
  grid-template-columns:9rem minmax(0,42em) auto;gap:var(--s2) var(--s3)}
.pg-spec .sp-lead{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:1.75rem;line-height:.95;letter-spacing:-.03em;color:var(--forest);
  font-variant-numeric:tabular-nums;display:grid;justify-items:start}
.pg-spec .sp-lead-u{font-size:var(--fig);letter-spacing:.06em;color:var(--mute);
  font-weight:700;margin-top:6px;max-width:8em;line-height:1.25}
.pg-spec .sp-body{display:grid;justify-items:start;gap:5px;min-width:0}
.pg-spec .sp-h3{font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--fig);
  letter-spacing:.12em;text-transform:uppercase;color:var(--mute)}
.pg-spec .sp-c{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-sub);line-height:1.15;letter-spacing:-.02em}
.pg-spec .sp-meta{font-size:var(--meta);color:var(--mute)}
.pg-spec .sp-views{margin-top:var(--s1);display:grid;gap:var(--s1) var(--s2);
  grid-template-columns:repeat(4,minmax(0,1fr));width:100%}
.pg-spec .sv{display:grid;justify-items:start;gap:2px;background:var(--white);
  padding:10px var(--s2);border-radius:var(--r-ui)}
.pg-spec .sv-l{font-size:var(--fig);letter-spacing:.07em;text-transform:uppercase;
  color:var(--mute);font-family:"Gabarito",sans-serif;font-weight:700}
.pg-spec .sv-w{font-family:"Gabarito",sans-serif;font-weight:700;font-size:1.0625rem;
  font-variant-numeric:tabular-nums}
.pg-spec .sv-h{font-size:var(--meta);color:var(--mute);font-variant-numeric:tabular-nums}
.pg-spec .sv-x .sv-w{color:var(--mute);font-size:var(--meta);font-weight:400}

/* SUPERSCRIPT WITHOUT A SUPERSCRIPT CHARACTER. U+00B2 is in neither inlined
   subset, so "m²" rendered its last character in the system face on thirteen
   rows of floors.html. A plain digit raised by CSS is in both faces. The
   default <sup> also inflates line-height, which would push those rows around,
   so the line-height is pinned. Nothing here goes below the 14px floor because
   the digit is drawn small but the box is not a type size in the scale. */
sup{font-size:.62em;line-height:0;vertical-align:.42em;
  font-variant-numeric:tabular-nums}

/* ---------- the elevations, 88 of them, session M ----------
   The drawings sit ABOVE the label and the figure inside each view cell, so a
   reader meets the shape before the number. Width and height are written on
   every <img> by the builder from the bitmap's own pixels, so the box is
   reserved before the file arrives and nothing on this page reflows as it
   loads -- the four views of a row would otherwise jump sideways one at a time.
   `align-self:end` sits every drawing on a common baseline, which is what makes
   four elevations at one scale read as one structure rather than four pictures.
   No frame, no ground, no shadow: the drawings are on white already and a box
   around them would be another line. */
.pg-spec .sv{align-content:start}
.pg-spec .sv-img{height:auto;max-width:100%;align-self:end;justify-self:center;
  margin-bottom:6px}
.pg-spec .sv{justify-items:center;text-align:center}
.pg-spec .sv-h,.pg-spec .sv-w,.pg-spec .sv-l{text-align:center}

/* the opening: one structure, four faces, one scale */
.pg-spec .sp-open{background:var(--white);padding-block:var(--s4) var(--s6)}
.pg-spec .sp-open h1{font-size:var(--t-mid);max-width:16em;margin-top:var(--s3)}
/* ONE SCALE, AND THE SCALE SURVIVES A NARROW SCREEN. The first build wrapped
   these four onto two rows and capped each figure at 46% of the row, which
   made the 100 cm side render nearly as wide as the 609.60 cm front. The
   heading says "same scale", so the layout has to be the thing that guarantees
   it rather than the thing that quietly breaks it.
   `flex:<bitmap width> 0 0` divides the row, minus its gaps, in exact
   proportion to the drawings' own widths at every viewport, so the ratio is
   held by the layout and cannot drift. The side views end up genuinely small
   on a phone, and that is the fact this page exists to show. */
.pg-spec .sp-open-row{margin-top:var(--s5);display:flex;flex-wrap:nowrap;
  align-items:flex-end;gap:var(--s3)}
.pg-spec .sp-of{margin:0;min-width:0;display:grid;justify-items:center;gap:6px;
  align-content:end}
.pg-spec .sp-of .sv-img{width:100%;height:auto;margin-bottom:0}
/* THE FOUR DRAWINGS STAND ON ONE LINE. They are bottom-aligned, so anything
   below them that is two lines tall in one column and one line tall in the
   next lifts that column's drawing and the ground under the set stops being
   flat. At 1024 "100.00 cm" is 78px against a 75px column and wraps; the front
   does not. Reserving two lines under every drawing costs nothing and the
   elevations sit on the same line at every width. */
.pg-spec .sp-of .sv-l{order:3;min-height:2.3em}
.pg-spec .sp-of .sp-ofc{min-height:2.3em}
.pg-spec .sp-ofc{order:2;font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:1.0625rem;font-variant-numeric:tabular-nums}
/* The key is the four figures again on one line. It exists for the widths at
   which a 100 cm side elevation is 21 CSS px across and no word fits under it.
   Above 520px the label and the figure sit under their own drawing, which is
   better, and the key is not rendered at all rather than repeated. */
.pg-spec .sp-open-key{display:none}
@media(max-width:520px){
  .pg-spec .sp-open-row{gap:var(--s2);align-items:flex-end}
  /* THE WORDS LEAVE THE ROW, THE DRAWINGS KEEP THE SCALE. Measured at 320:
     "Side, left" ran 131-175 and "Side, right" began at 168. Two labels seven
     pixels into one another, and the same on the two figures. */
  .pg-spec .sp-of .sv-l,.pg-spec .sp-ofc{display:none}
  .pg-spec .sp-open-key{display:block;margin-top:var(--s2);
    font-size:var(--body);color:var(--ink);line-height:1.7}
  .pg-spec .sp-open-key b{font-family:"Gabarito",sans-serif;font-weight:700;
    font-variant-numeric:tabular-nums;white-space:nowrap}
}
.pg-spec .sp-note-x{display:none}
.pg-spec .sp-body .sp-note{font-size:var(--meta);color:var(--ink);max-width:38em;
  background:var(--white);padding:10px var(--s2);border-radius:var(--r-ui);margin-top:4px}
.pg-spec .sp-det{font-size:var(--meta);color:var(--mute);max-width:38em}
.pg-spec [data-verbatim]{font-style:italic}

.pg-spec .sp-note p{margin-top:var(--s3);max-width:34em;font-size:var(--body);color:var(--mute)}
.pg-spec .sp-note p b{color:var(--ink)}
.pg-spec .sp-note .btn{margin-top:var(--s4)}

@media(max-width:1080px){
  .pg-spec .sp{justify-content:stretch;grid-template-columns:9rem minmax(0,1fr) auto}
}
@media(max-width:820px){.pg-spec .sp-views{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media(max-width:700px){
  .pg-spec .sp{grid-template-columns:minmax(0,1fr) auto;
    grid-template-areas:"lead add" "body body";gap:var(--s2)}
  .pg-spec .sp-lead{grid-area:lead;grid-auto-flow:column;align-items:baseline;gap:8px}
  .pg-spec .sp-lead-u{margin-top:0;max-width:none}
  .pg-spec .sp-body{grid-area:body}
  .pg-spec .sp .add{grid-area:add;justify-self:end;align-self:center}
}

/* ==========================================================================
   MOTION, SESSION J.  The eight pages that had none.

   Before this block: index 13 scroll-driven rules, boards 3, playground 1,
   tables 1, and NOTHING on therapy, projects, contact, floors, storage, play,
   learning or specsheets. Nine of twelve pages were static, which on a site
   whose first page moves reads as nine pages someone else finished.

   THREE RULES, and they are the whole of it.

   1. ONE CURVE, ONE DURATION. var(--settle) cubic-bezier(0.65,0.05,0,1) at
      var(--slow) 0.75s, the same tokens page one already uses. No new easing
      and no new duration enters the build here.

   2. IT MUST HELP SOMEONE ORIENT. Every rule below animates a thing ARRIVING
      -- a section, a heading, a picture. Nothing animates to decorate, nothing
      animates twice, and nothing animates that a reader is already looking at.

   3. NO CASCADES, AND THIS IS THE ONE THAT SHAPED EVERY DECISION HERE.
      A staggered run of six items each with its own delay is a template
      signature. So on the long pages the ROWS DO NOT MOVE. storage has 64
      rows, play 37 posted objects, learning 156: animating those would be the
      single most generic thing this build could do, and it would also make a
      156-row price ladder unreadable while it assembled. What moves is the
      thing that tells you where you have arrived.

   Varied per page ON PURPOSE, because the same treatment twelve times is
   itself the tell. Each page moves in the direction its own composition is
   organised in: floors lifts (it is sorted by area), storage arrives from the
   left (it is a column of named answers), play travels furthest on the widest
   objects, learning holds still except where it changes subject, specsheets
   moves the least of all because it is a page of measurements.

   Native scroll-driven animation throughout: animation-timeline: view(),
   no scroll listener, no library, no JavaScript. Browsers without it simply
   show the finished state, which is the correct fallback.
   ========================================================================== */

/* ---------- floors: the two halves lift by two different amounts ----------
   The page is sorted by how much floor a thing covers, so its motion is
   vertical. Carpets are large and get the full 26px settle; mats are the
   tighter half and get 16px, the same 10px difference their rows already
   carry. The picture and its figure arrive together as one object, because
   the figure describes the picture. */
@media(prefers-reduced-motion:no-preference){
  .pg-floors .fl-carpets .fr-fig,
  .pg-floors .fl-carpets .fr-cov{
    animation:settleUp var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 2% cover 20%}
  .pg-floors .fl-mats .fr-fig,
  .pg-floors .fl-mats .fr-th{
    animation:rowIn var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 2% cover 18%}
}

/* ---------- storage: the named groups arrive, the 64 rows do not ----------
   This page's composition is "what are you putting in it", answered by the
   group heading. So the heading is what moves, from the left, because it is
   read as a label on a column rather than as a thing rising off the page. */
@media(prefers-reduced-motion:no-preference){
  .pg-storage .stg-h{
    animation:fromLeft var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 0% cover 16%}
}

/* ---------- play: the widest objects travel furthest ----------
   19 installed structures are drawn few and wide, one per line; 37 posted
   objects are drawn many and tight in CSS columns. The installed ones get the
   58px rise, which is the largest travel anywhere in this build and is
   earned: a flying fox arriving should not feel like a skipping rope
   arriving. The 37 posted objects do not animate at all. */
@media(prefers-reduced-motion:no-preference){
  .pg-play .pl-inst .pr{
    animation:riseIn var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 0% cover 24%}
  .pg-play .pl-post h2,.pg-play .pl-post .pl-say{
    animation:settleUp var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 0% cover 18%}
}

/* ---------- learning: 156 rows hold absolutely still ----------
   The page is one unbroken price ladder read cheapest first, and a ladder
   that assembles itself as you descend it cannot be read. Only the two
   section heads move, and the unpriced section moves further than the priced
   one because it is the point of the page and a different kind of thing. */
@media(prefers-reduced-motion:no-preference){
  .pg-learning .lr-ladder h2{
    animation:chipIn var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 0% cover 14%}
  .pg-learning .lr-none h2,.pg-learning .lr-none .lr-h3{
    animation:settleUp var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 0% cover 20%}
}

/* ---------- specsheets: the smallest movement on the site ----------
   22 structures, four measured widths each. A page of figures should not
   bounce; 14px is enough to say a new structure has begun and not enough to
   make a number hard to read on the way in. */
@media(prefers-reduced-motion:no-preference){
  .pg-spec .sp-lead{
    animation:chipIn var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 0% cover 16%}
}

/* ---------- therapy: the group bands ---------- */
@media(prefers-reduced-motion:no-preference){
  .pg-therapy .band h2{
    animation:settleUp var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 0% cover 18%}
}

/* ---------- projects: the photograph, then its caption ----------
   The only page on the site whose subject is a photograph of a finished room,
   so here the picture is what arrives and the caption follows it by arriving
   over a slightly longer range rather than by carrying a delay. A range is
   scroll position; a delay is a timer, and a timer is what makes a stagger
   feel like a template. */
@media(prefers-reduced-motion:no-preference){
  .pg-projects .pj-f img{
    animation:settleUp var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 2% cover 22%}
  .pg-projects .pj-t{
    animation:settleUp var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 2% cover 30%}
}

/* ---------- contact: the numbered rows ----------
   Five ways to reach a person. They arrive from the left because the number
   leads the row and the number is what you read first. */
@media(prefers-reduced-motion:no-preference){
  .pg-contact .nrow{
    animation:fromLeft var(--slow) var(--settle) both;
    animation-timeline:view();animation-range:entry 0% cover 20%}
}

/* REDUCED MOTION. Not a shorter animation and not a fade: the finished state,
   immediately. Everything this block adds is an arrival, so removing the
   arrival must leave the thing present and in place, never half transparent
   and never offset. */
@media(prefers-reduced-motion:reduce){
  .pg-floors .fr-fig,.pg-floors .fr-cov,.pg-floors .fr-th,
  .pg-storage .stg-h,
  .pg-play .pl-inst .pr,.pg-play .pl-post h2,.pg-play .pl-post .pl-say,
  .pg-learning .lr-ladder h2,.pg-learning .lr-none h2,.pg-learning .lr-none .lr-h3,
  .pg-spec .sp-lead,
  .pg-therapy .band h2,
  .pg-projects .pj-f img,.pg-projects .pj-t,
  .pg-contact .nrow{
    animation:none !important;opacity:1 !important;transform:none !important}
}

/* ---------- shared photographs, session M ----------
   Our books print one photograph against two or more rows in a handful of
   places. Session K's answer was to show none of them, which left the largest
   block of blank rows on the site; a buyer will not enquire about a thing they
   cannot see. The picture is published now and this line says whose it is, so
   nothing implies the photograph is of that row alone. It sits INSIDE the
   figure, under the image, because it is a fact about the picture and not
   about the product. No rule above it and no box around it: it is told apart
   by being smaller and quieter, which is how every other secondary line on
   this site is told apart. */
.sh-note{display:block;margin-top:6px;font-size:var(--meta);color:var(--mute);
  max-width:32em;text-wrap:pretty}

/* --------------------------------------------------------------------------
   404.html  ->  body.pg-404
   Cloned from contact.html so the header, menu, search, enquiry dock, footer
   and the two inlined faces are the same objects every other page uses. Only
   these five rules are its own. No borders, no shadows, one radius.
   -------------------------------------------------------------------------- */
.pg-404 .nf-top{background:var(--forest);color:#FFF;padding-block:var(--s5) var(--s7)}
.pg-404 .nf-top h1{color:#FFF;font-size:var(--t-huge);max-width:8em}
.pg-404 .nf-top .crumb{color:rgba(255,255,255,.72)}
.pg-404 .nf-lede{margin-top:var(--s3);max-width:33em;font-size:var(--body);
  color:rgba(255,255,255,.9);line-height:1.55}
.pg-404 .nf-wa{margin-top:var(--s4)}
.pg-404 .nf-list{background:var(--beige);padding-block:var(--s6)}
.pg-404 .nf-list h2{font-size:var(--t-big);max-width:12em}
.pg-404 .nf-ul{margin-top:var(--s4);list-style:none;padding:0;display:grid;
  gap:var(--s2) var(--s5);grid-template-columns:repeat(auto-fill,minmax(min(260px,100%),1fr))}
.pg-404 .nf-ul a{display:flex;gap:var(--s2);align-items:baseline;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--t-mid);
  letter-spacing:-.02em;color:var(--ink);text-decoration:none;padding-block:6px;
  min-height:48px;align-content:center}
.pg-404 .nf-ul .num{font-family:"Poppins",sans-serif;font-weight:400;
  font-size:var(--meta);color:var(--mute);font-variant-numeric:tabular-nums}
@media(hover:hover){.pg-404 .nf-ul a:hover{color:var(--price)}}
@media(hover:none){.pg-404 .nf-ul a:hover{color:var(--ink)}}

/* ==========================================================================
   SESSION T. THE FIVE SIBLING OPENINGS.
   (c) 2026 PPT EDU SUPPLIES

   playground, floors, play, therapy and projects all opened crumb / big left
   headline / two columns of body copy on a pale ground. Five of thirteen
   arriving identically is a template, whatever is written inside them, and it
   was the loudest complaint left on this build.

   Each opening below comes from what its own page contains, and each is a
   different KIND of beginning from the other eight:

     index       a dark forest field with a product illustration
     contact     type alone, no photograph
     storage     one object at full height beside the headline
     tables      a quotation from the client's own book
     learning    two photographs at wildly different sizes
     boards      a grid of colour swatches
     specsheets  four elevations of one structure at one scale
     404         a forest field and a list of ways out
   -- and now --
     playground  a measuring instrument: 14 bars at true relative width
     floors      a plan: ten carpets from above, butted, no gap
     projects    one photograph at full bleed, the largest on the site
     play        a fork: two doors, chosen before any scrolling
     therapy     the entire priced inventory as one block at scroll 0

   TYPOGRAPHY IS VARIED BY USE, NOT BY TYPEFACE. Gabarito Bold and Poppins
   only, as everywhere else. What changes is which register carries the page:
   figures on playground, captions on floors, two numerals on play, a plate on
   projects, and 72 lines at the 14px floor on therapy. No new font file, no
   new weight, no font preload tag.

   NO LINES, NO GRADIENTS, NO SHADOWS. Every label that sits on a photograph
   is a filled field, which is the house convention for a control, and every
   pair below was measured for contrast rather than eyedropped.
   ========================================================================== */

/* ---------- PLAYGROUND. A MEASURING INSTRUMENT. -----------------------------
   The page exists to be measured against a room, so it opens as a rule. Every
   bar is that frame's own printed width as a percentage of the widest one we
   make, written into --w by tools/t_openings.py from the page's own rows.
   Nothing here is a photograph and nothing is decorative: the bar IS the
   number. 5.73 times between the smallest and the largest is a thing you can
   see the length of, which two figures in a table are not. */
.pg-playground .pg-scale{background:var(--g-play);padding-block:var(--s5) var(--s6)}
.pg-playground .pg-scale h1{font-size:var(--t-big);max-width:15em;margin-top:var(--s2)}
.pg-playground .sc-say{margin-top:var(--s4);max-width:38em;font-size:var(--body);
  color:var(--mute);line-height:1.55}
.pg-playground .sc-say b{color:var(--ink)}
.pg-playground .sc-list{margin:var(--s4) 0 0;padding:0;list-style:none;
  display:grid;gap:var(--s3)}
/* The name sits WITH its own figure on the line above its own bar. A first
   attempt put the name under the bar in a second grid row and, looked at, it
   read as belonging to the NEXT frame down: 4px of gap inside a row against
   10px between rows is not a difference the eye can use. */
.pg-playground .sc-b{display:grid;gap:6px}
.pg-playground .sc-hd{display:flex;gap:.75em;align-items:baseline;flex-wrap:wrap}
.pg-playground .sc-cm{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-sub);letter-spacing:-.02em;font-variant-numeric:tabular-nums;
  color:var(--ink);min-width:4.4em}
.pg-playground .sc-nm{font-size:var(--fig);letter-spacing:.1em;
  text-transform:uppercase;color:var(--mute)}
.pg-playground .sc-r{display:block;height:18px;width:var(--w);min-width:3px;
  background:var(--forest);border-radius:var(--r-ui);transform-origin:left center}
/* The full three figures stay in the accessibility tree, out of the drawing,
   using the site's own .vh utility rather than a second copy of it. */
.pg-playground .sc-foot{margin-top:var(--s4);max-width:32em;font-size:var(--meta);
  color:var(--mute)}
@media(max-width:560px){
  .pg-playground .sc-list{gap:var(--s2)}
  .pg-playground .sc-cm{min-width:0}
}

/* ---------- FLOORS. A PLAN. -------------------------------------------------
   A floor is only ever seen from above, so the carpets are laid edge to edge
   with no gap, each given a share of the row in proportion to the square
   metres it actually covers. The areas are the page's own: it already prints
   m2 against every carpet, computed from that carpet's two printed sides.
   flex-grow carries the proportion, so the plan re-proportions itself if a
   carpet is added. --a is the area in square metres. */
.pg-floors .fl-plan{background:var(--g-paper);padding-bottom:var(--s6)}
.pg-floors .pl-floor{margin:0;padding:0;list-style:none;display:flex;flex-wrap:wrap;
  align-items:stretch}
.pg-floors .pl-t{flex:var(--a) 1 clamp(150px,17vw,240px);min-width:0;
  display:flex;flex-direction:column;background:var(--white)}
.pg-floors .pl-t img{display:block;width:100%;flex:1 1 auto;min-height:0;
  aspect-ratio:4/3;object-fit:cover}
/* THE CAPTION IS IN FLOW, NOT OVER THE PICTURE. A first version put a filled
   white chip on top of the carpet. The overlap sweep fired at 1440 and at
   760x420 and it was right for a reason that only shows up in the sweep: the
   <sup> in "m2" is set with line-height:0, so its box hangs outside the chip
   and sits on the photograph. In flow the tiles still butt with no gap and
   the band still reads as a plan, and nothing overlaps anything. */
.pg-floors .pl-c{background:var(--white);color:var(--ink);padding:8px 10px 10px;
  font-size:var(--fig);font-weight:600;line-height:1.3;display:flex;
  flex-direction:column;gap:2px}
/* The area figure is the subject of this plan, so it carries the caption, and
   it is set at --t-mid, which is the size the SAME figure already takes in
   .fr-cov in the price list below. Two off-scale attempts came first and both
   were caught by the sub-14px sweep rather than by reading the CSS: at
   var(--fig) the raised 2 measured 8.68px and at 1.375rem it measured 13.64px.
   sup is .62em, so the base has to be at least 22.6px for the raised digit to
   clear the floor. --t-mid is 28px and gives 17.4px. */
.pg-floors .pl-c i{font-style:normal;font-family:"Gabarito",sans-serif;
  font-weight:700;font-size:var(--t-mid);letter-spacing:-.026em;color:var(--ink);
  font-variant-numeric:tabular-nums;line-height:1.05}
.pg-floors .pl-under{padding-top:var(--s5)}
.pg-floors .pl-under h1{font-size:var(--t-big);max-width:14em;margin-top:var(--s2)}
.pg-floors .pl-say{margin-top:var(--s3);max-width:34em;font-size:var(--body);
  color:var(--mute);line-height:1.55}
.pg-floors .pl-say b{color:var(--ink)}
.pg-floors .pl-none{margin-top:var(--s3);max-width:40em;font-size:var(--meta);
  color:var(--mute)}
@media(max-width:640px){
  /* Ten tiles across a 320 screen is 32px each, which is not a plan, it is a
     texture. Two to a row keeps it readable and still butted. */
  .pg-floors .pl-t{flex:1 1 50%}
  .pg-floors .pl-t img{aspect-ratio:1/1}
  .pg-floors .pl-c{padding:6px 8px 8px}
}

/* ---------- PROJECTS. ONE ROOM, FULL BLEED. ---------------------------------
   A portfolio leads with a job. This is the only full bleed photograph on the
   site: on the other twelve pages the largest image sits inside a column, and
   the difference is the point. The caption is a filled forest field, white on
   --forest at 12.60:1, so nothing is scrimmed, tinted or shadowed over the
   photograph. No scale() on hover: an installed room is the content. */
.pg-projects .pj-lead{background:var(--white)}
.pg-projects .pj-bleed{margin:0;position:relative;width:100%}
.pg-projects .pj-bleed img{display:block;width:100%;height:clamp(320px,62vh,660px);
  object-fit:cover}
/* THE PLATE SITS UNDER THE PHOTOGRAPH, NOT ON IT. Set into the image it read
   well and failed the overlap sweep at seven of the eight viewports, and the
   sweep is right: this is the one picture on the site nobody has controlled
   the tone of, so no guarantee can be made about text on it. Under the plate
   it is white on --forest at 12.60:1 everywhere, and a label beneath the work
   is what a portfolio does anyway. The band runs the full width, butted to
   the photograph with no gap, so the two still read as one object. */
.pg-projects .pj-cap{background:var(--forest);color:#FFF;
  padding:var(--s3) var(--room);display:grid;gap:3px}
.pg-projects .pj-lk{font-size:var(--fig);letter-spacing:.12em;text-transform:uppercase;
  color:rgba(255,255,255,.78);font-family:"Gabarito",sans-serif;font-weight:700}
.pg-projects .pj-ln{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-mid);letter-spacing:-.024em;line-height:1.05}
.pg-projects .pj-lp{font-size:var(--meta);color:rgba(255,255,255,.86)}
.pg-projects .pj-under{padding-block:var(--s5) var(--s6)}
.pg-projects .pj-under h1{font-size:var(--t-big);max-width:13em;margin-top:var(--s2)}
.pg-projects .pj-say{margin-top:var(--s3);max-width:40em;font-size:var(--body);
  color:var(--mute);line-height:1.55}
@media(max-width:560px){
  .pg-projects .pj-bleed img{height:clamp(260px,52vh,380px)}
  .pg-projects .pj-ln{font-size:var(--t-sub)}
}

/* ---------- PLAY. A FORK. ---------------------------------------------------
   This page's own argument is that the first question is not price, it is
   whether the thing has to be bolted to your floor. So it asks before you
   scroll. The two doors carry --g-inst and --g-post, which are the grounds
   already sampled from the products in the two bands they lead to, so the
   colour is the route. The numeral is the largest type on the page and the
   only place on this site where a count is set at display size. */
.pg-play .pl-fork{background:var(--white);padding-block:var(--s5) var(--s6)}
.pg-play .pl-fork h1{font-size:var(--t-big);max-width:16em;margin-top:var(--s2)}
.pg-play .fk-say{margin-top:var(--s3);max-width:34em;font-size:var(--body);
  color:var(--mute);line-height:1.55}
.pg-play .fk{margin-top:var(--s4);display:grid;gap:var(--s2);
  grid-template-columns:repeat(auto-fit,minmax(min(280px,100%),1fr))}
.pg-play .fk-o{display:grid;gap:6px;align-content:start;padding:var(--s3);
  border-radius:var(--r-ui);text-decoration:none;color:var(--ink);min-height:48px}
.pg-play .fk-van{background:var(--g-inst)}
.pg-play .fk-post{background:var(--g-post)}
.pg-play .fk-n{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:clamp(3.25rem,9vw,5.5rem);line-height:.88;letter-spacing:-.03em;
  font-variant-numeric:tabular-nums;color:var(--ink)}
.pg-play .fk-h{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-mid);letter-spacing:-.022em;line-height:1.1;max-width:11em}
.pg-play .fk-d{margin-top:6px;font-size:var(--meta);color:var(--mute);
  line-height:1.5;max-width:26em}
.pg-play .fk-go{margin-top:var(--s2);font-size:var(--fig);letter-spacing:.1em;
  text-transform:uppercase;font-family:"Gabarito",sans-serif;font-weight:700;
  color:var(--ink)}
.pg-play .fk-foot{margin-top:var(--s4);max-width:38em;font-size:var(--meta);
  color:var(--mute)}
@media(hover:hover){
  .pg-play .fk-o:hover .fk-go{color:var(--price)}
}
@media(hover:none){
  .pg-play .fk-o:hover .fk-go{color:var(--ink)}
}

/* ---------- THERAPY. THE WHOLE CONTENTS AT SCROLL 0. ------------------------
   No other page on this site can show everything it holds without scrolling,
   because no other page is small enough. This one is, just. So the opening is
   the entire priced inventory as one block, set at 14px, the floor for this
   site, in columns that fill the measure. It is meant to read as one object
   whose shape is the size of the page, not as a list to work through. The
   prices are --price because --price is prices only, everywhere. */
.pg-therapy .t-all{background:var(--white);padding-block:var(--s5) var(--s6)}
.pg-therapy .t-all h1{font-size:var(--t-big);max-width:12em;margin-top:var(--s2)}
.pg-therapy .tv{margin:var(--s4) 0 0;padding:0;list-style:none;
  columns:3 17rem;column-gap:var(--s4)}
.pg-therapy .tv-i{break-inside:avoid;display:flex;justify-content:space-between;
  gap:var(--s2);align-items:baseline;padding-block:3px;font-size:var(--fig);
  line-height:1.35}
.pg-therapy .tv-n{color:var(--ink)}
.pg-therapy .tv-p{color:var(--price);font-family:"Gabarito",sans-serif;
  font-weight:700;font-variant-numeric:tabular-nums;white-space:nowrap}
.pg-therapy .t-say{margin-top:var(--s4);max-width:40em;font-size:var(--body);
  color:var(--mute);line-height:1.55}
.pg-therapy .t-say b{color:var(--ink)}
@media(max-width:560px){
  .pg-therapy .tv{columns:1}
}

/* ---------- SESSION T MOTION. FIVE OPENINGS, AND THE PAGE'S SUBJECT DRIVES IT.
   Native animation-timeline:view() throughout, the one house easing, and NO
   CASCADE ON A LONG LIST. Therapy's 72 names and the plan's ten tiles arrive
   as ONE object each, on a single range, because 72 items each with its own
   delay is a template signature and it makes a page feel slow. Where a stagger
   exists at all it is over four elements or fewer.

   The moments are different per page because the subjects are:
     playground  a rule is laid down: the bars extend from the left, together
     floors      a floor is laid: the plan wipes left to right, then captions
     projects    a photograph settles, then its plate arrives from the left
     play        the fork opens: the two doors come from opposite sides
     therapy     the inventory appears as one block, never as 72 arrivals   */
@keyframes ruleOut{from{transform:scaleX(0)}to{transform:scaleX(1)}}
/* THE ONE MOMENT ON THIS SITE THAT IS NOT SCROLL DRIVEN, and the reason is a
   defect that was caught by looking. On a view() timeline each bar sits at its
   own scroll progress, so at scroll 0 the 195 cm frame drew SHORTER than the
   105 cm one and the scale reported lengths that were not true. It runs once,
   on load, all fourteen together, and ends at the measured width. */
@media(prefers-reduced-motion:no-preference){
  .pg-playground .sc-r{animation:ruleOut var(--slow) var(--settle) both}
}
/* layFloor was a clip-path wipe. Same reason as buildUp: it repaints every
   frame. 30px matches fromLeft, the horizontal entrance already in use. */
@keyframes layFloor{from{opacity:0;transform:translate3d(-30px,0,0)}to{opacity:1;transform:none}}
/* THE FORK. Read the two paragraphs below together, because each one is a
   real constraint and the bug lived for several sessions in the gap between
   them.

   The first, written when openL/openR were clip-path wipes: translate3d(30px)
   on the right hand door pushed a full width block 30px past the viewport at
   320, 360, 390 and 440 and put a horizontal scrollbar on the page. A
   transform that moves a full width element to the RIGHT always can.

   The second, written in session AB: clip-path cannot be handed to the
   compositor, so it repaints every frame on a scroll timeline that never
   ends. openL/openR were moved onto transform for that reason, directly
   beneath the paragraph forbidding it, and the 10px overflow came back.
   ac_verify has reported it at 390 ever since.

   Both are right. What was wrong was applying a horizontal opening at a
   width where the doors are not side by side. Below two columns the fork is
   a stack, the figure it is drawn from does not exist, and a full width
   block cannot travel right without leaving the page. So the pair is now
   guarded to the two column case and the stack takes settleUp, the arrival
   .fk-foot directly below it already uses.

   The guard is 700px and not the grid's own switch point. auto-fit turns two
   columns at a content width of 576px, which is a viewport of about 632px;
   700px clears it with margin, and at 700px --room computes to 32.2px, which
   is more than the 30px the right hand door travels. That is the whole
   arithmetic: the door can only move right by less than the gutter it moves
   into. */
@keyframes openL{from{opacity:0;transform:translate3d(-30px,0,0)}to{opacity:1;transform:none}}
@keyframes openR{from{opacity:0;transform:translate3d(30px,0,0)}to{opacity:1;transform:none}}

@supports (animation-timeline:view()){
  @media(prefers-reduced-motion:no-preference){
    /* playground: the scale draws itself. One range for all fourteen bars, so
       they extend together like a rule being laid, not like a list loading. */
    .pg-playground .sc-say{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 2% cover 20%}
    .pg-playground .sc-foot{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 4% cover 24%}

    /* floors: the whole plan is laid in one pass, then the captions land. */
    .pg-floors .pl-floor{animation:layFloor var(--settle) both;
      animation-timeline:view();animation-range:entry 0% cover 22%}
    .pg-floors .pl-under h1{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 4% cover 24%}
    .pg-floors .pl-say{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 6% cover 28%}

    /* projects: the room settles, the plate arrives after it. */
    .pg-projects .pj-bleed img{animation:photoIn linear both;
      animation-timeline:view();animation-range:entry 0% cover 26%}
    .pg-projects .pj-cap{animation:fromLeft linear both;
      animation-timeline:view();animation-range:entry 10% cover 34%}
    .pg-projects .pj-under h1{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 4% cover 24%}

    /* play: the fork opens. Two doors, two directions, one range. Stacked,
       there are no two directions, so it arrives the way the line under it
       does. See the note above @keyframes openL. */
    .pg-play .fk-van,.pg-play .fk-post{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 4% cover 26%}
    @media(min-width:700px){
      .pg-play .fk-van{animation:openL var(--settle) both;
        animation-timeline:view();animation-range:entry 4% cover 26%}
      .pg-play .fk-post{animation:openR var(--settle) both;
        animation-timeline:view();animation-range:entry 4% cover 26%}
    }
    .pg-play .fk-foot{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 8% cover 30%}

    /* therapy: ONE object. Seventy-two names do not arrive seventy-two times. */
    .pg-therapy .tv{animation:buildUp var(--settle) both;
      animation-timeline:view();animation-range:entry 2% cover 20%}
    .pg-therapy .t-say{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 6% cover 28%}

    /* learning: THE PAGE THAT WAS NEVER GIVEN ITS ROW. Measured at 390 in
       session AF, learning.html is 35,443px, forty two screens, the tallest
       page on the site, and it carried six animated elements. Between them
       sat 27,665px, thirty two screens, in which nothing arrived at all.
       Every comparable page already had an answer: tables animates the
       photograph in each of its 35 cards, boards its 93 plates, specsheets
       its 22. This is the same kind of list, 156 rows with a photograph
       each, so it takes the same figure tables uses, the same keyframe, the
       same range: the picture arrives and the row is simply there. Nothing
       new is introduced and no second thing moves in a row. */
    .pg-learning .lr-fig img{animation:tbIn var(--slow) var(--settle) both;
      animation-timeline:view();animation-range:entry 2% cover 22%}
  }
}
@media(prefers-reduced-motion:reduce){
  .pg-playground .sc-r,.pg-playground .sc-say,.pg-playground .sc-foot,
  .pg-floors .pl-floor,.pg-floors .pl-under h1,.pg-floors .pl-say,
  .pg-projects .pj-bleed img,.pg-projects .pj-cap,.pg-projects .pj-under h1,
  .pg-play .fk-van,.pg-play .fk-post,.pg-play .fk-foot,
  .pg-therapy .tv,.pg-therapy .t-say{
    animation:none;opacity:1;transform:none;clip-path:none}
}

/* ---------- SESSION T. THE FIVE PAGES THAT HAD ONE MOMENT OR NONE. ---------
   Measured in a browser with tools/t_look.py motion, scrolling rather than
   screenshotting: storage, tables, contact and specsheets each animated ONE
   thing and 404 animated nothing at all, against nineteen moments on index.
   The session N rules below are kept exactly as they are, including the two
   that matter most -- the long lists still do not move, and every rule still
   animates a thing ARRIVING rather than decorating. What is added is a second
   and third moment on each of the five, and each one is chosen from what that
   page is actually about.

   The timing function on a view() driven rule is `linear` ON PURPOSE and it
   is not a second easing curve: the scroll position IS the progress, so an
   easing on top of it would ease twice and the page would feel sticky. The
   house curve is used wherever the animation is driven by time. A19 confirms
   one curve site-wide.                                                       */
@supports (animation-timeline:view()){
  @media(prefers-reduced-motion:no-preference){

    /* STORAGE. The page is a column of named answers and the heading already
       arrives from the left. Two more: the 175 cm bookshelf that opens the
       page rises, because it is the tallest object on the site and height is
       the thing it is being shown for; and each group's lead object arrives
       after its heading, because the picture is the answer the heading asks
       for. The 64 rows still do not move. */
    .pg-storage .st-hero img{animation:riseIn linear both;
      animation-timeline:view();animation-range:entry 0% cover 24%}
    .pg-storage .stg-lead img{animation:photoIn linear both;
      animation-timeline:view();animation-range:entry 6% cover 30%}

    /* TABLES. The page opens on a sentence from the client's own book, so the
       quotation is what arrives, and it arrives as type: up, once, whole. The
       band headings follow. The 74 rows still do not move; only the 35
       photographs inside the priced lines do, as before. */
    .pg-tables .m5-quote{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 0% cover 18%}
    .pg-tables .m5-src{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 6% cover 26%}
    .pg-tables .band-h3{animation:fromLeft linear both;
      animation-timeline:view();animation-range:entry 4% cover 24%}

    /* CONTACT. There is no product on this page and no photograph. The thing
       a visitor came for is the number, so the number is the thing that
       arrives, and the address after it. Type alone, moving as type. */
    .pg-contact .big-addr{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 2% cover 20%}
    .pg-contact .cost{animation:chipIn linear both;
      animation-timeline:view();animation-range:entry 6% cover 26%}

    /* SPECSHEETS. Session N decided this page should move less than any other
       because it is a page of measurements, and that decision stands: the
       elevations themselves are left alone. What is added is the one thing on
       the page that is not a measurement, the name of each structure, and the
       four-view row it heads, wiped up from the common baseline the four
       drawings already sit on, so a row reads as one structure arriving
       rather than four pictures. */
    .pg-spec .sp-name{animation:fromLeft linear both;
      animation-timeline:view();animation-range:entry 4% cover 22%}
    .pg-spec .sp-views{animation:buildUp linear both;
      animation-timeline:view();animation-range:entry 0% cover 20%}

    /* 404. Nothing moved here at all. This page has one job, which is to get
       somebody back to a page that exists, so the ways out arrive. */
    .pg-404 .nf-lede{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 2% cover 20%}
    .pg-404 .nf-ul li{animation:chipIn linear both;
      animation-timeline:view();animation-range:entry 8% cover 30%}
    .pg-404 .nf-wa{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 6% cover 26%}
  }
}
@media(prefers-reduced-motion:reduce){
  .pg-storage .st-hero img,.pg-storage .stg-lead img,
  .pg-tables .m5-quote,.pg-tables .m5-src,.pg-tables .band-h3,
  .pg-contact .big-addr,.pg-contact .cost,
  .pg-spec .sp-name,.pg-spec .sp-views,
  .pg-404 .nf-lede,.pg-404 .nf-ul li,.pg-404 .nf-wa{
    animation:none;opacity:1;transform:none;clip-path:none}
}

/* ==================================================================
   SECTION INDEX, session AB.

   Why it exists. These pages are long: index 17,048px, boards 27,420px,
   tables 27,176px, learning 33,779px. At 390px that is 20 to 40 screens.
   Until now the only way to reach the bottom of learning.html was to
   scroll all of it, and the only way back was to scroll all of it again.
   That is half of what "the website is very lagging" meant; the other
   half was scroll-behavior:smooth, dealt with at the top of this file.

   How it is built. position:sticky needs a flow ancestor and these pages
   put the header in normal flow, so the bar is fixed and revealed by an
   IntersectionObserver watching the header, never by a scroll handler.
   It moves on transform only, so it composites. One easing curve and one
   duration, both already in the file.

   Height is a variable because scroll-margin-top has to equal it exactly
   or every jump lands with its heading hidden under the bar.
   ================================================================== */
:root{ --secnav-h:52px }
@media(max-width:640px){ :root{ --secnav-h:48px } }

/* THE REVEAL WAS THE LAG, NOT THE FRAME RATE. Session AB, second pass.

   Measured at 390px with a 6x CPU throttle on a sustained drag-style
   scroll: with the bar showing, learning.html holds p50 16.7ms, p95
   16.8ms, 5 frames over 32ms out of 418. Hiding the bar made it no
   better and on index.html made it worse. So the bar is not costing
   frames and there is nothing to optimise there.

   What it was doing is sliding in over 300ms every time the header
   crosses out of view, and sliding out again on the way back. Near the
   top of a page, where people scroll up and down to get their bearings,
   that is a bar that is permanently mid-animation and always a beat
   behind the finger. That reads as lag even at a locked 60fps, because
   the thing on screen is not where the scroll says it should be.

   120ms, not 0. At zero it snaps hard enough to read as a glitch when it
   crosses the threshold; 120ms is under the ~150ms at which a response
   stops feeling immediate, so it arrives with the finger rather than
   after it. --fast (300ms) stays the token for everything the user
   deliberately triggers; this one nobody asks for. */
.secnav{position:fixed;top:0;left:0;right:0;z-index:72;
  background:var(--forest);color:#FFF;
  transform:translateY(-100%);transition:transform 120ms var(--settle);
  will-change:transform}
.secnav[data-show="1"]{transform:none}
/* While the search overlay is open the header itself becomes fixed at the
   top, so the bar would sit on top of it. Stand down instead. */
body.searching .secnav{transform:translateY(-100%)}

.secnav-in{display:flex;align-items:center;gap:var(--s2);
  min-height:var(--secnav-h);padding-inline:var(--room)}

/* ==================================================================
   AND THE BAR IS NOW THE HEADER, COMPACTED. Session AC.

   The header sits in normal flow, which was the right call and is not
   being undone. What it means though is that it is gone after 144px, and
   these pages are 23,011px on storage.html and 33,779px on learning.html
   at 390. For about 99.4% of storage.html the only things on the screen
   were a section name and a Top button: no way home, no way to search
   623 products, no way to the enquiry list, no way to the other twelve
   pages. The brief for this pass was a parent on a phone who has never
   seen the site and must never get lost.

   It carries the four controls the header carries, in the header's own
   order, and it introduces no new vocabulary to do it. Home is set in
   the header's own .home treatment. The magnifier is the header's
   magnifier at the same 2.4 stroke. The list glyph is --lb-ico, the
   token the header's own count already uses. The three bars are the
   header's own .bars, by extending that selector rather than by drawing
   a second set. And every one of them presses the header's existing
   button, so there is one implementation of search, of the list and of
   the menu on this site, not two.

   IT COSTS NOTHING VERTICALLY. The bar already existed, already 48px on
   a phone, already arriving the moment the header leaves. The controls
   are 44px inside that 48px, which is the comfortable size on a coarse
   pointer and needed no extra height to get.
   ================================================================== */
.secnav-home{flex:none;display:inline-flex;align-items:center;height:44px;
  padding-inline:8px;border-radius:var(--r-ui);text-decoration:none;white-space:nowrap;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--fig);
  letter-spacing:.14em;text-transform:uppercase;color:rgba(255,255,255,.72);
  transition:color var(--fast) var(--settle),background var(--fast) var(--settle)}
@media (hover:hover){.secnav-home:hover{color:#FFF;background:rgba(255,255,255,.18)}}

.secnav-act{flex:none;display:inline-flex;align-items:center;justify-content:center;
  gap:7px;width:44px;height:44px;padding:0;border:0;border-radius:var(--r-ui);
  cursor:pointer;background:rgba(255,255,255,.18);color:#FFF;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--meta);
  transition:background var(--fast) var(--settle),color var(--fast) var(--settle)}
.secnav-act svg{width:19px;height:19px}
@media (hover:hover){.secnav-act:hover{background:#FFF;color:var(--forest)}}
@media (hover:none){.secnav-act:hover{background:rgba(255,255,255,.18);color:#FFF}}

/* The count is the information, the same ruling the header made at 560.
   Full it carries their own price red, because that is already what a
   count means on this site. */
.secnav-list{width:auto;padding-inline:10px}
.secnav-list::before{content:"";width:17px;height:17px;flex:none;background:currentColor;
  -webkit-mask:var(--lb-ico) center/contain no-repeat;mask:var(--lb-ico) center/contain no-repeat}
.secnav-list .lb-n{font-family:"Poppins",sans-serif;font-weight:700;
  font-variant-numeric:tabular-nums;font-size:var(--meta);
  color:rgba(255,255,255,.66)}
.secnav-list[data-n="1"] .lb-n{background:var(--price);color:#FFF;
  border-radius:var(--r-ui);padding:1px 6px;min-width:1.4em;text-align:center}
@media (hover:hover){
  .secnav-act:hover .lb-n{color:var(--forest)}
  .secnav-list[data-n="1"]:hover .lb-n{color:#FFF}
}

/* A page with fewer than three things to index has no toggle, so this
   holds the controls at the right edge where they are on every other
   page. Nothing moves between pages. */
.secnav-gap{flex:1;min-width:0}


/* The toggle carries the current section name, so the bar answers "where
   am I" before it is ever tapped. */
.secnav-cur{flex:1;min-width:0;display:flex;align-items:center;gap:10px;
  background:transparent;border:0;color:#FFF;cursor:pointer;
  font:inherit;font-size:var(--meta);text-align:left;
  padding:11px 0;letter-spacing:-0.01em} /* 42px tall at 10px; 11px clears the 44px floor */
.secnav-cur .lbl{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;font-weight:600}
.secnav-cur .of{opacity:.62;flex:none;font-variant-numeric:tabular-nums}
.secnav-cur .chev{flex:none;width:12px;height:12px;
  border-right:2px solid currentColor;border-bottom:2px solid currentColor;
  transform:rotate(45deg) translate(-2px,-2px);
  transition:transform var(--fast) var(--settle)}
.secnav-cur[aria-expanded="true"] .chev{transform:rotate(225deg) translate(-2px,-2px)}

.secnav-top{flex:none;background:transparent;border:1px solid rgba(255,255,255,.34);
  color:#FFF;border-radius:var(--r-ui);cursor:pointer;font:inherit;
  font-size:var(--meta);padding:7px 13px;
  transition:background var(--fast) var(--settle)}
.secnav-top:hover{background:rgba(255,255,255,.12)}

/* The panel. boards.html has 19 sections, so it scrolls rather than
   growing past the viewport, and it stops the page scrolling behind it. */
.secnav-panel{position:absolute;top:100%;left:0;right:0;
  background:var(--forest);border-top:1px solid rgba(255,255,255,.16);
  max-height:min(64vh,520px);overflow-y:auto;overscroll-behavior:contain;
  display:none;padding:6px var(--room) 14px}
.secnav[data-open="1"] .secnav-panel{display:block}
.secnav-panel ol{list-style:none;margin:0;padding:0;
  counter-reset:secnav}
.secnav-panel li{counter-increment:secnav}
.secnav-panel a{display:flex;gap:12px;align-items:baseline;
  padding:11px 0;color:rgba(255,255,255,.86);text-decoration:none;
  border-bottom:1px solid rgba(255,255,255,.10);
  font-size:var(--meta);line-height:1.35}
.secnav-panel li:last-child a{border-bottom:0}
.secnav-panel a::before{content:counter(secnav,decimal-leading-zero);
  flex:none;opacity:.5;font-variant-numeric:tabular-nums}
.secnav-panel a:hover{color:#FFF}
.secnav-panel a[aria-current="true"]{color:var(--zest);font-weight:600}
.secnav-panel a:focus-visible{outline:2px solid var(--zest);outline-offset:2px}

/* The whole reason the height is a token. Without this every jump lands
   with its own heading hidden behind the bar that sent you there. */
.secnav-target{scroll-margin-top:calc(var(--secnav-h) + 14px)}
/* And the same for anything the address bar points at. The panel's own
   links go through the class above; a plain href="#list" in the page does
   not, and there are fifteen of those across the site. Without this they
   land with the top of the section tucked under the bar. */
:target{scroll-margin-top:calc(var(--secnav-h) + 14px)}

/* THE LADDER, AND IT IS THE HEADER'S OWN LADDER.
   The header drops WhatsApp at 900, the word Home at 760 and the words
   "Your list" at 560, keeping the count. The bar steps down the same way
   rather than inventing a second set of breakpoints.

   Measured at 390 with --room at its 20px floor: 350px of content. Home
   51, search 44, list 52, menu 44 and four gaps is 231px, which leaves
   119px for the section label, and 119px of Poppins at 14px is about
   twelve characters. So at 560 the counter goes and Top goes, the room
   inside the bar comes down to 14 and the gap to 10, and the label gets
   about 137px instead. Twelve characters is a stub. Fifteen is a label,
   and the full sentence is one tap away in the panel where nothing is
   clipped.

   Top is the one thing dropped rather than shrunk. It was the sixth
   control and the only one whose job is already done twice over on a
   phone: the mark returns you to the top of the home page, and section
   01 in the panel is the top of this one. Somebody lost on a phone wants
   out, not up. It stays above 560 where there is room for it. */
@media(max-width:560px){
  .secnav-in{padding-inline:12px;gap:8px}
  .secnav-top{display:none}
  .secnav-cur .of{display:none}
  .secnav-cur{gap:6px}
  .secnav-home{padding-inline:2px;letter-spacing:.1em}
  .secnav-list{padding-inline:8px}
}

@media(prefers-reduced-motion:reduce){
  .secnav{transition:none}
  .secnav-cur .chev{transition:none}
}

/* ==================================================================
   THE ENQUIRY DOCK, REWORKED. Session AB.

   This is the feature the client paid extra for and the only thing on
   this site that exists nowhere else. It was driven with 21 real products
   across four pages at 390px before any of this was written, and it was
   failing in four ways that only show up when you actually use it:

   1. `.dock-in` set `padding:var(--s2) 0`, a shorthand, which overrode the
      `padding-inline:var(--room)` it inherits from `.wrap`. So "Clear",
      the item summary and the send button all ran hard into the left edge
      of the screen. One shorthand, three visible defects.
   2. The item summary was one run-on string joined with " · ", clamped to
      max-height:52px with overflow:auto, so at 21 items it was clipped
      mid-word and no one could read their own list.
   3. There was no way to remove ONE item. Only Clear, which empties the
      lot. Tap the wrong product at item 19 and you start again.
   4. Below 560px the white send button took `flex:1 1 100%` inside a
      container with zero inline padding, so it rendered as a full-bleed
      white slab breaking out of the forest panel. It read as a bug.

   The shape now: a one-line bar that always fits, and a panel above it
   holding the real list. The panel is absolutely positioned against the
   bar's top edge, so `measureBar()` keeps measuring the bar alone and the
   page reserve does not jump to 78vh when the panel opens.
   ================================================================== */
.dock-in{padding-block:10px;padding-inline:var(--room);gap:var(--s2);
  flex-wrap:nowrap;position:relative}
/* 64px = the 44px touch target this site already uses for .clear, plus
   10px of breathing room top and bottom. It was 272px, a third of an
   844px phone screen, showing a summary nobody could read. */
.dock{min-height:64px}

.dock .lbl{margin-right:0;white-space:nowrap}
/* Below 560px the words "YOUR ENQUIRY LIST" cost more room than they earn:
   an orange badge with a number in it, sitting above a Send button, is
   already unambiguous. The count and the money stay at every width. */
@media(max-width:559px){ .dock .lbl{display:none} }
.dock .sum{margin-right:auto;white-space:nowrap;font-size:var(--fig);
  color:rgba(255,255,255,.92);font-variant-numeric:tabular-nums}
.dock .sum b{font-family:"Gabarito",sans-serif}

/* THE BAR IS A ROUTE TO THE LIST. Session AE.

   Session AB put a Review control in the bar which expanded a panel over
   the page, and that panel is what Dan asked to become a page. What is
   left is smaller in every direction than what it replaces.

   THE WHOLE BAR IS THE LINK, not a button at one end of it. Measured at
   390: a 38px badge, 16px, a money figure at 155px, 16px, and a labelled
   button will not share a 350px row, which is why the bar wrapped to two
   rows and stood 120px tall on an 844px screen. One link, one row of
   64px, a target of 350 x 64 rather than 140 x 44, and 56px of catalogue
   handed back on all thirteen pages.

   It still wraps rather than clips. flex-wrap stays on and the money
   figure is allowed to shrink before anything else does, because a total
   cut off mid-figure is the one failure this bar must not have, and a
   bar that grows by one row is measured by measureBar() like any other.

   The label follows the house rule the header already uses on .lb-t and
   on .home: the words go first, the thing that carries the information
   stays. The chevron is the caret the Review control used, turned to
   point the way it now goes. */
.dock .dock-in{text-decoration:none;color:inherit;min-height:64px;
  transition:background var(--fast) var(--settle)}
@media (hover:hover){.dock .dock-in:hover{background:rgba(255,255,255,.10)}}
@media (hover:none){.dock .dock-in:hover{background:none}}
.dock .dock-in:focus-visible{outline-offset:-4px}
.dock .sum{min-width:0;overflow:hidden;text-overflow:ellipsis}
.dock-go{flex:none;display:inline-flex;align-items:center;gap:9px;
  min-height:44px;padding:0 15px;border-radius:var(--r-ui);
  background:#FFF;color:var(--forest);white-space:nowrap;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--meta)}
.dock-go .car{width:9px;height:9px;flex:none;border-right:2px solid currentColor;
  border-bottom:2px solid currentColor;transform:rotate(-45deg) translate(-1px,-1px)}
@media(max-width:559px){.dock-go .dock-go-x{position:absolute;width:1px;height:1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}}
@media(prefers-reduced-motion:reduce){.dock .dock-in{transition:none}}

/* ==================================================================
   THE ADD CONTROL, session AB.

   Driven at 390px across four pages, the two states were the wrong way
   round. Not-added was `rgba(55,54,54,.10)`, a 10% grey wash, and added
   was solid --forest. So the single most important action on a 623
   product catalogue looked like a disabled control, while the thing you
   had already finished doing wore the brand's strongest colour. Shop for
   twenty minutes and the page fills with heavy dark blocks marking work
   already done, and the eye is pulled to all of it.

   Inverted. Adding is now solid forest, because it is the primary action
   of the entire site and the only thing the site is for. Added recedes to
   an outline in the same colour: still unmistakable, still carries the
   tick, but it stops shouting once its job is done. No new colour is
   introduced; both states are --forest.
   ================================================================== */
.add{background:var(--forest);color:#FFF}
.add[aria-pressed="true"]{background:transparent;color:var(--forest);
  box-shadow:inset 0 0 0 1px var(--forest)}
@media (hover:hover){
  .add:hover{background:var(--ink);color:#FFF}
  .add[aria-pressed="true"]:hover{background:rgba(0,60,32,.10);color:var(--forest)}
}
@media (hover:none){
  .add:hover{background:var(--forest);color:#FFF}
  .add[aria-pressed="true"]:hover{background:transparent;color:var(--forest)}
}
/* On a dark ground the solid forest fill disappears into the panel, so the
   pair swaps to white. Same logic, same two states. */
.on-dark .add,.ft .add{background:#FFF;color:var(--forest)}
.on-dark .add[aria-pressed="true"],.ft .add[aria-pressed="true"]{
  background:transparent;color:#FFF;box-shadow:inset 0 0 0 1px rgba(255,255,255,.6)}

/* ==================================================================
   SEARCH, session AB. Two changes, both measured.

   1. iOS ZOOMS THE WHOLE PAGE when a text field under 16px is focused.
      The field was var(--meta), which computes to 14px, so every tap on
      search on an iPhone zoomed the page and left the visitor scrolled
      sideways in a catalogue they were trying to search. 16px is the
      threshold, not a preference, which is why it is written as a literal
      here rather than taken from the type scale: the scale does not know
      about Safari.
   2. A query for "shelf" returns 88 rows and the code will render up to
      260, each with an image, a code, a size, a price and a button. Rows
      outside the scrollport now cost nothing to lay out or paint.
      contain-intrinsic-size is the measured height of a real result row
      at 390px, so the scrollbar does not jump as they resolve.
   ================================================================== */
@media(max-width:900px){
  .find-field input{font-size:16px}
}
/* The size itself is set once, further down, next to the measurement that
   produced it. This rule carried a second, older `contain-intrinsic-size:
   auto 280px` that the later rule overrode, so the file stated two
   different heights for the same row and only one of them was ever used. */
.find-res .prow{content-visibility:auto}

/* ==================================================================
   THE 54 PRODUCT STRIP: THE SCROLL TIMELINE IS STOOD DOWN. Session AD.

   Session AB bound this strip to animation-timeline:scroll(root block),
   so it advanced only while the page was being scrolled and stood dead
   whenever the visitor stopped. Dan asked for the opposite: it should
   drift on its own, without anyone touching the page. So the scroll
   binding is removed and the session AA rule in section 08 governs
   again, unchanged and unedited:

       animation:driftPan var(--drift-d,240s) linear infinite

   Nothing new was introduced to do it. Same keyframes (driftPan, one
   sequence, -50% to 0, so the loop has no seam), same duration variable
   --drift-d, same 28px a second held constant across viewports by the
   page script, same pauses. --settle stays what it has always been, the
   one house curve for transitions, and is deliberately NOT used here:
   an eased curve on an infinite loop pulses at every cycle boundary,
   which is exactly the ticker behaviour that was not wanted. linear is
   what a constant-rate drift requires and what section 08 already said.

   COST. One compositor-only transform on one element. No scroll
   listener, no layout property, no new bytes. The lag this site had was
   dozens of never-ending animations at once plus scroll-behavior:smooth
   over a 33,800px page, not a single translated track.

   REDUCED MOTION is untouched and still handled where every other
   effect on this site handles it, in the @media block in section 08:
   animation stood down to none, the duplicate sequence display:none,
   the strip left as a hand scroller. Removing this override means the
   strip now obeys that one rule instead of two competing ones.
   ================================================================== */

/* "ask us" where a price used to be. Session AB suppressed 14 prices the
   catalogue does not actually print per product; this makes the
   replacement read as a note rather than as a price. Same treatment as
   .lr-p-none, which learning.html already used for the same purpose, so
   both pages say it in one voice. Muted, not red: --price is reserved for
   figures a visitor can hold us to. */
.rm-none{color:var(--mute);font-size:var(--fig);letter-spacing:.09em;
  text-transform:uppercase;font-variant-numeric:normal}

/* index.html headed the AirBnb job "Maya Bay Residence", presenting a
   BUILDING as the customer. The catalogue prints no company name for that
   job, only the place. projects.html already got this right, and index.html
   contradicted both projects.html and its own body copy, which says in as
   many words that one job prints no client name. Same treatment as
   .pj-anon so the two pages now say the same thing in the same voice. */
.pg-home .mosaic h3.anon{color:var(--mute);font-style:italic}

/* ==================================================================
   SCROLL PROGRESS IN THE SECTION BAR. Session AB.

   Why this exists, and it is a fix rather than an ornament. The bar
   holds a section label and a counter. learning.html has four sections
   across 33,779px, so on a forty-screen page that label changes three
   times. A control that does not visibly answer the scroll is a control
   that feels dead, and dead is what "the sidebar looks laggy" describes.
   Frame times were already a locked 16.7ms at a 6x CPU throttle, so
   there was never anything to speed up: what was missing was a response.

   This is the one thing on the bar that moves continuously with the
   finger. It is the same mechanism as the 54 product strip,
   animation-timeline: scroll(root), so it runs off the main thread and
   adds no script, no listener and no bytes worth counting.

   Every value:
   * 2px. The panel below it already carries a 1px rule, so a 1px line
     would read as another border rather than as an indicator, and 3px
     starts to read as a chrome band in its own right. 2px is the
     smallest that reads as deliberate next to an existing 1px.
   * --zest, and no new colour: it is already what the bar uses to mark
     the section you are in, so position is told in one colour throughout.
   * transform-origin:left, so it grows the way the page reads and the
     way the product strip travels.
   * scaleX only. Animating width would lay out the bar every frame.

   Behind @supports because without a scroll timeline the line would sit
   frozen at zero and read as a broken loading bar. Absent is better than
   wrong. Under reduced motion it stays: it tracks the visitor's own
   scrolling, exactly as a scrollbar does, and reports position rather
   than performing.
   ================================================================== */
@keyframes secnavProgress{from{transform:scaleX(0)}to{transform:scaleX(1)}}
@supports (animation-timeline: scroll()) {
  .secnav::after{
    content:"";position:absolute;left:0;right:0;bottom:0;height:2px;
    background:var(--zest);transform-origin:left center;
    animation:secnavProgress linear both;
    animation-duration:auto;
    animation-timeline:scroll(root block);
    pointer-events:none}
}

/* ==================================================================
   SESSION AC. THE SECTION PLATE.

   THE PROBLEM IT ANSWERS, MEASURED FIRST.

   Fifteen section tints all sat between 79.8% and 91.0% lightness.
   Against each other they measured 1.01 to 1.43, and the two grounds
   tables.html put side by side, #EEE9E2 and #F1E7DD, measured 1.01,
   which is not a light difference, it is the same colour printed
   twice. Collapsing them to four fixed every boundary between two
   sections and did nothing whatever for the inside of one, because
   learning.html is not fifteen bands. It is three, and one of them is
   26,359px tall out of 34,008. storage.html is 14,639 out of 23,011.
   A ground colour cannot mark a place you are already standing in.

   WHY DARK AND NOT ANOTHER TINT.

   Computed, not assumed: white against beige is 1.22, white against
   the blue 1.43, beige against the blue 1.17. That is the ceiling for
   light grounds on this site, because a ground pale enough to carry
   --mute at 4.5:1 is by definition close to every other ground that
   can. Forest against those three is 8.80 to 12.60. So the dark plate
   is not a flourish, it is the only step on this page with a number
   worth having in front of it.

   NOTHING HERE IS NEW TO THE SITE.

   Forest as a full band: index.html has carried two since session G,
   and contact.html, 404.html and every footer carry one. Two-digit
   section numbers: therapy.html has printed 01 to 04 against its four
   bands and projects.html 01 to 03, both since session P. The menu
   overlay numbers all thirteen pages 01 to 13 and marks the current
   one in zest. The plate is those three decisions used together on the
   pages that needed them, not a fourth decision.

   THE ACCENT FINALLY HOLDS AN AREA.

   --zest was a 2px progress line and one 40px badge, on a site whose
   own standing rule is that colour needs area before it registers. It
   is now the field the section number sits in: 88px square at 390 and
   104px at desktop, forest on zest at 4.66:1, which is the pairing
   their Playground Catalogue prints on page 5 with PLAYGROUND set in
   the forest and Collection in the orange script. It is not used any
   larger than this on purpose. The buyers are kindergarten operators
   and parents of children in therapy, and a page that shouts orange at
   them is not the same thing as a page that is easy to move around.
   ================================================================== */
.plate{
  background:var(--g-deep);
  color:#FFF;
  /* breaks out of .wrap's own padding rather than using 100vw, so it
     cannot introduce the sideways scroll ac_verify.py checks for */
  margin-inline:calc(var(--room) * -1);
  padding:var(--s5) var(--room) var(--s5);
  margin-block-end:var(--s5);
}
/* Sit the plate flush against the top edge of its section. Without this
   the section's own top padding leaves a strip of ground above the
   plate and it reads as a card that has come loose rather than as the
   top of a chapter. Where :has() is unsupported the strip comes back
   and nothing else changes, which is a fair thing to degrade to. */
section:has(> .wrap > .plate){padding-block-start:0}
/* THE CLASS IS REPEATED ON PURPOSE, AND THIS IS WHY.

   A plate reverses the ground under text that every page already colours
   for a light ground: .pg-floors .fl-say sets --mute and the <b> runs
   inside it set --ink. Those selectors carry more specificity than
   ".plate p" does, so they won and the render came out with #373636 on
   #003C20, which is 1.05:1. On floors.html two whole sentences of the
   lede were invisible. It was caught by looking at the page, not by the
   contrast script, which had been told to check the text colour against
   forest and was reporting the numbers correctly to nobody.

   Repeating the class lifts this to (0,3,x) so it beats the page rules
   without !important and without naming all eight pages. */
.plate.plate h2{color:#FFF;font-size:var(--t-big);line-height:1.02;letter-spacing:-.028em;max-width:15em}
.plate.plate p{color:rgba(255,255,255,.72);max-width:34em}   /* 6.98:1 on forest. 38em measured 76ch at 1440; 34em is 68 */
.plate.plate b, .plate.plate strong{color:#FFF}
.plate.plate a{color:#FFF}
.plate.plate .rm{color:#FFC9A6}  /* --price is 1.81:1 on forest, unreadable */
.plate.plate [data-verbatim]{color:rgba(255,255,255,.72)}
.plate.plate .code{color:rgba(255,255,255,.72)}
.plate.plate .plate-no{color:var(--forest)}   /* forest on zest, 4.66:1 */
.rung.rung .rung-n{color:var(--forest)}
.band-h3.band-h3{color:#FFF}
.plate-no,
.pg-therapy .bhead .bno,
.pg-projects .plate .bno{
  display:grid;place-items:center;
  inline-size:clamp(88px,23vw,104px);block-size:clamp(88px,23vw,104px);
  background:var(--zest);color:var(--forest);
  font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-mid);line-height:1;letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;
  margin-block-end:var(--s3);
}
/* therapy.html already had the wrapper this needs, built in session P as
   .wrap.bhead, so that page takes the plate by class and its markup is
   not touched at all. */
.pg-therapy .bhead{
  background:var(--g-deep);color:#FFF;max-width:none;
  padding-block:var(--s5);margin-block-end:var(--s5);
}
.pg-therapy .bhead.bhead h2{color:#FFF}
.pg-therapy .bhead.bhead .bcount,
.pg-therapy .bhead.bhead .blede{color:rgba(255,255,255,.72)}
.pg-therapy .bhead.bhead b, .pg-therapy .bhead.bhead strong{color:#FFF}
.pg-therapy .bhead.bhead .bspan{color:#FFC9A6}
.pg-therapy .band:has(> .bhead){padding-block-start:0}
@media(min-width:900px){
  /* At width the plate is two columns, the number holding the left edge,
     because a 104px square adrift in a 1200px band is the timid version
     of the same idea. */
  .plate{display:grid;grid-template-columns:auto minmax(0,1fr);
    column-gap:var(--s4);align-items:start;padding-block:var(--s6)}
  .plate-no{margin-block-end:0;grid-row:1/span 9}
  .plate-in{grid-column:2}
  .pg-therapy .bhead{display:grid;grid-template-columns:auto minmax(0,1fr);
    column-gap:var(--s4);align-items:start;padding-block:var(--s6)}
  .pg-therapy .bhead .bno{margin-block-end:0;grid-row:1/span 9}
  .pg-therapy .bhead h2,
  .pg-therapy .bhead .bcount,
  .pg-therapy .bhead .blede{grid-column:2}
}

/* ==================================================================
   SESSION AC. RUNGS, AND THE INSIDE OF A LONG SECTION.

   The plate marks where a section starts. It cannot mark where you are
   once you are inside one, and that is the whole of the problem on the
   two worst pages: learning.html is one section of 26,359px and
   storage.html one of 14,639px. So the same material, forest with a
   zest field, is used again one size down, on whatever sub-structure
   the page already has.

   storage.html had eight of them already and was not using them. Every
   group carries a real printed heading, Somewhere for coats, Somewhere
   for books, Somewhere for shoes, and a count of how many of the 64
   landed there. Alternating their ground turns one flat band into eight
   blocks that each start somewhere, and costs one selector.

   learning.html had nothing to hang anything on, which is why
   tools/ac_rung.py had to put four markers into the list itself. They
   are prices read off the rows, at thresholds taken from the spread of
   the page's own figures.
   ================================================================== */
.rung{
  grid-column:1/-1;
  background:var(--g-deep);color:#FFF;
  margin-inline:calc(var(--room) * -1);
  padding:var(--s5) var(--room);
  /* flush, no margins: the stretch above ends on it and the stretch below
     starts on it, so the rung is the join between two rooms rather than an
     object floating between them */
  margin-block:0;
  display:flex;flex-wrap:wrap;align-items:baseline;gap:var(--s2);
}
/* SIZED BY LOOKING, TWICE.

   At var(--s3) of padding the rung was about 100px in a 34,859px page and
   it did not read as an arrival, it read as a row that had gone dark. The
   plates on storage.html work because they are around 700px and you cannot
   scroll past one without noticing. This is the same device one size down,
   not two: six of them come to roughly 1,800px, which is 5% of the page,
   the same share the plates take on storage.

   It is also where --zest finally holds an area. The price is set at
   --t-big in a filled field, so the chip is about 190 x 90 at 390px. The
   accent was a 2px line and one 40px badge on a site whose own rule is
   that colour needs area before it registers. */
.rung-n{
  background:var(--zest);color:var(--forest);
  font-family:"Gabarito",sans-serif;font-weight:700;
  /* var(--t-huge), not var(--t-big), and not a bespoke clamp. --t-big
     bottoms out at 2rem so on a phone the figure came out at 32px and the
     field around it at 46px, which is a badge again. A hand-written
     clamp(2.25rem,11vw,3.5rem) fixed the size and put a 43px on a page
     that already had a 44px on it, which is the type-scale version of the
     same mistake this session is here to undo. --t-huge is 40px at 390,
     it is already on the ladder, and it is the largest thing on the page,
     which is what a landmark should be. */
  font-size:var(--t-huge);line-height:1;letter-spacing:-.028em;
  font-variant-numeric:tabular-nums;
  padding:.22em .3em;
}
.rung-t{
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--fig);
  letter-spacing:.14em;text-transform:uppercase;color:rgba(255,255,255,.72);
}
/* The first row after a rung should not sit tight against it. */
.rung + li{padding-block-start:var(--s3)}

/* THE STRETCH BETWEEN TWO RUNGS IS A BAND, NOT JUST A GAP.

   Four rungs across 26,359px was one landmark every 6,600px, which on a
   390px phone is ten screens of scrolling between them, and looking at it
   that is exactly what it felt like. Six rungs is better and still not
   enough on its own, because between two of them the ground was still one
   flat colour for four thousand pixels.

   So the stretches alternate as well, and they do it with no extra markup
   at all: each rule needs one more preceding .rung than the last, so it is
   both more specific and later, and the cascade hands each stretch the
   ground the rule above it could not reach. Seven stretches, warm, paper,
   warm, paper, warm, paper, warm, with a forest rung on every join. */
.lr-list > li:not(.rung), .pl-list-p > li:not(.rung){
  margin-inline:calc(var(--room) * -1);
  padding-inline:var(--room);
}
.lr-list > .rung ~ li:not(.rung),
.pl-list-p > .rung ~ li:not(.rung){background:var(--g-paper)}
.lr-list > .rung ~ .rung ~ li:not(.rung),
.pl-list-p > .rung ~ .rung ~ li:not(.rung){background:transparent}
.lr-list > .rung ~ .rung ~ .rung ~ li:not(.rung),
.pl-list-p > .rung ~ .rung ~ .rung ~ li:not(.rung){background:var(--g-paper)}
.lr-list > .rung ~ .rung ~ .rung ~ .rung ~ li:not(.rung),
.pl-list-p > .rung ~ .rung ~ .rung ~ .rung ~ li:not(.rung){background:transparent}
.lr-list > .rung ~ .rung ~ .rung ~ .rung ~ .rung ~ li:not(.rung){background:var(--g-paper)}
.lr-list > .rung ~ .rung ~ .rung ~ .rung ~ .rung ~ .rung ~ li:not(.rung){background:transparent}

/* specsheets.html. 22 drawings over 19,476px on one cool ground. Each row
   is ~885px, so this is the one long list where the row itself is big
   enough to be its own block and a zebra is the right device rather than a
   marker. playground.html has run the same alternation over its fourteen
   frames since session T. */
.pg-spec .sp-list > li{
  margin-inline:calc(var(--room) * -1);
  padding:var(--s4) var(--room);
}
.pg-spec .sp-list > li:nth-child(even){background:var(--g-paper)}

/* storage.html: the groups carry their own top padding, so the plate does
   not need to add more under itself. */
.pg-storage .st-named .plate{margin-block-end:0}

/* storage.html. Eight groups, alternating ground, full bleed out of the
   wrap's own padding so the block reads as a band rather than a card. */
.pg-storage .st-named .stg{
  margin-inline:calc(var(--room) * -1);
  padding:var(--s6) var(--room) var(--s6);
  margin-block-start:0;
}
.pg-storage .st-named .stg:nth-of-type(even){background:var(--g-paper)}

/* tables.html gets the same treatment for its band-h3 headings, but it has
   to be written in that page's own <style> block rather than here: its body
   class is "p5", not "pg-tables", so every .pg-tables rule in this file has
   been dead for as long as it has existed. Worth someone's time later; it
   is not this session's job and the page does not currently need them. */

/* ==========================================================================
   SESSION AD. THE SEARCH RESULT, RELAID.

   The panel, its markup and its structure are unchanged. Every rule below
   moves things that were already in the row, or draws something the row was
   already asked to say. Nothing here was added to a page.

   WHAT WAS MEASURED ON THE RENDER AT 390, BEFORE ANY OF IT.
     one row              263px
     the pane it sits in  552px
     rows on screen       2.1
     left edges in a row  two, at 28px and 124px
     a link to a product  none
     "shelf"              88 rows, 27,352px of scrolling
     the chips row        1,606px of content in 390px, no edge, no scrollbar

   Each of those is answered below, in that order.
   ========================================================================== */

/* 1. THE ROW IS A LINK.
   The name carries the anchor and a pseudo-element stretches it over the row,
   so the whole 390 x 139 card opens the product instead of a 145 x 20 tag.
   The Add button and the page tag are lifted above the stretch, because they
   are the two things in the row that are not "open this".
   No underline: nothing on this site is underlined and that rule holds here.
   The chevron is the affordance instead, and it is the same 2.4 stroke as the
   magnifier in the field and the arrows in the mastheads. */
.find-res .prow{position:relative}
.find-res .prow a.nm{color:var(--ink);text-decoration:none;display:inline}
.find-res .prow a.nm::before{content:"";position:absolute;inset:0;z-index:1}
/* THE ARROW FOLLOWS THE LAST WORD, it does not hang off the right of the row.
   The first pass floated it, which on a 1280 row put it 592px away from the
   end of the name with nothing in between, reading as a stray mark rather
   than as part of the link. Inline after the text, it belongs to the words. */
.find-res .prow a.nm .nm-w{white-space:nowrap}
.find-res .prow a.nm .nm-w::after{
  content:"";display:inline-block;vertical-align:-.11em;
  width:.95em;height:.95em;margin-left:.42em;
  background:currentColor;opacity:.42;
  -webkit-mask:var(--nm-go) center/contain no-repeat;
  mask:var(--nm-go) center/contain no-repeat;
  transition:opacity var(--fast) var(--settle),transform var(--fast) var(--settle)}
:root{--nm-go:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 12h14M13 6l6 6-6 6'/%3E%3C/svg%3E")}
.find-res .prow .add-wrap,.find-res .prow .where{position:relative;z-index:2}
@media (hover:hover){
  .find-res .prow:hover a.nm{color:var(--zest-t)}
  .find-res .prow:hover a.nm .nm-w::after{opacity:1;transform:translateX(3px)}
}
/* The ring goes round the row, not round the words, because the row is what
   the key press will open. The second rule is the floor for anything without
   :has, and it is why the outline is on the anchor as well. */
.find-res .prow:has(a.nm:focus-visible){box-shadow:inset 0 0 0 3px var(--zest)}
.find-res .prow a.nm:focus-visible{outline:3px solid var(--zest);outline-offset:2px}
.find-res .prow:has(a.nm:focus-visible) a.nm:focus-visible{outline:none}
/* a row whose page has not shipped is still not a link, and now it says why
   rather than looking like a link that failed */
.find-res .prow .where-soon::after{content:" (page not published yet)";color:var(--mute)}

/* 2 and 3. ONE LEFT EDGE, AND A ROW A THIRD OF THE HEIGHT.
   Two edges is what a reader sees as ragged: the name began at 124px because
   it sat beside a 84px picture, while the price, the button and the tag all
   began at 28px because they sat under it. Everything that is words now
   shares one column and therefore one edge, and the picture is the only thing
   to the left of it.

   .meat is released with display:contents so the name and the facts can take
   rows of their own in the row's own grid. It is the same problem session AB
   solved with grid-area:auto, one level further in.

   The picture drops 84 -> 56. At 56px a shelf is still recognisably a shelf
   and three more results fit the screen, which is the trade this whole
   session is about. */
@media(max-width:560px){
  .find-res .prow{
    grid-template-columns:76px auto minmax(0,1fr) auto;
    grid-template-areas:none;
    grid-template-rows:auto auto auto;
    row-gap:6px;column-gap:10px;padding-block:13px;align-items:start}
  .find-res .prow .meat{display:contents}
  /* THE PICTURE WAS SMALLER THAN THE SPACE IT ALREADY OWNED.
     Measured at 390 with a query running: rows are 129 to 173px tall, the
     picture cell spans all three of the row's tracks, and it was rendering
     64px tall with the image capped at 56. Sixty-five to a hundred and nine
     pixels of the column sat empty next to every result.

     So this is not the old density trade being reversed. The row height is
     set by the name, the facts and the price stack beside it, and none of
     those move. The picture simply stops being smaller than its own cell:
     it stretches to the row and is capped at 104px so it can never be the
     thing that makes a row taller. People buy a shelf by looking at it. */
  /* The grid track and the cell width have to be changed together. Widening
     only the cell left it overflowing a 56px track and the product name,
     which starts at the next track, printed straight over the picture. */
  .find-res .prow .th{grid-area:1/1/4/2;width:76px;min-height:56px;
    align-self:stretch;max-height:104px;overflow:hidden;
    display:grid;place-items:center}
  /* an explicit pixel cap, not 100%: a percentage max-height against a
     stretched grid cell let the taller pictures run past the bottom of
     their own row. */
  .find-res .prow .th img{max-width:76px;max-height:104px;
    width:auto;height:auto;object-fit:contain}
  /* the name and the facts each take the whole text width, so neither is
     squeezed into a column by something sitting beside it. The first attempt
     put the facts and the price side by side and left the facts 153px, which
     wrapped "Code QWA005F Size 121 x 40 x 83 cm" onto two lines and stranded
     the separator at the head of the second. Measured, then moved. */
  .find-res .prow .nm{grid-area:1/2/2/5;font-size:var(--body);line-height:1.16}
  /* no column gap: the separator below carries its own space on both sides,
     and a gap on top of it rendered "QWA005F .   121 x 40 x 83 cm" with a
     hole in the middle */
  .find-res .prow .facts{grid-area:2/2/3/5;gap:2px 0;align-self:center}
  .find-res .prow .rm{grid-area:3/2/4/3;text-align:left;justify-items:start;
    align-self:center}
  /* IT WRAPS, IT DOES NOT ELLIPSISE. On the first pass this was one nowrap
     line and 390px rendered it "Storage and s…", which tells a reader nothing
     and looks like a fault. The row is 44px tall because the Add button is,
     and two lines of 14px is 32px, so wrapping this costs no height at all. */
  /* The note above assumed this wraps to two lines and therefore costs no
     height next to a 44px Add button. Measured at 390px it takes THREE lines
     for the longer category names, so the bottom row of every result carried
     a 48px block of low-value text wedged between the price and the button.
     The destination is already stated by the link the whole row points at, so
     it goes below 560 rather than being squeezed. It returns above that. */
  .find-res .prow .where{display:none}
  .find-res .prow .add-wrap{grid-area:3/4/4/5;justify-self:end;align-self:center}
  /* "printed price" was set under every price in the sheet. On a row this
     size it is a line of type repeated once per result to say what the red
     RM figure already says, and the rows that have no price still print
     "No price printed. Ask and they will quote" in full, so the distinction
     it was carrying is not lost. It comes back above 560. */
  .find-res .prow .rm i{display:none}
  .find-res .prow .rm b{font-size:var(--t-sub)}
  .find-res .noprice{text-align:left;line-height:1.25}
  .find-res .noprice i{display:none}
  /* THE PICTURE CELL IS 56px AND THE SENTENCE IN IT WAS WRITTEN FOR 84.
     Shrinking the column made "no photograph in their catalogue" overrun it
     and print straight across the product name of the row below. Caught by
     looking at the render, which is the only way this class of fault is ever
     caught on this project.

     The statement is not dropped, it moves. 46% of the range has no
     photograph and a silent gap reads as an oversight, which is why it was
     written in the first place; it now sits at the end of the facts line
     with the other things the catalogue does not print, which is where the
     row already says "Code not printed" and "Size not printed". The original
     text stays in the DOM for anything reading the page aloud. */
  .find-res .prow .th.nopic{background:none}
  .find-res .prow .th.nopic i{position:absolute;width:1px;height:1px;padding:0;
    margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
  .find-res .prow:has(.th.nopic) .facts::after{
    content:"no photograph in their catalogue";
    font-family:"Poppins",sans-serif;font-size:var(--meta);color:var(--mute)}
  /* the labels go for fields that are filled, because "QWA040C" and
     "100 x 40 x 82 cm" cannot be mistaken for each other, and keeping them
     pushed the line to 280px in 248px of column and wrapped it. The
     missing-field notes keep theirs: "Code not printed" needs the word Code.
     The separator hangs off the END of the chunk before it, with a
     non-breaking space, so it can never be left heading a wrapped line the
     way a leading ::before was. */
  .find-res .prow .f:not(.none) i{display:none}
  .find-res .prow .f:not(:last-child)::after{content:"\00A0\00B7\00A0";color:var(--mute)}
  .find-res .prow:has(.th.nopic) .facts::after{margin-left:.5em}
  .find-res .prow .add{min-height:44px;min-width:44px;padding-inline:0;
    justify-content:center;position:relative}
  /* the label is clipped, not removed. Hiding it outright would leave a
     button whose only name is an SVG, and the label is also the thing that
     changes to "Added": a screen reader still hears both. */
  .find-res .prow .add .t{position:absolute;width:1px;height:1px;padding:0;
    margin:-1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}
}
/* 360 is the floor this row breaks at: a 44px square, a price and the page
   tag do not share 232px. The tag is the one that goes, because the row is a
   link to that page and the arrow on the name already says so. */
@media(max-width:389px){
  .find-res .prow{grid-template-columns:64px auto minmax(0,1fr) auto;column-gap:9px}
  .find-res .prow .th{grid-area:1/1/4/2;width:64px;min-height:48px;
    align-self:stretch;max-height:96px;overflow:hidden;
    display:grid;place-items:center}
  .find-res .prow .th img{max-width:64px;max-height:96px;
    width:auto;height:auto;object-fit:contain}
  .find-res .prow .where{font-size:var(--fig)}
}
@media(max-width:340px){
  .find-res .prow .th{min-height:44px}
}

/* 4. "SHELF" IS 88 ROWS AND IS NOT 88 SCREENS.
   The control is the count, so nobody has to guess what pressing it costs. */
.find-more-wrap{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);
  padding-block:var(--s3)}
.find-more-btn{min-height:48px;padding:0 var(--s3);border-radius:var(--r-ui);
  background:var(--ink);color:#FFF;cursor:pointer;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--body);
  letter-spacing:-.01em;
  transition:background var(--fast) var(--settle)}
@media (hover:hover){.find-more-btn:hover{background:var(--forest)}}
.find-more-btn:focus-visible{outline:3px solid var(--zest);outline-offset:2px}
.find-more-n{font-size:var(--meta);color:var(--mute);
  font-variant-numeric:tabular-nums}
/* the correction, said out loud rather than applied quietly */
.find-fix{font-size:var(--meta);color:var(--mute);padding-block:var(--s2)}
.find-fix b{color:var(--ink);font-family:"Gabarito",sans-serif;font-weight:700}

/* 5. THE CHIPS ROW ADMITS IT SCROLLS.
   Driven by the row's own scroll position with animation-timeline, so there
   is no scroll handler and no JavaScript in the loop at all: site.js only
   decides whether the row overflows in the first place, twice, and adds the
   class. A row that fits keeps both edges hard, because a fade over nothing
   is a lie about where the filters are.
   Guarded, because without the guard an unsupported animation-timeline runs
   this on the document timeline at zero duration and lands on the end state,
   which would fade the left edge of a row scrolled to the left. */
@supports (animation-timeline:scroll()){
  @property --chip-l{syntax:"<length>";inherits:false;initial-value:0px}
  @property --chip-r{syntax:"<length>";inherits:false;initial-value:36px}
  .find-chips.has-more{
    -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 var(--chip-l),
      #000 calc(100% - var(--chip-r)),transparent 100%);
    mask-image:linear-gradient(90deg,transparent 0,#000 var(--chip-l),
      #000 calc(100% - var(--chip-r)),transparent 100%);
    animation:chipEdge linear both;
    animation-timeline:scroll(self inline)}
  @keyframes chipEdge{from{--chip-l:0px;--chip-r:36px}to{--chip-l:36px;--chip-r:0px}}
}

/* 6. WHERE THE JUMP LANDED.
   156 near-identical shelves on one page, so a jump that says nothing leaves
   the customer unsure it moved. The zest is the one already carrying the
   scroll line and every focus ring. It holds still long enough to be read,
   then leaves on the house curve, and it leaves immediately if the customer
   touches anything, because by then they have found it. */
/* OUTLINE, NOT AN INSET SHADOW, and the difference is visible on the live
   page. A box-shadow is painted with the element's own background, so on
   therapy.html the card's white photograph panel painted straight over the
   top of it and the mark came out bracketing the text with the picture
   outside it. An outline is painted after every descendant, so it goes round
   the whole card whatever is inside it. Offset inwards so it reads as part of
   the card rather than as a halo floating off its edge. */
.pick-hit{outline:3px solid var(--zest);outline-offset:-3px;
  animation:pickFade var(--slow) var(--settle) 1.8s both}
@keyframes pickFade{to{outline-color:rgba(255,116,31,0)}}
@media(prefers-reduced-motion:reduce){
  .pick-hit{animation:none}
  .find-res .prow a.nm .nm-w::after{transition:none}
}

/* THE ONE PLACE THE ROW HEIGHT IS STATED, and both numbers are measured.
   content-visibility was first told a row is about 280px, then 139 and 190.
   Session AF drove the real search, eight queries at four widths in each
   bucket, and took the height of every row that rendered: 366 rows below
   561 and 417 rows above it.

     <=560   median 165, mean 158, quartiles 149 and 165   was 139
     >=561   median 222, mean 209, quartiles 222 and 222   was 190

   Both were low, so the scrollbar was optimistic in both buckets and the
   thumb moved under the thumb as rows resolved. The MEAN is what goes in,
   not the median: the scrollbar is the sum of the rows, and the sum is the
   count times the mean. 149 appears above as the lower quartile at phone
   width, which is what the earlier audit had caught. */
.find-res .prow{contain-intrinsic-size:auto 158px}
@media(min-width:561px){.find-res .prow{contain-intrinsic-size:auto 209px}}

/* ==========================================================================
   PAGE FOURTEEN. THE ENQUIRY LIST.  ->  body.pg-enquiry
   (c) 2026 PPT EDU SUPPLIES

   WHY IT IS A PAGE AND NOT A PANEL.

   The list was a panel that expanded out of the fixed bar at the foot of
   the window. On a 390px screen the bar is 120px before it opens and the
   panel then covers most of what is left, so reviewing eight products
   meant scrolling a list inside a sheet on top of a page that was still
   scrolling behind it. Dan asked for the thing a shop does: browse,
   collect, then go somewhere built for checking before you commit.

   NOTHING BELOW IS A NEW DESIGN LANGUAGE.

   Four grounds, deep for the masthead and the footer, warm for the list,
   cool for the total. One radius, --r-ui. One curve, --settle. No line,
   no border, no shadow, no gradient: a row is told apart from the page by
   being white on beige, and the photograph well by being beige on white,
   which is the same filled-field rule every control on this site follows.
   The type is the two faces already loaded and the scale already set.

   THE FOURTEENTH KIND OF OPENING, and session T's rule is why there is
   one. Every other masthead on this site states a figure taken off the
   client's catalogue. This page holds the one figure that is not theirs:
   the count the visitor produced. So the count is the opening, set above
   the display scale, alone. It is the only page whose headline the
   customer writes.

   MEASURED CONTRAST. #FFFFFF on --g-deep #003C20 is 15.1:1 and
   rgba(255,255,255,.72) is 6.98:1, both already published above.
   --price #B50526 on --g-cool #C3DCE3 is 4.87:1 and --mute #5A5857 is
   4.94:1, the two figures the four-ground decision was set by.
   ========================================================================== */

/* ---------- the masthead: one figure, and it is theirs -------------------- */
.pg-enquiry .eq-top{background:var(--g-deep);color:#FFF;
  padding-block:var(--s5) var(--s6)}
.pg-enquiry .eq-top .crumb{color:rgba(255,255,255,.72)}
.pg-enquiry .eq-head{margin-top:var(--s4);display:grid;gap:var(--s4)}
.pg-enquiry .eq-tally{display:grid;gap:var(--s1);align-content:start}
/* Above the type scale on purpose. --t-huge tops out at 5rem and is the
   size a page HEADING is set at here; this is not a heading, it is the
   subject, so it is given its own step and nothing else on the page comes
   near it. 22vw holds the same share of the line at 390 as at 1440, which
   a fixed size cannot. Tabular figures so 9 and 10 occupy the same box and
   the label under them does not shift as items are removed. */
.pg-enquiry .eq-num{display:block;font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:clamp(4.5rem,22vw,11rem);line-height:1;letter-spacing:-.045em;
  font-variant-numeric:tabular-nums;color:#FFF}
.pg-enquiry .eq-of{display:block;font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--fig);letter-spacing:.14em;text-transform:uppercase;
  color:rgba(255,255,255,.72);max-width:22em}
.pg-enquiry .eq-top h1{color:#FFF;font-size:var(--t-big);max-width:12em}
.pg-enquiry .eq-nb{white-space:nowrap}
.pg-enquiry .eq-lede{margin-top:var(--s3);max-width:34em;font-size:var(--body);
  color:rgba(255,255,255,.86);line-height:1.55}
@media(min-width:900px){
  /* The figure holds the left edge and the words run beside it, which is
     the two-column arrangement the plate already uses at this breakpoint.
     Not a new layout, the same one. */
  .pg-enquiry .eq-head{grid-template-columns:auto minmax(0,1fr);
    column-gap:var(--s6);align-items:start}
}

/* ---------- the list ------------------------------------------------------ */
.pg-enquiry .eq-list{background:var(--g-warm);padding-block:var(--s6)}
.pg-enquiry .eq-list h2{font-size:var(--t-big);max-width:14em}
.pg-enquiry .eq-count{margin-top:var(--s2);max-width:36em;font-size:var(--body);
  color:var(--mute)}
.pg-enquiry .eq-count b{color:var(--ink)}
/* A MEASURE, BECAUSE A ROW IS NOT A BAND.
   At 1440 the rows ran the full 1296px of the type area and put the name at
   x=262 and its price at x=1200, with a thousand pixels of nothing between
   the two things the customer is comparing. Same failure the contact page
   band was rebuilt for. Capped, and capped left rather than centred, which
   is the rule this whole site is built to. */
.pg-enquiry .eq-rows{list-style:none;margin:var(--s5) 0 0;padding:0;
  display:grid;gap:var(--s2);max-width:64rem}

/* THE ROW IS THE WHOLE POINT OF THE PAGE, so it is given the room the
   panel could not. 88px of photograph at 390 against the 44px thumbnail
   the dock had no space for at all, the code and the printed size on
   their own line rather than run together with a middot, and the price
   in the client's own red at body size instead of --fig.
   Three columns and two rows: the picture and the remove control each
   span both rows, so the name and the price stack between them and the
   row grows with the name rather than clipping it. */
.pg-enquiry .eq-row{display:grid;
  grid-template-columns:88px minmax(0,1fr) 44px;
  grid-template-areas:"fig txt rm" "fig prc rm";
  column-gap:var(--s2);row-gap:6px;align-items:start;
  background:var(--g-paper);padding:var(--s2);border-radius:var(--r-ui)}
/* The well is the page's own ground showing through the card. These are
   catalogue cut-outs on white, so a white well would give the photograph
   no edge at all and a grey one would invent a colour. contain, never
   cover: a shelf cropped square is a different shelf. */
.pg-enquiry .eq-fig{grid-area:fig;display:block;inline-size:88px;block-size:88px;
  background:var(--g-warm);border-radius:var(--r-ui);overflow:hidden}
.pg-enquiry .eq-fig img{inline-size:100%;block-size:100%;object-fit:contain}
/* THE SENTENCE IS 84px WIDE AND THE TILE GAVE IT 76, so `overflow:hidden`
   sliced the last letters off "catalogue". 14px is this site's documented
   floor and the wording is the client's, so neither moves: the 6px padding
   comes off instead, which is the one number here nobody chose for a reason.
   84px of tile for 84px of sentence, measured at 390. */
.pg-enquiry .eq-fig.eq-nopic{display:grid;place-items:center;padding:2px;
  font-size:var(--fig);line-height:1.25;text-align:center;color:var(--mute)}
.pg-enquiry .eq-txt{grid-area:txt;display:grid;gap:4px;align-content:start}
.pg-enquiry .eq-n{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:1.0625rem;letter-spacing:-.02em;line-height:1.2;color:var(--ink)}
/* One link per row and it is the name, exactly as a search result is one
   link and it is the name. The arrow travels with the last word so it can
   never be left alone on a line of its own. */
.pg-enquiry a.eq-n{text-decoration:none;
  transition:color var(--fast) var(--settle)}
.pg-enquiry a.eq-n .eq-w{white-space:nowrap}
.pg-enquiry a.eq-n .eq-w::after{content:"\2009\2192";color:var(--mute);
  transition:color var(--fast) var(--settle)}
@media(hover:hover){
  .pg-enquiry a.eq-n:hover{color:var(--price)}
  .pg-enquiry a.eq-n:hover .eq-w::after{color:var(--price)}
}
@media(hover:none){.pg-enquiry a.eq-n:hover{color:var(--ink)}}
.pg-enquiry .eq-meta{display:flex;flex-wrap:wrap;gap:2px var(--s2);
  font-size:var(--fig);color:var(--mute)}
.pg-enquiry .eq-meta .code{letter-spacing:.09em}
.pg-enquiry .eq-prc{grid-area:prc;display:flex;flex-wrap:wrap;
  align-items:baseline;gap:2px var(--s1)}
/* 18px against 17px body is a 6% step, which is not a step. The search
   results already set their price line at --t-sub and this is the same line
   at the other end of the same journey, so it is the same size. The census
   missed this pair because an empty list renders no rows; it was found by
   adding three items through the page's own control and measuring. */
.pg-enquiry .eq-prc .rm{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-sub);letter-spacing:-.02em}
/* Where the catalogue prints no price the row says so in the site's own
   words and gets the same amount of ink as a price does, because an empty
   space there reads as free and a small grey note reads as an oversight. */
.pg-enquiry .eq-prc .eq-none{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-sub);letter-spacing:-.02em;color:var(--mute)}
.pg-enquiry .eq-prc .eq-ask{font-size:var(--fig);color:var(--mute)}
.pg-enquiry .eq-x{grid-area:rm;align-self:center;inline-size:44px;block-size:44px;
  display:grid;place-items:center;border-radius:var(--r-ui);cursor:pointer;
  font-size:1.5rem;line-height:1;color:var(--mute);
  transition:background var(--fast) var(--settle),color var(--fast) var(--settle)}
@media(hover:hover){
  .pg-enquiry .eq-x:hover{background:var(--price);color:#FFF}
}
@media(hover:none){.pg-enquiry .eq-x:hover{color:var(--mute)}}
@media(min-width:700px){
  /* At width the price leaves the name's column and takes one of its own,
     so nine prices read down a single edge and can be compared. */
  .pg-enquiry .eq-row{grid-template-columns:112px minmax(0,1fr) auto 44px;
    grid-template-areas:"fig txt prc rm";
    column-gap:var(--s4);align-items:center;padding:var(--s2) var(--s3)}
  .pg-enquiry .eq-fig{inline-size:112px;block-size:112px}
  .pg-enquiry .eq-prc{display:grid;justify-items:end;text-align:right;gap:2px}
  .pg-enquiry .eq-n{font-size:var(--t-sub)}
}

/* ---------- nothing on it yet ---------------------------------------------
   [hidden] is a UA rule at (0,0,1) and .pg-enquiry .eq-rows is (0,2,0), so
   display:grid beat it and an emptied list still laid out its own 64px of
   margin. Two of these declarations restore about 150px of the empty state
   that was being spent on nothing. */
.pg-enquiry .eq-rows[hidden],.pg-enquiry .eq-count[hidden]{display:none}
.pg-enquiry .eq-empty{margin-top:var(--s4);max-width:52em}
.pg-enquiry .eq-empty-say{font-size:var(--body);color:var(--mute);max-width:36em}
.pg-enquiry .eq-empty-say b{color:var(--ink)}
.pg-enquiry .eq-empty-h{margin-top:var(--s5);font-size:var(--t-mid);
  letter-spacing:-.02em}
.pg-enquiry .eq-ways{margin-top:var(--s3);list-style:none;padding:0;display:grid;
  gap:var(--s1) var(--s5);
  grid-template-columns:repeat(auto-fill,minmax(min(260px,100%),1fr))}
.pg-enquiry .eq-ways a{display:flex;gap:var(--s2);align-items:center;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--t-sub);
  letter-spacing:-.02em;color:var(--ink);text-decoration:none;min-height:48px;
  transition:color var(--fast) var(--settle)}
.pg-enquiry .eq-ways .num{font-family:"Poppins",sans-serif;font-weight:400;
  font-size:var(--meta);color:var(--mute);font-variant-numeric:tabular-nums}
@media(hover:hover){.pg-enquiry .eq-ways a:hover{color:var(--price)}}
@media(hover:none){.pg-enquiry .eq-ways a:hover{color:var(--ink)}}
.pg-enquiry .eq-empty-find{margin-top:var(--s4);max-width:34em;
  font-size:var(--meta);color:var(--mute)}

/* ---------- the total, and sending it ------------------------------------- */
.pg-enquiry .eq-send{background:var(--g-cool);padding-block:var(--s6)}
.pg-enquiry .eq-send h2{font-size:var(--t-big);max-width:12em}
.pg-enquiry .eq-sum{margin-top:var(--s4)}
.pg-enquiry .eq-sum-fig{font-family:"Gabarito",sans-serif;font-weight:700;
  font-size:var(--t-big);line-height:1;letter-spacing:-.03em;
  font-variant-numeric:tabular-nums;color:var(--price)}
.pg-enquiry .eq-sum-fig .eq-sum-for{display:block;font-family:"Poppins",sans-serif;
  font-weight:400;font-size:var(--fig);letter-spacing:.14em;text-transform:uppercase;
  color:var(--mute);margin-bottom:var(--s1)}
.pg-enquiry .eq-sum-note{margin-top:var(--s2);max-width:34em;font-size:var(--body);
  color:var(--mute)}
.pg-enquiry .eq-sum-note b{color:var(--ink)}
.pg-enquiry .eq-terms{margin-top:var(--s4);max-width:36em;font-size:var(--meta);
  color:var(--mute)}
/* The two controls size to their own text. Stretched to the full width they
   read as two equal slabs, and printing is not equal to sending. The footer
   two blocks below sets its pair the same way. */
.pg-enquiry .eq-acts{margin-top:var(--s5);display:flex;flex-wrap:wrap;gap:var(--s2)}
.pg-enquiry .eq-reassure{margin-top:var(--s3);max-width:32em;font-size:var(--meta);
  color:var(--mute)}
/* CLEAR IS SET APART FROM SEND, AND IT ASKS TWICE.
   In the bar these two sat one thumb-width from each other and Clear is
   the only action on this site a customer cannot undo: there is no copy
   of the list anywhere else. It is quiet, it is below the terms rather
   than beside the send button, and the first press only arms it. */
.pg-enquiry .eq-wipe{margin-top:var(--s4)}
.pg-enquiry .eq-clear{display:inline-flex;align-items:center;min-height:44px;
  padding:0 var(--s2);margin-inline-start:calc(var(--s2) * -1);
  border-radius:var(--r-ui);cursor:pointer;
  font-family:"Gabarito",sans-serif;font-weight:700;font-size:var(--meta);
  color:var(--mute);transition:color var(--fast) var(--settle),
  background var(--fast) var(--settle)}
.pg-enquiry .eq-clear[data-armed="1"]{background:var(--price);color:#FFF}
@media(hover:hover){.pg-enquiry .eq-clear:hover{color:var(--price)}
  .pg-enquiry .eq-clear[data-armed="1"]:hover{color:#FFF}}
@media(hover:none){.pg-enquiry .eq-clear:hover{color:var(--mute)}
  .pg-enquiry .eq-clear[data-armed="1"]:hover{color:#FFF}}

/* The header control is a link to this page on the other thirteen. Here it
   is a link to the page you are standing on, so it is marked as current and
   it returns you to the top rather than reloading. */
.pg-enquiry .listBtn[aria-current="page"]{background:var(--ink);color:#FFF}
.pg-enquiry .listBtn[aria-current="page"][data-n="0"] .lb-n{color:rgba(255,255,255,.72)}

/* Motion. The same two scroll-driven devices the other pages use, on the
   same curve and the same ranges, and nothing that is not already in the
   file. The rows are deliberately still: a list you are checking should
   not be moving while you count it. */
@supports (animation-timeline:view()){
  @media(prefers-reduced-motion:no-preference){
    .pg-enquiry .eq-sum-fig{animation:settleUp linear both;
      animation-timeline:view();animation-range:entry 2% cover 20%}
    .pg-enquiry .eq-ways li{animation:chipIn linear both;
      animation-timeline:view();animation-range:entry 8% cover 30%}
  }
}
@media(prefers-reduced-motion:reduce){
  .pg-enquiry .eq-sum-fig,.pg-enquiry .eq-ways li{
    animation:none;opacity:1;transform:none;clip-path:none}
}
