/* generated by scripts/generate-seo-pages.mts — do not edit by hand */
/* Google Fonts are loaded via <link rel="preconnect"> + <link rel="stylesheet"> in each HTML
   head (index.html, admin.html, the schede template) instead of an @import here — an @import
   blocks on a second round-trip before the browser even discovers the font request, which was
   costing measurable CLS. Keep every head in sync if the font stack below ever changes. */

:root {
  --ink: #1a1714;
  --ink-2: #2c2722;
  --rust: #b5532a;
  --rust-d: #8f3d1c;
  --paper: #f3ece0;
  --paper-2: #fbf7ef;
  --line: #d2c6b4;
  --muted: #6b6056;
  --slate: #4f5d63;
  --ok: #2e7d52;
  --display: 'Saira Condensed', 'Arial Narrow', sans-serif;
  --body: 'Archivo', system-ui, sans-serif;
  --mono: 'JetBrains Mono', ui-monospace, Menlo, monospace;
}

* { box-sizing: border-box; }

/* Defensive baseline against iOS Safari's mobile-only automatic text-size "boosting": WebKit on
   iOS (never Chrome, never desktop Safari — so it's invisible to Chrome-engine device checks) can
   inflate the font size of a "readable" text block it judges too small for a narrow column,
   independent of the viewport meta tag above and applied AFTER layout, so a line that fit at its
   authored size can grow past its container's right edge. This project had no reset for it at
   all. Pin the adjustment factor to 100% (i.e. off) so text always renders at its authored size —
   no downside on any other engine, since only iOS WebKit implements the boosting in the first
   place. */
html {
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--paper);
  background-image: radial-gradient(rgba(26, 23, 20, 0.045) 1px, transparent 1px);
  background-size: 4px 4px;
  color: var(--ink);
  font-family: var(--body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

#app {
  max-width: 880px;
  margin: 0 auto;
  padding: 0 22px 80px;
}

/* Industrial top hazard bar */
#app::before {
  content: '';
  display: block;
  height: 8px;
  margin: 0 -22px 26px;
  background: repeating-linear-gradient(
    -45deg,
    var(--ink) 0 14px,
    var(--rust) 14px 28px
  );
}

/* Marketing hero — prospect-only landing header, shown above the masthead when no API key is
   installed (see chrome.ts heroSection). Same industrial/display system as the rest of the app;
   deliberately bolder rust-on-paper for the headline so it reads as a claim, not a section title. */
.hero { margin: 0 0 34px; }
/* Header row (user request, 2026-07-22): the brand lockup and the util-bar (MERCATO/LINGUA) used
   to render as two stacked rows — selectors alone top-right, the brand a row below on the left —
   leaving dead space above the selectors and an odd two-row top. Merged into one flex row, right
   under the hazard stripe. .util-bar keeps margin-left:auto so it still hugs the right edge even
   when the brand is absent (a returning workshop with an API key — see chrome.ts headerRow). */
.header-row { display: flex; align-items: center; gap: 16px; margin: 0 0 18px; }
.header-row .util-bar { margin-left: auto; }
/* A small brand lockup above the fold — the full "RICAMBIO" wordmark otherwise only appears
   ~950px down (below the hero + proof + pricing), past most mobile scrolls. Decorative/redundant
   with the masthead's own <h1> a few lines down, so it's hidden from assistive tech. */
/* Bigger + higher (user request, 2026-07-22): now the first thing in .header-row instead of
   buried a row below the selectors, so it can carry more visual weight. Scaled the mark, its
   glyph and the wordmark together (44px/1.85rem -> 54px/2.3rem) to keep the lockup's proportions. */
.hero-brand { display: flex; align-items: center; gap: 12px; }
.hero-brand-mark {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 54px;
  height: 54px;
  background: var(--ink);
  color: var(--paper-2);
  font-family: var(--display);
  font-weight: 800;
  font-size: 2.3rem;
  flex: none;
}
.hero-brand-name {
  font-family: var(--display);
  font-weight: 700;
  font-size: 2.3rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--ink);
}
/* Bigger than the masthead kicker (user request, 2026-07-22) — on the landing it's the first
   line of copy and read too small at 0.68rem; scoped to .hero so the app masthead kicker is
   unaffected. */
.hero .kicker { font-size: 0.88rem; letter-spacing: 0.28em; margin-bottom: 8px; }
.hero-headline {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2rem, 6vw, 3.2rem);
  line-height: 0.98;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--rust-d);
  margin: 4px 0 14px;
  /* Balances line lengths across wraps instead of the browser's default greedy fill, so the
     last line never ends up carrying a single orphaned word. Backed further by an nbsp bound
     into the last two words of any locale string whose final word is short enough (≤4 chars)
     to still orphan on a browser without text-wrap:balance support. */
  text-wrap: balance;
}
.hero-sub {
  font-size: 1.08rem;
  color: var(--ink-2);
  max-width: 62ch;
  margin: 0 0 20px;
}
.hero-ctas { display: flex; flex-wrap: wrap; gap: 12px; }
/* The secondary hero CTA gets a true outline treatment (transparent + ink border) instead of the
   solid-ink .cta.ghost used elsewhere (e.g. the print bar) — next to the solid rust primary CTA,
   a second solid-dark button competed with it instead of reading as secondary. */
.hero-ctas .cta.ghost {
  background: transparent;
  color: var(--ink);
  border: 2px solid var(--ink);
}
.hero-ctas .cta.ghost:hover { background: var(--ink); color: var(--paper-2); }

/* Proof strip: four donor/honesty/pricing/Telegram cards, reusing the .note callout treatment. A
   real <ul role="list"> (not a bare div grid) — role="list" is needed because list-style:none
   strips the implicit list semantics in Safari/VoiceOver, so it's restored explicitly. 2x2 grid
   (not 3+1) so all four cards fill complete rows instead of the 4th orphaning alone under two
   empty cells. */
.hero-proof { margin: 0 0 22px; }
.hero-proof-list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 12px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.hero-proof-item { margin: 0; }
.hero-proof-item strong {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.05rem;
  text-transform: uppercase;
  letter-spacing: 0.01em;
  margin-bottom: 6px;
}
.hero-proof-item p { margin: 0; font-size: 0.92rem; color: var(--ink-2); }
/* Colour rhythm (user request, 2026-07-22): the proof cards alternate the rust accent with the
   palette's slate blue-grey — a warm/cool two-note beat instead of four identical rust bars, on
   a deliberately-restrained industrial palette. Even cards (the right column of the 2x2 grid) go
   slate; rust stays the left column — and every ACTION (CTAs, the recommended tier) stays rust. */
.hero-proof-item:nth-child(even) { border-left-color: var(--slate); }

/* Featured builds: the 3 hardest real jobs, promoted out of the small muted .hero-schede line
   into a prominent bordered card grid — the job label is the star (rust, mono kicker), the
   vehicle is bold display type, the piece count is a small muted footnote. */
.featured-builds { margin: 22px 0 26px; }
.featured-title {
  font-family: var(--display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: clamp(1.2rem, 4.5vw, 1.6rem);
  letter-spacing: -0.01em;
  margin: 0 0 14px;
  color: var(--ink);
}
.featured-list {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.featured-card a {
  display: flex;
  flex-direction: column;
  gap: 4px;
  height: 100%;
  padding: 16px;
  border: 2px solid var(--ink);
  background: var(--paper);
  text-decoration: none;
  transition: background 0.12s, transform 0.12s;
}
.featured-card a:hover,
.featured-card a:focus-visible { background: #fff; transform: translateY(-2px); }
.featured-job {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--rust-d);
}
.featured-vehicle {
  font-family: var(--display);
  font-weight: 800;
  font-size: 1.35rem;
  line-height: 1.02;
  text-transform: uppercase;
  color: var(--ink);
}
.featured-count { font-size: 0.82rem; color: var(--muted); margin-top: auto; }

.hero-schede {
  font-size: 0.9rem;
  color: var(--muted);
  margin: 12px 0 0;
}
.hero-schede a { color: var(--rust-d); }

/* Pricing panel: reuses .panel (the hard-offset shadow card) for the same weight as the input
   panel below, so the offer reads as equally "real" rather than a lightweight footnote. Sits at
   the END of the keyless page (see chrome.ts) — proof and the tool itself come first, price last.
   Two columns on desktop (design-critic spec, 2026-07-22): LEFT (.hero-pricing-left) carries the
   claim/price/terms/CTAs, RIGHT (.hero-pricing-right) is the benefit checklist — fills the ~40%
   void that made the card read flat. Collapses to one stacked column at ≤560px (see the mobile
   media block below) — mobile already read fine, this only touches desktop. */
.hero-pricing { margin: 0 0 34px; }
.hero-pricing.panel {
  grid-template-columns: 3fr 2fr;
  align-items: start;
}
.hero-pricing-right {
  border-left: 2px solid var(--line);
  padding-left: 24px;
  align-self: center;
}
/* .hero-pricing-title is a <p>, not an <h2> — kept out of the heading outline on purpose (it's a
   label, not a document section), styled to match the h2 section-header look above by hand
   instead of inheriting the bare h2 tag selector. */
.hero-pricing-title {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.5rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-bottom: 2px solid var(--line);
  padding-bottom: 5px;
  margin: 0 0 10px;
}
/* The new benefit headline (design-critic finding: the section's biggest element used to be a
   lonely number) — the eye lands on a claim first, the price second. */
.hero-pricing-headline {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(1.35rem, 2.6vw, 1.85rem);
  line-height: 1.05;
  text-transform: uppercase;
  color: var(--ink);
  margin: 0 0 8px;
}
.hero-pricing-price {
  font-family: var(--display);
  font-weight: 800;
  font-size: 2.2rem;
  color: var(--rust-d);
  margin: 0 0 4px;
}
/* Value anchor: reframes €49 against a cost the reader already has a feel for. */
.hero-pricing-anchor {
  font-size: 0.86rem;
  font-style: italic;
  color: var(--muted);
  margin: 0 0 10px;
}
.hero-pricing-terms { margin: 0 0 10px; font-size: 0.98rem; }
/* The trial line is the strongest hook on the page (free, no card) — give it the same rust
   left-accent as .donor-banner/.trial-banner instead of burying it in the faintest --muted grey. */
.hero-pricing-trial {
  margin: 0 0 8px;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink-2);
  border-left: 3px solid var(--rust);
  padding-left: 10px;
}
/* No-card badge: the strongest friction-killer on the page, promoted out of the trial line's
   body text into its own small pill next to the free CTA. */
.badge {
  display: inline-block;
  background: var(--rust);
  color: var(--paper-2);
  font-family: var(--mono);
  font-size: 0.64rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  padding: 3px 8px;
  margin: 0 0 12px;
}
/* The instant-access reassurance line + the two CTAs sit below the trial line, tightened to the
   same margin-0 rhythm as the rest of the panel (a bare <p class="hint"> would otherwise fall
   back to the browser's default 1em paragraph margin and look loose next to its siblings). */
.hero-pricing .hint { margin: 10px 0 0; }
.hero-pricing .hero-ctas { margin: 14px 0 0; }
.hero-pricing #pricing-sub-failed { display: block; margin: 10px 0 0; }
/* Payment-trust microline under the subscribe CTA — the only trust cue near the buy button used
   to be "senza carta" buried in the trial line above; this adds a second, payment-specific one. */
.hero-pricing-trust { font-size: 0.78rem; color: var(--muted); margin: 10px 0 0; }

/* Benefit checklist (right column): rust-tick items via a CSS ::before glyph — never a literal
   character typed into markup (same convention as .carrello-kicker--rec's ▸ mark below). */
.hero-pricing-includes { list-style: none; margin: 0; padding: 0; display: grid; gap: 12px; }
.hero-pricing-includes li {
  position: relative;
  padding-left: 24px;
  font-size: 0.92rem;
  color: var(--ink-2);
}
.hero-pricing-includes li::before {
  content: '✓';
  position: absolute;
  left: 0;
  top: 0;
  color: var(--rust-d);
  font-weight: 700;
}

/* STORICO keyless lean CTA pair (see pricingCtasLean in chrome.ts) — mirrors .hero-pricing's
   bottom rhythm without its .panel card treatment. */
.hero-pricing-lean { margin: 0 0 34px; }
.hero-pricing-lean #pricing-sub-failed { display: block; margin: 10px 0 0; }

/* Masthead — asymmetric, display type pinned against the language selector */
.masthead {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 18px;
  border-bottom: 3px solid var(--ink);
  padding-bottom: 12px;
}
.kicker {
  display: block;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.32em;
  text-transform: uppercase;
  color: var(--rust-d);
}
.masthead h1 {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2.6rem, 9vw, 4.6rem);
  line-height: 0.86;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  margin: 0.3em 0 0;
}
/* Keyless masthead (design-critic finding, 2026-07-22): the "RICAMBIO" wordmark relabelled to an
   action line (ui.mastheadAction — see chrome.ts) so it reads as an invitation to use the tool
   rather than a second brand statement below the hero. Same size/weight as .masthead h1 above,
   coloured rust to read as a call to action rather than a heading. */
.masthead-action {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2.6rem, 9vw, 4.6rem);
  line-height: 0.86;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: var(--rust-d);
  margin: 0.3em 0 0;
}
/* Schede pages: the wordmark is a plain link back to "/", NOT a heading — the page's own
   <h1> ("Ricambi <Vehicle>") must be the document's first heading, in correct DOM order. Styled
   to look identical to the SPA's plain-text .masthead h1 above. */
.wordmark-link {
  display: block;
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(2.6rem, 9vw, 4.6rem);
  line-height: 0.86;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  color: inherit;
  text-decoration: none;
  margin: 4px 0 0;
}
/* The schede page title itself — unique per vehicle, one step down from the wordmark. */
.schede-h1 {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(1.8rem, 5vw, 2.6rem);
  line-height: 1;
  letter-spacing: -0.005em;
  text-transform: uppercase;
  color: var(--ink);
  border-bottom: 3px solid var(--ink);
  padding-bottom: 12px;
  margin: 22px 0 18px;
}
/* MERCATO + LINGUA (user request, 2026-07-21): moved out of the mid-page masthead into a
   compact top-right strip at the very top of the page — the first real element inside #app,
   right after the hazard stripe (#app::before is a ::before pseudo-element and always paints
   first regardless of DOM order, so this is the next thing a visitor sees, on every page: hero
   or no hero, keyed or keyless). Renamed from the old .selectors (same flex-row layout, now
   right-aligned on its own full-width row instead of space-between against the masthead). */
.util-bar {
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  gap: 14px;
}
.lang,
.market {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
}
.lang select,
.market select {
  font-family: var(--body);
  font-size: 0.95rem;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
  background: var(--paper-2);
  border: 2px solid var(--ink);
  padding: 6px 10px;
  cursor: pointer;
}

.tagline {
  font-size: 1.08rem;
  color: var(--ink-2);
  max-width: 56ch;
  margin: 18px 0 26px;
}

/* Input-mode switcher — contributed by the registered identifiers */
.modes { margin: 0 0 16px; }
.modes-label {
  display: block;
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 6px;
}
.mode-row { display: flex; flex-wrap: wrap; gap: 0; }
.mode-btn {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.05rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--paper-2);
  border: 2px solid var(--ink);
  margin: 0 -2px -2px 0;
  padding: 8px 16px;
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}
.mode-btn:hover { background: #ece2d2; }
.mode-btn.on { background: var(--rust); color: var(--paper-2); }
.mode-icon {
  display: inline-block;
  width: 18px;
  height: 18px;
  margin: 0 6px -4px 0;
  vertical-align: middle;
}
.mode-icon svg { width: 100%; height: 100%; }

/* Officina panel — the workshop identity card */
.officina {
  border: 2px solid var(--ink);
  border-left: 6px solid var(--slate);
  background: var(--paper-2);
  padding: 14px 18px 18px;
  margin: 0 0 22px;
}
.officina h2 { margin: 0 0 10px; font-size: 1.2rem; border: none; padding: 0; }
.officina .field { margin-bottom: 12px; }
.officina-none { font-size: 0.95rem; color: var(--muted); margin: 0 0 12px; }
.officina-label {
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}
.officina-name { font-family: var(--display); font-weight: 700; font-size: 1.35rem; text-transform: uppercase; margin: 2px 0 8px; }
.officina-code-line { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 6px; }
.officina-code-line .label {
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
}
#officina-code { font-size: 0.95em; user-select: all; }
.officina-open { margin-top: 14px; border-top: 1px dashed var(--line); padding-top: 12px; }
.officina-open summary {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--rust-d);
  cursor: pointer;
}
/* One disclosure vocabulary across the OFFICINA view: the free-trial signup, access-request and
   (keyless) demoted API-key <details> panels get the SAME mono/uppercase/letter-spaced/rust
   treatment as .officina-open above, instead of each rendering the browser's default serif
   summary marker. */
.free-signup summary,
.access-request summary,
.feedback-panel summary,
.api-key-details summary,
.faq-item summary {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--rust-d);
  cursor: pointer;
  margin: 14px 0 6px;
}

/* The static /faq/ page (scripts/lib/seo-pages.ts faqPageHtml) — a plain accordion list below the
   page's own .schede-h1 title (same bordered-heading treatment as the schede/blog static pages —
   see .schede-h1 further down). Collapsed by default: reference material, not a money path, so
   .faq-item summary reuses the same mono/uppercase/rust disclosure vocabulary as the OFFICINA
   accordions above rather than inventing a second style. */
.faq-list { margin: 18px 0 0; }
.faq-item { border-bottom: 1px dashed var(--line); padding: 6px 0; }
.faq-item:last-child { border-bottom: none; }
.faq-item summary { margin: 8px 0; }
.faq-item p { margin: 0 0 10px; font-size: 0.92rem; color: var(--ink-2); }
/* OFFICINA money-paths — keyless landing only (see moneyPathsSection/officinaView in chrome.ts):
   the free-trial (primary) and €49/mese subscribe (secondary) cards, ranked and adjacent.
   Design-critic spec, 2026-07-20 money-path cluster rework. */
.money-paths {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 14px;
  margin: 0 0 20px;
}
.path-card {
  border: 2px solid var(--ink);
  background: var(--paper-2);
  padding: 20px;
  /* Grid items default to min-width:auto (content-based) — without this override, the primary
     card's nested Turnstile iframe (~300px min-content) floors the grid ITEM's width above what
     the 1fr track can honor at 320-405px viewports, forcing the whole grid wider than the
     viewport regardless of any max-width rule on the card itself. Design-critic finding,
     2026-07-20 re-render: this was the actual root cause of the 320/375px horizontal scroll. */
  min-width: 0;
}
/* Rust left-accent out-ranks the secondary card — the same device as .trial-banner/
   .hero-pricing-trial, reused here for the same "this is the one to notice" signal. */
.path-primary { border-left: 6px solid var(--rust); }
/* The subscribe card (no form, just a title/button/fine-print) was stretching to match the taller
   free-trial card's height in the desktop 2-col grid, leaving a large empty void below its fine
   print. Size it to its own content instead. Design-critic finding, 2026-07-20 re-render. */
.path-secondary { align-self: start; }
/* Mirrors .hero-pricing-title's font treatment without sharing the class — the PREZZO block's
   own border-bottom + responsive behaviour stay untouched. */
.path-card-title {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.5rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  margin: 0 0 8px;
}
.path-card-benefit { margin: 0 0 16px; font-size: 0.95rem; color: var(--ink-2); }
.path-card .cta { width: 100%; min-height: 48px; text-align: center; }
/* The shared free-signup <form class="panel"> gets its OWN border/background/shadow/padding —
   nested inside .path-card (already boxed), that double-boxes, and combined with the Turnstile
   iframe's min-content was the other half of the 320/375px overflow (see .path-card's min-width
   comment above). Drop the .panel box chrome ONLY here; the KEYED accordion's form (not inside
   .path-card) keeps its normal .panel box untouched. Design-critic finding, 2026-07-20 re-render. */
.path-card .panel {
  border: none;
  background: transparent;
  box-shadow: none;
  padding: 0;
}
/* The subscribe CTA merges the price INTO the button (user amendment, 2026-07-20) instead of a
   standalone price line — "ABBONATI — €49/MESE" on one line at desktop; the flex-wrap + centered
   alignment let it fall back to two stacked lines at mobile (sized differently — see the 780px
   block below) without any JS or manual <br>. */
.price-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  column-gap: 6px;
}
.price-cta-amount::before { content: '— '; }
/* Fine print demoted below the button as calm stacked lines — replaces the old cramped inline
   <small> pair that wrapped around/beside the button on mobile. */
.path-fineprint {
  display: block;
  font-size: 0.8rem;
  line-height: 1.5;
  color: var(--muted);
  margin-top: 10px;
  max-width: 52ch;
}
.path-fineprint small { display: block; margin-top: 4px; }
.path-fineprint small:first-child { margin-top: 0; }
/* The three remaining disclosures (have-a-key / request-access / suggestions), demoted below a
   hairline divider so they read as "other options", not peers of the money paths above. */
.officina-disclosures {
  border-top: 1px solid var(--line);
  margin-top: 20px;
  padding-top: 14px;
}
/* 780px (not the app's usual 560px mobile breakpoint): between 640-780px the 2-column grid was
   still active, and the primary card's Turnstile min-content forced an uneven split (measured
   396px/231px at 685px) — 1fr 1fr only holds when both columns actually have room. Stacking
   earlier sidesteps that lopsided band entirely. Design-critic finding, 2026-07-20 re-render. */
@media (max-width: 780px) {
  .money-paths { grid-template-columns: 1fr; }
  .path-card { padding: 18px; }
  .path-card-title { font-size: 1.3rem; }
  /* Constrain card content so the Turnstile widget fits inside the primary card instead of
     forcing horizontal overflow (see .path-card/.path-card .panel's min-width/de-boxing rules
     above for the actual root-cause fix — this is the remaining safety net). */
  .path-card, .cf-turnstile { max-width: 100%; }
  /* The merged price-CTA falls back to two stacked, differently-sized lines instead of wrapping
     the single-line text mid-word — ABBONATI stays prominent, the price stays scannable below
     it. A bit taller than the shared .path-card .cta min-height since it's now two lines. */
  .price-cta { flex-direction: column; gap: 2px; min-height: 60px; }
  .price-cta-label { font-size: 1.25rem; }
  .price-cta-amount { font-size: 1rem; }
  .price-cta-amount::before { content: none; }
}

.officina-open-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.officina-open-row input { flex: 1 1 220px; }
.officina input[type='text'] {
  font-family: var(--mono);
  font-size: 0.95rem;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--line);
  padding: 9px 11px;
}
.officina input[type='text']:focus { outline: none; border-color: var(--rust); }

.link-btn {
  font-family: var(--mono);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rust-d);
  background: none;
  border: none;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
}
.link-btn:hover { color: var(--rust); }
.link-btn.danger { color: var(--muted); margin-top: 6px; display: inline-block; }
.link-btn.danger:hover { color: var(--rust-d); }

/* Bot honeypot — visually removed (off-screen, 1px). Externalized from an inline style= so a
   strict CSP without 'unsafe-inline' for style attributes still hides it from real users. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

/* Screen-reader-only: content stays in the accessibility tree but is visually hidden — unlike
   .hp above (which also hides from assistive tech via aria-hidden at the call site), this is
   content that SHOULD be announced (table headers, the "Consigliato" marker on the starred
   basket row). Standard clip-based pattern (not display:none, which VoiceOver/NVDA also skip). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Skip-to-content link (schede pages): off-screen until keyboard-focused, then jumps to the top
   so a keyboard/screen-reader user isn't forced through the masthead on every page load. */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 10;
  background: var(--ink);
  color: var(--paper-2);
  padding: 10px 16px;
  font-family: var(--mono);
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  text-decoration: none;
}
.skip-link:focus { left: 0; }

/* Branded focus ring: the default browser outline is a thin blue rectangle that clashes with the
   rest of the system — this is the same ink/offset language used everywhere else (masthead,
   panel shadow) so keyboard focus reads as "on-brand", not "browser default". */
.cta:focus-visible,
.link-btn:focus-visible,
.hero-schede a:focus-visible,
.schede-links a:focus-visible,
/* a11y fix (2026-07-21): .field text/email/number inputs previously signaled focus with a
   border-color change ONLY (see the :focus rules further down) — color-only feedback is easy to
   miss and fails WCAG 2.4.7 for anyone who can't rely on subtle hue shifts. This adds the SAME
   outline every other interactive element gets, without touching the existing border-color
   behavior (both apply together). :focus-visible (not :focus) keeps it keyboard/programmatic-only,
   same as the buttons above — a mouse click still gets just the border-color change. */
.field input[type='text']:focus-visible,
.field input[type='email']:focus-visible,
.field input[type='number']:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* Top-level tabs: Source vs Storico */
.tabs { display: flex; gap: 0; margin: 0 0 22px; border-bottom: 3px solid var(--ink); }
.tab-btn {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.15rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink);
  background: none;
  border: none;
  border-bottom: 4px solid transparent;
  margin-bottom: -3px;
  padding: 8px 18px;
  cursor: pointer;
}
.tab-btn:hover { color: var(--rust-d); }
.tab-btn.on { color: var(--rust); border-bottom-color: var(--rust); }
/* No-key indicator on the OFFICINA tab */
.tab-dot {
  display: inline-block;
  width: 7px;
  height: 7px;
  background: var(--rust);
  border-radius: 50%;
  margin-left: 5px;
  vertical-align: middle;
  position: relative;
  top: -2px;
}

/* Storico — saved-job list */
.storico h2 { margin-top: 0; }
.storico-msg { font-size: 1rem; color: var(--muted); padding: 14px 0; }
.storico-list { list-style: none; margin: 0; padding: 0; }
.storico-item { border: 2px solid var(--line); border-left: 6px solid var(--rust); background: var(--paper-2); padding: 12px 16px; margin-bottom: 10px; }
.storico-item.is-ordered { border-left-color: var(--ok); }
.storico-head { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px; align-items: baseline; }
.storico-vehicle { font-family: var(--display); font-weight: 600; font-size: 1.15rem; text-transform: uppercase; letter-spacing: 0.02em; }
.storico-date { font-family: var(--mono); font-size: 0.72rem; color: var(--muted); }
.storico-job { margin: 4px 0 10px; font-size: 0.98rem; color: var(--ink-2); }
.storico-actions { display: flex; flex-wrap: wrap; gap: 16px; align-items: center; }
.storico-ordered { font-family: var(--mono); font-size: 0.72rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--ok); }
#storico-detail { margin-top: 28px; border-top: 3px solid var(--ink); padding-top: 14px; }

/* Pezzo mode: the canned part identification card */
.part-card {
  border: 2px solid var(--ink);
  border-left: 6px solid var(--rust);
  background: var(--paper-2);
  padding: 14px 16px;
  display: grid;
  gap: 8px;
}
.part-id-label {
  font-family: var(--mono);
  font-size: 0.62rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}
.part-id-name { font-size: 1.25rem; margin: 0; }
.part-cat {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--paper-2);
  background: var(--slate);
  padding: 2px 7px;
  margin-left: 6px;
}
.part-conf { font-family: var(--mono); font-size: 0.8rem; color: var(--rust-d); margin-left: 6px; }
.part-demo-note { font-family: var(--mono); font-size: 0.62rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--rust-d); margin: 8px 0 0; }

/* Input panel — one confident block, not a card grid */
.panel {
  background: var(--paper-2);
  border: 2px solid var(--ink);
  box-shadow: 8px 8px 0 rgba(26, 23, 20, 0.12);
  padding: 22px;
  display: grid;
  gap: 18px;
}
.field { display: flex; flex-direction: column; gap: 6px; }
.field > label, .field > .field-label {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
}
/* Accessible custom file input (libretto/car photo, part photo) — replaces the native
   <input type=file>'s browser-language "Choose File / No file chosen" chrome with our own
   localized label-button + filename span (see fileInputHtml in chrome.ts). The real input stays
   in the layout (not display:none, so Tab and screen readers still reach it) but is visually
   clipped to 1px — the standard sr-only technique, applied to a form control instead of text. */
.file-input { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.file-input-native {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.file-input-btn {
  font-family: var(--mono);
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--paper-2);
  border: 2px solid var(--ink);
  padding: 8px 12px;
  cursor: pointer;
}
.file-input-btn:hover { background: #ece2d2; }
/* The native input is what actually receives keyboard focus — style its sibling label-button via
   the adjacent-sibling combinator so Tab still shows a visible focus ring. */
.file-input-native:focus-visible + .file-input-btn {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
.file-input-name {
  font-family: var(--body);
  font-size: 0.85rem;
  color: var(--ink-2);
}
.field textarea {
  font-family: var(--body);
  font-size: 1.05rem;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--line);
  padding: 11px 13px;
  resize: vertical;
}
.field textarea:focus { outline: none; border-color: var(--rust); }
/* Panel selects (the fuel-type override) + the KM number input — match the bordered, body-font
   look of the textarea instead of the unstyled browser default (an Arial 0-padding sliver). */
.field select,
.field input[type='number'] {
  font-family: var(--body);
  font-size: 1rem;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--line);
  padding: 9px 12px;
}
.field select { cursor: pointer; }
.field select:focus,
.field input[type='number']:focus { outline: none; border-color: var(--rust); }
/* Text/email panel inputs (the free-trial signup form's name/email fields) — same bordered
   treatment as select/number above; without this they render as raw Arial-on-white browser
   defaults instead of the app's branded fields. */
.field input[type='text'],
.field input[type='email'] {
  font-family: var(--mono);
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--line);
  padding: 9px 12px;
}
.field input[type='text']:focus,
.field input[type='email']:focus { outline: none; border-color: var(--rust); }
/* The free-trial signup's pro/amateur radio group — same mono/uppercase legend as .field > label
   above, and a rust accent-color on the native radio so it matches the panel's palette instead of
   the browser's default blue. A real <fieldset>/<legend> (a11y fix, 2026-07-21 — announces "Pro
   o privato?" as the group's accessible name, same as role="radiogroup" would) needs its UA
   default border/padding/margin/min-width reset to match the plain-<div> look this had before. */
.field-radio { border: 0; margin: 0; padding: 0; min-width: 0; }
.field-radio .radio-legend {
  padding: 0;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
}
.field-radio .radio-option {
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  color: var(--ink);
  cursor: pointer;
}
.field-radio input[type='radio'] { accent-color: var(--rust); cursor: pointer; }
.hint, .ack, .error { font-size: 0.8rem; }
.hint { color: var(--muted); }
.ack { color: var(--ok); font-weight: 600; }
.error { color: var(--rust-d); font-weight: 600; }
/* The post-signup "check your email" ack: a plain 0.8rem .ack line read as "did this even work?"
   on mobile — this promotes it to a proper panel (same treatment as .note, green accent) so it's
   unmissable right after the form clears. */
.ack-panel {
  font-size: 1.05rem;
  background: var(--paper-2);
  border: 2px solid var(--line);
  border-left: 6px solid var(--ok);
  padding: 14px 16px;
  margin: 0;
}
.ack-panel strong { font-family: var(--mono); }
/* API key field: full-width, branded password input */
.api-key {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0 0 18px;
}
#api-key {
  width: 100%;
  padding: 10px 12px;
  border: 2px solid var(--line);
  background: var(--paper);
  font-family: var(--mono);
  font-size: 0.95rem;
  color: var(--ink);
}
#api-key:focus { outline: none; border-color: var(--rust); }
.api-key-status {
  display: block;
  font-size: 0.7rem;
  font-family: var(--mono);
  letter-spacing: 0.06em;
  margin-top: 6px;
  color: var(--muted);
}
.api-key-status.ok { color: var(--ok); }
.api-key-status.warn { color: var(--rust); }

.cta {
  display: inline-block;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.25rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--paper-2);
  background: var(--rust);
  border: 2px solid var(--ink);
  padding: 12px 20px;
  cursor: pointer;
  justify-self: start;
  transition: transform 0.08s ease, background 0.12s ease;
}
.cta:hover { background: var(--rust-d); transform: translate(-2px, -2px); }
.cta:active { transform: translate(0, 0); }
.cta:disabled { opacity: 0.55; cursor: wait; }
.cta.ghost { background: var(--ink); }

/* A search runs ~60-120s with an empty #results below — without a floor here the whole loading
   block (a couple of small text lines + a thin bar) collapses to almost nothing and the site
   footer visibly rides up under it mid-search. min-height keeps the footer off-screen for the
   duration of a real search, on mobile and desktop alike. */
.loading {
  font-family: var(--mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--rust-d);
  margin: 30px 0;
  position: relative;
  padding-left: 28px;
  min-height: 55vh;
}
.loading::before {
  content: '';
  position: absolute;
  left: 0; top: 6px;
  width: 16px; height: 16px;
  border: 3px solid var(--line);
  border-top-color: var(--rust);
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* Display-scale (same family as the hero headline) so "Sto cercando i ricambi…" reads as the
   page's main event during a search, not a caption easy to miss on mobile. */
.loading-text {
  display: block;
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(1.4rem, 5.5vw, 2.1rem);
  line-height: 1.2;
}
/* Estimated-progress bar: the sourcing call is one opaque ~60-120s generation with no true %,
   so the fill is time-based (eased toward ~92% in app.ts) and only hits 100% when the sheet
   actually lands — it never sits at "done" while still working. */
.loading-bar {
  display: block;
  height: 6px;
  max-width: 380px;
  margin: 12px 0 9px;
  background: var(--line);
  overflow: hidden;
}
.loading-bar > i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--rust);
  transition: width 0.9s ease-out;
}
.loading-hint {
  display: block;
  font-size: 0.9rem;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin-top: 10px;
}

/* Results */
.results { margin-top: 36px; }
.sheet-head { border-top: 3px solid var(--ink); padding-top: 14px; }
.sheet-head .vehicle { font-size: 1.15rem; }
.sheet-head .engine { font-size: 0.98rem; margin-top: 2px; }
.sheet-head .label {
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--muted);
  margin-right: 4px;
}
.price-note { font-size: 0.85rem; color: var(--muted); margin: 10px 0 0; }

/* Donor banner: the cross-reference moat, surfaced. A modest rust-accented line under the
   vehicle header so an obscure 4×4's mainstream donors read first. The label inherits the
   muted .sheet-head .label treatment beside it. Omitted for mainstream vehicles. */
.donor-banner {
  font-size: 0.95rem;
  margin: 10px 0 0;
  padding-left: 10px;
  border-left: 3px solid var(--rust);
}

/* The free-trial meter is a persistent, recurring upsell (shown on every free-tier sheet) — give
   it the same rust left-accent as .donor-banner so it reads as a first-class banner, not a
   dangling afterthought line. */
.trial-banner {
  padding-left: 10px;
  border-left: 3px solid var(--rust);
}

/* The demo banner is the FIRST thing a keyless visitor sees on the sheet — it must read as a
   first-class notice (not a faint footnote), so it gets the same rust left-accent treatment as
   .donor-banner/.trial-banner. */
.demo-banner {
  padding-left: 10px;
  border-left: 3px solid var(--rust);
}
/* Alignment finding (2026-07-22), same fix as .note above: border-left(3) + padding-left(10) =
   13px of this trio's own inset, hung into the margin so their text lands flush with body copy
   instead of a third, different left edge. */
.donor-banner, .trial-banner, .demo-banner { margin-left: -13px; }

h2 {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.5rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-bottom: 2px solid var(--line);
  padding-bottom: 5px;
  margin: 34px 0 10px;
}

/* Anti-upsell callout: the 4 upgrades that actually matter, above the baskets.
   Alignment finding (2026-07-22): border-left(6) + padding-left(16) = 22px pushed this box's
   TEXT 22px right of every other body line (h1, tagline, .schede-intro, item copy) — one of
   several competing left text edges on the page. margin-left:-22px hangs that whole inset into
   the margin (the same trick #app::before already uses to bleed the hazard bar edge-to-edge:
   negative margin equal to the padding being cancelled), so the box's own text lands flush with
   body copy again; only the accent bar + background now extend slightly into the gutter. One
   rule fixes every .note instance at once (schede-honesty, suggested-note, affiliate-note,
   carrelli-estimate — see their class list in src/render.ts / scripts/lib/seo-pages.ts), not a
   per-class hack. */
.note {
  background: var(--paper-2);
  border: 2px solid var(--line);
  border-left: 6px solid var(--rust);
  padding: 12px 16px;
  font-size: 0.95rem;
  margin: 0 0 12px;
  margin-left: -22px;
}

/* "I tre carrelli" — three stacked cards, one per tier, replacing a <table> whose rows used to
   blend a short label and a long reasoning paragraph into one text run (user finding, 2026-07-22:
   "three paragraphs, can't compare at a glance"). Full-width stacked cards (not a 3-column row)
   avoid ragged-note-height voids and mobile overflow entirely, and render identically at every
   breakpoint — see the design-critic spec for the full rationale. */
.carrelli-list { display: flex; flex-direction: column; gap: 12px; margin-top: 14px; }

.carrello {
  position: relative;                /* anchor for the top-right price block */
  background: var(--paper-2);
  border: 2px solid var(--line);
  padding: 15px 160px 16px 18px;     /* right gutter reserved for the absolute price column */
}
/* ONE recommended device: 6px rust left-accent (same language as .note). No background tint, no
   extra weight — the single signal, not three competing for the eye. */
.carrello--recommended { border-left: 6px solid var(--rust); padding-left: 14px; }

/* The head no longer lays out the price — it just carries the kicker. The price is pulled OUT to
   the top-right gutter (absolute, below) so a tall multi-line qualifier can never inflate this row
   and push the label down, which broke label alignment across cards (user finding, 2026-07-22). */
.carrello-head {
  margin-bottom: 9px;
}

/* Kicker chip — reuses the item-row .tag vocabulary (mono, 700, uppercase). Three ranked fills:
   BASE = slate, CONSIGLIATO = rust, COMPLETO = ink outline ("available, not pushed"). The
   recommended kicker's accent is a CSS glyph, never a literal star character in markup — that
   codepoint (U+2605) falls in the block design_audit's no-emoji rule flags (U+2600–27BF). */
.carrello-kicker {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding: 3px 8px;
  white-space: nowrap;
  flex: 0 0 auto;
}
.carrello-kicker--base { background: var(--slate); color: var(--paper-2); }
.carrello-kicker--rec { background: var(--rust); color: var(--paper-2); }
.carrello-kicker--rec::before { content: '▸'; margin-right: 0.35em; }
.carrello-kicker--full {
  background: transparent;
  color: var(--ink);
  border: 1.5px solid var(--line);
  padding: 1.5px 6.5px;
}

.carrello-price { position: absolute; top: 15px; right: 18px; width: 132px; text-align: right; }
.carrello-figure {
  display: block;
  font-family: var(--mono);
  font-weight: 700;
  font-size: 1.15rem;
  color: var(--rust-d);
  white-space: normal;
}
/* Each price amount stays intact; the figure may wrap only at the " + " between amounts. */
.carrello-figure .amt { white-space: nowrap; }
.carrello-qualifier {
  display: block;
  margin: 3px 0 0 auto;
  max-width: 24ch;
  font-family: var(--body);
  font-weight: 400;
  font-size: 0.72rem;
  line-height: 1.35;
  /* Left-aligned so a surviving qualifier reads L→R (ragged-right, normal) instead of a
     right-aligned ragged block; the price figure above it stays right-aligned. */
  text-align: left;
  color: var(--muted);
}

.carrello-label {
  margin: 0 0 8px;
  font-family: var(--body);
  font-weight: 600;
  font-size: 1.02rem;
  line-height: 1.32;
  color: var(--ink);
}
/* The blob-breaker: reasoning filed BELOW a hairline, small + muted — clearly the "why",
   secondary to the label above it, never in the tier-comparison path. */
.carrello-note {
  margin: 0;
  padding-top: 9px;
  border-top: 1px solid var(--line);
  font-family: var(--body);
  font-weight: 400;
  font-size: 0.86rem;
  line-height: 1.5;
  color: var(--muted);
  max-width: 70ch;
}
/* The carrelli footnote is a .note, which hangs its accent -22px into the margin so a STANDALONE
   note's text lines up with body copy. But this one sits directly under the row of .carrello cards
   (whose left edge is at the content column, no hang), so the -22px made its rust bar jut 22px left
   of the cards — a visible jag (user finding, 2026-07-22; measured 369 vs the cards' 391). Cancel the
   hang here so the footnote's left edge aligns flush with the card boxes above it. */
.carrelli-estimate { margin-left: 0; }

/* Requested vs "Conviene anche": two deliberately distinct zones. Requested keeps the rust
   accent (the quoted job). The suggested zone flips to the cooler SLATE accent + its own
   hazard rule, so the anti-upsell extras read as a separate band, not part of the bill. */
.requested-title { margin-top: 30px; }
.suggested {
  margin-top: 44px;
  border: 2px solid var(--slate);
  border-top-width: 0;
  background: var(--paper-2);
  padding: 0 18px 18px;
}
/* slate-on-ink hazard rule caps the suggested band, echoing the masthead bar in a cool key */
.suggested::before {
  content: '';
  display: block;
  height: 7px;
  margin: 0 -18px 4px;
  background: repeating-linear-gradient(-45deg, var(--ink) 0 13px, var(--slate) 13px 26px);
}
.suggested-title {
  color: var(--slate);
  border-bottom-color: var(--slate);
  margin-top: 16px;
}
.suggested .item .num { color: var(--slate); }
.suggested .tag.up { background: var(--slate); }
.suggested-note { border-left-color: var(--slate); }
/* User finding (2026-07-22): moved from the END of the item list (a running-total-style
   border-top rule read fine there) to directly under the section heading — flipped to a
   border-BOTTOM so the rule now caps off the figure before the explanatory note + items that
   follow it, instead of oddly doubling up against the h2's own border-bottom just above. */
.add-subtotal {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.35rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--slate);
  border-bottom: 2px solid var(--slate);
  margin: 6px 0 14px;
  padding-bottom: 12px;
}

.items { margin-top: 8px; }
.item { border-bottom: 1px solid var(--line); padding: 16px 0; }
.item h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.3rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 0 0 8px;
}
.item .num { color: var(--rust); margin-right: 4px; }
.cat-icon {
  display: inline-block;
  width: 16px;
  height: 16px;
  margin: 0 2px -3px 0;
  color: var(--muted);
}
.cat-icon svg { width: 100%; height: 100%; }
.item .flag {
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  margin-left: 8px;
}

.row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  align-items: baseline;
  margin: 5px 0;
}
.tag {
  font-family: var(--mono);
  font-weight: 700;
  font-size: 0.62rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--paper-2);
  padding: 3px 8px;
  white-space: nowrap;
}
.tag.std { background: var(--slate); }
.tag.up { background: var(--rust); }
.brand { font-weight: 600; }
code {
  font-family: var(--mono);
  font-size: 0.9em;
  background: #ece2d2;
  padding: 1px 6px;
  color: var(--rust-d);
}
.price { font-family: var(--mono); font-weight: 700; white-space: nowrap; }
.stock { font-family: var(--mono); font-size: 0.78rem; color: var(--ok); }
.why { color: var(--muted); font-style: italic; font-size: 0.95rem; margin: 4px 0 0; }
.oem { color: var(--muted); font-size: 0.85rem; margin: 6px 0 0; }
/* Legibility finding (2026-07-27): the OEM chip codes and the part-row brand+partNo code were
   reading small at their inherited size — bumped one notch, scoped to these two spots only (the
   bare `code` rule above still governs the engine/plate codes in the vehicle header, untouched). */
.oem code { font-size: 0.95rem; }
.row code { font-size: 1rem; }
.oem .label {
  font-family: var(--mono);
  font-size: 0.6rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  margin-right: 4px;
}

a.btn {
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--paper-2);
  background: var(--ink-2);
  text-decoration: none;
  padding: 4px 10px;
}
a.btn:hover { background: var(--rust); }

/* The honest-omission search link (see src/enrich/ebay-search-links.ts): NOT a confident
   "vedi ricambio" buy button — a ghost/outline variant (paper fill, ink border/text) reading as
   "search", not "buy", mirroring .cta.ghost's understated treatment elsewhere on the page. */
a.btn.btn-search {
  background: var(--paper-2);
  color: var(--ink-2);
  border: 1px solid var(--ink-2);
}
a.btn.btn-search:hover { background: var(--ink-2); color: var(--paper-2); }

.actions { margin-top: 30px; }

/* The quiet "segnalacelo" feedback prompt under a rendered sheet — same mono/uppercase/rust
   disclosure vocabulary as the OFFICINA <details> panels, but understated (smaller, muted until
   hovered) since it is not the point of the page. */
.feedback-inline { margin-top: 18px; border-top: 1px dashed var(--line); padding-top: 12px; }
.feedback-inline summary {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.08em;
  color: var(--muted);
  cursor: pointer;
}
.feedback-inline summary:hover { color: var(--rust-d); }
.feedback-inline textarea { width: 100%; }

/* Error banner: the #error element shown on sourcing/validation failures. A <div> (not <p> — see
   chrome.ts) so the trial-exhausted upgrade panel below can nest block content (a price line + a
   real .cta button), not just a text-only message. */
.error-banner {
  background: var(--paper-2);
  border: 2px solid var(--line);
  border-left: 6px solid var(--rust-d);
  padding: 12px 16px;
  font-family: var(--body);
  font-size: 0.95rem;
  color: var(--rust-d);
  font-weight: 600;
  margin: 12px 0;
}
/* Trial-exhausted upgrade panel (see showTrialExhaustedPanel in app.ts) — the highest-intent
   moment in the funnel, so it gets the same weight as the pricing panel's own price line and a
   real primary .cta button, not the plain link-btn used elsewhere in this banner. */
.error-banner p { margin: 0 0 8px; }
.error-banner .trial-exhausted-price {
  font-family: var(--display);
  font-weight: 800;
  font-size: 1.6rem;
  color: var(--rust-d);
}
.error-banner .cta { margin-top: 2px; }

/* Trial-exhausted upgrade card (see showTrialExhaustedPanel in app.ts): badge + headline + terms
   + price + CTA + trust line, scoped by the 'trial-panel' class so it never leaks onto a plain
   showError() line (both showError and clearError strip the class). */
.error-banner.trial-panel {
  border: 2px solid var(--ink);
  border-left: 6px solid var(--rust-d);
  background: #f6ebdd;
  padding: 20px;
  color: var(--ink);
  font-weight: 400;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.trial-panel-badge {
  align-self: flex-start;
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--paper-2);
  background: var(--rust-d);
  padding: 4px 10px;
}
.trial-panel-headline {
  font-family: var(--display);
  font-weight: 800;
  text-transform: uppercase;
  font-size: clamp(1.1rem, 4vw, 1.4rem);
  line-height: 1.05;
  color: var(--ink);
  margin: 4px 0 0;
}
.trial-panel-value {
  font-size: 0.92rem;
  color: var(--muted);
  font-weight: 400;
  margin: 0;
}
.trial-panel-benefit {
  position: relative;
  padding-left: 22px;
  font-size: 0.92rem;
  color: var(--ink);
  margin: 2px 0 0;
}
.trial-panel-benefit::before {
  content: '✓';
  position: absolute;
  left: 0;
  color: var(--rust-d);
  font-weight: 700;
}
.error-banner.trial-panel .trial-exhausted-price { font-size: 2rem; margin: 4px 0 0; }
.error-banner.trial-panel .cta { display: block; width: 100%; text-align: center; margin: 6px 0 0; }
.trial-panel-trust {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: 4px 0 0;
}

@media (max-width: 560px) {
  .hero-proof-list { grid-template-columns: 1fr; }
  .featured-list { grid-template-columns: 1fr; }
  .hero-ctas { flex-direction: column; align-items: stretch; }
  .hero-ctas .cta { text-align: center; }
  .masthead { flex-direction: column; align-items: flex-start; gap: 10px; }
  /* Pricing panel collapses back to one stacked column (it already read fine on mobile — this
     desktop 2-col rework must not touch it). */
  .hero-pricing.panel { grid-template-columns: 1fr; }
  .hero-pricing-right { border-left: none; padding-left: 0; margin-top: 8px; }
  /* Fix 6: prevent sideways scroll at ≤360px */
  #app { padding: 0 16px 64px; }
  /* The hazard bar bleeds out by -22px to cancel #app's DESKTOP 22px padding. This block drops
     that padding to 16px, so the bar over-bleeds 6px each side (343 content + 44 = 387px) and
     its right edge lands at 381 — a 6px phantom horizontal scroll on every mobile page. Match
     the bleed to the padding. */
  #app::before { margin-left: -16px; margin-right: -16px; }
  .panel { padding: 16px; box-shadow: 4px 4px 0 rgba(26, 23, 20, 0.12); }
  .field input[type='file'] { max-width: 100%; }
  .officina { padding: 12px 14px 14px; }
  /* Fix 7: mode switcher wraps 4+1 → 2-column grid */
  .mode-row { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0; }
  .mode-btn { text-align: center; }
  /* .mode-btn carries margin-right:-2px to collapse borders in the DESKTOP flex row.
     In this grid that makes every button 2px wider than its column, so the right-hand
     column overhangs the row and the whole page scrolls 6px sideways. Keep the collapse
     between the two columns; drop it on the right-hand button only. */
  .mode-btn:nth-child(2n) { margin-right: 0; }
  /* Fix 9: tap targets ≥ ~44px */
  .lang select, .market select { padding: 9px 10px; }
  /* Header row (user request, 2026-07-22): brand lockup + util-bar no longer fit one line at
     375px (the bigger 54px/2.3rem lockup alone is already ~260px). Stack instead — brand on top,
     selectors below, kept right-aligned via align-self so the row still reads as one balanced
     unit rather than two unrelated stacked lines. */
  .header-row { flex-direction: column; align-items: flex-start; gap: 10px; margin: 0 0 14px; }
  .header-row .util-bar { align-self: flex-end; margin-left: 0; }
  /* The top util bar (MERCATO + LINGUA — user request, 2026-07-21) collapses further at this
     width: the visible mono-caps label above each select hides (sr-only, same clip technique as
     .sr-only above — the <label> wrapper still gives the select its accessible name), so the
     row stays one compact line of just the two selects instead of crowding the brand below. */
  .util-bar { gap: 8px; }
  .util-bar .lang > span:first-child,
  .util-bar .market > span:first-child {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
  .util-bar .lang select,
  .util-bar .market select {
    font-size: 0.82rem;
    padding: 8px 6px;
  }
  .field input, .field select, .field textarea { min-height: 44px; }
  /* Same tap-target concern as Fix 9, applied to the footer's Privacy/Termini/mailto links: at
     0.72rem with no padding their hit area is far under 44px on a touch screen. */
  .site-footer-links a { display: inline-block; padding: 8px 0; }
  /* Less separation needed than desktop's 7rem — there's less content stacked above the footer
     on a narrow, single-column mobile layout. */
  .site-footer { margin-top: 4rem; }
  /* Fix 10: tiny mono labels — nudge up from ~10px */
  .officina-label,
  .sheet-head .label,
  .modes-label,
  .lang,
  .market { font-size: 0.66rem; letter-spacing: 0.12em; }

  /* Fix 11 / carrelli: cards are already full-width blocks at every breakpoint (no fixed table
     columns to collapse), so mobile only needs tighter padding + a smaller price figure. Head
     stays a flex row down to 320px: kicker (~105px) + gap + longest price ("~€2.600–3.800" ~120px)
     ~= 235px < 290px content width, so nothing wraps or overflows. */
  .carrello { padding: 14px 15px 15px; }        /* no right gutter on mobile */
  .carrello--recommended { padding-left: 11px; }
  /* Mobile keeps the simple flex head (chip left, price right); the gutter/absolute layout is
     desktop-only — on a narrow card the head is naturally tall so no label-gap appears there. */
  .carrello-head { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: flex-start; gap: 10px; }
  .carrello-price { position: static; width: auto; margin-left: auto; }
  .carrello-figure { font-size: 1.05rem; }

  /* Fix 12: per-item prices (.price, in the item rows below the baskets table — distinct from
     the basket total cell handled above) also carry `white-space: nowrap`. A long real price
     string ("~€95-140 cad. — conferma disponibilità") can't wrap, punches past 375px and drags
     the WHOLE page into horizontal scroll — the same bleeding-sheet bug as Fix 11, on a
     different element. Confirmed on real generated data (lada-niva, uaz-hunter schede pages).
     Applies to the live SPA sheet too (any real search can produce an equally long price). */
  .price { white-space: normal; overflow-wrap: anywhere; }
}

/* Prerendered SEO spec sheets (public/schede/<slug>/) — static pages, no JS, reusing this same
   stylesheet. The masthead/tagline/note/panel/cta primitives above carry the visual system; these
   few rules cover the page-specific bits (the intro paragraph, the closing CTA, the cross-links). */
.schede-intro { font-size: 1.02rem; color: var(--ink-2); max-width: 68ch; margin: 0 0 26px; }
/* Closing CTA: the question in display caps and the button centered under it, sized as the
   page's final action rather than a left-hugging afterthought (user finding, 2026-07-21). */
.schede-cta { text-align: center; margin: 40px 0 26px; padding: 28px 20px; }
.schede-cta p {
  margin: 0 0 18px;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.45rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}
.schede-cta .cta { display: inline-block; justify-self: center; font-size: 1.3rem; padding: 14px 28px; }
.schede-links { border-top: 2px solid var(--line); padding-top: 16px; margin-bottom: 40px; }
.schede-links h2 { margin-top: 0; }
.schede-links ul { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 8px 20px; }
.schede-links a { color: var(--rust-d); font-weight: 600; }

/* Prose links (legal pages: public/privacy/*, public/termini/*, and any other .tagline/.panel
   copy) had no color rule at all — browsers fall back to UA default blue (#0000EE), off-brand and
   a WCAG-irrelevant but jarring mismatch against the paper background. Scoped to <p>/<li>
   descendants specifically (not every .panel — the app's own panels are forms, not prose, and the
   one exception, the free-signup accept-note's <p><a>, already sets this same color explicitly). */
.tagline a, .panel p a, .panel li a { color: var(--rust-d); }

/* Legal pages (public/privacy/*, public/termini/*) run noticeably wider than the design's other
   prose (.schede-intro caps at 68ch; the rendered sheet's own text sits in the 56-62ch range) —
   scoped to `.legal` (set on those pages' <main>) so the app's own .panel forms are untouched. */
.legal .panel p { max-width: 68ch; }

/* Blog article hero (public/blog/<slug>/) — above-title image. Same hard border + offset shadow
   as .blog-card / .panel so it reads as part of the brutalist system, not a soft stock photo. The
   fixed aspect-ratio box + object-fit crop any minor source-height variance and hold layout so the
   eager LCP image causes no shift. */
.blog-hero { margin: 8px 0 22px; }
.blog-hero img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  border: 2px solid var(--ink);
  box-shadow: 8px 8px 0 rgba(26, 23, 20, 0.12);
}

/* Blog article body (public/blog/<slug>/) — same 68ch prose measure as the legal pages above,
   scoped to `.blog-article` rather than `.legal p` so it doesn't also widen the unrelated
   privacy/termini `.tagline` intro line, which already has its own (narrower) max-width. */
.blog-article p { max-width: 68ch; }

/* Blog index (public/blog/) — a simple stack of article cards, reusing .panel's border/shadow
   treatment rather than a new card style. */
.blog-list { display: grid; gap: 18px; margin: 26px 0 40px; }
.blog-card {
  background: var(--paper-2);
  border: 2px solid var(--ink);
  box-shadow: 8px 8px 0 rgba(26, 23, 20, 0.12);
  padding: 20px 22px;
}
.blog-card h2 { margin: 4px 0 8px; border: none; padding: 0; font-size: 1.3rem; }
.blog-card h2 a { color: inherit; text-decoration: none; }
.blog-card h2 a:hover, .blog-card h2 a:focus-visible { text-decoration: underline; }
.blog-card p { margin: 0; max-width: 68ch; }
.blog-card-date {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 4px;
}

/* The raw free-trial key on /api/free/verify's success page — the ONE place it's ever shown.
   `user-select: all` means a single tap/click selects the WHOLE key instead of one word of it, so
   copying works without a JS clipboard button (the page's CSP has no script-src 'unsafe-inline'). */
.key-display {
  display: block;
  font-family: var(--mono);
  font-size: 1.05rem;
  font-weight: 700;
  word-break: break-all;
  color: var(--ink);
  background: var(--paper);
  border: 2px solid var(--line);
  padding: 12px 14px;
  margin: 14px 0;
  user-select: all;
  -webkit-user-select: all;
}

/* Site-wide legal/contact footer — quiet by design (small, muted, mono), sits below the app on
   every view. Hidden in print (see @media print below); this is chrome, not part of the quote. */
/* User feedback (2026-07-20): the footer sat too close under the PREZZO/CTA card — the company
   line and a CTA button were visible in the same glance, reading as if the page had already
   ended. Pushed lower via margin-top (not card padding) so any card above it gets clear runway;
   less separation needed at mobile since there's less content stacked above it. */
.site-footer {
  margin-top: 7rem;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
}
.site-footer-links { margin: 0; }
.site-footer-links a { color: var(--muted); }
.site-footer-links a:hover { color: var(--rust-d); }

/* Signup acceptance line (under the free-trial submit button) + the Telegram-bot proof card's
   inline link — both reuse the same rust-on-hover link treatment as the rest of the app. */
.accept-note { font-size: 0.75rem; color: var(--muted); margin: 6px 0 0; }
.accept-note a,
.hero-proof-item a { color: var(--rust-d); }

/* Print-bar: the "Stampa / Salva PDF" affordance at the head of the rendered sheet. Unobtrusive
   on screen (ghost CTA, the existing .cta.ghost treatment); removed entirely in print. */
.print-bar { margin: 0 0 18px; }

/* Quote header: the customer-facing quote heading (title + date + officina + vehicle). Built in
   app.ts but hidden on screen — it surfaces ONLY under @media print, so the on-screen sheet is
   unchanged. Brand display type + rust accents carry over (this is a customer-facing document). */
.quote-header { display: none; }

/* Print = the one-page customer quote the mechanic hands over. White page + dark ink (clean,
   ink-saving), but the brand display font + rust accents stay — this is a professional quote, not
   a raw dump. Everything that is app chrome (forms, selectors, tabs, panels, the print button
   itself, the demo banner) is removed; only the quote header + the rendered sheet remain. */
@media print {
  body {
    background: #fff;
    background-image: none;
    color: var(--ink);
  }
  #app { max-width: none; padding: 0; }
  /* Hide the industrial hazard bar + all app chrome and input affordances. */
  #app::before,
  .skip-link,
  .hero,
  .hero-proof,
  .hero-pricing,
  .masthead,
  .tagline,
  .modes,
  .api-key,
  .subscribe,
  .access-request,
  .free-signup,
  .officina,
  .tabs,
  .panel,
  #loading,
  #error,
  #view-storico,
  #view-officina,
  .demo-banner,
  .trial-banner,
  .print-bar,
  .site-footer,
  .actions,
  .feedback-inline,
  .feedback-panel { display: none !important; }

  /* The print-only quote header now shows, on-brand: display type, rust title rule. */
  .quote-header {
    display: block;
    border-bottom: 3px solid var(--ink);
    padding-bottom: 12px;
    margin-bottom: 20px;
  }
  .quote-title {
    font-family: var(--display);
    font-weight: 800;
    font-size: 2.1rem;
    letter-spacing: 0.01em;
    text-transform: uppercase;
    color: var(--rust-d);
    border: none;
    margin: 0;
    padding: 0;
  }
  .quote-meta {
    font-family: var(--mono);
    font-size: 0.8rem;
    letter-spacing: 0.04em;
    color: var(--ink-2);
    margin: 6px 0 0;
  }
  .quote-officina { font-weight: 700; }
  .quote-officina::before { content: ' · '; font-weight: 400; }
  .quote-vehicle { font-size: 1.05rem; margin: 8px 0 0; }
  .quote-engine { font-size: 0.95rem; margin: 4px 0 0; }
  .quote-header .label {
    font-family: var(--mono);
    font-size: 0.58rem;
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--muted);
    margin-right: 4px;
  }

  /* The header carries the full vehicle ID (make/model/year/kW + engine + plate), so the sheet's
     own vehicle + engine lines would just repeat it on paper — hide them. The donor
     "Compatibilità" banner is unique to the sheet-head, so it stays. */
  .sheet-head .vehicle,
  .sheet-head .engine { display: none; }

  /* The sheet, on paper: keep the rust accents, drop the cream panel fills so ink is spent on
     the parts, not backgrounds. Buy-links are noise on a customer quote — the mechanic orders
     the parts — so the link buttons and the "indicative price" / affiliate notes are dropped. */
  .sheet-head { border-top: none; padding-top: 0; }
  .note { background: #fff; }
  a.btn { display: none; }
  .price-note, .affiliate-note, .carrelli-estimate { display: none; }

  /* Don't split an individual part block or a basket card across the page break. The suggested
     SECTION itself is left to flow — keeping the whole multi-item band together would shove it
     to a fresh page and leave a large gap; only its item children stay intact. */
  .item { break-inside: avoid; }
  .carrello { break-inside: avoid; }

  /* Sensible page margins for the printed quote. */
  @page { margin: 16mm 14mm; }
}
