/* components part 9 of 13 — split for the host's 16KB FTP ceiling.
   Generated by split_for_ftp.py; edit the prototype, not this. */

.wrap-wide:has(#tf-all:focus-visible) .tst-filters label[for=tf-all],.wrap-wide:has(#tf-no:focus-visible) .tst-filters label[for=tf-no],.wrap-wide:has(#tf-uk:focus-visible) .tst-filters label[for=tf-uk],.wrap-wide:has(#tf-fi:focus-visible) .tst-filters label[for=tf-fi],.wrap-wide:has(#tf-ee:focus-visible) .tst-filters label[for=tf-ee],.wrap-wide:has(#tr-all:focus-visible) .tst-filters label[for=tr-all],.wrap-wide:has(#tr-architect:focus-visible) .tst-filters label[for=tr-architect],.wrap-wide:has(#tr-developer:focus-visible) .tst-filters label[for=tr-developer],.wrap-wide:has(#tr-agency:focus-visible) .tst-filters label[for=tr-agency]{outline:2px solid var(--accent);outline-offset:2px}
.tst-list{display:grid;gap:clamp(22px,3vw,34px)}
.tst{display:grid;grid-template-columns:minmax(0,1.04fr) minmax(0,1fr);border:1px solid var(--hair);border-radius:14px;overflow:hidden;background:var(--white)}
.tst.solo{grid-template-columns:minmax(0,1fr)}
.tst.solo blockquote{max-width:72ch}
.tst-media{position:relative;height:clamp(320px,34vw,460px);min-height:clamp(320px,34vw,460px);background:var(--hair)}
.tst-mi{position:absolute;inset:0;display:grid;grid-template-rows:1.5fr 1fr;gap:3px}
/* A card can legitimately carry a single image: a client may withdraw the others at any
   time (Antti, 23 Sep). Without this the 1fr sub-row is still reserved and the card shows
   a grey band under the one image, which reads as a broken thumbnail rather than a choice. */
.tst-mi:not(:has(.tst-sub)){grid-template-rows:1fr}
.tst-mi>*{min-width:0;min-height:0}
/* min-height/width:0 matters here specifically for the two .tst-sub images: they're grid
   items of a NESTED grid (.tst-sub inside .tst-mi), and .tst-mi>* only reaches its direct
   child (.tst-sub itself), not these grandchildren. Without it each kept its default
   min-height:auto and rendered at its own intrinsic aspect (~438px) instead of the 183px
   row height:100% asked for -- masked visually by .tst{overflow:hidden} most of the time,
   but it meant object-position was being computed against the wrong, much taller box. */
.tst-mi img{width:100%;height:100%;min-width:0;min-height:0;object-fit:cover;display:block}
.tst-sub{display:grid;grid-template-columns:1fr 1fr;gap:3px}
.tst-body{padding:clamp(24px,3.2vw,42px);display:flex;flex-direction:column;justify-content:center;min-width:0}
.tst blockquote{margin:0 0 30px;font-family:var(--disp);font-size:clamp(16px,1.35vw,19px);line-height:1.45;color:var(--ink);font-weight:500;letter-spacing:-0.02em;text-wrap:pretty}
.tst blockquote strong{font-weight:inherit;color:var(--accent-d);text-decoration:underline;text-decoration-color:var(--accent);text-decoration-thickness:2px;text-underline-offset:5px}
.tst blockquote p+p{margin-top:13px}
.tst-who{display:flex;align-items:flex-start;gap:14px 16px;flex-wrap:nowrap;padding-top:24px;border-top:1px solid var(--hair)}
.tst-ph{width:54px;height:54px;border-radius:50%;object-fit:cover;flex:0 0 auto}
.tst-id{min-width:0;flex:1 1 0%}
.tst-id .tst-name{display:block;margin:0;font-family:var(--disp);font-size:16.5px;font-weight:700;color:var(--ink);letter-spacing:-0.01em;line-height:1.25}
.tst-role{display:block;font-size:14px;color:var(--muted);line-height:1.4;margin-top:2px}
.tst-link{display:block;font-size:13px;font-weight:600;color:var(--accent-d);margin-top:3px;text-decoration:none}
.tst-link:hover{text-decoration:underline}
.tst-ctry{display:flex;align-items:center;gap:7px;font-size:13px;color:var(--muted);margin-top:3px}
.tst-flag{width:20px;height:14px;border-radius:2px;box-shadow:0 0 0 1px rgba(0,0,0,.14);flex:0 0 auto;display:block}
.tst-logo{flex:0 0 auto;width:calc(112px * var(--logo-scale,1));margin-left:10px;color:var(--ink);display:flex;align-items:center}
/* Same flattening as the hero marks, so every card logo lands on the identical #999
   rather than each keeping its own weight (plain grayscale() left Tokvam and Larsen near
   black, since they inherit currentColor, and Oma's red a dark charcoal).
   --mark-filter is the escape hatch for a mark that flattening would destroy: Oma's is
   white lettering (#F5F1F2) knocked out of a red field (#CF0A2C), and brightness(0)
   drives BOTH to black and swallows the word. That one is set in reviews.py to
   grayscale+opacity instead, tuned so the red field composites to the same #999 while
   the knocked-out lettering stays white. */
.tst-logo svg,.tst-logo img{width:100%;height:auto;max-height:52px;display:block;filter:var(--mark-filter,brightness(0) invert(.6))}
.tst-meta{margin-top:8px;font-size:13px;color:var(--muted);letter-spacing:.01em;line-height:1.5}
.tst-meta a{color:inherit;text-decoration:underline;text-underline-offset:2px}@media(max-width:820px){
  .tst,.tst.solo{grid-template-columns:minmax(0,1fr)}
  .tst-media{height:clamp(280px,68vw,420px);min-height:clamp(280px,68vw,420px)}
  .tst-head{align-items:flex-start}
  .tst-logo{margin-left:0;width:calc(96px * var(--logo-scale,1))}}
.tst-ph-ph{width:54px;height:54px;border-radius:50%;flex:0 0 auto;display:flex;align-items:center;justify-content:center;background:var(--grey);border:1px solid var(--hair);color:var(--faint)}
.tst-ph-ph svg{width:24px;height:24px}@media(min-width:1180px){
  .tst-ph,.tst-ph-ph{width:64px;height:64px}
  .tst-ph-ph svg{width:28px;height:28px}
  .tst-logo{width:calc(132px * var(--logo-scale,1))}}
.tst-mi img,.tst-media>img{cursor:zoom-in}
/* Floats under the header on scroll, same idea as .pf-side on /portfolio/ (a horizontal
   strip pinned at the sticky header's real height, kept in sync in --head-h). Only two
   short rows here, so no collapse/scroll-inside machinery is needed, just the pin. */
.tst-filters{position:sticky;top:var(--head-h);z-index:40;display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:14px;margin:0 0 clamp(26px,3.2vw,40px);padding-block:14px;background:var(--white);border-bottom:1px solid var(--hair)}
.tst-frow{display:flex;flex-wrap:wrap;gap:8px}@media(max-width:900px){
  .tst-head{grid-template-columns:minmax(0,1fr)}
  /* Mobile reverts to left: this is where it stacks under the H1 and picks up the sitewide
     bled/scroll treatment, which is built to sit flush with the left edge like everything
     else on a phone, not the desktop-only right-alignment above. */
  .tst-side{align-items:flex-start;text-align:left;gap:22px;margin-top:22px}
  /* Same 50%-resolves-against-this-box trap as .clients above: capped at 640px, this
     column's centre sat 24px left of the viewport's, so the bled .client-rating row
     started 24px left of the H1 instead of flush under it. The paragraph keeps its own
     48ch measure, so dropping the cap here only lets the H1 use the full gutter width. */
  .tst-head-txt{max-width:none}}@media(max-width:640px){
  .tst-filters{flex-direction:column;align-items:stretch}
  /* Each row (country, role) is its own horizontal scroller instead of wrapping onto a
     second line -- same one-row-per-group idiom as .pf-side on /portfolio/ at this width. */
  .tst-frow{flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
  .tst-frow::-webkit-scrollbar{display:none}
  .tst-frow .tst-chip{flex:0 0 auto}}


/* ---------------------------------------------------------------- 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}