/* /assets/css/site.css — the one stylesheet every presive.co page shares (b105; b106).
 *
 * Values come from /assets/css/tokens.css (linked first by the load-order block, b106 S01); this file holds the
 * components and global rules that must be identical on every page. It loads after each page's own <style>
 * (motion-includes partial), so an equal-specificity rule here wins by order.
 * The --pv-* names are b105's; they now alias tokens.
 */
:root{
  --pv-consent-z:88;                        /* above the float pill (80), below menu (90) / sheets (95) / skip (200) */
  --pv-consent-w:420px;                     /* b106 D5: the card's width below 1024 px; from 1024 px (b107) it is one row, at most 940 px */
  --pv-consent-bg:var(--panel-2);                 /* b107 N11: opaque; captions showed through at 96 % */
  --pv-consent-shadow:0 18px 50px -18px color-mix(in srgb,var(--bg-darker) 70%,transparent);
  --pv-gutter:var(--gutter);
}

/* ============================================================
   CONSENT BANNER (b105 step 02; b106 S02 / D5)
   ≥ 1024 px (b107): one row, at most 940 px wide, at the bottom left on every page (the b106 top placement on the
   home page went with the ribbon field; the html.pv-consent-top rules below are kept for a page that needs them).
   Below 1024 px: a short sheet across the bottom: one line, two buttons, the close button.
   While it is open, cookies.js publishes its height as --pv-banner-h and sets html.pv-consent-open; the
   scroll-padding and bottom room below keep every focused control clear of it (WCAG 2.4.11). cookies.js also sets
   html.pv-consent-top on the home page (classes, not :has() on <html>: those re-match on every DOM change).
============================================================ */
.pv-consent{
  position:fixed;z-index:var(--pv-consent-z);
  left:var(--pv-gutter);bottom:max(var(--pv-gutter),env(safe-area-inset-bottom));
  width:min(var(--pv-consent-w),calc(100% - 2 * var(--pv-gutter)));
  color:var(--white);
  background:var(--pv-consent-bg);
  border:1px solid var(--line-strong);
  border-radius:var(--radius-card);
  box-shadow:var(--pv-consent-shadow);
  font-family:inherit;
}
.pv-consent[hidden]{display:none}
/* b108 A07/A10: forced colours drop background-only shapes. A border costs nothing in normal mode and gives
   every button, the consent card and the wizard's progress bar an outline that high-contrast users can see. */
@media (forced-colors:active){
  .btn,.pv-consent__btn,button{border:1px solid ButtonText}
  .pv-consent{border:1px solid CanvasText}
  .q-bar,.q-bar__fill,progress{border:1px solid CanvasText;forced-color-adjust:none}
}
/* b108 N01: below 1024 px the sheet is the full width of the screen's foot, so anything at the bottom of a
   short page (the quote wizard's Next, most of all) sat underneath it on a first visit. While the sheet is
   up the page reserves its height, so every control can be scrolled clear. cookies.js publishes the height. */
@media (max-width:1023px){
  html.pv-consent-open body{padding-bottom:calc(var(--pv-banner-h,140px) + 16px)}
}
.pv-consent__in{position:relative;display:flex;flex-direction:column;gap:14px;padding:18px 18px 18px 20px}
.pv-consent__copy{min-width:0;padding-right:36px}
.pv-consent__title{
  font-size:var(--fs-micro);font-weight:600;letter-spacing:var(--ls-caps);
  text-transform:uppercase;color:var(--foot-cat);line-height:1.2;
}
.pv-consent__txt{margin-top:6px;font-size:var(--fs-xs);line-height:1.5;color:var(--gray-3);text-wrap:pretty}
.pv-consent__txt a{color:var(--white);text-decoration:underline;text-underline-offset:3px;text-decoration-thickness:1px;white-space:nowrap}
.pv-consent__state:empty{display:none}
.pv-consent__act{display:flex;gap:8px}
.pv-consent__btn{
  flex:1 1 0;min-height:44px;padding:0 14px;border-radius:var(--radius-btn);white-space:nowrap;
  font:inherit;font-size:var(--fs-m);font-weight:500;cursor:pointer;
  color:var(--white);background:transparent;border:1px solid var(--line-strong);
  transition-property:background-color,color;transition-duration:var(--t-press);transition-timing-function:var(--ease);
}
@media (hover:hover){ .pv-consent__btn:hover{background:color-mix(in srgb,var(--white) 8%,transparent)} }
.pv-consent__btn--primary{background:var(--btn-light);color:var(--ink);border-color:transparent}
@media (hover:hover){ .pv-consent__btn--primary:hover{background:var(--btn-light-hover)} }
.pv-consent__x{
  position:absolute;top:8px;right:8px;width:44px;height:44px;display:grid;place-items:center;
  border-radius:var(--radius-btn);background:none;border:0;cursor:pointer;
  color:var(--gray-3);font:inherit;font-size:22px;line-height:1;
  transition-property:color,background-color;transition-duration:var(--t-press);transition-timing-function:var(--ease);
}
@media (hover:hover){ .pv-consent__x:hover{color:var(--white);background:color-mix(in srgb,var(--white) 8%,transparent)} }
.pv-consent :is(a,button):focus-visible{outline:var(--focus-ring-dark);outline-offset:3px}
@media (prefers-reduced-motion:no-preference){
  .pv-consent{transition:opacity var(--t-fast) var(--ease-out),transform var(--t-mid) var(--ease-out)}
  .pv-consent:not(.is-open){opacity:0;transform:translateY(16px)}
  .pv-consent.is-leaving{transition-duration:.25s;transition-timing-function:var(--ease-in)}
}
/* the home page at every width (b106 S14): below 1024 px the bottom sheet covered the hero's only button */
html.pv-consent-top .pv-consent{top:calc(var(--bar-h) + var(--pv-gutter));bottom:auto}
@media (prefers-reduced-motion:no-preference){ html.pv-consent-top .pv-consent:not(.is-open){transform:translateY(-16px)} }

/* b112 — the consent gate. Until the visitor has answered, the panel is a modal and the page behind it is
   inert: there is no way past it that records nothing. Both answers stay one click and equally prominent,
   because a wall that only takes yes is not a choice. Centred with inset + margin, never with a transform,
   so the entrance above still has its translateY to leave from. No backdrop-filter: the perf gate budgets
   that at zero, and a flat scrim does the same job. */
html.pv-consent-gate{overflow:hidden}
html.pv-consent-gate body{padding-bottom:0}
/* The two z-indexes sit together on purpose. The panel does not keep the --pv-consent-z token at runtime:
   frames.css (b111) puts .pv-consent in the bar layer at 60, and a scrim derived from the token landed on
   top of it. Written as a pair, they cannot drift apart again. */
html.pv-consent-gate::before{content:"";position:fixed;inset:0;z-index:98;
  background:color-mix(in srgb,var(--bg-darker) 82%,transparent)}
html.pv-consent-gate .pv-consent{z-index:99;left:0;right:0;top:0;bottom:0;margin:auto;height:max-content;
  width:min(560px,calc(100% - 2 * var(--pv-gutter)))}
html.pv-consent-gate .pv-consent__x{display:none}
html.pv-consent-gate .pv-consent__copy{padding-right:0}
/* As a modal it is read, not glanced at: the heading comes back out of the screen-reader-only box, the
   sentence about what is stored is shown in full, and the two answers sit side by side at the foot.
   From 1024 px the bar layout turns all of that off, so these have to win there too. */
html.pv-consent-gate .pv-consent__in{flex-direction:column;align-items:stretch;gap:18px;padding:26px 26px 24px}
html.pv-consent-gate .pv-consent__title{position:static;width:auto;height:auto;overflow:visible;
  clip-path:none;white-space:normal;font-size:var(--fs-l);font-weight:600;letter-spacing:-.01em;
  text-transform:none;color:var(--white);line-height:1.15}
html.pv-consent-gate .pv-consent__txt{margin-top:10px;font-size:var(--fs-s)}
html.pv-consent-gate .pv-consent__more{display:inline}
html.pv-consent-gate .pv-consent__act{flex:none;display:grid;grid-template-columns:1fr 1fr;gap:10px}
html.pv-consent-gate .pv-consent__btn{min-height:48px}
/* a short landscape phone: the hero title fills the top-left and its button the bottom-left, so the card takes the
   right half of the top (measured at 844x390, b106 round 2) */
@media (max-width:1023px) and (max-height:500px) and (orientation:landscape){
  html.pv-consent-top .pv-consent{left:auto;right:max(var(--pv-gutter),env(safe-area-inset-right));width:min(var(--pv-consent-w),calc(50% - var(--pv-gutter)))}
}
/* b107 S08 (C06): from 1024 px the card is one row (text, two buttons, close; ≤ 80 px tall), so it no longer covers a
   page's first product image or the wizard's Next; the home page no longer takes the top placement (the ribbon field
   it sat over is gone, and the card covered the new hero's label there). */
@media (min-width:1024px){
  .pv-consent{width:min(940px,calc(100% - 2 * var(--pv-gutter)))}
  .pv-consent__in{flex-direction:row;align-items:center;gap:16px;padding:10px 60px 10px 20px}
  .pv-consent__copy{flex:1 1 auto;padding-right:0}
  .pv-consent__title{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
  .pv-consent__txt{margin-top:0}
  .pv-consent__more{display:none}
  .pv-consent__act{flex:none}
  .pv-consent__x{top:50%;transform:translateY(-50%)}
}
@media (max-width:1023px){
  .pv-consent{right:var(--pv-gutter);width:auto}
  .pv-consent__in{flex-direction:row;flex-wrap:wrap;align-items:center;gap:10px 12px;padding:12px 12px 12px 16px}
  .pv-consent__copy{flex:1 1 100%;padding-right:40px}
  .pv-consent__title{display:none}
  .pv-consent__txt{margin-top:0;font-size:var(--fs-xs)}
  .pv-consent__more{display:none}
  .pv-consent__act{flex:1 1 100%}
  .pv-consent__x{top:2px;right:2px}
}
/* keep focus clear of the open banner: scroll targets stop above it, and the page gets room to scroll its
   last controls out from underneath (the banner sits over the page's own bottom otherwise) */
html.pv-consent-open{scroll-padding-bottom:calc(var(--pv-banner-h,140px) + 2 * var(--pv-gutter))}
html.pv-consent-open body{padding-bottom:calc(var(--pv-banner-h,140px) + 2 * var(--pv-gutter))}
html.pv-consent-top.pv-consent-open{
  scroll-padding-top:calc(var(--bar-h) + var(--pv-banner-h,160px) + 2 * var(--pv-gutter));scroll-padding-bottom:auto}
html.pv-consent-top.pv-consent-open body{padding-bottom:0}
/* the footer's "Privacy settings" control reads like the Privacy link next to it */
.foot-consent{font:inherit;color:inherit;background:none;border:0;padding:0;cursor:pointer}
.foot-consent:hover{color:var(--white,#f8f4ee)}
@media print{.pv-consent{display:none}}
/* b106 S14: no grey tap flash, no double-tap zoom delay on controls */
html{-webkit-tap-highlight-color:transparent}
:is(a,button,summary,label){touch-action:manipulation}
/* cookies.js moves focus to <main> after a first-visit choice; the landmark itself needs no ring */
main[tabindex="-1"]:focus{outline:none}

/* ============================================================
   FOOTER (step 11) — one grid on every page.
   Markup: site-source/partials/footer.html. These rules are scoped under footer.site-foot so
   they outrank the older per-page footer CSS each page still carries (same specificity would
   also win by order, this file loads after the page's <style>, but scoping makes it explicit).
   Columns: 4 at desktop / 2 at tablet / 1 at phone. The bar uses the same columns, so its
   items sit under the column heads instead of being spread by space-between.
============================================================ */
:root{
  --pv-foot-gap:clamp(24px,3vw,48px);
  --pv-foot-row:clamp(28px,4vw,44px);
}
footer.site-foot .foot-grid{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  column-gap:var(--pv-foot-gap);row-gap:var(--pv-foot-row);
  padding-bottom:var(--space-m,clamp(44px,6vh,72px));
  align-items:start;
}
footer.site-foot .foot-col{display:flex;flex-direction:column;align-items:flex-start;gap:12px;min-width:0}
footer.site-foot .foot-cat{margin-bottom:4px;line-height:1.2}
footer.site-foot .foot-about p{margin-top:0;max-width:32ch}
footer.site-foot .foot-col a{line-height:1.3}
footer.site-foot .foot-now{margin-top:4px}
/* the watermark rests 6 % below its box (.watermark.in); the gap keeps it off the bar's hairline */
footer.site-foot .watermark{margin-bottom:clamp(20px,3vw,44px)}
footer.site-foot .foot-bar{
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
  column-gap:var(--pv-foot-gap);row-gap:10px;align-items:baseline;
  font-size:var(--fs-s,.84375rem);
}
footer.site-foot .foot-bar > *{justify-self:start;min-width:0}
footer.site-foot .foot-bar .build{justify-self:end}
@media (max-width:1024px){
  footer.site-foot .foot-grid,
  footer.site-foot .foot-bar{grid-template-columns:repeat(2,minmax(0,1fr))}
  footer.site-foot .foot-bar .build{justify-self:end}
}
@media (max-width:600px){
  footer.site-foot .foot-grid{grid-template-columns:minmax(0,1fr)}
  footer.site-foot .foot-bar{grid-template-columns:repeat(2,minmax(0,1fr))}
}
@media (pointer:coarse){
  footer.site-foot .foot-consent{min-height:44px;display:inline-flex;align-items:center}
  footer.site-foot .foot-bar a{min-height:44px;display:inline-flex;align-items:center}
}

/* ============================================================
   TOP BAR — the quote link stays on phones (b105 step 18; b106 S02)
   Every page hid .tb-cta at 480 px and below, which left /about, /services and /privacy with no ask in the
   first screen of a phone. It is a text link with a rule under it (no pill); margin-left:auto from the page's
   own 900 px rule keeps it against the burger. At 360 px and below the bar's 28 px gaps drop to the phone
   gutter and the link reads "Get a quote" (the " fixed" span hides), so the burger is never clipped at 320 px
   (WCAG 1.4.10; b105 blocker 4: #burger spanned 292–336 px in a 320 px viewport).
============================================================ */
@media (max-width:480px){
  .topbar .tb-cta{display:inline-flex;align-items:center;white-space:nowrap;margin-right:6px}
}
@media (max-width:360px){
  .topbar .wrap{gap:var(--gutter)}
  .topbar .tb-cta{margin-right:0}
  .topbar .tb-cta-x{display:none}
}

/* ============================================================
   b106 S02 — global rules
============================================================ */
/* b107: the b106 D1 grayscale rule (main img, main video, .grade) is gone; photos are colour, graded in the
   file (overhaul/qa/grade.mjs). */

/* headings never leave a one-word last line */
h1, h2{text-wrap:balance}

/* D3: a service hero's subline is the page's value proposition — body prose, not a micro label */
.ec-hero .ec-sub, .bi-hero .bi-sub{
  font-size:var(--fs-body);font-weight:400;letter-spacing:normal;text-transform:none;
  line-height:1.6;max-width:62ch;text-wrap:pretty;color:var(--gray-3);
}
[data-stage="light"] .ec-hero .ec-sub{color:var(--muted-on-light)}
/* D3: service heroes are left-aligned (web-design, web-development and ecommerce were centred; home, ai-assistants and
   brand-identity already read left). Equal specificity to the pages' own centring rules, and this file loads later. */
.ec-hero .ec-hero-in{text-align:left;justify-self:stretch;width:100%}   /* the hero grid centres a shrink-wrapped block: stretch it to the page edge */
.ec-hero .ec-sub{margin-inline:0}
.ec-hero .ec-hero-act{justify-content:flex-start}

/* statements: a short display lead + body prose, the service's own proof beside it (b105 ran 7–12 lines of
   52 px type at line-height 1.12 in the left half, with the right half empty) */
.pv-state{display:grid;grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr);gap:clamp(32px,5vw,96px);align-items:center}
:is(.ec-state,.bi-state) .pv-state p{grid-column:auto;max-width:none;margin:0}
:is(.ec-state,.bi-state) .pv-state .pv-state-lead{
  font-size:clamp(26px,2.9vw,42px);font-weight:500;line-height:1.2;letter-spacing:-.012em;max-width:24ch;text-wrap:balance}
:is(.ec-state,.bi-state) .pv-state .pv-state-body{
  margin-top:clamp(16px,1.8vw,26px);font-size:var(--fs-body);line-height:1.65;color:var(--gray-3);max-width:56ch;text-wrap:pretty}
[data-stage="light"] .pv-state .pv-state-body{color:var(--muted-on-light)}
/* a positioning line quoted verbatim can be long: smaller and wider, still ≤ 4 lines at 1440 */
:is(.ec-state,.bi-state) .pv-state--long .pv-state-lead{font-size:clamp(24px,2.3vw,34px);max-width:36ch}
.pv-state-proof{margin:0;justify-self:end;width:100%;border-radius:var(--radius-card);overflow:hidden;
  border:1px solid var(--line-strong);background:var(--panel)}
[data-stage="light"] .pv-state-proof{border-color:var(--line-dark)}
.pv-state-proof img{display:block;width:100%;height:auto}
.pv-state-proof--phone{max-width:300px;border-radius:30px}
.pv-state-proof--screen{max-width:620px}
@media (max-width:900px){
  .pv-state{grid-template-columns:minmax(0,1fr)}
  .pv-state-proof{justify-self:start}
  .pv-state-proof--phone{max-width:240px}
}
/* the mark rows lost their lone "+" glyph: the copy starts at the grid's first column */
.ec-markrow .ec-copy{grid-column:1 / span 8}
@media (max-width:900px){ .ec-markrow .ec-copy{grid-column:1 / -1} }

/* ============================================================================================
   Quote step (b116 S02) — the first screen of the quote wizard. Moved here verbatim from
   /contact's inline CSS so /contact and the landing's closing block share ONE component.
   Order kept: base rules, then the <= 480 px narrowing that overrides them.
   ============================================================================================ */
.q-progress{display:flex;gap:6px;width:180px;margin-left:auto}
.q-progress-seg{height:3px;border-radius:var(--radius-pill);background:var(--line-dark-strong);flex:1;transition:background .3s var(--ease)}
.q-progress-seg.done{background:var(--ink)}
.q-question{display:block;font-size:clamp(22px,2.6vw,30px);font-weight:600;letter-spacing:-.012em;color:var(--ink);margin-bottom:22px}
.svc-pick-hint{font-size:var(--fs-s);color:var(--muted-on-light);margin-bottom:18px}
.svc-pick{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
@media (max-width:640px){ .svc-pick{grid-template-columns:minmax(0,1fr)} }   /* b106: no "AI / Assistants" wraps at 390. b107 S08 (C46): 640, not 420 — with the 01-05 index shown at every width, two columns wrapped a name at every width up to 580 ("Web / Development" measured at 481-580) */
.svc-card{position:relative;display:block;cursor:pointer;min-width:0}
.svc-card:last-child:nth-child(odd){grid-column:1 / -1}
.svc-card input{position:absolute;opacity:0;width:1px;height:1px;margin:0}
.svc-card-in{display:grid;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:12px;
  min-height:64px;padding:12px 14px 12px 16px;border:1px solid var(--field-border);border-radius:var(--radius-img);
  transition:background var(--t-micro) var(--ease),border-color var(--t-micro) var(--ease)}
.svc-card-n{font-size:var(--fs-micro);letter-spacing:var(--ls-caps);color:var(--muted-on-light);font-variant-numeric:tabular-nums}
.svc-card-k{font-size:var(--fs-b);font-weight:600;color:var(--ink);line-height:1.25}
.svc-card-tick{position:relative;width:22px;height:22px;border-radius:50%;border:1px solid var(--field-border)}
.svc-card-tick::after{content:"";position:absolute;left:7px;top:3px;width:5px;height:10px;border:solid var(--ink);border-width:0 2px 2px 0;
  transform:rotate(45deg) scale(0);transition:transform var(--t-micro) var(--ease-settle)}
.svc-card input:checked + .svc-card-in{background:var(--ink);border-color:var(--ink)}
.svc-card input:checked + .svc-card-in .svc-card-k{color:var(--white)}
.svc-card input:checked + .svc-card-in .svc-card-n{color:var(--gray-3)}
.svc-card input:checked + .svc-card-in .svc-card-tick{background:var(--white);border-color:var(--white)}
.svc-card input:checked + .svc-card-in .svc-card-tick::after{transform:rotate(45deg) scale(1)}
.svc-card input:not(:checked) + .svc-card-in:hover{border-color:var(--ink)}   /* b116: stronger than rest, from tokens */
.svc-card input:focus-visible + .svc-card-in{outline:2px solid var(--focus-light);outline-offset:3px}
.svc-pick-fs .q-err.show{margin-top:10px}
.q-err{
  display:flex;align-items:center;gap:8px;font-size:var(--fs-xs);color:var(--ink);
  max-height:0;opacity:0;overflow:hidden;margin-top:0;
  transition:opacity var(--t-micro) var(--ease),max-height var(--t-micro) var(--ease),margin-top var(--t-micro) var(--ease);
}
.q-err svg{width:8px;height:13px;flex:none}
.q-err circle{fill:currentColor}
/* the invalid state is per FIELD, not per step: a step can hold two inputs now, and
   step-scoped selectors reddened both borders and opened both messages for one bad value */
.q-err.show{max-height:64px;opacity:1;margin-top:8px}   /* b106: the fix-it messages run to 2–3 lines on a phone */
@media (max-width:480px){
  .svc-card-in{min-height:58px;padding:10px 10px 10px 12px;gap:8px}   /* b107 S08 (C46): the 01-05 index shows at every width, as on /services */
  .svc-card-k{font-size:var(--fs-m)}
}
