/* =====================================================================
   Open Air Gallery — site stylesheet

   Dark only. The murals are the colour; the page is not. Every colour in
   this file is named once in :root and used through var() everywhere else,
   so a bare hex outside this block is a bug.

   Contrast, computed in docs/contrast.py and asserted at build time:
     --mint on --ink        13.8:1  (AAA)
     --mint-deep on --ink    7.7:1  (AA at every size)
     --muted on --ink        8.9:1
     --ink on --mint        13.8:1  (the primary button)
   Two rules that go with it: mint never carries body copy under 16px, and
   mint never sits on --wall.
   ===================================================================== */
:root{
  --ink:#0A0A0B; --ink-2:#121214; --ink-3:#1A1A1D;
  --paper:#F4F3EF; --muted:#A6A49E; --dim:#6E6C67;
  --mint:#71EEB8; --mint-deep:#55B38A; --wall:#E9E7E2;
  --line:rgba(244,243,239,.10); --line-2:rgba(244,243,239,.20);
  --mint-glow:rgba(113,238,184,.14); --shade:rgba(10,10,11,.72);
  --scrim:rgba(10,10,11,.88); --scrim-soft:rgba(10,10,11,.45);
  /* Ink on the one light panel: the ghost of a buff job across its corner,
     its hairlines, and the muted grey that stands in for --muted there.
     --ink-mute on --wall is 7.4:1. --mint on --wall is 1.16:1, which is why
     PLAN.md §2 says the accent never lands on this colour: on the band it
     appears the one way it may, as ink on mint. */
  --ghost:rgba(10,10,11,.10); --ink-line:rgba(10,10,11,.22); --ink-mute:#4B4842;
  --radius:4px;                 /* near-square — signwriting, not SaaS */
  --max:1240px; --nav-h:72px;
  /* Archivo carries the width axis the dimension figures need; the fallbacks
     are metric-near grotesques so a 9rem figure does not reflow the page
     while the variable font is still in flight. */
  --font-display:"Archivo","Helvetica Neue",Helvetica,Arial,"Liberation Sans",sans-serif;
  --font-body:"Inter",system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
  --ease:cubic-bezier(.16,1,.3,1); --ease-roll:cubic-bezier(.62,.03,.2,1);
}

*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--ink);color:var(--paper);
  font-family:var(--font-body);font-size:16px;line-height:1.65;
  font-variant-numeric:tabular-nums;-webkit-font-smoothing:antialiased;
}
img,picture,video,svg{max-width:100%;display:block}
a{color:inherit;text-decoration:none}
ul{list-style:none;margin:0;padding:0}
:focus-visible{outline:2px solid var(--mint);outline-offset:3px}

/* --- type ------------------------------------------------------------ */
h1,h2,h3,h4{margin:0;line-height:1.1;font-weight:400}
h1,h2,.display{
  font-family:var(--font-display);text-transform:uppercase;
  font-variation-settings:'wdth' 110,'wght' 800;
  letter-spacing:-.005em;line-height:.94;
}
h1{font-size:clamp(2.6rem,7.4vw,5.6rem)}
h2{font-size:clamp(2rem,4.6vw,3.6rem)}
h3{font-family:var(--font-body);font-weight:700;font-size:1.2rem;letter-spacing:-.01em;line-height:1.3}
h4{font-family:var(--font-body);font-weight:700;font-size:.82rem;letter-spacing:.18em;text-transform:uppercase;color:var(--dim)}
p{margin:0 0 1em}
b,strong{font-weight:700}
small{font-size:.82rem}
.muted{color:var(--muted)}
.lead{font-size:1.1rem;color:var(--muted);max-width:62ch}
.eyebrow{
  display:inline-block;font-family:var(--font-display);
  font-variation-settings:'wdth' 75,'wght' 700;
  font-size:.78rem;letter-spacing:.24em;text-transform:uppercase;
  color:var(--mint);margin-bottom:.9rem;
}

/* --- shell ----------------------------------------------------------- */
.wrap{width:min(var(--max),calc(100% - 2rem));margin-inline:auto}
section{padding:clamp(4.5rem,10vw,8rem) 0;position:relative}
.section-head{max-width:760px;margin-bottom:clamp(2rem,5vw,3.5rem)}
.section-head .lead{margin-top:1.1rem}
.alt{background:var(--ink-2)}
.skip{position:absolute;left:-999px;top:0;background:var(--mint);color:var(--ink);padding:.5rem 1rem;z-index:1000;font-weight:700}
.skip:focus{left:1rem;top:1rem}

/* --- buttons --------------------------------------------------------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:.5rem;
  padding:.85rem 1.5rem;border-radius:var(--radius);
  font-family:var(--font-body);font-weight:700;font-size:.95rem;line-height:1;
  border:1px solid transparent;cursor:pointer;white-space:nowrap;
  transition:background .2s,border-color .2s,color .2s,transform .2s var(--ease);
}
.btn-mint{background:var(--mint);color:var(--ink)}
.btn-mint:hover{background:var(--mint-deep);color:var(--ink)}
.btn-ghost{background:transparent;border-color:var(--line-2);color:var(--paper)}
.btn-ghost:hover{border-color:var(--mint);color:var(--mint)}
.btn-sm{padding:.6rem 1.05rem;font-size:.85rem}
.btn svg{width:17px;height:17px;flex:none}
.btn-row{display:flex;gap:.7rem;flex-wrap:wrap}
@media (prefers-reduced-motion:no-preference){
  .btn:hover{transform:translateY(-1px)}
}

/* --- breakpoints: base, 640, 960, 1240 ------------------------------- */
@media (min-width:640px){
  .wrap{width:min(var(--max),calc(100% - 3rem))}
  .lead{font-size:1.15rem}
}
@media (min-width:960px){
  body{font-size:17px}
  .wrap{width:min(var(--max),calc(100% - 4rem))}
}
@media (min-width:1240px){
  .wrap{width:var(--max)}
}

/* =====================================================================
   Nav — wordmark, five links, one mint pill. No dropdowns: six pages, all
   one click away. Below 960 the links become a full-height panel behind
   the hamburger, which is the only thing the toggle does.
   ===================================================================== */
.nav{position:fixed;inset:0 0 auto 0;z-index:100;height:var(--nav-h);border-bottom:1px solid transparent;transition:border-color .3s}
/* The blur lives on a pseudo-element: backdrop-filter on .nav itself would
   make it the containing block for the panel and clip it to 72px. */
.nav::before{content:"";position:absolute;inset:0;z-index:-1;background:var(--ink);opacity:0;transition:opacity .3s;pointer-events:none;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}
.nav.scrolled::before,.nav.open::before{opacity:.94}
/* At the top of a page the nav has no background, and every page on this site
   opens on a photograph — so at rest the links can be sitting on a bright sky.
   A short gradient under the bar, on all the time and faded out the moment the
   solid background arrives, keeps them on their own dark ground without ever
   putting a box around the wordmark. */
.nav::after{content:"";position:absolute;inset:0 0 -40px 0;z-index:-2;pointer-events:none;background:linear-gradient(180deg,var(--scrim) 0%,transparent 100%);transition:opacity .3s}
.nav.scrolled::after,.nav.open::after{opacity:0}
.nav.scrolled,.nav.open{border-color:var(--line)}
.nav .wrap{display:flex;align-items:center;justify-content:space-between;height:100%;gap:1rem}

.brand{display:grid;font-family:var(--font-display);font-variation-settings:'wdth' 125,'wght' 800;font-size:.98rem;line-height:.94;letter-spacing:.02em;text-transform:uppercase;color:var(--paper)}

.nav-links{
  position:fixed;top:var(--nav-h);left:0;right:0;bottom:0;
  height:calc(100dvh - var(--nav-h));
  display:none;flex-direction:column;align-items:stretch;gap:.1rem;
  padding:1.2rem 1rem 2.5rem;overflow:auto;
  background:var(--ink);border-top:1px solid var(--line);
}
.nav.open .nav-links{display:flex}
.nav-links li:not(.nav-cta) a{display:flex;align-items:center;padding:.85rem .6rem;border-radius:var(--radius);font-size:1.05rem;font-weight:600;color:var(--muted);transition:color .2s,background .2s}
.nav-links li:not(.nav-cta) a:hover{color:var(--paper)}
.nav-links li:not(.nav-cta) a.active{color:var(--mint)}
.nav-cta{margin-top:1.1rem}
.nav-cta .btn{width:100%}

.nav-toggle{display:inline-flex;align-items:center;justify-content:center;width:44px;height:44px;flex:none;background:none;border:1px solid var(--line-2);border-radius:var(--radius);color:var(--paper);cursor:pointer}
.nav-toggle svg{width:22px;height:22px}

@media (min-width:960px){
  .nav-toggle{display:none}
  .nav-links{position:static;height:auto;display:flex;flex-direction:row;align-items:center;gap:.15rem;padding:0;overflow:visible;background:none;border-top:0}
  .nav-links li:not(.nav-cta) a{padding:.5rem .7rem;font-size:.92rem}
  .nav-cta{margin:0 0 0 .5rem}
  .nav-cta .btn{width:auto}
  .brand{font-size:1.05rem}
}

/* --- page hero -------------------------------------------------------- */
.page-hero{padding:calc(var(--nav-h) + clamp(3.5rem,9vw,7rem)) 0 clamp(3.5rem,9vw,7rem);position:relative;isolation:isolate;overflow:hidden}
.page-hero .hero-media{position:absolute;inset:0;z-index:-2}
.page-hero .hero-media img{width:100%;height:100%;object-fit:cover}
.hero-shade{position:absolute;inset:0;z-index:-1;background:linear-gradient(180deg,var(--shade) 0%,rgba(10,10,11,.25) 38%,var(--ink) 100%)}
.hero-inner{max-width:900px}
.page-hero .lead{margin-top:1.3rem;color:var(--paper);opacity:.86}
.crumbs{display:flex;gap:.5rem;align-items:center;font-size:.8rem;letter-spacing:.06em;text-transform:uppercase;color:var(--dim);margin-bottom:1.2rem}
.crumbs a:hover{color:var(--paper)}

/* --- closing call to action ------------------------------------------- */
.cta{border:1px solid var(--line);border-radius:var(--radius);background:var(--ink-2);padding:clamp(1.8rem,5vw,3.4rem)}
.cta h2{margin-bottom:.7rem}
.cta p{color:var(--muted);max-width:60ch}
.cta .btn-row{margin-top:1.6rem}

/* --- footer ----------------------------------------------------------- */
footer{border-top:1px solid var(--line);background:var(--ink-2);padding:clamp(3rem,7vw,5rem) 0 1.8rem}
.foot-grid{display:grid;gap:2.4rem}
.foot-brand p{color:var(--muted);max-width:36ch;margin:1.1rem 0}
.foot-contact li{margin-bottom:.4rem}
.foot-contact a{display:inline-flex;align-items:center;gap:.55rem;color:var(--muted);transition:color .2s}
.foot-contact a:hover{color:var(--mint)}
.foot-contact svg{width:16px;height:16px;flex:none;color:var(--dim)}
footer h4{margin-bottom:1rem}
footer .foot-grid ul li{margin-bottom:.5rem}
footer .foot-grid ul a{color:var(--muted);transition:color .2s}
footer .foot-grid ul a:hover{color:var(--paper)}
.foot-bottom{display:flex;flex-wrap:wrap;justify-content:space-between;gap:.6rem 1.6rem;margin-top:clamp(2.2rem,5vw,3.2rem);padding-top:1.4rem;border-top:1px solid var(--line);color:var(--dim);font-size:.85rem}

@media (min-width:640px){.foot-grid{grid-template-columns:1fr 1fr}.foot-brand{grid-column:1 / -1}}
@media (min-width:960px){.foot-grid{grid-template-columns:1.5fr 1fr 1fr}.foot-brand{grid-column:auto}}

/* =====================================================================
   MOTION — Roll · Scale · Cure

   The contract, and it is not negotiable: the pages render complete without
   JavaScript. Everything that hides anything lives under html.motion, which
   the inline bootstrap in <head> adds only when a script runs and the visitor
   has not asked for reduced motion — and every hidden thing carries
   `animation:oaSelf 0s linear 2.8s forwards`, so a page whose script never
   arrives still shows all of its text 2.8 seconds later. One observer adds
   .in. No counters, no scores, no labels: the murals do the talking.

     Roll   a reveal arrives like a roller pass — a clip-path wipe
            left-to-right with the last 18px of rise.
     Scale  the dimension figures widen into place, wdth 100 -> 125.
     Cure   one slow mint sheen down the rule under a figure as it lands,
            once, never repeated.
   ===================================================================== */

/* --- Roll ------------------------------------------------------------- */
/* The wipe is a mask, not a clip-path. clip-path on the observed element
   collapses its intersection rectangle to nothing, so the observer that is
   supposed to reveal it never fires and the section sits there until the
   2.8s self-reveal bails it out. A mask is paint-only: the observer still
   sees the full box. The 88% stop leaves a soft wet edge on the roller. */
html.motion .rv{
  opacity:0;transform:translateY(18px);
  -webkit-mask-image:linear-gradient(90deg,var(--paper) 88%,transparent);
          mask-image:linear-gradient(90deg,var(--paper) 88%,transparent);
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:0% 100%;mask-size:0% 100%;
  transition:opacity .5s var(--ease),transform .8s var(--ease),
             -webkit-mask-size .8s var(--ease-roll),mask-size .8s var(--ease-roll);
  animation:oaSelf 0s linear 2.8s forwards;
}
html.motion .rv.rv.in{opacity:1;transform:none;-webkit-mask-size:130% 100%;mask-size:130% 100%;animation:none}
.rv.in{opacity:1;transform:none;animation:none}
@keyframes oaSelf{to{opacity:1;transform:none;-webkit-mask-size:130% 100%;mask-size:130% 100%}}
/* A roller does not repaint the whole wall at once. */
html.motion .rv-d1{transition-delay:.08s}
html.motion .rv-d2{transition-delay:.16s}
html.motion .rv-d3{transition-delay:.24s}

/* --- Scale ------------------------------------------------------------ */
/* The figures are never hidden — they arrive narrow and widen out to their
   real width, so a visitor who never gets the script still reads 81' x 80'
   at full width. The type itself is set with the component. */
.dims{position:relative}
html.motion .dims{
  font-variation-settings:'wdth' 100,'wght' 800;
  transition:font-variation-settings .9s var(--ease);
  animation:oaDims 0s linear 2.8s forwards;
}
html.motion .dims.dims.in{font-variation-settings:'wdth' 125,'wght' 800;animation:none}
@keyframes oaDims{to{font-variation-settings:'wdth' 125,'wght' 800}}

/* --- Cure ------------------------------------------------------------- */
/* One pass of mint down the rule, then it is done for good: .in is added
   once and the observer stops watching. */
/* The sheen is a background travelling inside the rule, not an element
   sliding along it. It used to be a 40%-wide box translated 250% of its own
   width, which worked on a card — .pcard clips — and on a project page, where
   the figures sit in an unclipped .wrap, pushed the document 150px wider than
   the window and gave the whole page a horizontal scrollbar. A background can
   be positioned outside its box; it cannot overflow it. */
html.motion .dims.in::before{
  content:"";position:absolute;left:0;right:0;bottom:-.16em;height:1px;
  background:linear-gradient(90deg,transparent,var(--mint),transparent) no-repeat;
  background-size:40% 100%;
  animation:oaCure 1.8s var(--ease) .4s 1 both;pointer-events:none;
}
@keyframes oaCure{
  from{background-position:-45% 0;opacity:0}
  18%{opacity:1}
  to{background-position:145% 0;opacity:0}
}

/* --- and the way out -------------------------------------------------- */
/* html.motion is already withheld when the visitor asks for reduced motion.
   This is the second lock: if the class ever arrives anyway, nothing here
   moves and nothing here hides. */
@media (prefers-reduced-motion:reduce){
  html.motion .rv,html.motion .rv.in{opacity:1;transform:none;-webkit-mask-image:none;mask-image:none;transition:none;animation:none}
  html.motion .dims{font-variation-settings:'wdth' 125,'wght' 800;transition:none;animation:none}
  html.motion .dims.in::before{content:none;animation:none}
  html{scroll-behavior:auto}
}

/* =====================================================================
   THE DIMENSIONS HOOK

   The measurements are the headline. Archivo's width axis is what makes
   that work: at wdth 125 the figures are physically wide, so 81' x 80'
   occupies the page the way the wall occupies the street. Mint touches the
   prime marks and the times sign, and nothing else.
   ===================================================================== */
.dims{
  display:block;font-family:var(--font-display);
  font-variation-settings:'wdth' 125,'wght' 800;
  font-size:clamp(3.4rem,11vw,9rem);line-height:.82;letter-spacing:-.02em;
  color:var(--paper);
}
.dims .f,.dims .x{font-size:.38em;color:var(--mint);vertical-align:.34em}
.dims .x{margin:0 .12em}
/* The rule Cure sweeps. */
.dims::after{content:"";position:absolute;left:0;right:0;bottom:-.16em;height:1px;background:var(--line)}
.dims-sm{font-size:clamp(1.5rem,4.6vw,2.2rem)}
/* A card sets the figures at 2.2rem, where .38em prime marks land near 13px and
   read as dots rather than marks. They carry the only mint on a card, so they
   have to be legible: .46em at card size only — the project-page figures are
   three times as big and stay as specified. The vertical-align is retuned from
   .34em to .28em because it is relative to the mark's own size, and .28 x .46
   is the same optical lift off the baseline as .34 x .38 was. */
.dims-sm .f,.dims-sm .x{font-size:.46em;vertical-align:.28em}

/* --- project card ----------------------------------------------------- */
/* The card is the link. Photo at 4:3, the figures over a scrim at the foot,
   a hairline that goes mint when you reach it. */
.pcard{position:relative;display:block;overflow:hidden;border:1px solid var(--line);border-radius:var(--radius);background:var(--ink-2);transition:border-color .25s,transform .35s var(--ease)}
.pcard:hover,.pcard:focus-visible{border-color:var(--mint)}
.pcard-img{aspect-ratio:4/3;background:var(--ink-3)}
.pcard-img picture,.pcard-img img{display:block;width:100%;height:100%}
.pcard-img img{object-fit:cover;transition:transform .7s var(--ease)}
.pcard:hover .pcard-img img{transform:scale(1.04)}
.pcard-body{position:absolute;inset:auto 0 0 0;padding:1.1rem 1.15rem 1.15rem;background:linear-gradient(0deg,var(--scrim) 0%,var(--scrim-soft) 58%,transparent 100%)}
.pcard h3{margin-top:.55rem;font-size:1.02rem}
.pcard .place{display:block;margin-top:.2rem;font-size:.76rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}

.pgrid{display:grid;gap:1rem;grid-template-columns:1fr}
@media (min-width:640px){.pgrid{grid-template-columns:repeat(2,1fr);gap:1.2rem}}
@media (min-width:960px){.pgrid{grid-template-columns:repeat(3,1fr)}}
@media (prefers-reduced-motion:no-preference){.pcard:hover{transform:translateY(-3px)}}

/* =====================================================================
   HOME

   Four blocks between the hero and the call to action: what the work adds
   up to, six of the twelve walls, the two Rochester commissions side by
   side, and Ephraim's three stages in his own words. Nothing here invents a
   component the rest of the site does not already have — the card is the
   card, the figures are the figures.
   ===================================================================== */

/* The hero gets room for a mural rather than just room for the words. The
   photograph is the page's largest paint, so the block is sized off the
   viewport and the words sit on its floor. */
.page-hero.tall{min-height:clamp(560px,86vh,880px);display:flex;align-items:flex-end;padding-bottom:clamp(3rem,7vw,5.5rem)}
/* The standard shade is lightest at 38% down, which on a tall hero is exactly
   where the eyebrow lands — and a mint eyebrow over a lit-up mural is the one
   contrast failure PLAN.md §9(e) names. So the tall hero keeps the top of the
   photograph clear and puts the words on a floor of near-black instead. */
.page-hero.tall .hero-shade{background:linear-gradient(180deg,var(--scrim-soft) 0%,transparent 18%,transparent 30%,var(--scrim) 50%,var(--ink) 100%)}
/* And the last few per cent: a mint eyebrow is small type, and small type over
   a photograph of a flower mural needs its own edge. An ink halo does that
   without drawing a box round the words. */
.page-hero .hero-inner .eyebrow,.page-hero .hero-inner .lead,.page-hero .hero-inner .crumbs{text-shadow:0 1px 14px var(--ink)}
.page-hero.tall .wrap{width:min(var(--max),calc(100% - 2rem))}
.page-hero .hero-media picture{width:100%;height:100%}
@media (min-width:640px){.page-hero.tall .wrap{width:min(var(--max),calc(100% - 3rem))}}
@media (min-width:960px){.page-hero.tall .wrap{width:min(var(--max),calc(100% - 4rem))}}
@media (min-width:1240px){.page-hero.tall .wrap{width:var(--max)}}

/* --- the scale statement --------------------------------------------- */
/* Every figure in this row is projects.py adding itself up. The prime mark
   is the same mint mark it is on a card, so the row reads as measurements
   rather than as marketing. */
.stats{display:grid;grid-template-columns:repeat(2,1fr);gap:1.6rem 1.2rem}
.stat{border-top:1px solid var(--line-2);padding-top:1rem}
.stat-n{
  display:block;font-family:var(--font-display);
  font-variation-settings:'wdth' 118,'wght' 800;
  font-size:clamp(2.1rem,5.4vw,3.2rem);line-height:.9;letter-spacing:-.02em;
  color:var(--paper);
}
.stat-n .f{font-size:.42em;color:var(--mint);vertical-align:.3em}
.stat-l{display:block;margin-top:.5rem;font-size:.76rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}
@media (min-width:960px){.stats{grid-template-columns:repeat(4,1fr);gap:1.6rem}}

/* --- the end of a card row -------------------------------------------- */
.row-end{margin-top:clamp(1.6rem,4vw,2.4rem)}

/* --- the two Rochester walls ------------------------------------------ */
/* Same card, more of it: the pair is the only place two photographs get half
   the page each, because the two walls are the same size to the foot and the
   point is that you can see that. */
.pgrid.pair{grid-template-columns:1fr}
@media (min-width:640px){.pgrid.pair{grid-template-columns:repeat(2,1fr)}}
.pcard-lg .pcard-img{aspect-ratio:3/2}
.pcard-lg .pcard-body{padding:1.4rem 1.4rem 1.5rem}
.pcard-lg .dims-sm{font-size:clamp(2rem,5.4vw,3.1rem)}
.pcard-lg h3{font-size:1.15rem;margin-top:.7rem}

/* --- his three stages -------------------------------------------------- */
.beats{list-style:none;margin:0;padding:0;display:grid;gap:2.2rem}
.beat{border-top:1px solid var(--line-2);padding-top:1.5rem}
.beat h3{
  font-family:var(--font-display);text-transform:uppercase;
  font-variation-settings:'wdth' 100,'wght' 800;
  font-size:1.18rem;letter-spacing:.01em;margin-bottom:.9rem;
}
.beat h3::before{content:"";display:block;width:34px;height:2px;background:var(--mint);margin-bottom:.9rem}
.beat blockquote{margin:0}
.beat blockquote p{margin:0;color:var(--paper);opacity:.9;max-width:52ch}
@media (min-width:960px){.beats{grid-template-columns:repeat(3,1fr);gap:2rem}}

/* =====================================================================
   WORK — the index

   One grid of twelve with four chips over it, and the filtering is CSS.
   The chips are ordinary anchors pointing at four empty targets that sit
   just above the grid; :target then hides the cards that do not match.
   No script is involved, so the filter survives a blocked or broken
   site.js, and with nothing targeted every wall is on screen — which is
   the state the page should open in anyway.
   ===================================================================== */
/* The hero already ends on a paragraph; a second full section pad under it
   just leaves a hole above the chips. */
.work{padding-top:clamp(2.5rem,6vw,4rem)}
.ftarget{display:block;height:0;scroll-margin-top:calc(var(--nav-h) + 1.5rem)}
.filters{display:flex;flex-wrap:wrap;gap:.5rem;margin-bottom:clamp(1.6rem,4vw,2.4rem)}
.chip{
  display:inline-flex;align-items:center;gap:.5rem;
  padding:.55rem 1rem;border:1px solid var(--line-2);border-radius:999px;
  font-size:.86rem;font-weight:600;color:var(--muted);
  transition:border-color .2s,color .2s,background .2s;
}
.chip:hover{border-color:var(--mint);color:var(--paper)}
.chip .ct{font-size:.74rem;color:var(--dim);font-variant-numeric:tabular-nums}
/* The chip that is doing the filtering, and All when nothing else is. */
.chip[href="#f-all"],
#f-brand:target ~ .filters .chip[href="#f-brand"],
#f-portrait:target ~ .filters .chip[href="#f-portrait"],
#f-civic:target ~ .filters .chip[href="#f-civic"]{background:var(--mint);border-color:var(--mint);color:var(--ink)}
.chip[href="#f-all"] .ct,
#f-brand:target ~ .filters .chip[href="#f-brand"] .ct,
#f-portrait:target ~ .filters .chip[href="#f-portrait"] .ct,
#f-civic:target ~ .filters .chip[href="#f-civic"] .ct{color:var(--ink);opacity:.62}
#f-brand:target ~ .filters .chip[href="#f-all"],
#f-portrait:target ~ .filters .chip[href="#f-all"],
#f-civic:target ~ .filters .chip[href="#f-all"]{background:transparent;border-color:var(--line-2);color:var(--muted)}
#f-brand:target ~ .filters .chip[href="#f-all"] .ct,
#f-portrait:target ~ .filters .chip[href="#f-all"] .ct,
#f-civic:target ~ .filters .chip[href="#f-all"] .ct{color:var(--dim);opacity:1}
/* and the filtering itself */
#f-brand:target ~ .pgrid .pcard:not(.cat-brand),
#f-portrait:target ~ .pgrid .pcard:not(.cat-portrait),
#f-civic:target ~ .pgrid .pcard:not(.cat-civic){display:none}

/* =====================================================================
   A PROJECT PAGE

   The wall at full bleed with a six-foot figure standing on it, then the
   measurements set large, then what is known about the wall and nothing
   that is not.
   ===================================================================== */
.phero{padding:var(--nav-h) 0 0}
.scale{position:relative;isolation:isolate}
/* The height is written out rather than left to aspect-ratio with a
   max-height on the same box: given both, the browser keeps the ratio by
   *narrowing* the box, and the full-bleed hero came out 1280px wide in a
   1440px window with ink down the right-hand side. A height in vh and vw
   says what is meant — as tall as the ratio allows, never taller than the
   window — and the box stays the full width of the page. */
.scale-media{position:relative;height:min(68vh,120vw);overflow:hidden;background:var(--ink-3)}
.scale-media picture,.scale-media img{width:100%;height:100%;object-fit:cover}
@media (min-width:640px){.scale-media{height:min(74vh,80vw)}}
@media (min-width:960px){.scale-media{height:min(80vh,56.25vw)}}

/* The baseline the figure stands on, and the figure. --ft is the wall's
   width in feet, written on the element by build.py; she is two feet wide
   and six tall, so 2/ft of the picture's width at an aspect ratio of 1:3
   makes her exactly (6 / ft) of it high. On Gucci that is a thirtieth of
   the frame, which is the entire point of putting her there. --fx is where
   she stands, 0 to 1; step 14 gives her the drag. */
.scale-base{position:absolute;left:0;right:0;bottom:var(--base,7%);height:1px;background:var(--mint);opacity:.5}
.fig{
  position:absolute;bottom:var(--base,7%);
  --fw:calc(200% / var(--ft, 40));
  left:calc(var(--fx,.12) * (100% - var(--fw)));
  width:var(--fw);min-width:9px;aspect-ratio:1/3;
  padding:0;border:0;background:none;color:var(--mint);
  filter:drop-shadow(0 1px 3px var(--ink));
}
.fig-svg{width:100%;height:100%}
.fig-cap{
  position:absolute;left:50%;bottom:calc(100% + .45rem);transform:translateX(-50%);
  font-size:.66rem;letter-spacing:.14em;text-transform:uppercase;color:var(--mint);
  white-space:nowrap;text-shadow:0 1px 8px var(--ink);
}

/* --- the words under the wall ----------------------------------------- */
.pintro{padding-top:clamp(2.5rem,6vw,4rem)}
.pintro .dims{margin:1.4rem 0 1.8rem}
.pstory{max-width:58ch}
.pmeta{display:flex;flex-wrap:wrap;gap:1.6rem 2.6rem;margin:2rem 0 0;padding-top:1.5rem;border-top:1px solid var(--line)}
.pmeta dt{font-size:.74rem;letter-spacing:.18em;text-transform:uppercase;color:var(--dim)}
.pmeta dd{margin:.35rem 0 0;font-weight:600}
.credit{margin-top:1.4rem;font-size:.86rem;color:var(--dim)}

/* --- previous and next ------------------------------------------------- */
.pnav-wrap{padding-top:0}
.pnav{display:grid;gap:.8rem;border-top:1px solid var(--line);padding-top:1.6rem}
.pnav a{display:grid;gap:.4rem;padding:1.2rem 1.3rem;border:1px solid var(--line);border-radius:var(--radius);background:var(--ink-2);transition:border-color .25s}
.pnav a:hover,.pnav a:focus-visible{border-color:var(--mint)}
.pnav-next{text-align:right}
.pnav-k{display:flex;align-items:center;gap:.4rem;font-size:.74rem;letter-spacing:.18em;text-transform:uppercase;color:var(--dim)}
.pnav-next .pnav-k{flex-direction:row-reverse}
.pnav-k svg{width:15px;height:15px}
.pnav-t{font-weight:700;font-size:1.05rem}
.pnav-d{font-family:var(--font-display);font-variation-settings:'wdth' 118,'wght' 800;font-size:1.3rem;color:var(--mint)}
@media (min-width:640px){.pnav{grid-template-columns:1fr 1fr;gap:1rem}}

/* =====================================================================
   SWIPE — the gallery rail

   With no script the rail is a native scroll-snap strip you can drag with
   a finger or reach with the keyboard, so the arrows and the dots never
   render: they live behind html.js because they are enhancement, and a
   control that cannot do anything should not be on the page at all.
   ===================================================================== */
.swipe{position:relative}
.swipe-rail{display:flex;gap:1rem;overflow-x:auto;scroll-snap-type:x mandatory;scroll-behavior:smooth;
  padding-bottom:1rem;scrollbar-width:none;-ms-overflow-style:none}
.swipe-rail::-webkit-scrollbar{display:none}
.swipe-rail:focus-visible{outline:2px solid var(--mint);outline-offset:4px;border-radius:var(--radius)}
html.js .swipe-rail{cursor:grab}
html.js .swipe-rail.dragging{cursor:grabbing;scroll-behavior:auto;scroll-snap-type:none;user-select:none}
.swipe-slide{flex:0 0 min(92%,var(--slide,900px));scroll-snap-align:center}
.swipe-slide:first-child{scroll-snap-align:start}
.swipe-slide:last-child{scroll-snap-align:end}
.swipe-slide>*{width:100%;margin:0}
.swipe-slide img{width:100%;height:auto;border-radius:var(--radius)}
.swipe.fits .swipe-dots,.swipe.fits .swipe-arw,.swipe.fits .swipe-hint{display:none}
.swipe-arw{display:none;position:absolute;top:calc(50% - 1rem);translate:0 -50%;z-index:4;
  width:46px;height:46px;border-radius:50%;place-items:center;cursor:pointer;
  background:var(--ink-2);border:1px solid var(--line-2);color:var(--paper);
  transition:background .2s,border-color .2s,color .2s,opacity .2s}
html.js .swipe-arw{display:grid}
.swipe-arw svg{width:20px;height:20px}
.swipe-arw:hover{background:var(--mint);border-color:var(--mint);color:var(--ink)}
.swipe-arw.prev{left:2px}
.swipe-arw.next{right:2px}
.swipe-arw[disabled]{opacity:0;pointer-events:none}
@media (hover:none){.swipe-arw{display:none!important}}
.swipe-dots{display:none;justify-content:center;gap:.4rem;margin-top:.2rem}
html.js .swipe-dots{display:flex}
.swipe-dot{width:8px;height:8px;padding:0;border:0;border-radius:999px;background:var(--line-2);cursor:pointer;transition:width .3s var(--ease),background .3s}
.swipe-dot.is-on{width:26px;background:var(--mint)}
.swipe-hint{display:none;position:absolute;right:.2rem;top:-1.9rem;font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;color:var(--dim)}
@media (hover:none){html.js .swipe-hint{display:block}}

/* =====================================================================
   ABOUT

   Two halves and a photograph, twice: Ephraim beside his own paragraph,
   the crew beside a picture of the lift. Then his three stages at full
   width — on Home they are three columns of quotation, here they are the
   page — and the cities the roster has actually reached.
   ===================================================================== */
.duo{display:grid;gap:clamp(1.8rem,5vw,3.2rem);align-items:center}
@media (min-width:960px){
  .duo{grid-template-columns:5fr 7fr}
  .duo.wide{grid-template-columns:7fr 5fr}
}

/* The portrait is a cut-out with a real alpha channel, so it wants no frame
   and no crop: he stands on the page itself. The hairline under him is the
   same mint baseline the scale figure stands on, at the same weight. */
.portrait{position:relative;max-width:420px;margin-inline:auto}
.portrait img{width:100%;height:auto}
.portrait::after{content:"";position:absolute;left:0;right:0;bottom:0;height:1px;background:var(--mint);opacity:.5}

/* Every other photograph on this site is a photograph of a wall, so it gets
   the hairline and the near-square corner the cards already use. */
.figframe{margin:0;border:1px solid var(--line);border-radius:var(--radius);overflow:hidden;background:var(--ink-3)}
.figframe img{width:100%;height:auto}
.figcap{padding:.7rem .9rem .8rem;font-size:.8rem;letter-spacing:.06em;color:var(--dim);background:var(--ink-2);border-top:1px solid var(--line)}

/* His sentences, set as his sentences: a mint rule down the side, larger
   than body copy, and the attribution small underneath. */
.pull{margin:1.4rem 0 0;border-left:2px solid var(--mint);padding-left:clamp(1rem,3vw,1.6rem)}
.pull p{font-size:1.08rem;line-height:1.6;color:var(--paper);opacity:.92;max-width:56ch}
.pull cite{display:block;margin-top:.9rem;font-style:normal;font-size:.74rem;letter-spacing:.18em;text-transform:uppercase;color:var(--dim)}
@media (min-width:640px){.pull p{font-size:1.15rem}}

/* --- the three stages, at length -------------------------------------- */
/* Home runs them as three columns. About gives each one a row of its own,
   the stage number in mint where the short mint rule sits on Home, and the
   quotation at a reading measure beside it. */
.beats-full{counter-reset:beat}
@media (min-width:960px){.beats-full{grid-template-columns:1fr;gap:3rem}}
.beats-full .beat{counter-increment:beat;padding-top:1.8rem}
@media (min-width:860px){
  .beats-full .beat{display:grid;grid-template-columns:minmax(0,16rem) minmax(0,1fr);gap:2.6rem;align-items:start}
}
.beats-full .beat h3{font-size:clamp(1.3rem,3vw,1.9rem);margin-bottom:0}
.beats-full .beat h3::before{
  content:"0" counter(beat);display:block;width:auto;height:auto;background:none;
  font-family:var(--font-display);font-variation-settings:'wdth' 125,'wght' 800;
  font-size:1.5rem;letter-spacing:-.02em;color:var(--mint);margin-bottom:.45rem;
}
.beats-full .beat blockquote p{max-width:62ch;font-size:1.02rem;margin-top:.9rem}
@media (min-width:860px){.beats-full .beat blockquote p{margin-top:0}}

/* --- where the walls are ---------------------------------------------- */
.cities{display:grid;grid-template-columns:repeat(2,1fr);gap:1.4rem 1.2rem}
@media (min-width:640px){.cities{grid-template-columns:repeat(4,1fr)}}
.city{border-top:1px solid var(--line-2);padding-top:.9rem}
.city b{display:block;font-family:var(--font-display);text-transform:uppercase;font-variation-settings:'wdth' 100,'wght' 800;font-size:1.05rem;letter-spacing:.01em;font-weight:400}
.city span{display:block;margin-top:.3rem;font-size:.74rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted)}

/* =====================================================================
   SERVICES

   Four blocks: the walls, the signs, the paint, and the cleaning side of
   the business that has a page of its own. Nothing new is invented here —
   the quotation, the stat row and the two-column split are the components
   Home and About already use — except the roster of sign jobs, which is a
   list of names because not one photograph of them has been fetched.
   ===================================================================== */
/* The stat row sits in half a page here rather than the full width, so it
   stays two across at every size instead of opening out to four. */
@media (min-width:960px){.stats-2{grid-template-columns:repeat(2,1fr)}}

.roster{display:grid;grid-template-columns:repeat(2,1fr);gap:.8rem}
@media (min-width:640px){.roster{grid-template-columns:repeat(3,1fr)}}
@media (min-width:960px){.roster{grid-template-columns:repeat(5,1fr)}}
.roster li{
  display:flex;align-items:center;min-height:5.2rem;
  border:1px solid var(--line);border-radius:var(--radius);background:var(--ink-3);
  padding:1rem;font-family:var(--font-display);text-transform:uppercase;
  font-variation-settings:'wdth' 90,'wght' 800;font-size:1rem;line-height:1.1;
}

.tags{display:flex;flex-wrap:wrap;gap:.5rem;margin-top:.4rem}
.tags li{border:1px solid var(--line-2);border-radius:999px;padding:.45rem .95rem;font-size:.86rem;font-weight:600;color:var(--muted)}

/* A label over a quotation, in the display face so it reads as a stage name
   rather than as a sentence. */
.svc-h{
  font-family:var(--font-display);text-transform:uppercase;font-weight:400;
  font-variation-settings:'wdth' 100,'wght' 800;
  font-size:1.05rem;letter-spacing:.02em;margin-top:2.2rem;
}

/* =====================================================================
   CONTACT

   The form on the left, four cards on the right, and every one of them is
   real without JavaScript: the fields are server-rendered, the two link
   cards are ordinary links, and the note under the button carries the
   address to write to when nothing else works.
   ===================================================================== */
.contact-grid{display:grid;gap:clamp(2rem,5vw,3.2rem);align-items:start}
@media (min-width:960px){.contact-grid{grid-template-columns:7fr 5fr}}

.form{display:grid;gap:1.1rem;margin-top:1.8rem}
.form .row{display:grid;grid-template-columns:1fr;gap:1.1rem}
@media (min-width:560px){.form .row{grid-template-columns:1fr 1fr}}
.field{display:grid;gap:.45rem}
.field label{font-size:.72rem;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:var(--muted)}
.field input,.field textarea,.field select{
  width:100%;background:var(--ink);border:1px solid var(--line-2);color:var(--paper);
  padding:.85rem 1rem;border-radius:var(--radius);font:inherit;font-size:1rem;
  transition:border-color .2s,box-shadow .2s;
}
.field input::placeholder,.field textarea::placeholder{color:var(--dim)}
.field input:focus,.field textarea:focus,.field select:focus{outline:none;border-color:var(--mint);box-shadow:0 0 0 3px var(--mint-glow)}
.field textarea{min-height:150px;resize:vertical}
.field option{background:var(--ink);color:var(--paper)}
.field .hint{font-size:.8rem;line-height:1.5;color:var(--dim)}
/* The chevron is drawn with a border rather than an inline SVG: a background
   image would have to name a colour in hex, and the only hexes on this site
   live in :root. */
.sel{position:relative;display:block}
.sel select{appearance:none;-webkit-appearance:none;padding-right:2.6rem}
.sel::after{
  content:"";position:absolute;right:1.1rem;top:50%;width:.46rem;height:.46rem;
  border-right:2px solid var(--muted);border-bottom:2px solid var(--muted);
  transform:translateY(-70%) rotate(45deg);pointer-events:none;
}
/* The honeypot: off the screen, out of the tab order, never shown to anybody
   who is not a robot. display:none would keep some bots from filling it. */
.gotcha{position:absolute;left:-9999px;top:auto;width:1px;height:1px;overflow:hidden}
.form-note{margin:0;font-size:.86rem;line-height:1.6;color:var(--dim)}
.form-note a{color:var(--mint)}
.form-status{display:none;padding:1rem 1.2rem;border:1px solid var(--line-2);border-radius:var(--radius);background:var(--ink-3)}
.form-status.on{display:block}
.form-status a{color:var(--mint)}

.contact-side{display:grid;gap:1rem}
.contact-card{display:flex;gap:1rem;align-items:flex-start;padding:1.4rem;background:var(--ink-2);border:1px solid var(--line);border-radius:var(--radius);transition:border-color .2s}
a.contact-card:hover,a.contact-card:focus-visible{border-color:var(--mint)}
.contact-card .ic{flex:none;display:grid;place-items:center;width:42px;height:42px;border:1px solid var(--line-2);border-radius:var(--radius);background:var(--ink-3);color:var(--mint)}
.contact-card .ic svg{width:20px;height:20px}
.contact-card b{display:block;margin-bottom:.25rem;font-size:.95rem}
.contact-card span{display:block;color:var(--muted);font-size:.9rem;line-height:1.6;word-break:break-word}

/* =====================================================================
   GRAFFITI REMOVAL

   The other half of the business. The mechanic from wall.py is the hero —
   the words sit beside it rather than over a photograph — and the rest of
   the page is the components Home and Services already use, with one new
   one: the before/after.
   ===================================================================== */
/* No photograph behind these words, so no shade and no text-shadow: the
   hero is the dark canvas with the wall standing on it. */
.gr-hero{padding-bottom:clamp(3rem,7vw,5rem)}
.gr-hero-grid{display:grid;gap:clamp(2rem,5vw,3.2rem);align-items:center}
.gr-hero .hero-inner{max-width:38ch}
.gr-hero .lead{color:var(--paper);opacity:.86}
.gr-hero .btn-row{margin-top:1.8rem}
/* The wall is the widest thing in the hero, and on a phone it comes after
   the sentence that explains why it is there. */
.gr-hero-wall{min-width:0}
@media (min-width:960px){
  .gr-hero-grid{grid-template-columns:minmax(0,5fr) minmax(0,7fr)}
}

/* The four beats of a job want four columns where the three stages take
   three, and two before that rather than one. */
@media (min-width:640px){.beats-4{grid-template-columns:repeat(2,1fr)}}
@media (min-width:960px){.beats-4{grid-template-columns:repeat(4,1fr);gap:1.6rem}}

/* --- before / after ---------------------------------------------------- */
/* Two pictures, side by side and labelled. That is the whole component with
   no script, and it is honest: both walls are real content in the server
   HTML and both are readable at once.
   With a script the pair stacks into one frame under a mint divider — the
   same two layers, one on top of the other. Everything that does the
   stacking is scoped to html.motion, so the reduced-motion render and the
   no-script render are the same page, and the drag is never the only way to
   see either state. */
.ba{margin:0}
.ba-stage{display:grid;gap:1rem;grid-template-columns:1fr}
@media (min-width:640px){.ba-stage{grid-template-columns:1fr 1fr}}
/* The after layer is first in the DOM because the before has to stack on top
   of it under html.motion. Side by side, the pair reads the other way round —
   before, then after — so the grid puts it back. */
.ba-before{order:-1}
.ba-layer{
  position:relative;overflow:hidden;line-height:0;
  border:1px solid var(--line);border-radius:var(--radius);background:var(--ink-3);
}
.ba-layer svg{display:block;width:100%;height:auto;aspect-ratio:16/9}
.ba-lab{
  position:absolute;left:.7rem;bottom:.7rem;z-index:2;
  padding:.3rem .7rem;border-radius:999px;
  background:var(--scrim);border:1px solid var(--line-2);
  font-family:var(--font-body);font-size:.7rem;font-weight:700;line-height:1.4;
  letter-spacing:.18em;text-transform:uppercase;color:var(--paper);
}
.ba-handle{display:none}
.ba-cap{margin-top:.85rem;font-size:.86rem;line-height:1.5;color:var(--muted)}
.ba-hint{display:none;color:var(--paper)}

html.motion .ba-stage{
  display:block;position:relative;aspect-ratio:16/9;overflow:hidden;
  border:1px solid var(--line);border-radius:var(--radius);background:var(--ink-3);
  touch-action:pan-y;cursor:ew-resize;
}
html.motion .ba-layer{position:absolute;inset:0;border:0;border-radius:0;background:none}
html.motion .ba-layer svg{width:100%;height:100%;aspect-ratio:auto}
/* The before is clipped, not moved: a clip is paint-only, so the divider can
   sit anywhere without either picture ever changing size. --bx is written by
   site.js in one rAF and starts, in the stylesheet, at half way. */
html.motion .ba-before{clip-path:inset(0 calc(100% - var(--bx,50%)) 0 0)}
html.motion .ba-after .ba-lab{left:auto;right:.7rem}
html.motion .ba-hint{display:inline}
html.motion .ba-handle{
  display:block;position:absolute;top:0;bottom:0;z-index:3;
  left:var(--bx,50%);translate:-50% 0;width:44px;padding:0;
  background:none;border:0;cursor:ew-resize;touch-action:none;
}
/* The mint line itself, drawn inside the button so the grab area is a finger
   wide and the divider is one pixel. */
html.motion .ba-handle::before{
  content:"";position:absolute;top:0;bottom:0;left:calc(50% - 1px);width:2px;
  background:var(--mint);box-shadow:0 0 0 1px var(--shade);
}
html.motion .ba-handle:focus-visible{outline:2px solid var(--mint);outline-offset:2px;border-radius:var(--radius)}
.ba-grip{
  position:absolute;top:50%;left:50%;translate:-50% -50%;
  display:flex;align-items:center;justify-content:center;
  width:44px;height:44px;border-radius:50%;
  background:var(--mint);color:var(--ink);
}
.ba-grip svg{width:15px;height:15px;margin:0 -3px}
@media (prefers-reduced-motion:no-preference){
  html.motion .ba-handle:hover .ba-grip,html.motion .ba-stage.is-live .ba-grip{transform:scale(1.06)}
  .ba-grip{transition:transform .2s var(--ease)}
}

/* =====================================================================
   THE GRAFFITI-REMOVAL BAND AND STRIP

   The other half of the business, advertised on the pages of the first —
   the shape DroneGodMax uses for Green Flash (gf_band()/gf_strip()), with
   this site's content and this site's palette rule.

   The band is the ONE light block on a dark site: a scrubbed --wall panel
   with near-black type and a diagonal ghost of overpaint fading out of the
   top-left corner. That is the pitch in one image, and it is why it may
   only happen once. Mint never sits on --wall (1.16:1), so on this panel
   the accent appears the only way it is allowed to — ink on mint, in the
   eyebrow pill and in the primary button, both 13.8:1.
   ===================================================================== */
.gr-band-wrap{padding-top:0}
.gr-band{
  position:relative;isolation:isolate;overflow:hidden;
  border-radius:var(--radius);
  background:var(--wall);color:var(--ink);
  padding:clamp(1.8rem,5vw,3.4rem);
  display:grid;gap:clamp(1.8rem,4vw,3rem);align-items:center;
}
/* The ghost of the last buff job: whoever painted this wall out did not
   match the colour, and the patch is still there in a raking light. */
.gr-band::before{
  content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:linear-gradient(115deg,var(--ghost),transparent 42%);
}
@media (min-width:960px){
  .gr-band{grid-template-columns:minmax(0,1fr) minmax(0,1.05fr)}
  /* Words first in the document, wall first on the page. */
  .gr-band-wall{order:-1}
}
.gr-band h2{margin-bottom:1rem}
.gr-band .lead{color:var(--ink-mute);opacity:1;max-width:56ch}
.gr-band .btn-row{margin-top:1.8rem}
/* The eyebrow is mint everywhere else on the site; here it is the mint. */
.gr-band .eyebrow{
  color:var(--ink);background:var(--mint);
  padding:.4rem .8rem;border-radius:999px;margin-bottom:1.1rem;
}
.gr-band .tags{margin-top:1.4rem}
.gr-band .tags li{border-color:var(--ink-line);color:var(--ink-mute)}
.gr-band .btn-ghost{border-color:var(--ink-line);color:var(--ink)}
.gr-band .btn-ghost:hover{border-color:var(--ink);color:var(--ink)}
/* The wall figure comes from wash.css, which is written for the dark canvas.
   Three colours have to come back the other way up on this panel; nothing
   else in that file names a colour that the light breaks. */
.gr-band .wall-stage,.gr-band .wall-thumb{border-color:var(--ink-line)}
.gr-band .wall-cap{color:var(--ink-mute)}

/* --- the slim one-liner ------------------------------------------------ */
/* Work, Services and About get the offer in one line, on the dark canvas
   they already are: the light panel is Home's, once. */
.gr-strip-wrap{padding-top:0}
.gr-strip{
  display:grid;gap:1.2rem;align-items:center;
  padding:1.3rem clamp(1.2rem,3vw,1.8rem);
  border:1px solid var(--line-2);border-radius:var(--radius);
  background:linear-gradient(90deg,var(--mint-glow),transparent 55%),var(--ink-3);
}
.gr-strip p{margin:0;color:var(--muted);font-size:.98rem;line-height:1.6}
.gr-strip p b{color:var(--paper)}
.gr-strip-links{display:flex;gap:.5rem;flex-wrap:wrap}
@media (min-width:860px){.gr-strip{grid-template-columns:1fr auto;gap:1.8rem}}

/* =====================================================================
   SCALE — the six-foot figure, dragged

   The static scale bar is the component: she is server-rendered at
   --fx:.12, sized (6 / ft) of the picture, with a real "6 ft" caption, and
   that is a complete and honest answer to "how big is this wall" with no
   script at all. Everything below only lets a visitor walk her along it.

   So it is all scoped to html.motion, which the bootstrap withholds when
   the visitor has asked for reduced motion — the reduced-motion render is
   the static one, and site.js binds no listeners there either. No counter,
   no percentage, no readout: the answer is how small she looks.
   ===================================================================== */
/* pan-y, not none: a finger must still be able to scroll the page past a
   wall that fills it. A horizontal drag reaches the handler regardless. */
html.motion .scale{touch-action:pan-y}
html.motion .fig{cursor:grab;touch-action:none}
html.motion .scale.is-live .fig{cursor:grabbing}
/* Keyboard steps and the tilt settle; a drag does not, because a figure that
   lags the finger holding it does not feel like a figure. */
html.motion .fig{transition:left .18s var(--ease)}
html.motion .scale.is-live .fig,html.motion .scale.is-tilting .fig{transition:none}
html.motion .fig:focus-visible{outline:2px solid var(--mint);outline-offset:3px;border-radius:2px}

/* The hint is an instruction for something only the script can do, so it is
   not in the document until the script could do it — and it goes as soon as
   the visitor has done it once. */
.scale-hint{
  display:none;position:absolute;right:1rem;bottom:calc(var(--base,7%) + .6rem);
  font-size:.66rem;letter-spacing:.14em;text-transform:uppercase;color:var(--mint);
  text-shadow:0 1px 8px var(--ink);pointer-events:none;
}
html.motion .scale-hint{display:block}
html.motion .scale.is-used .scale-hint{opacity:0}
@media (prefers-reduced-motion:no-preference){
  html.motion .scale-hint{transition:opacity .4s var(--ease)}
}
@media (prefers-reduced-motion:reduce){
  html.motion .fig{transition:none}
  html.motion .scale-hint{display:none}
}
