/* components part 7 of 11 — split for the host's 16KB FTP ceiling.
   Generated by split_for_ftp.py; edit the prototype, not this. */
@media(max-width:760px){
  .vs-cols{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:84%;overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:12px;-webkit-overflow-scrolling:touch}
  .vs-cols>.vs-card{scroll-snap-align:start}}@media(max-width:700px){
  .cardgrid.four{grid-template-columns:none!important;grid-auto-flow:column;grid-auto-columns:74%;overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:12px;-webkit-overflow-scrolling:touch}
  .cardgrid.four>.mini{scroll-snap-align:start}
  .timeline{grid-auto-columns:80%}}@media(max-width:900px){
  .cardgrid.ind{grid-template-columns:repeat(3,1fr);grid-auto-flow:row;grid-template-rows:none;grid-auto-columns:auto;overflow-x:visible;gap:14px 10px;padding-bottom:0}
  .cardgrid.ind .mini.svc{flex-direction:column;text-align:center;gap:10px;padding:14px 8px}
  .cardgrid.ind .mini.svc .svc-img{width:100%;height:auto;max-width:104px}
  .cardgrid.ind .mini.svc .svc-body{text-align:center}
  .cardgrid.ind .mini.svc h3{font-size:14px;line-height:1.2}
  .cardgrid.ind .mini.svc p,.cardgrid.ind .mini.svc .svc-more{display:none}}
/* reveal */
.rv{opacity:0;transform:translateY(20px);transition:opacity 1s ease,transform 1s cubic-bezier(.2,.7,.2,1)}
.rv.in{opacity:1;transform:none}@media(prefers-reduced-motion:reduce){.rv{opacity:1;transform:none;transition:none}.card img,.mini.svc .svc-img,.mini.why .why-img{transition:none}}

/* live colour switcher */
body.mono .btn-p{background:var(--ink);border-color:var(--ink)}
body.mono .btn-p:hover{background:#000;border-color:#000}
body.mono .ba-knob svg{color:var(--ink)}
.note{position:fixed;right:12px;bottom:12px;max-width:330px;z-index:210;background:var(--white);border:1px solid var(--hair);color:var(--muted);font-size:10.5px;letter-spacing:.02em;line-height:1.4;text-align:center;padding:9px 13px;border-radius:6px;box-shadow:0 6px 24px rgba(0,0,0,.1)}


/* ---------------------------------------------------------------- the wall
   Layout is pure CSS. flex-grow AND flex-basis both scale with the aspect ratio,
   so every card in a wrapped row grows by one shared factor and the row lands flush
   at a single height with native ratios intact. No JS measures or positions
   anything, which is what makes appending cards mid-scroll safe. */
.pf-wrap{background:var(--white)}
/* No right gutter. The wall runs to the edge of the viewport exactly like the hero
   image on a service page — an inset gallery reads as a widget dropped into a page. */
/* container-type makes the wall queryable by its OWN width, which is the whole point:
   the wall is not the viewport. On desktop the page is a `212px minmax(0,1fr)` grid, so a
   1280px laptop leaves the wall 1014px and a 1200px one leaves 938px — three columns of
   ~310px — while at 1099px the sidebar collapses and the wall jumps back to full width.
   Viewport media queries cannot see any of that. `inline-size` is safe here: the wall's
   width comes from its grid track, never from its contents, and the contain it implies
   (layout/style/inline-size) does not disturb .c's percentage flex-basis or the absolutely
   positioned .pbadge inside each card. */
.wall{display:flex;flex-wrap:wrap;gap:var(--wgap);--wgap:8px;
      --gw:100%;--dens:5.0;--colscale:1;--rowcap:1.9;
      width:100%;padding-right:0;
      container-type:inline-size;container-name:wall}
.pf-band{display:contents}   /* pacing carrier, never a box. pf-band, not band: the
   shared design system already owns .band as a two-column content band with a grid and
   a gap, and WALL_CSS is spliced into /services/interior-rendering/ after that page's
   own style. display:contents then collapsed every content band on that page. */
/* Density as a class rather than an inline row height: an inline style cannot be
   beaten by a media query, so the responsive column counts below could never win. */
/* A row breaks on the SUM of its frames' ratios, not on any one frame — the ordering
   interleaves aspect ratios, so a row is typically 16:9 + 16:9 + 3:2 rather than three
   of a kind. n frames share a row while  sum(ar) / d + gaps <= 1.  A three-frame row
   here sums to about 5.05, so d=6.1 fits it (83%) and d=4.8 cannot (105%), which is
   what makes `open` the two-up pf-band and `normal` the three-up one. Tuning these
   against a single ratio is what produced a wall of nothing but threes. */
.pf-band[data-band="open"]{--dens:4.8}
.pf-band[data-band="normal"]{--dens:6.1}
.pf-band[data-band="dense"]{--dens:6.4}

/* hello-elementor paints EVERY <button> #CC3366 and our page buttons inherit it. The
   components reset covers bare elements but not this, and chrome.css only scopes its
   reset to the header and footer. It surfaced the moment the wall's images were parked:
   an <img> with no src falls back to its ALT TEXT, and that text was rendering in
   hello-elementor's pink across every placeholder. */
.wall .c,.pf-side button,.lb button,.strip-arrow,.fg-clear,.fg-toggle{color:var(--ink)}
.lb button,.lb-nav,.pano-nav,.pano-play{color:#fff}
/* A parked tile is a grey box and nothing else.
   visibility:hidden, not color:transparent. An <img> with no src and a non-empty alt
   puts Chrome into alt-text fallback layout, and that draws a broken-image GLYPH from a
   UA shadow tree next to the text. Killing the colour and the font size hid the text and
   left the icon, which is the little square Siim was seeing in the corner of every
   placeholder. visibility:hidden stops the element being painted at all, glyph included,
   while keeping its box so the grid does not reflow.
   The attribute clears itself the moment the loader assigns a src, so this needs no
   teardown. */
.wall img[data-s]{visibility:hidden}

.c{--rowh:calc(var(--gw) / var(--dens) * var(--colscale));
   position:relative;display:block;padding:0;border:0;background:var(--grey);
   cursor:zoom-in;overflow:hidden;
   flex-grow:var(--ar);flex-basis:calc(var(--ar) * var(--rowh));flex-shrink:1;min-width:0;
   /* the spike killer: without it an unlucky row inflates to 2.26x --rowh */
   /* 1.9, not 1.5. The cap stops one freak-ratio frame spiking a row, but at 1.5 it sat
      BELOW the growth factor ordinary rows legitimately need — measured up to 1.909
      uncapped at a 360px wall — so it was clipping normal rows and leaving the white
      wedge on the right. 1.9 is the smallest value that clears the current 267-frame
      set: zero short mid-list rows at every wall width from 360 to 3000, with row
      composition byte-identical to 1.5 at all of them. Re-check it if the density or
      the aspect-ratio clamp changes. */
   max-width:calc(var(--ar) * var(--rowh) * var(--rowcap));
   aspect-ratio:var(--ar)}
/* content-visibility:auto used to live here as an optimisation for 233 frames. It
   also stops the browser rendering the subtree until it is nearly on screen, and a
   lazy <img> inside unrendered content does not begin loading either — so fast
   scrolling landed on rows of empty grey boxes that only filled in after they were
   already in view. The aspect-ratio box already reserves the layout, which was the
   other thing content-visibility was buying. */
.c img{width:100%;height:100%;object-fit:cover;display:block;transition:filter .35s}
/* The hover says "this opens" and nothing else — no title card, no zoom, matching
   the gallery on the service pages. */
.c:hover,.c:focus,.c:focus-visible{background:var(--grey);color:var(--ink)}
.c:hover img,.c:focus-visible img{filter:brightness(.9)}
.c:focus-visible{outline:2px solid var(--accent);outline-offset:2px;z-index:2}
/* the last row must not stretch its survivors across the full viewport */
.wall::after{content:"";flex-grow:1e6;flex-basis:0;min-width:0}@container wall (width < 1041px){ .c{--colscale:1.30}}@container wall (width >= 2500px){ .c{--colscale:.78}}@supports not (container-type:inline-size){
  @media(max-width:760px){ .wall{--colscale:1.30} }}
/* One frame may be pulled out of the justified run and given a row to itself, so it
   reads as roughly a two-row element. It cannot be done inside a row: every card in a
   line lands at `--rowh + freespace/sum(grow)`, a height independent of the card,
   because grow and basis are both proportional to --ar — that identity IS the flush
   row. A card with a doubled basis is therefore taller than its row-mates, the line's
   cross size takes the max, and align-items:stretch overrides the others'
   aspect-ratio: measured, a 16:9 exterior beside a doubled card rendered at 1.035 and
   lost 42% of its width to object-fit:cover.
   Two zero-height full-width spacers fence the frame onto its own line instead, and
   nothing else in the wall changes size at all.
   2.5 and not 2: a three-up row settles about 26% above --rowh once free space is
   distributed, so 2.5x --rowh lands at 1.98x the neighbouring rows.
   flex-basis:100% is the shortcut that does NOT work — line breaking uses the
   hypothetical main size, which is already clamped by max-width, so the cap silently
   lets a neighbour back onto the line and crushes it. */
.brk{flex:0 0 100%;height:0;margin:0;padding:0}
/* A frame named in `pair` runs two-up instead of three, for emphasis. Doubling the basis
   is safe HERE and not in a normal row because these are declared in adjacent pairs: both
   cards in the row get the same multiplier, so the row still lands flush and neither is
   stretched past its aspect ratio. Reuses --colscale rather than a new mechanism. */
.c.pair{--colscale:1.5}
/* Full width, not a centred island. At 2.5x --rowh the frame came out 813px inside a
   1322px wall — 61% — which left white down both sides and read as a broken row rather
   than a feature. Taking the whole width removes the gaps and makes the emphasis look
   deliberate.
   The height comes from aspect-ratio, NOT from `height:calc(var(--rowh)*2.5)`. --rowh is
   a PERCENTAGE (calc(100%/6.1)), and a percentage height resolves against the parent's
   height — which for a flex item in a wrap container is indefinite, so it silently fell
   back to auto and the frame rendered 1148px tall at its native 1.5. 2.4 is two normal
   rows at any wall width, and .c img is object-fit:cover so the crop takes sky and
   foreground off a dusk aerial, which it can afford. */
.c.feat{flex:0 0 100%;max-width:100%;aspect-ratio:2.4}
/* A filtered-out feature must take its fences with it, or the wall keeps a hole and a
   stranded two-up where the frame used to be. */
.c.feat[hidden] + .brk,
.brk:has(+ .c.feat[hidden]){display:none}
.c[hidden]{display:none}