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

.case-stack .cs-3{transform:translate(34px,-30px) rotate(4deg);z-index:1;filter:brightness(.93)}
.case-stack .cs-2{transform:translate(17px,-15px) rotate(2deg);z-index:2;filter:brightness(.97)}
.case-stack .cs-front{z-index:3}
.case-stack:hover .cs-3{transform:translate(44px,-40px) rotate(6deg)}
.case-stack:hover .cs-2{transform:translate(24px,-22px) rotate(3deg)}
.case-stack .cs-count{position:absolute;bottom:12px;left:12px;z-index:4;background:rgba(20,18,14,.72);backdrop-filter:blur(6px);color:#fff;font-size:12px;font-weight:700;letter-spacing:.03em;padding:8px 14px;border-radius:999px;display:inline-flex;align-items:center;gap:7px}
.case-stack .cs-count svg{width:13px;height:13px}@media(max-width:820px){.case-grid{grid-template-columns:minmax(0,1fr);gap:40px;padding-left:0}.case-copy{padding-inline:var(--gut)}.case-media{order:-1}.case-stack{margin:20px 40px 10px 0}.case-stats{gap:12px}}

/* FAQ */
.faq-grid{display:grid;grid-template-columns:0.72fr 1.28fr;gap:clamp(36px,5vw,72px);align-items:start}
.faq-aside{position:sticky;top:110px}
.faq-aside h2{font-size:clamp(30px,3.6vw,46px);font-weight:800;letter-spacing:-0.035em;line-height:1.03;margin-bottom:16px}
.faq-aside p{color:var(--muted);font-size:16.5px;line-height:1.6;margin-bottom:24px}
details.faq{border-bottom:1px solid var(--hair)}
details.faq summary{list-style:none;cursor:pointer;padding:24px 0;display:flex;align-items:center;gap:20px;font-family:var(--sans)}
details.faq summary::-webkit-details-marker{display:none}
details.faq:hover summary .q{color:var(--accent)}
details.faq summary .q{font-size:19px;font-weight:700;color:var(--ink);flex:1;letter-spacing:-0.015em;transition:color .2s}
details.faq summary .pl{width:20px;height:20px;position:relative;flex:0 0 20px}
details.faq summary .pl::before,details.faq summary .pl::after{content:"";position:absolute;background:var(--accent);transition:.3s;border-radius:2px}
details.faq summary .pl::before{top:9px;left:0;width:20px;height:2.5px}
details.faq summary .pl::after{top:0;left:9px;width:2.5px;height:20px}
details.faq[open] summary .pl::after{transform:rotate(90deg);opacity:0}
details.faq .a{padding:0 0 26px;color:var(--body);max-width:74ch;line-height:1.72;font-size:16px}@media(max-width:820px){.faq-grid{grid-template-columns:1fr;gap:20px}.faq-aside{position:static}}

/* PAGE CTA */
.pagecta{position:relative;overflow:hidden;background:var(--warm)}
.pagecta img.bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:0}
.pagecta::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,rgba(20,19,16,.74),rgba(20,19,16,.86));z-index:1}
.pagecta .wrap{position:relative;z-index:2;text-align:center;padding-block:clamp(68px,8vw,112px)}
.pagecta .eyebrow{color:#d8b98a}
.pagecta h2{color:#fff;font-size:clamp(34px,4.6vw,58px);font-weight:800;letter-spacing:-0.035em;line-height:1.03;max-width:20ch;margin:0 auto 20px}
.pagecta p{color:rgba(255,255,255,.82);font-size:18px;line-height:1.6;max-width:56ch;margin:0 auto 30px}
.pagecta .cta-btns{display:flex;gap:14px;justify-content:center;flex-wrap:wrap}
.pagecta .btn-g{--btn-fg:#fff;color:#fff;border-color:rgba(255,255,255,.55)}
.pagecta .btn-g:hover,.pagecta .btn-g:focus{background:rgba(255,255,255,.14)}
.pagecta .offer{margin-top:24px;font-size:14.5px;color:rgba(255,255,255,.66);font-weight:600;letter-spacing:.02em;max-width:56ch;margin-inline:auto}
/* [hidden] must actually hide. The UA rule is `[hidden]{display:none}` at specificity
   (0,1,0), and this sheet carries `img:not([class*="elementor"]){display:block}` and
   `.lb img{display:block}`, both (0,1,1) — so every `el.hidden = true` in our JS was
   silently doing nothing to images. It surfaced in the panorama viewer: opening a 360
   sets lbImg.hidden and removes its src, but the element stayed in flow carrying the
   ALT TEXT of whatever image was opened before it, which the browser then drew as
   broken-image text across the viewer. !important because the whole point of the
   attribute is that it wins. */
[hidden]{display:none!important}
/* The rating cluster in the footer. Deliberately NOT .client-rating: that component is
   drawn for a light band, its rules live in the page-gated components sheet which loads
   after this one, and at equal specificity it won every property — `Rated 4.9` computed
   to var(--ink) on a var(--dark) footer, i.e. black on black. Standalone rules instead.
   `.foot-rating .hero-avatars img` is (0,2,1) so it also clears the bare
   `.hero-avatars img` rule at (0,1,1). */
/* "See our reviews" is coming out everywhere: the plan is to link the Clutch profile
   from the rating itself. Hidden rather than deleted so the markup and the data-proof
   hooks stay intact for that swap. */
.rl-more{display:none}@media(max-width:1040px){
  .timeline{grid-template-columns:none;grid-auto-flow:column;grid-auto-columns:44%;overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:14px;-webkit-overflow-scrolling:touch}
  .timeline .step{scroll-snap-align:start}
  .timeline .step:not(:last-child)::after{display:none}}@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}