/* ProWebLocal: warm paper.
   Two radius tokens only: interactive things are round, surfaces are square.
   That contrast is deliberate; uniform rounding reads as a component library. */
:root{
  color-scheme:light;               /* opt out of forced dark inversion */
  --paper:#FBF7F0; --paper-2:#F5EEE3; --raised:#FFFDF9;
  --line:#E4D8C4; --line-2:#D9CBB6;
  --tint:#E7DAC7;                   /* hairline under the raised surface */
  --tint-2:#B8A78A;                 /* small decorative marks */
  --ghost:#EADDC8;                  /* the oversized step numerals */
  --ink:#1F1B15; --ink-2:#574F44; --ink-3:#726858;
  --green:#2C6A4E; --green-2:#245840; --green-tint:#E9EFE9;
  --terra:#9A5530; --terra-tint:#F3E7DA;
  --r-pill:100px; --r-media:3px;
  --hdr:66px;
  --max:1180px; --pad:clamp(20px,5vw,56px);
  --serif:Newsreader,Georgia,"Times New Roman",serif;
  --sans:"Public Sans",ui-sans-serif,system-ui,-apple-system,"Segoe UI",Helvetica,sans-serif;
}
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;scroll-behavior:smooth;
  scroll-padding-top:calc(var(--hdr) + 16px)}
@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}}
body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans);
  font-size:17px;line-height:1.66;-webkit-font-smoothing:antialiased}
h1,h2,h3{font-family:var(--serif);font-weight:400;letter-spacing:-.012em;line-height:1.09;margin:0}
p{margin:0 0 1em}
a{color:var(--green)}
a:hover{color:var(--terra)}
strong{font-weight:600}
.wrap{max-width:var(--max);margin-inline:auto;padding-inline:var(--pad)}
:focus-visible{outline:2.5px solid var(--green);outline-offset:3px}
.skip{position:absolute;left:-9999px}
.skip:focus{left:12px;top:12px;z-index:99;background:var(--green);color:var(--paper);
  padding:10px 16px;border-radius:var(--r-pill)}

/* ---- the paper stack -------------------------------------------------
   Fixed and overscanned by a whole grain tile, so the drift never exposes an
   edge and the loop restarts on a seam nobody can see. */
.paper{position:fixed;inset:0;z-index:0;pointer-events:none;overflow:hidden}
.paper>*{position:absolute;inset:0}
/* Only the drifting layer needs to overscan; the lights animate
   opacity alone, so overscanning them was pure overdraw. */
.p-grain{inset:-260px}
header,main,footer{position:relative;z-index:1}
.p-lightA{background:radial-gradient(120% 80% at 92% -8%,rgba(214,150,72,.20),rgba(214,150,72,0) 58%);opacity:.55}
.p-lightB{background:radial-gradient(90% 65% at -10% 26%,rgba(176,120,40,.13),rgba(176,120,40,0) 62%);opacity:.42}
.p-shaft{background:linear-gradient(104deg,rgba(255,255,255,0) 34%,rgba(255,248,232,.50) 44%,
  rgba(255,251,242,.26) 52%,rgba(255,255,255,0) 63%);opacity:.5}
/* Range-compressed noise: feComponentTransfer squeezes the turbulence into a
   narrow band, so it reads as paper tooth rather than television static.
   One grain layer only: a second at .05 over this one is not perceptible and
   costs another full-viewport composited layer on every frame. */
.p-grain{inset:-260px;background-size:260px 260px;opacity:.085;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='260' height='260'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' seed='7' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncR type='linear' slope='0.45' intercept='0.55'/%3E%3CfeFuncG type='linear' slope='0.45' intercept='0.55'/%3E%3CfeFuncB type='linear' slope='0.45' intercept='0.55'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='260' height='260' filter='url(%23n)'/%3E%3C/svg%3E")}
.p-grain2{display:none}   /* the second grain div can come out of the markup */

/* Motion is OPT-IN: static is this stylesheet's default state. */
@media (prefers-reduced-motion:no-preference){
  .p-grain{animation:driftA 140s linear infinite;will-change:transform}
  .p-lightA{animation:breatheA 37s ease-in-out infinite}
  .p-lightB{animation:breatheB 53s ease-in-out infinite}
  .ink path{animation:draw 2.4s cubic-bezier(.22,.85,.3,1) .2s forwards}
  .ink path:nth-child(2){animation-delay:.5s}
}
/* A whole tile in both axes, so the restart is invisible. */
@keyframes driftA{from{transform:translate3d(0,0,0)}to{transform:translate3d(-260px,260px,0)}}
/* Narrow bands: this light sits under body text, so its swing must not move
   the contrast ratio of anything printed on it. */
@keyframes breatheA{0%,100%{opacity:.50}50%{opacity:.62}}
@keyframes breatheB{0%,100%{opacity:.46}50%{opacity:.36}}
@keyframes draw{to{stroke-dashoffset:0}}
@media (prefers-reduced-motion:reduce){
  .paper *,.ink path,.btn,.demo .shot{animation:none!important;transition:none!important}
  .btn:active,.demo:hover .shot{transform:none!important}
  .p-lightA{opacity:.55}.p-lightB{opacity:.42}
  .ink path{stroke-dashoffset:0!important}
}
.ink{--ink-mask:40%;position:absolute;left:0;right:0;width:100%;pointer-events:none;
  -webkit-mask-image:linear-gradient(100deg,transparent 0 var(--ink-mask),
    rgba(0,0,0,.3) calc(var(--ink-mask) + 12%),#000 calc(var(--ink-mask) + 28%));
  mask-image:linear-gradient(100deg,transparent 0 var(--ink-mask),
    rgba(0,0,0,.3) calc(var(--ink-mask) + 12%),#000 calc(var(--ink-mask) + 28%))}
/* Full bleed. Without this the grid's own `.sec>*` rule traps the flourish
   inside the ten-column measure and the curves never reach the margin. */
.sec>.ink{grid-column:1/-1}
/* Both paths measure ~1227 units, so the dash matches and there is no dead
   lead-in before the stroke starts appearing. */
.ink path{fill:none;stroke:var(--green);stroke-opacity:.17;stroke-width:1.6;
  stroke-linecap:round;filter:url(#wobble);stroke-dasharray:1240;stroke-dashoffset:1240}
@media (max-width:1180px){.ink{--ink-mask:55%}}
/* Narrower than this the reveal begins on top of the headline, so: no ink. */
@media (max-width:820px){.ink{display:none}}

/* ---- header ---------------------------------------------------------- */
.hdr{position:sticky;top:0;z-index:40;background:rgba(251,247,240,.88);
  -webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);
  border-bottom:1px solid var(--line)}
.hdr-in{display:flex;align-items:center;gap:12px;min-height:var(--hdr);
  flex-wrap:nowrap;min-width:0}
.hdr-in>.btn{flex:none}
.brand{display:flex;align-items:center;gap:10px;margin-right:auto;text-decoration:none;
  white-space:nowrap;color:var(--ink)}
.brand .mark{width:30px;height:30px;flex:none;display:block}
.wm{font-family:var(--serif);font-weight:500;font-size:23px;letter-spacing:-.02em;line-height:1}
/* "Web" carries the accent so the three words read apart at a glance */
.wm-b{color:var(--green)}
.brand .dom{font-family:var(--sans);font-size:12px;color:var(--ink-3);letter-spacing:.04em;
  align-self:flex-end;padding-bottom:2px}
/* The domain suffix is the one part of the header that can yield: it shows
   only where the section nav is not already using the room. */
@media (max-width:700px),(min-width:1181px){.brand .dom{display:none}}
.nav{display:flex;gap:2px}
.nav a{font-size:15px;color:var(--ink-2);text-decoration:none;padding:8px 11px;
  white-space:nowrap;border-radius:var(--r-pill);transition:background .2s,color .2s}
.nav a:hover{background:var(--paper-2);color:var(--ink)}
.nav a[aria-current="page"]{background:var(--paper-2);color:var(--ink)}
/* Hidden below the width where the longest language still fits on one line. */
@media (max-width:1180px){.nav{display:none}}
.lang{display:flex;gap:2px;border:1px solid var(--line);border-radius:var(--r-pill);padding:3px}
.lang a{position:relative;font-size:12.5px;font-weight:700;letter-spacing:.05em;color:var(--ink-2);
  text-decoration:none;border-radius:var(--r-pill);padding:11px 10px;line-height:1;
  transition:color .2s,background .2s}
/* The pill stays small on purpose: it must not out-shout the CTA in a header
   that has already dropped its nav: so the thumb target is grown with a
   transparent overlay rather than with padding: 45px to a finger, unchanged to
   the eye. Vertical only, so neighbouring pills can never overlap. */
.lang a::after{content:"";position:absolute;inset:-5px 0}
.lang a:hover{color:var(--ink)}
.lang a[aria-current="page"]{background:var(--green-tint);color:var(--green)}
/* The pills sit 2px apart, so an offset ring would straddle both neighbours. */
.lang a:focus-visible{outline-offset:0;box-shadow:inset 0 0 0 2px var(--green)}
.page>.lang{width:max-content}
@media (max-width:700px){.hdr-in>.btn{padding:9px 15px;font-size:14px}}
/* The phone header used to keep the wordmark and the language pill and drop
   the only thing a visitor can act on: on the site whose whole argument is
   that customers arrive on a phone. So the button stays at every width and
   the pill is the thing that yields: same control, smaller, since choosing a
   language is the least urgent thing anyone does here. */
@media (max-width:470px){
  .hdr-in{gap:8px}
  .hdr-in>.btn{flex:0 1 auto;min-width:0;padding:7px 13px;font-size:13.5px;line-height:1.25}
  .lang{padding:2px}
  .lang a{font-size:11px;padding:9px 6px;letter-spacing:.03em}
}
@media (max-width:560px){.brand{font-size:20px}}
/* The narrowest phones still in use: the wordmark and the pill both give up a
   little more so the call to action keeps a sane two-line label in Romanian,
   whose header string is the longest of the three. */
@media (max-width:400px){
  .brand{font-size:18px}
  .hdr-in{gap:6px}
  .lang a{font-size:10.5px;padding:9px 5px}
  .hdr-in>.btn{padding:6px 11px;font-size:13px}
}

.btn{display:inline-flex;align-items:center;justify-content:center;gap:9px;
  padding:14px 26px;border-radius:var(--r-pill);font-size:15.5px;font-weight:600;
  text-decoration:none;border:1.5px solid transparent;cursor:pointer;
  transition:transform .15s,background .2s,border-color .2s;white-space:nowrap}
.btn:active{transform:translateY(1px)}
.btn--brand{background:var(--green);color:var(--paper)}
/* Hover darkens. Lightening it dropped the label under AA at the exact moment
   the pointer was on the button. */
.btn--brand:hover{background:var(--green-2);color:var(--paper)}
.btn--ghost{border-color:var(--line-2);color:var(--ink)}
.btn--ghost:hover{border-color:var(--ink-3);color:var(--ink)}
.btn--sm{padding:10px 20px;font-size:14.5px}
.btn--wide{width:100%}
/* On a narrow phone a long label wraps rather than pushing the page sideways. */
@media (max-width:560px){.btn{white-space:normal;text-align:center}}

/* ---- editorial grid: a live margin column beside the measure ---------- */
.sec{max-width:var(--max);margin-inline:auto;padding-inline:var(--pad);
  display:grid;grid-template-columns:136px repeat(10,minmax(0,1fr));
  column-gap:20px;position:relative}
.sec>*{grid-column:2/span 10}
.mar{grid-column:1;text-align:right}
.marnote{font-family:var(--serif);font-style:italic;font-size:13.5px;color:var(--ink-3);margin:10px 0 0}
@media (max-width:900px){
  /* minmax(0,…): a long unbreakable label must not widen the whole column. */
  .sec{grid-template-columns:minmax(0,1fr)}
  .sec>*{grid-column:1}
  .mar{text-align:left}
  .marnote{margin:0 0 10px}
}

/* Slab = the default block: a rule and open space, not a filled box. */
.slab{border-top:2px solid var(--ink);padding:18px 0 0}
/* The same rank of heading gets the same air as `.cols h3`. Less than 18px,
   because a slab heading has no rule of its own to clear: but never nothing:
   a 23px serif sitting directly on 17px body read as a mistake. */
.slab h3{margin-bottom:11px}
/* Reserve the one filled surface for the thing that should carry emphasis. */
.raised{background:var(--raised);border:0;border-radius:var(--r-media);
  padding:clamp(24px,3.4vw,34px);position:relative;
  box-shadow:0 1px 0 var(--tint),0 30px 60px -34px rgba(31,27,21,.40)}
.ghost{font-family:var(--serif);font-weight:300;line-height:1;color:var(--ghost);user-select:none}

/* The rule locks to the first line, not to the centre of a wrapped block. */
.eyebrow{display:flex;align-items:flex-start;gap:11px;font-size:12.5px;font-weight:700;
  letter-spacing:.15em;text-transform:uppercase;color:var(--terra);margin:0 0 20px}
.eyebrow::before{content:"";width:30px;height:1.5px;background:var(--terra);flex:none;margin-top:.55em}
.lead-in{font-family:var(--serif);font-style:italic;font-weight:300;font-size:clamp(18px,2.2vw,21px);
  color:var(--ink-3);margin:0 0 2px}
.lede{color:var(--ink-2);font-size:clamp(17px,2.1vw,20.5px);max-width:52ch;margin:0 0 32px}
/* Unclassed paragraphs only: `.sec p` outranks every component rule below it
   and was silently repainting the eyebrow, the margin notes and the lead-in. */
.sec p:not([class]){color:var(--ink-2)}

/* ---- sections: rhythm varies by weight, not on a metronome ------------ */
.hero{padding-block:clamp(56px,8vw,88px) clamp(48px,6vw,70px)}
.hero h1{font-size:clamp(40px,7vw,74px);margin-bottom:24px}
.hero h1 em{font-style:italic;font-weight:300}
.hero-cta{display:flex;flex-wrap:wrap;gap:12px;margin-bottom:34px}
/* Marked items on an auto-fitting track, not a wrapped line of prose: a gap
   alone separated the facts only while they happened to fit two per line, so
   Romanian: whose words run longest: degraded into a ragged paragraph. */
.facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));
  gap:9px 22px;align-items:start;font-size:14px;color:var(--ink-2)}
.facts>span{position:relative;padding-left:15px}
.facts>span::before{content:"";position:absolute;left:0;top:.62em;width:5px;height:5px;
  border-radius:50%;background:var(--tint-2)}
/* No glyph separators: as independent items they stranded at the end of every
   wrapped line (all languages on a phone, French at full width), and they were
   read out as "middle dot". The markers do the separating now. */
.facts .sep{display:none}
.facts .green{color:var(--terra)}
.s-how{padding-block:clamp(64px,8vw,104px) clamp(40px,5vw,52px)}
.s-inc{padding-block:clamp(36px,5vw,48px) clamp(36px,5vw,44px)}
.s-price{padding-block:clamp(72px,9vw,118px) clamp(64px,8vw,96px)}
.s-ex{padding-block:clamp(40px,6vw,52px) clamp(60px,8vw,88px)}
.s-faq{padding-block:clamp(52px,7vw,70px) clamp(60px,8vw,84px)}
.s-cta{padding-block:clamp(20px,3vw,24px) clamp(64px,9vw,92px)}
h2{font-size:clamp(30px,5vw,54px);margin-bottom:14px}
h2.big{font-size:clamp(34px,6vw,62px)}
h3{font-size:clamp(19px,2.2vw,23px);font-weight:500}
/* A short display measure is a gesture of the desktop grid: it buys an open
   right margin beside a ten-column heading. Once the grid collapses to one
   column that same cap only strands the heading in a half-empty line box, so
   it lifts at the breakpoint. (`min(10ch,100%)` cannot express this: min()
   keeps the ch value whenever it is the smaller of the two: which on a phone
   is exactly the case where it hurts.)
   The cap is a per-language quantity, though, so it is a token: between 901
   and 1080 the headline has the full ten columns to itself, and there 13ch
   broke French into four lines under a hanging "pour vous." where 14.5ch sets
   three clean ones. Romanian keeps the default: wider rags, narrower strands
   "el." on a line of its own: and above 1080 the six-column hero column is
   narrower than any of these caps, so the token only acts where it helps. */
:root:lang(fr){--h1-measure:14.5ch}
@media (min-width:901px){
  .hero h1{max-width:var(--h1-measure,13ch)}
  h2.big{max-width:10ch}
  .cta h2{max-width:16ch}
}

/* steps: a staircase reads as a sequence; three equal boxes read as options.
   The taper is 5/4/4, not 5/4/3: the last step carries the most copy and
   must not also be the narrowest column. */
.steps{display:grid;grid-template-columns:repeat(13,minmax(0,1fr));column-gap:22px;align-items:start}
.steps>*{position:relative}
.steps>:nth-child(1){grid-column:1/6;margin-top:78px}
.steps>:nth-child(2){grid-column:6/10;margin-top:128px}
.steps>:nth-child(3){grid-column:10/14;margin-top:178px}
.steps .n{position:absolute;left:-4px;top:-74px;font-size:96px}
.steps .slab p{max-width:62ch}
/* Collapse before the narrowest column drops under a readable measure. */
@media (max-width:1080px){
  .steps{grid-template-columns:1fr;row-gap:44px}
  .steps>:nth-child(n){grid-column:1;margin-top:0}
  .steps .n{position:static;display:block;font-size:56px;margin-bottom:-2px}
}

/* Three column heads on one line: a 22px/8px offset was too small to read as
   a stagger and too large to read as alignment. */
.cols{display:grid;grid-template-columns:minmax(0,1.15fr) minmax(0,1fr) minmax(0,1fr);
  column-gap:26px;row-gap:24px}
.cols h3{font-family:var(--sans);font-size:15px;font-weight:600;letter-spacing:.09em;
  text-transform:uppercase;color:var(--terra);padding-bottom:14px;
  border-bottom:1px solid var(--line-2);margin-bottom:18px}
@media (max-width:900px){.cols{grid-template-columns:1fr}}

/* hand-cut ticks, two shapes alternating: never one stroke twelve times.
   The tick hangs in the gutter: making the li a flex container split every
   sentence containing a <strong> into two side-by-side columns. */
.ticks{list-style:none;margin:0;padding:0;display:grid;gap:12px}
.ticks li{position:relative;padding-left:28px;color:var(--ink-2);font-size:15.5px}
.ticks li::before{content:"";position:absolute;left:0;top:4px;width:16px;height:17px;
  background:var(--green);opacity:.85;
  -webkit-mask:var(--tick-a) center/contain no-repeat;mask:var(--tick-a) center/contain no-repeat}
.ticks li:nth-child(even)::before{-webkit-mask-image:var(--tick-b);mask-image:var(--tick-b)}
:root{
  --tick-a:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 17'%3E%3Cpath d='M1.6 8.9C2.9 8.5 4.2 9.8 5.4 11.7 7.5 7.6 10.3 4.1 13.9 1.4c.7-.5 1.4.3.9 1.1C11.5 6.6 8.6 11.1 6.5 15.4c-.5 1-1.8.9-2.3-.2C3.6 13.1 2.7 11 1.6 10.3Z'/%3E%3C/svg%3E");
  --tick-b:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 17'%3E%3Cpath d='M1.3 9.4C2.8 9.1 4 10.6 5.1 12.4 7.4 8.1 10.1 4.6 13.4 1.7c.8-.7 1.7.2 1.1 1C11.4 6.9 8.9 11.3 6.8 15.6c-.5.9-1.8.8-2.2-.3C4 13.4 2.6 11.1 1.3 10.6Z'/%3E%3C/svg%3E");
}
.ticks strong{color:var(--ink)}


/* currency choice sits on the pricing heading line, like the gallery filters */
.price-head{display:flex;align-items:center;justify-content:space-between;
  gap:14px 18px;flex-wrap:wrap;margin-bottom:14px}
.price-head h2{margin-bottom:0}

/* pricing: the two columns are read across, so they have to agree:
   headings on one baseline, buttons on one baseline. */
/* The row above is a section-level call to action, so it needs enough air to
   read as one: flush against the cards, its ghost pill landed on the raised
   card's top-left corner beside the badge and read as if it belonged to the
   yearly plan. The badge overhangs the card by 11px, so the gap is measured
   from the badge, not from the card's box. */
.prices{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.08fr);
  column-gap:34px;row-gap:26px;align-items:stretch;margin-top:clamp(44px,5vw,64px)}
/* Stacked, the badge of the second card hangs 11px into the gap above it, so
   the row gap has to clear the first card's button by more than the column
   gap ever needed to. */
@media (max-width:900px){.prices{grid-template-columns:1fr;row-gap:40px}}
.price{padding-top:26px}
/* 46px = the plain column's 26px offset + its 2px rule + 18px of slab padding,
   so the two headings sit on the same line. */
.price.raised{padding-top:46px}
@media (min-width:901px){
  .price{display:flex;flex-direction:column}
  .price>.slab{flex:1;display:flex;flex-direction:column;padding-bottom:clamp(24px,3.4vw,34px)}
  .price .btn--wide{margin-top:auto}
}
.price .amount{font-family:var(--serif);font-size:clamp(44px,6vw,54px);font-weight:400;line-height:1.1}
.raised .amount{font-size:clamp(48px,7vw,62px)}
.amount del{font-size:.5em;font-weight:400;color:var(--ink-3);
  text-decoration:line-through;text-decoration-thickness:2px;margin-right:.1em}
.amount .cv{font-size:inherit;font-weight:inherit;color:inherit;letter-spacing:inherit}
.amount>span{white-space:nowrap}
.amount span{font-family:var(--sans);font-size:16px;font-weight:400;color:var(--ink-3)}
.price h3{font-size:19px;margin-bottom:10px}
.price .note{color:var(--ink-3);font-size:14.5px;margin:10px 0 22px}
.price .ticks{margin-bottom:24px}
.badge{position:absolute;top:-11px;left:clamp(24px,3.4vw,34px);background:var(--terra);
  color:var(--paper);font-size:11px;font-weight:700;letter-spacing:.11em;text-transform:uppercase;
  padding:5px 12px;border-radius:var(--r-pill)}
/* Left, like everything else on the page: centred, it was the one block that
   looked borrowed from another site. */
.pay-note{color:var(--ink-3);font-size:14px;margin-top:26px;max-width:72ch}

/* examples */
.demos{display:grid;grid-template-columns:minmax(0,1.2fr) minmax(0,1fr);
  column-gap:26px;row-gap:26px;align-items:start}
.demos>:nth-child(2){margin-top:46px}
@media (max-width:900px){.demos{grid-template-columns:1fr}.demos>:nth-child(2){margin-top:0}}
.demo{display:block;text-decoration:none;color:inherit}
.demo .shot{aspect-ratio:16/11;border-radius:var(--r-media);
  box-shadow:0 18px 40px -26px rgba(31,27,21,.55);transition:transform .2s}
.demo:hover .shot{transform:translateY(-2px)}
.d1{background:radial-gradient(120% 90% at 25% 20%,#5A3A26,#241A13 65%,#17120F)}
.d2{background:linear-gradient(145deg,#F0D9C8,#D9A684 55%,#C0563A)}
.demo .meta{padding-top:16px;border-top:1px solid var(--line-2);margin-top:16px}
.demo .meta strong{display:block;font-family:var(--serif);font-size:20px;font-weight:500;margin-bottom:3px}
.demo .meta span{color:var(--ink-3);font-size:14.5px}

/* faq: un-boxed rows with hanging numerals, counted in CSS.
   The padding belongs to the summary, not the details, so the space around a
   question is part of the control rather than dead to a thumb. */
.faq{counter-reset:q}
.faq details{border-top:1px solid var(--line-2);padding:0;background:none;border-radius:0}
.faq details[open]{border-top-color:var(--ink-3)}
/* Capped to the answer's own measure (58px of hanging indent + 62ch) so the
   +/- stays beside its question instead of drifting to the far margin. */
.faq summary{cursor:pointer;list-style:none;display:grid;grid-template-columns:42px 1fr auto;
  column-gap:16px;align-items:baseline;font-weight:600;font-size:17.5px;color:var(--ink);
  padding:22px 0;max-width:calc(62ch + 58px)}
.faq details[open] summary{padding-bottom:10px}
.faq summary::-webkit-details-marker{display:none}
/* The numeral and the +/− are decorative: the alt-text syntax keeps them out
   of the accessible name, where <details> already announces its own state. */
.faq summary::before{counter-increment:q;content:counter(q) / "";font-family:var(--serif);
  font-weight:300;font-size:30px;color:var(--tint-2);line-height:1}
.faq summary::after{content:"+" / "";color:var(--terra);font-size:22px;font-weight:400;line-height:1}
.faq details[open] summary::after{content:"−" / ""}
.faq details p{color:var(--ink-2);font-size:15.5px;margin:0 0 22px 58px;max-width:62ch}
@media (max-width:560px){.faq details p{margin-left:0}
  .faq summary{grid-template-columns:30px 1fr auto;font-size:16.5px}}

/* contact + footer */
.cta h2{margin:6px 0 16px}
.cta p{max-width:54ch}
.cta-actions{display:flex;flex-wrap:wrap;gap:12px}
footer{border-top:1px solid var(--line);padding-block:30px;font-size:14.5px;color:var(--ink-3)}
.f-in{display:flex;flex-wrap:wrap;gap:14px 26px;align-items:center}
footer a{color:var(--ink-2);text-decoration:none}
footer a:hover{color:var(--terra)}
.f-in a{display:inline-block;padding-block:10px}
.f-brand{font-family:var(--serif);font-weight:500;font-size:18px;color:var(--ink);margin-right:auto}

/* ---- inner pages (terms, legal, unsubscribe, requests) ---------------- */
.page{max-width:820px;margin-inline:auto;padding:clamp(28px,6vw,62px) clamp(20px,5vw,72px) clamp(40px,8vw,78px)}
.page h1{font-size:clamp(32px,5.6vw,50px);margin:0 0 12px}
/* Rule above the heading, the same way `.slab` does it everywhere else. */
.page h2{font-family:var(--serif);font-size:clamp(21px,3vw,24px);font-weight:500;
  margin:38px 0 12px;padding-top:12px;border-top:1px solid var(--line-2);
  text-transform:none;letter-spacing:-.012em;color:var(--ink)}
.page p{color:var(--ink-2);font-size:16.5px;line-height:1.7;max-width:64ch}
.sub{color:var(--ink-3);font-size:14.5px;margin-bottom:30px}
/* The one grouped surface on the unsubscribe page. */
.card{background:var(--raised);border-radius:var(--r-media);padding:clamp(18px,4vw,24px);
  box-shadow:0 1px 0 var(--tint),0 24px 48px -32px rgba(31,27,21,.35);margin:0 0 26px}
.card p:last-child{margin-bottom:0}
dl{margin:0;display:grid;grid-template-columns:auto 1fr;gap:9px 22px;font-size:15.5px}
dt{color:var(--ink-3)}
dd{margin:0;color:var(--ink)}
@media (max-width:560px){dl{grid-template-columns:1fr;gap:2px}dd{margin-bottom:12px}
  code{white-space:normal;overflow-wrap:anywhere}}
.back{display:inline-block;margin-top:48px;padding-top:24px;border-top:1px solid var(--line-2);
  width:100%;text-decoration:none;color:var(--ink-2);font-size:15px}
.back:hover{color:var(--terra)}
/* ---- language router -------------------------------------------------
   Its own three rules, rather than four style attributes on one page: the
   router is the only file that had any, which made it the one page that
   looked hand-patched. */
.router{text-align:center}
.router h1{font-size:28px;margin:0 0 8px}
.router .sub{margin-bottom:26px}
.router-langs{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
.centered{display:grid;place-items:center;min-height:100svh}
.narrow{max-width:640px;padding:clamp(28px,6vw,48px) clamp(20px,5vw,32px)}
.small{font-size:14.5px;color:var(--ink-3)}
/* An address is one token: never split it into two half-pills. */
code{background:var(--paper-2);border:1px solid var(--line-2);border-radius:var(--r-media);
  padding:2px 7px;font-size:14.5px;color:var(--ink);white-space:nowrap;
  -webkit-box-decoration-break:clone;box-decoration-break:clone}

/* ---- work gallery ----------------------------------------------------
   Live previews, not screenshots: each tile iframes the real demo at full
   desktop width and scales it down, so what you see IS the running site.
   The scale factor is set per tile by JS against the measured width. */
/* Not a flex row: heading plus lede is always wide enough to push the filters
   onto their own line anyway, where margin-left:auto then stranded them hard
   right with nothing beside them and a band of dead space above. So they get
   a deliberate row, on the same left edge as everything else. */
.work-head{margin-bottom:26px}
.work-head h2{margin:0 0 14px}
.work-head .lede{margin-bottom:22px}
.filters[hidden]{display:none}
.filters{display:flex;gap:6px;flex-wrap:wrap}
.filters button{font-family:var(--sans);font-size:13.5px;font-weight:600;color:var(--ink-2);
  background:none;border:1px solid var(--line-2);border-radius:var(--r-pill);
  padding:8px 15px;cursor:pointer;transition:background .18s,border-color .18s,color .18s}
.filters button:hover{border-color:var(--ink-3);color:var(--ink)}
.filters button[aria-pressed="true"]{background:var(--green);border-color:var(--green);color:var(--paper)}

.work-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:28px 24px}
.work-item{display:flex;flex-direction:column}
.work-item[hidden]{display:none}
.work-a{display:block;text-decoration:none;color:inherit}

/* the frame: a restrained rule and a domain line, not fake macOS chrome */
.frame{position:relative;border:1px solid var(--line-2);border-radius:var(--r-media);
  background:#fff;overflow:hidden;box-shadow:0 1px 0 var(--tint),0 22px 44px -30px rgba(31,27,21,.45);
  transition:transform .22s ease,box-shadow .22s ease}
.work-a:hover .frame{transform:translateY(-3px);box-shadow:0 1px 0 var(--tint),0 30px 56px -30px rgba(31,27,21,.55)}
.frame-bar{display:flex;align-items:center;gap:8px;height:26px;padding:0 10px;
  background:var(--paper-2);border-bottom:1px solid var(--line-2)}
/* One mark, not three: three dots at the left of a title bar is exactly the
   macOS chrome the comment above says this frame is not. */
.frame-bar i{width:6px;height:6px;border-radius:50%;background:var(--tint-2);flex:none}
.frame-bar span{font-size:10.5px;letter-spacing:.06em;color:var(--ink-3);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.frame-view{position:relative;aspect-ratio:4/3;overflow:hidden}
.frame-view iframe{position:absolute;top:0;left:0;width:1280px;height:960px;border:0;
  transform-origin:0 0;pointer-events:none;
  /* Overridden precisely by preview.js; this default keeps the frame from ever
     painting at full width, which would push the page sideways before JS runs. */
  transform:scale(.25)}
/* No opacity gate. The scale above is the fallback, not a placeholder: if
   preview.js is blocked, 404s, or JS is off entirely, every preview still
   renders instead of leaving a blank white rectangle where the proof of the
   work should be. Nothing on the page sets a .no-js class, so gating on one
   only ever meant "hidden forever".
   Without JS there is no measurement to scale by, so the gallery tiles size
   the frame in percentages instead: 400% of the tile at scale(.25) is exactly
   the tile, whatever width it landed on. preview.js drops the rule the moment
   it measures, by setting data-scaled, and the fixed 1280px width above takes
   over so its own scale arithmetic still holds. */
.work-grid .frame-view:not([data-scaled]) iframe{width:400%;height:400%}

.work-meta{display:flex;align-items:baseline;gap:10px;padding-top:14px;margin-top:14px;
  border-top:1px solid var(--line-2)}
.work-meta strong{font-family:var(--serif);font-size:19px;font-weight:500}
.work-meta .kind{margin-left:auto;font-size:11.5px;font-weight:700;letter-spacing:.1em;
  text-transform:uppercase;color:var(--terra)}
.work-desc{color:var(--ink-2);font-size:14.5px;margin:6px 0 0}
.work-open{font-size:13.5px;color:var(--green);font-weight:600}

/* the hero showpiece: a real demo, in a phone, because the pitch is that most
   customers arrive on one. A desktop frame would argue against the copy. */
.hero>.hero-copy{grid-column:2/span 6}
.hero>.hero-showcase{grid-column:8/span 4;align-self:center;justify-self:center}
@media (max-width:1080px){
  .hero>.hero-copy{grid-column:2/span 10}
  .hero>.hero-showcase{grid-column:2/span 10;margin-top:44px}
}
@media (max-width:900px){
  .hero>.hero-copy,.hero>.hero-showcase{grid-column:1}
}
.phone{position:relative;width:min(300px,78vw);padding:9px;border-radius:38px;
  background:linear-gradient(160deg,#2A251D,#171410);
  box-shadow:0 2px 3px rgba(31,27,21,.3),0 40px 70px -34px rgba(31,27,21,.62),
             inset 0 0 0 1px rgba(255,255,255,.06)}
/* the speaker slot: one small honest detail, not a fake iOS status bar */
.phone::before{content:"";position:absolute;top:18px;left:50%;transform:translateX(-50%);
  width:52px;height:4px;border-radius:4px;background:rgba(255,255,255,.16);z-index:2}
.phone-screen{position:relative;border-radius:30px;overflow:hidden;aspect-ratio:9/18.5;background:#fff}
.phone-screen iframe{position:absolute;top:0;left:0;width:390px;border:0;
  transform-origin:0 0;pointer-events:none;transform:scale(.72)}
/* On a phone the bezel is a picture of the thing the reader is holding, and it
   costs what the showpiece is for: inside a 292px bezel the demo rendered at
   about 11px effective, smaller than any other text on the page. Below 700px
   the chrome comes off and the demo gets the full column at a legible scale;
   the bezel returns at desktop widths, where it supplies the context the
   viewer genuinely lacks. */
@media (max-width:700px){
  .phone{width:100%;max-width:420px;padding:0;border-radius:var(--r-media);
    background:none;box-shadow:0 1px 0 var(--tint),0 24px 48px -30px rgba(31,27,21,.45)}
  .phone::before{display:none}
  .phone-screen{border-radius:var(--r-media);aspect-ratio:9/15;
    border:1px solid var(--line-2)}
  .hero>.hero-showcase{justify-self:stretch}
}
.phone-cap{margin-top:14px;text-align:center;font-size:13px;color:var(--ink-3)}
/* Nothing is centred once the showcase is a column-wide crop rather than a
   narrow device sitting in the middle of its column. */
@media (max-width:700px){.phone-cap{text-align:left}}
.phone-cap a{font-weight:600}

/* ---- forms (mockup + project requests) ------------------------------- */
.form{display:grid;gap:22px;margin:34px 0 8px;max-width:640px}
.field{display:grid;gap:8px}
.field.two{grid-template-columns:1fr 1fr;gap:14px 18px}
.field.two .hint{grid-column:1/-1}
.field.two>div{display:grid;gap:8px}
@media (max-width:560px){.field.two{grid-template-columns:1fr}}
.field label,.field legend{font-weight:600;font-size:15.5px;color:var(--ink)}
.field .sub-label{font-weight:400;font-size:14px;color:var(--ink-2);margin-top:8px}
.req{font-weight:600;font-size:11px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--terra);margin-left:6px}
.field input[type=text],.field input[type=email],.field input[type=tel],
.field input[type=url],.field select,.field textarea{
  font:inherit;font-size:16px;color:var(--ink);background:var(--raised);
  border:1px solid var(--line-2);border-radius:var(--r-media);padding:12px 14px;width:100%}
.field textarea{resize:vertical;min-height:72px}
.field input:focus-visible,.field select:focus-visible,.field textarea:focus-visible{
  outline:2.5px solid var(--green);outline-offset:1px}
.field input[type=file]{font-size:14.5px;color:var(--ink-2)}
.field input[type=file]::file-selector-button{font:inherit;font-size:14px;font-weight:600;
  color:var(--ink);background:none;border:1px solid var(--line-2);border-radius:var(--r-pill);
  padding:8px 16px;margin-right:12px;cursor:pointer}
fieldset.field{border:0;padding:0;margin:0}
.radio{display:inline-flex;align-items:center;gap:8px;font-weight:400;font-size:15.5px;
  color:var(--ink-2);margin-right:22px;min-height:44px}
.radio input{width:18px;height:18px;accent-color:var(--green)}
.reveal{margin-top:10px;display:grid;gap:8px}
.form .hint{color:var(--ink-3);font-size:14px;margin:0}
.form button[type=submit]{justify-self:start}
.hp{position:absolute !important;left:-9999px !important;height:0;overflow:hidden}
.ok-msg{background:var(--green-tint);color:var(--green);font-weight:600;font-size:16.5px;
  border-radius:4px;padding:22px 24px;margin:34px 0}
.err-msg{color:var(--terra);font-weight:600;font-size:15px;margin:0}

/* currency choice on the pricing heading: same pill vocabulary as the language switch */
.cur-links{width:max-content;margin-top:0}
