/* components part 9 of 9 — 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}}