/* ============================================================================
   build.css — "your Hut, assembling as you browse."

   THE LEAK THIS CLOSES. Measured before building it: the fit finder works out
   which blocks fit you and its only button was a bare <a href="signup.html">.
   The pricing calculator computes a real monthly figure into #resultPrice and
   its only button was the same bare link. signup.html read NOTHING — no
   URLSearchParams, no location.search anywhere in it. And across 28 package
   pages the only things the site persisted were tf_auth_error and tf_next.
   So the site worked out the right answer three separate times and threw it
   away each time, and every visitor arrived at signup as a stranger starting
   from zero.

   One mechanism fixes all of it: a selection that follows you, priced live,
   that signup receives. It also gives the forge's color ramp something true to
   encode — commitment instead of scroll depth, which was arbitrary.

   The bar is deliberately NOT a cookie banner: it sits on the plate like a
   tool tray, it only appears once you have chosen something, and it never
   covers the primary CTA on a phone (the page gets bottom padding instead).
   ========================================================================== */

:root{
  --tray-h:0px;              /* page padding so the tray never covers content */
  --commit:0;                /* 0..1 — how far through choosing you are */
}

/* --------------------------------------------------------------- the tray */

.tfb{
  position:fixed; left:0; right:0; bottom:0; z-index:60;
  pointer-events:none;
  transform:translateY(110%);
  transition:transform 520ms cubic-bezier(0.16,1,0.3,1);
}
.tfb.up{ transform:translateY(0); }
.tfb-inner{
  pointer-events:auto;
  max-width:1180px; margin:0 auto 14px; padding:0 18px;
}
.tfb-card{
  background:rgba(16,18,22,0.92);
  -webkit-backdrop-filter:blur(16px) saturate(130%);
          backdrop-filter:blur(16px) saturate(130%);
  border:1px solid var(--line-strong);
  border-radius:16px;
  box-shadow:0 20px 60px rgba(0,0,0,0.55), 0 0 0 1px rgba(255,255,255,0.03) inset;
  overflow:hidden;
}
/* a lit edge along the top, brightening as the build grows */
.tfb-card::before{
  content:""; display:block; height:2px;
  background:linear-gradient(90deg,
    var(--path-1) 0%, var(--path-2) 35%, var(--path-3) 70%, var(--path-4) 100%);
  transform:scaleX(var(--commit,0)); transform-origin:left;
  transition:transform 620ms cubic-bezier(0.16,1,0.3,1);
}

/* ------------------------------------------------------------- the summary */

.tfb-row{
  display:flex; align-items:center; gap:18px;
  padding:13px 16px 13px 18px;
}
/* The count is what the row is ABOUT and it was 11.2px tracked to 0.12em —
   the smallest text in the bar. Same shape as the 2026-10-06 finding that the
   font "was never a grey problem, it was tiny and tracked out". */
.tfb-count{
  font-family:var(--font-mono); font-size:0.82rem; letter-spacing:0.06em;
  text-transform:uppercase; color:var(--ink-faint); white-space:nowrap;
}
.tfb-count b{ color:var(--ink); font-weight:600; }
.tfb-price{
  font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-size:1.45rem; color:var(--path-4); letter-spacing:-0.01em;
  white-space:nowrap; margin-left:auto;
}
.tfb-price span{ font-size:0.78rem; color:var(--ink-faint); letter-spacing:0.06em; }
/* The one-time fee belongs BESIDE the monthly. It was only in the prose at the
   bottom of the drawer, so a visitor could read a total on a page promising
   "no hidden costs" and never meet the $149. */
.tfb-price em{
  display:block; font-style:normal; font-size:0.66rem; letter-spacing:0.06em;
  color:var(--ink-faint); margin-top:1px;
}
.tfb-go{
  flex:none; white-space:nowrap;
  padding:11px 22px; border-radius:10px; border:none; cursor:pointer;
  font-family:var(--font-body); font-size:0.95rem; font-weight:600;
  color:#17140a;
  background:linear-gradient(160deg, var(--path-4), var(--path-3));
  box-shadow:0 6px 20px rgba(244,197,33,0.22);
  transition:transform 160ms ease, box-shadow 160ms ease;
}
.tfb-go:hover{ transform:translateY(-1px); box-shadow:0 10px 26px rgba(244,197,33,0.30); }
.tfb-toggle{
  flex:none; appearance:none; background:transparent; cursor:pointer;
  border:1px solid var(--line-strong); border-radius:9px;
  color:var(--ink-dim); padding:9px 13px;
  font-family:var(--font-mono); font-size:0.66rem; letter-spacing:0.1em; text-transform:uppercase;
  transition:border-color 160ms ease, color 160ms ease;
}
.tfb-toggle:hover{ border-color:var(--path-3); color:var(--ember-hot); }

/* ------------------------------------------------------------ the drawer */

.tfb-drawer{
  display:none; border-top:1px solid var(--line);
  padding:14px 18px 16px;
}
.tfb.open .tfb-drawer{ display:block; }

.tfb-list{ display:flex; flex-wrap:wrap; gap:8px; margin-bottom:14px; }
.tfb-chip{
  display:inline-flex; align-items:center; gap:9px;
  padding:7px 9px 7px 12px; border-radius:999px;
  background:rgba(255,255,255,0.045); border:1px solid var(--line);
  font-size:0.84rem; color:var(--ink);
}
.tfb-chip .p{ font-family:var(--font-mono); font-size:0.72rem; color:var(--ink-faint); }
.tfb-chip button{
  appearance:none; border:none; cursor:pointer; background:transparent;
  color:var(--ink-faint); font-size:1rem; line-height:1; border-radius:50%;
  /* was 17x20 — under the 24px target minimum, and it is the control that
     deletes something */
  width:28px; height:28px; display:grid; place-items:center; padding:0;
  margin:-4px -4px -4px 0;
}
.tfb-chip button:focus-visible{ outline:2px solid var(--ember); outline-offset:1px; }
.tfb-chip button:hover{ color:#ff8a8a; }

.tfb-tier{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-bottom:12px; }
.tfb-tier .lab{
  font-family:var(--font-mono); font-size:0.64rem; letter-spacing:0.12em;
  text-transform:uppercase; color:var(--ink-faint);
}
.tfb-tier button{
  appearance:none; cursor:pointer; padding:6px 13px; border-radius:999px;
  background:rgba(255,255,255,0.04); border:1px solid var(--line);
  color:var(--ink-dim); font-family:var(--font-body); font-size:0.82rem;
  transition:border-color 160ms ease, color 160ms ease, background 160ms ease;
}
.tfb-tier button:hover{ border-color:rgba(244,197,33,0.5); color:var(--ember-hot); }
.tfb-tier button.on{
  border-color:var(--gold); color:var(--ember-hot); background:rgba(244,197,33,0.12);
}

/* ------------------------------------------------------- the arithmetic

   The page's headline is "See your exact price before you pay a dollar" and
   the tray showed a total with no working, while silently applying a 10%
   discount nobody was told about. */
.tfb-math{
  margin:0 0 12px; border-top:1px solid var(--line); padding-top:11px;
  font-family:var(--font-mono); font-size:0.78rem; font-variant-numeric:tabular-nums;
}
.tfb-math div{
  display:flex; justify-content:space-between; gap:18px; padding:3px 0;
  color:var(--ink-dim);
}
.tfb-math div.off em{ color:var(--path-4); }
.tfb-math div.tot{
  margin-top:7px; padding-top:9px; border-top:1px solid var(--line);
  color:var(--ink); font-size:0.88rem;
}
.tfb-math div.tot em{ color:var(--path-4); }
.tfb-math em{ font-style:normal; }

.tfb-note{
  font-size:0.8rem; color:var(--ink-faint); line-height:1.5;
  display:grid; gap:3px;   /* three separate reassurances, three lines */
}
.tfb-note .warnline{ color:var(--ember-hot); }

/* --------------------------------------------------- the prerequisite guard

   app.js documents a real bug this prevents: 156 selections priced a basket
   the server would refuse, so a visitor could build a cart, read a price,
   click buy and hit an error. TF.missingPrerequisites() is the same function
   the pricing page uses; the tray refuses to show a Continue that cannot
   complete, and offers the missing block instead of just complaining. */
.tfb-warn{
  display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  margin:0 0 12px; padding:10px 13px; border-radius:11px;
  background:rgba(255,183,94,0.09); border:1px solid rgba(255,183,94,0.32);
  font-size:0.85rem; color:#ffd7a8;
}
.tfb-warn button{
  appearance:none; cursor:pointer; padding:5px 12px; border-radius:999px;
  background:rgba(255,183,94,0.16); border:1px solid rgba(255,183,94,0.45);
  color:#ffd7a8; font-family:var(--font-body); font-size:0.8rem;
}
.tfb-warn button:hover{ background:rgba(255,183,94,0.26); }
.tfb-go[disabled]{ opacity:0.45; cursor:not-allowed; box-shadow:none; transform:none; }

/* ------------------------------------------------------------ add buttons */

.tfb-add{
  appearance:none; cursor:pointer;
  display:inline-flex; align-items:center; gap:8px;
  padding:11px 20px; border-radius:10px;
  background:rgba(244,197,33,0.10); border:1px solid rgba(244,197,33,0.38);
  color:var(--ember-hot); font-family:var(--font-body); font-size:0.92rem; font-weight:600;
  transition:background 160ms ease, border-color 160ms ease, color 160ms ease;
}
.tfb-add:hover{ background:rgba(244,197,33,0.18); border-color:var(--gold); }
.tfb-add::before{ content:"+"; font-size:1.05rem; line-height:1; }
.tfb-add.in{
  background:rgba(120,220,150,0.10); border-color:rgba(120,220,150,0.42); color:#8fe3ab;
}
.tfb-add.in::before{ content:"✓"; }

/* ------------------------------------------------------------- responsive */

@media (max-width:700px){
  .tfb-inner{ padding:0 10px; margin-bottom:10px; }
  .tfb-row{ flex-wrap:wrap; gap:10px; padding:12px 13px; }
  .tfb-count{ order:1; }
  .tfb-price{ order:2; font-size:1.3rem; }
  .tfb-toggle{ order:3; flex:1; }
  .tfb-go{ order:4; flex:2; text-align:center; }
}

@media (prefers-reduced-motion:reduce){
  .tfb{ transition:none; }
  .tfb-card::before{ transition:none; }
  .tfb-go{ transition:none; }
}

/* ------------------------------------------------- the carried-over basket

   Shown on signup, where the floating tray is switched off. Same numbers, same
   TF.estimatePrice call -- this is a different presentation of one state, not
   a second basket. */
.tfb-carry{
  margin:0 0 26px; padding:16px 18px; border-radius:14px;
  background:rgba(244,197,33,0.055);
  border:1px solid rgba(244,197,33,0.26);
}
.tfb-carry h2{
  font-family:var(--font-mono); font-size:0.66rem; letter-spacing:0.13em;
  text-transform:uppercase; color:var(--ember-hot);
  margin:0 0 12px; font-weight:600;
}
.tfb-carry ul{ list-style:none; margin:0 0 12px; padding:0; }
.tfb-carry li{
  display:flex; justify-content:space-between; gap:14px;
  padding:6px 0; font-size:0.9rem; color:var(--ink);
  border-bottom:1px solid rgba(255,255,255,0.05);
}
.tfb-carry li b{ font-family:var(--font-mono); font-variant-numeric:tabular-nums; color:var(--ink-dim); font-weight:400; }
.tfb-carry .tot{
  display:flex; justify-content:space-between; align-items:baseline; gap:14px;
  padding-top:10px;
}
.tfb-carry .tot span{ font-size:0.86rem; color:var(--ink-dim); }
.tfb-carry .tot b{
  font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-size:1.4rem; color:var(--path-4);
}
.tfb-carry p{ margin:10px 0 0; font-size:0.78rem; color:var(--ink-faint); line-height:1.5; }

/* ------------------------------------------------------------- the shop

   The homepage catalog was six hand-written cards with no block keys: a
   hand-maintained subset of 28 that nothing could be added FROM, so browsing
   could not feed the build. It is now rendered from TF.BLOCKS and grouped by
   the catalog's own categories, which are already written in the buyer's
   language -- "Never miss work", "Get paid", "Grow" -- so the section reads as
   outcomes to shop rather than features to study. The hand-written cards stay
   in the markup as the no-JS fallback. */
.shop-cat{ margin-bottom:clamp(30px,4vw,46px); }
.shop-cat:last-child{ margin-bottom:0; }
/* The label was 0.7rem mono, the same weight as everything else on the page,
   so five sections read as one undifferentiated list. The category name is now
   the heading it always was, with the two facts that decide whether to open it
   sitting beside it. */
.shop-cat-h{
  display:flex; align-items:baseline; gap:14px; flex-wrap:wrap;
  margin:0 0 18px; padding-bottom:12px;
  border-bottom:1px solid var(--line);
}
.shop-cat-t{
  font-family:var(--font-display,var(--font-body));
  font-size:clamp(1.15rem,2vw,1.5rem); font-weight:700;
  letter-spacing:-0.01em; color:var(--ink);
}
/* ---------------------------------------------- category photo band
   The heading sits ON the photograph rather than beside it, so the band reads as
   one object. Short (clamped by aspect-ratio, not a magic pixel height) and
   scrimmed left-to-right so the title keeps its contrast over any image. */
.shop-cat-h.has-shot{
  position:relative; isolation:isolate; overflow:hidden;
  border-radius:12px; padding:16px 18px; min-height:104px;
  align-items:flex-end; border:1px solid var(--line);
}
.shop-cat-h .shop-cat-img{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; z-index:-2; filter:saturate(0.85) contrast(1.05);
}
.shop-cat-h.has-shot::after{
  content:''; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(90deg, rgba(10,11,13,0.92) 0%, rgba(10,11,13,0.74) 45%, rgba(10,11,13,0.35) 100%);
}
.shop-cat-h.has-shot .shop-cat-t{ position:relative; }
@media (prefers-reduced-motion:no-preference){
  .shop-cat-h .shop-cat-img{ transition:transform 600ms cubic-bezier(.2,.7,.3,1); }
  .shop-cat-h.has-shot:hover .shop-cat-img{ transform:scale(1.03); }
}

.shop-cat-h em{
  font-style:normal; margin-left:auto; white-space:nowrap;
  font-family:var(--font-mono); font-size:0.68rem; letter-spacing:0.1em;
  text-transform:uppercase; color:var(--ink-faint);
}

.shop-grid{
  /* auto-FIT, not auto-fill: four categories have an odd card count, so the last
     row held one card with an empty half beside it. auto-fit collapses the empty
     track and the lone card fills the row, which reads as deliberate. */
  display:grid; grid-template-columns:repeat(auto-fit, minmax(280px,1fr));
  gap:14px;
  /* Without this, expanding one card's showcase stretched every sibling in the
     row to match it, leaving tall empty boxes beside the open one. */
  align-items:start;
}
.shop-card{
  display:flex; flex-direction:column; gap:10px;
  padding:20px 20px 18px; border-radius:14px;
  background:var(--carbon-800); border:1px solid var(--line);
  transition:border-color 200ms ease, transform 200ms ease;
}
.shop-card:hover{ border-color:var(--line-strong); transform:translateY(-2px); }
.shop-card.on{ border-color:rgba(244,197,33,0.45); }
.shop-card h4{ margin:0; font-size:1.02rem; color:var(--ink); line-height:1.3; }
.shop-card p{
  margin:0; font-size:0.87rem; color:var(--ink-dim); line-height:1.55; flex:1;
  /* THREE LINES MINIMUM, so every COLLAPSED card in a row is the same height and
     the price and Add button land on the same baseline across the row. Measured:
     descriptions ran 2, 3 or 4 lines (43 / 65 / 86px at a 21.58px line-height),
     so rows were ragged by exactly one line and the two four-line outliers were
     trimmed to three in index.html.
     THIS IS DELIBERATELY NOT align-items:stretch ON .shop-grid — the comment
     above that rule records why it is start: stretching made every sibling grow
     when one card's showcase expanded, leaving tall empty boxes beside the open
     one. A min-height on the copy fixes the collapsed state without reviving that. */
  min-height:calc(3 * 1.55 * 0.87rem);
  /* AND A CEILING, because a floor alone is not enough: wrapping is width-dependent,
     so a description that is three lines at 660px is four at 560px and the row goes
     ragged again at that breakpoint. Clamping to three makes the collapsed height
     identical at EVERY width. It rarely bites — three lines is ~126 characters at
     this column width and the longest description is shorter than that — so it acts
     as a guard, not as visible truncation. */
  display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:3; overflow:hidden;
}
/* TWO ROWS, not one. Three controls — See it run, the price, Add to my Hut —
   in a ~240px card left the button wrapping mid-phrase. The price gets its own
   line as the card's anchor; the two actions share the row beneath it. */
.shop-foot{
  display:grid; grid-template-columns:1fr auto; gap:10px 12px;
  align-items:center; margin-top:6px; padding-top:12px;
  border-top:1px solid var(--line);
}
.shop-foot .shop-price{ grid-column:1; grid-row:1; }
.shop-foot .sc-toggle{ grid-column:2; grid-row:1; }
.shop-foot .tfb-add{ grid-column:1 / -1; grid-row:2; justify-content:center; }
.shop-price{
  font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-size:1.25rem; color:var(--path-4); white-space:nowrap; letter-spacing:-0.01em;
}
.shop-price span{ font-size:0.72rem; color:var(--ink-faint); }
.shop-card .tfb-add{ padding:9px 14px; font-size:0.86rem; }

/* ONE PRIMARY ACTION PER CARD. "See it run" was a bordered button sitting beside
   the price, competing with "Add to my Hut" directly beneath it — two buttons of
   similar weight, and nothing told the eye which one the card wanted. It is a
   secondary, reversible action, so it now reads as a quiet mono link and the
   filled Add button is the only button on the card. */
.shop-card .sc-toggle{
  background:none; border:none; padding:0;
  font-family:var(--font-mono); font-size:0.66rem; letter-spacing:0.12em;
  text-transform:uppercase; color:var(--ink-faint);
  cursor:pointer; transition:color 160ms ease;
}
.shop-card .sc-toggle:hover,
.shop-card .sc-toggle:focus-visible{ color:var(--ember); }

@media (max-width:560px){
  .shop-grid{ grid-template-columns:1fr; }
}

/* ------------------------------------------------------- toast vs. tray

   theme.css puts the toast at bottom:26px / z-index:60 — exactly where the
   tray now lives, so "Review Requests added to your Hut" landed on top of the
   price it was announcing. --tray-h is 0 whenever the tray is down, so this
   lifts the toast only when there is something to clear and leaves its
   original position untouched otherwise. */
.toast{
  bottom:calc(26px + var(--tray-h, 0px));
  z-index:70;
}

/* ========================================================= the showcase

   "See it run" on each catalog card. Everything it shows is derived from the
   LIVE n8n recipe by scratchpad/make_showcase.mjs: the schedule is the real
   cron, the steps are the recipe's own data nodes, and the message is the
   recipe's own wording with its JavaScript expressions replaced by plainly
   sample values. Nothing here is marketing copy written to look like a
   product -- that was the whole point of deriving it instead of writing it. */

.sc-toggle{
  appearance:none; cursor:pointer; background:transparent;
  border:1px solid var(--line); border-radius:9px;
  color:var(--ink-faint); padding:8px 12px;
  font-family:var(--font-mono); font-size:0.62rem; letter-spacing:0.1em; text-transform:uppercase;
  transition:border-color 160ms ease, color 160ms ease;
}
.sc-toggle:hover{ border-color:var(--path-3); color:var(--ember-hot); }

.sc-panel{
  display:none; margin-top:14px; padding-top:14px;
  border-top:1px solid var(--line);
  /* The package pages centre their body text, which the showcase inherited --
     steps read centred while their bullets stayed hard left, and the message
     bubble centred too. The panel sets its own alignment rather than relying
     on wherever it is dropped. */
  text-align:left;
}
.shop-card.sc-open .sc-panel{ display:block; }

.sc-fires{
  display:flex; align-items:center; gap:8px; margin-bottom:12px;
  font-family:var(--font-mono); font-size:0.64rem; letter-spacing:0.09em;
  text-transform:uppercase; color:var(--path-4);
}
.sc-fires::before{
  content:""; width:6px; height:6px; border-radius:50%;
  background:var(--path-4); box-shadow:0 0 9px var(--path-glow);
  animation:sc-pulse 2.4s ease-in-out infinite;
}
@keyframes sc-pulse{ 0%,100%{opacity:1} 50%{opacity:0.35} }

.sc-steps{ list-style:none; margin:0 0 14px; padding:0; }
.sc-steps li{
  position:relative; padding:0 0 0 20px; margin-bottom:7px;
  font-size:0.8rem; color:var(--ink-dim); line-height:1.4;
  opacity:0.25; transition:opacity 420ms ease, color 420ms ease;
}
.sc-steps li::before{
  content:""; position:absolute; left:3px; top:6px;
  width:7px; height:7px; border-radius:50%;
  background:var(--steel-700); transition:background 420ms ease, box-shadow 420ms ease;
}
.sc-steps li::after{
  content:""; position:absolute; left:6px; top:15px; bottom:-7px; width:1px;
  background:var(--line);
}
.sc-steps li:last-child::after{ display:none; }
.sc-steps li.lit{ opacity:1; color:var(--ink); }
.sc-steps li.lit::before{ background:var(--path-3); box-shadow:0 0 10px var(--path-glow); }

.sc-msg{
  position:relative;
  padding:12px 14px; border-radius:13px; border-bottom-right-radius:5px;
  background:linear-gradient(160deg, rgba(244,197,33,0.17), rgba(244,197,33,0.09));
  border:1px solid rgba(244,197,33,0.28);
  font-size:0.84rem; line-height:1.5; color:var(--ink);
  white-space:pre-wrap;
  opacity:0; transform:translateY(10px);
  transition:opacity 420ms ease, transform 520ms cubic-bezier(0.16,1,0.3,1);
}
.sc-msg.lit{ opacity:1; transform:none; }
/* Sentence case: at three lines of uppercase mono this caption shouted louder
   than the message it was captioning. */
.sc-cap{
  margin:9px 0 0; font-size:0.72rem; line-height:1.45;
  color:var(--ink-faint); font-style:italic;
}

@media (prefers-reduced-motion:reduce){
  .sc-fires::before{ animation:none; }
  .sc-steps li, .sc-msg{ transition:none; }
}

/* --------------------------------------------------- the package-page bar

   A visitor landing on package-invoices.html from search previously had no way
   to add it -- the same dead end the finder had, left open on 29 pages. The
   bar sits directly under the page's own intro, where it stops introducing
   itself and the reader starts deciding. */
.pkg-bar{
  display:flex; align-items:center; gap:14px; flex-wrap:wrap;
  margin:22px 0 0; padding:16px 18px; border-radius:14px;
  background:rgba(244,197,33,0.055);
  border:1px solid rgba(244,197,33,0.24);
}
.pkg-price{
  font-family:var(--font-mono); font-variant-numeric:tabular-nums;
  font-size:1.6rem; color:var(--path-4); letter-spacing:-0.01em;
}
.pkg-price span{ font-size:0.78rem; color:var(--ink-faint); letter-spacing:0.06em; }
.pkg-bar .tfb-add{ margin-left:auto; }
.pkg-showcase{ margin:0 0 6px; }
.pkg-showcase .sc-panel{
  margin-top:0; padding:18px; border-top:none;
  border:1px solid var(--line); border-radius:14px;
  background:var(--carbon-800);
}
.pkg-showcase.sc-open .sc-panel{ display:block; margin-top:12px; }

@media (max-width:600px){
  .pkg-bar{ gap:10px; }
  .pkg-bar .tfb-add{ margin-left:0; flex:1; justify-content:center; }
  .pkg-bar .sc-toggle{ flex:1; }
}


/* ---------------------------------------------------- catalog teaser
   The landing page points AT the shop instead of being the shop (Garett,
   2026-10-14). One row: the three facts a visitor needs to decide whether to
   open it, and the way in. */
.cat-teaser{
  display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between;
  gap:16px 24px; margin-top:22px; padding:18px 20px;
  border:1px solid var(--line); border-radius:12px; background:var(--carbon-800);
}
.cat-teaser-facts{
  margin:0; font-family:var(--font-mono); font-size:0.82rem;
  letter-spacing:0.04em; color:var(--ink-dim);
}
.cat-teaser-facts strong{ color:var(--ink); font-weight:600; }


/* ------------------------------------------ packages.html category band
   The catalog moved off the landing page (Garett, 2026-10-14) and packages.html
   is now THE shop — so the photography follows it here. packages.html already
   has a considered colour system (a per-category dot and accent), so the scrim
   is kept dark and the existing dot/heading/count sit on top unchanged rather
   than being restyled. */
.cat-head.has-shot{
  position:relative; isolation:isolate; overflow:hidden;
  border-radius:12px; padding:18px 20px; min-height:108px;
  align-items:flex-end; border:1px solid var(--line);
}
.cat-head .cat-shot{
  position:absolute; inset:0; width:100%; height:100%;
  object-fit:cover; z-index:-2; filter:saturate(0.85) contrast(1.05);
}
.cat-head.has-shot::after{
  content:''; position:absolute; inset:0; z-index:-1;
  background:linear-gradient(90deg, rgba(10,11,13,0.92) 0%, rgba(10,11,13,0.72) 48%, rgba(10,11,13,0.3) 100%);
}
@media (prefers-reduced-motion:no-preference){
  .cat-head .cat-shot{ transition:transform 600ms cubic-bezier(.2,.7,.3,1); }
  .cat-head.has-shot:hover .cat-shot{ transform:scale(1.03); }
}
