/*
  Nachón Lumber & Hardware — public site stylesheet.

  impeccable direction record (new-work.md §5 — see pages/home.ts for the
  full contract; this file is where the world's tokens/rules actually land).

  F2 Task 3 REDESIGN (2026-08-04) — supersedes the F1 record below this
  paragraph. Owner feedback on the F1 shell, verbatim: "le falta mucho, se ve
  muy simple." This is a brief-pinned redirection (task-3-brief.md), not a
  free restyle: full-bleed hero with a dark legibility scrim (F1 explicitly
  banned this — reversed on purpose), real department photos on every
  category card, a trust band, a 3-column footer, more type hierarchy, and a
  primary/secondary CTA distinction. The banned patterns below stay banned —
  what changed is how bold the ALLOWED vocabulary is used within them.
  THESIS: a real Hialeah counter/signage system, not a generic SaaS
    marketing shell — refuses soft-shadow cards, gradient TEXT and
    rounded-everything AI palettes. A dark scrim behind hero text is a
    legibility technique (any photo-hero storefront banner uses one), not
    the banned "gradient text" (that's a gradient FILL on the text itself).
  OWN-WORLD: --nachon-blue as a solid opaque field (header/footer chrome,
    trust band, section headings' accent); --nachon-yellow as a sparing
    high-contrast accent (CTA buttons, focus rings, the anniversary badge,
    active nav, section-heading rule, category-card arrow). Boxy 2-4px
    corners, 2px borders instead of box-shadow for card structure, cool
    neutral grays (never warm cream/paper). System font stack (brief-pinned),
    now with real weight/size contrast between display and body type.
  FIRST VIEWPORT: full-bleed hero photo with a dark bottom scrim, H1/subtitle
    /CTA sitting directly on the photo, the 50th-anniversary badge as a
    yellow chip over the photo's top-right corner, a solid-blue trust band
    directly under the photo.
  FORM: brief-pinned Committed-color storefront-signage layout, redirected by
    task-3-brief.md — single staging, no concept-seed roll (brief pins the
    world already).
*/

/* ---------- Reset & base ---------- */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  font-family:
    -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  color: var(--color-ink);
  background: var(--color-paper);
  line-height: 1.5;
}

img {
  max-width: 100%;
  height: auto;
  display: block;
}

a {
  color: inherit;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

h1,
h2,
h3,
p {
  margin: 0 0 0.75em;
}

ul,
ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ---------- Tokens ---------- */

:root {
  /* Brand — sampled from the logo, Task 1 (docs/tenants/nachon/website-v2/content-source.md §d) */
  --nachon-blue: #2a54cc;
  --nachon-blue-dark: #1e3d99;
  --nachon-yellow: #fcfc54;
  --nachon-yellow-dark: #e6e64c;

  /* Cool neutrals — deliberately NOT warm cream/paper (see direction record above) */
  --color-ink: #1a1a1a;
  --color-ink-soft: #4b5157;
  --color-paper: #ffffff;
  --color-surface: #f2f3f5;
  --color-border: #d7dae0;

  --content-max: 72rem;
  --space-1: 0.5rem;
  --space-2: 1rem;
  --space-3: 1.5rem;
  --space-4: 2.5rem;
  --space-5: 4rem;

  --radius: 3px;
  --touch-target: 44px;

  /* Type scale (F2 Task 3 — more hierarchy, still system stack, no webfonts) */
  --text-h1: clamp(2.25rem, 6vw, 3.5rem);
  --text-h2: clamp(1.5rem, 3vw, 2.25rem);
  --text-h3: 1.125rem;
}

/* ---------- Type hierarchy (F2 Task 3) ---------- */

h1 {
  font-size: var(--text-h1);
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.08;
}

main h2 {
  display: inline-block;
  font-size: var(--text-h2);
  font-weight: 800;
  letter-spacing: -0.01em;
  padding-bottom: 0.3em;
  margin-bottom: var(--space-3);
  border-bottom: 3px solid var(--nachon-yellow);
}

h3 {
  font-size: var(--text-h3);
  font-weight: 700;
}

/* ---------- Focus & motion (global) ---------- */

a:focus-visible,
button:focus-visible {
  outline: 3px solid var(--nachon-yellow);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- Skip link (F2 Task 3, Assessment A minor observation) ---------- */

.skip-link {
  position: absolute;
  top: -100%;
  left: var(--space-2);
  z-index: 20;
  background: var(--nachon-yellow);
  color: var(--color-ink);
  border: 2px solid var(--color-ink);
  border-radius: var(--radius);
  padding: var(--space-1) var(--space-2);
  font-weight: 700;
  text-decoration: none;
}

.skip-link:focus-visible {
  top: var(--space-2);
}

/* ---------- Header (site-wide chrome, structure from layout.ts) ---------- */

.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--color-paper);
  border-bottom: 2px solid var(--nachon-blue);
}

.header-inner {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--space-2) var(--space-3);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.site-logo {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
  text-decoration: none;
  color: inherit;
}

.site-logo img {
  height: 48px;
  width: auto;
}

/* PR1 — wordmark next to the mascot (AUDITORIA §3.2: cabecera solo tenía
   mascota, sin nombre). Below 26rem (~416px) it is VISUALLY hidden (clip
   technique, same as .visually-hidden) so a very narrow phone keeps room for
   the nav-toggle button — but it stays in the accessibility tree: the mascot
   has alt="" and this span is the home link's only accessible name (PR1
   review HIGH: display:none would leave the link nameless below 416px). */
.site-wordmark {
  font-weight: 800;
  font-size: 1rem;
  letter-spacing: -0.01em;
  color: var(--nachon-blue-dark);
  white-space: nowrap;
}

@media (max-width: 26rem) {
  .site-wordmark {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
  }
}

.nav-toggle {
  margin-left: auto;
  display: inline-flex;
  flex-shrink: 0;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 4px;
  width: var(--touch-target);
  height: var(--touch-target);
  background: transparent;
  border: 2px solid var(--nachon-blue);
  border-radius: var(--radius);
  cursor: pointer;
}

.nav-toggle-bar {
  display: block;
  width: 20px;
  height: 2px;
  background: var(--nachon-blue);
}

.site-nav {
  display: none;
  flex-direction: column;
  width: 100%;
}

.nav-toggle[aria-expanded="true"] ~ .site-nav {
  display: flex;
}

.site-nav-list {
  display: flex;
  flex-direction: column;
}

.site-nav-list li a {
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: var(--space-1) var(--space-2);
  text-decoration: none;
  font-weight: 600;
  border-bottom: 1px solid var(--color-border);
}

.site-nav-list li a:hover {
  color: var(--nachon-blue-dark);
}

.site-nav-list li a[aria-current="page"] {
  color: var(--nachon-blue-dark);
  box-shadow: inset 0 -3px 0 var(--nachon-yellow);
}

.lang-switch {
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: var(--space-1) var(--space-2);
  text-decoration: underline;
}

@media (min-width: 64rem) {
  .nav-toggle {
    display: none;
  }

  .site-nav {
    display: flex !important;
    flex-direction: row;
    align-items: center;
    width: auto;
    margin-left: auto;
    gap: var(--space-1);
  }

  .site-nav-list {
    flex-direction: row;
    gap: var(--space-1);
  }

  .site-nav-list li a {
    border-bottom: none;
    white-space: nowrap;
  }
}

/* ---------- Hero (F2 Task 3: full-bleed photo, text ON the image) ---------- */

.hero {
  max-width: none;
  padding: 0;
}

.hero-media {
  position: relative;
  display: flex;
  align-items: flex-end;
  min-height: clamp(24rem, 58vw, 36rem);
  overflow: hidden;
}

.hero-image {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero-overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  /* Legibility scrim, not decoration: bottom-weighted so the photo still
     reads at the top, opaque enough that white text passes contrast at the
     bottom where it sits. */
  background: linear-gradient(
    to top,
    rgba(8, 13, 26, 0.88) 0%,
    rgba(8, 13, 26, 0.6) 40%,
    rgba(8, 13, 26, 0.05) 78%
  );
}

/* Subtle film grain on the hero (React Bits Noise, CSS-only). Lives on a
   pseudo under the overlay so it never fights text contrast; no purple glow,
   no gradient text. SVG feTurbulence via data-URL is fine under style-src
   'self' — the stylesheet is same-origin. */
.hero-overlay::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.18;
  mix-blend-mode: soft-light;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 180 180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 180px 180px;
}

.anniversary-badge {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  max-width: 14rem;
  background: var(--nachon-yellow);
  color: var(--color-ink);
  border: 2px solid var(--color-ink);
  border-radius: var(--radius);
  padding: var(--space-1);
  font-size: 0.75rem;
  font-weight: 700;
  line-height: 1.2;
}

.anniversary-badge img {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
}

.hero-content {
  position: relative;
  z-index: 2;
  width: 100%;
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--space-4) var(--space-3) var(--space-5);
  color: #fff;
}

.hero-content h1 {
  color: #fff;
}

.hero-content p {
  max-width: 40rem;
  font-size: 1.125rem;
  color: #eef1fb;
}

/* PR1 — store-hours line under the hero subtitle (AUDITORIA §3.2: "sin
   horario visible hasta el footer"). */
.hero-hours {
  max-width: 40rem;
  font-size: 0.9375rem;
  font-weight: 700;
  color: var(--nachon-yellow);
  margin-bottom: var(--space-2);
}

/* PR1 — primary (tel:) + secondary (Maps) hero CTAs, side by side once
   there's room, stacked full-width on a narrow phone. */
.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* The default .btn-secondary (transparent bg, dark-blue ink, blue border) is
   built for a white/light section — over the hero's dark scrim it fails
   AA contrast and reads as invisible outline-on-dark. This is the ONE
   context that needs the inverted treatment; every other .btn-secondary on
   the site (reviews CTA, doors filter form, contact "get directions") stays
   on a light background and keeps the default look untouched. */
.hero-content .btn-secondary {
  color: #fff;
  border-color: #fff;
}

.hero-content .btn-secondary:hover {
  background: #fff;
  color: var(--nachon-blue-dark);
}

/* ---------- Department banner (PR3) ----------
   pages/department.ts reuses `.hero`/`.hero-media`/`.hero-image`/
   `.hero-overlay`/`.hero-content` exactly — same scrim, same CTAs — this
   modifier only shortens the banner vs. the taller home hero
   (pr3-shape-brief.md §3: "~45vh"). No trust band, no anniversary badge. */
.dept-hero .hero-media {
  min-height: clamp(18rem, 45vw, 28rem);
}

/* ---------- Trust band (F2 Task 3 — 50 years · rating · rebar shop · reach) ---------- */

.trust-band {
  background: var(--nachon-blue-dark);
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
}

.trust-item {
  color: #fff;
  font-weight: 700;
  font-size: 0.875rem;
  letter-spacing: 0.01em;
  padding: 0.4rem 0.85rem;
  border: 1px solid rgba(255, 255, 255, 0.35);
  border-radius: var(--radius);
  white-space: nowrap;
}

/* ---------- CTA buttons (F2 Task 3: primary vs. secondary) ---------- */

.btn-cta,
.btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-target);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius);
  font-weight: 700;
  text-decoration: none;
  transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
}

.btn-cta {
  /* GlareHover port (React Bits) — diagonal light as a sliding background
     layer so label text stays above the sweep (no ::after covering glyphs).
     Fine-pointer hover; reduced-motion keeps a plain color change only. */
  background-color: var(--nachon-yellow);
  background-image: linear-gradient(
    105deg,
    transparent 0%,
    transparent 42%,
    rgb(255 255 255 / 0.5) 50%,
    transparent 58%,
    transparent 100%
  );
  background-size: 220% 100%;
  background-position: 100% 0;
  background-repeat: no-repeat;
  color: var(--color-ink);
  border: 2px solid var(--color-ink);
  transition:
    background-color 120ms ease,
    background-position 0.55s cubic-bezier(0.22, 1, 0.36, 1),
    color 120ms ease,
    border-color 120ms ease;
}

.btn-cta:hover {
  background-color: var(--nachon-yellow-dark);
}

@media (hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference) {
  .btn-cta:hover {
    background-position: 0 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .btn-cta {
    background-image: none;
    transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
  }
}

.btn-secondary {
  background: transparent;
  color: var(--nachon-blue-dark);
  border: 2px solid var(--nachon-blue);
}

.btn-secondary:hover {
  background: var(--nachon-blue);
  color: #fff;
}

/* ---------- Generic content sections ---------- */

main > section {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--space-4) var(--space-3);
}

.cta-band,
.visit-cta {
  background: var(--color-surface);
  text-align: center;
}

.cta-band-lead {
  max-width: 32rem;
  margin: 0 auto var(--space-2);
  font-weight: 700;
  color: var(--color-ink-soft);
}

.page-header {
  padding-bottom: var(--space-2);
  border-bottom: 2px solid var(--nachon-blue);
}

/* ---------- PR4: Terms & Careers reading column ---------- */

/* Narrows `main > section`'s 72rem down to a readable line length for
   paragraph copy (Terms' 5 legal paragraphs, Careers' intro). 40rem is the
   same narrow-content width the cta-band lead and FAQ column already use
   elsewhere in this file — not a new measure. */
.prose {
  max-width: 40rem;
}

.prose p + p,
.prose h2 {
  margin-top: var(--space-2);
}

/* Keep program identity and canonical legal URLs readable on narrow screens. */
.sms-identity-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-style: normal;
  overflow-wrap: anywhere;
}

.legal-doc {
  overflow-wrap: anywhere;
}

/* Neutral border (var(--color-border), not brand blue/yellow) — a callout,
   not an accent card, so it stays gray on all 4 sides rather than a
   colored border-left (the detector's side-tab tell). */
.notice {
  margin-bottom: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  color: var(--color-ink-soft);
}

.careers-offer {
  margin: var(--space-1) 0 var(--space-3);
  padding-left: 1.25em;
  list-style: disc;
}

.careers-offer li + li {
  margin-top: 0.4rem;
}

/* ---------- Categories (F2 Task 3: photo + title + accent rule + arrow) ---------- */

.categories-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--space-3);
}

.category-card {
  display: block;
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-paper);
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  transition: border-color 120ms ease;
}

a.category-card:hover,
a.category-card:focus-visible {
  border-color: var(--nachon-blue);
}

.category-card-image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

.category-card-body {
  position: relative;
  padding: var(--space-2) var(--space-3) var(--space-3);
  border-top: 3px solid var(--nachon-blue);
}

.category-card-body h3 {
  margin-bottom: 0.3em;
  color: var(--nachon-blue-dark);
}

.category-card-body p {
  margin-bottom: 0;
  color: var(--color-ink-soft);
  font-size: 0.9375rem;
}

/* Arrow affordance: PR3 renders this on all 5 category cards — Doors and
   the 4 department pages (pages/department.ts) are all real links now, so
   the "only the clickable card gets the arrow" asymmetry this comment used
   to describe no longer applies. */
.category-card-arrow {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-2);
  width: 16px;
  height: 16px;
  color: var(--nachon-blue);
  transition: transform 150ms ease;
}

.category-card-arrow::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  width: 12px;
  height: 2px;
  background: currentColor;
  transform: translateY(-50%);
}

.category-card-arrow::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 0;
  width: 7px;
  height: 7px;
  border-top: 2px solid currentColor;
  border-right: 2px solid currentColor;
  transform: translateY(-50%) rotate(45deg);
}

a.category-card:hover .category-card-arrow,
a.category-card:focus-visible .category-card-arrow {
  transform: translateX(4px);
}

/* ---------- Department aisle grid + sections (PR3) ----------
   "Letreros de pasillo" (pr3-shape-brief.md §3): boxy anchor cards, no
   photo (no per-subcategory assets exist), 2px border + a bottom accent
   rule instead of the category-card's top rule — same boxy/no-shadow
   vocabulary as the rest of the site, just flatter since there's no photo
   to anchor the card's weight. */
.aisle-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
}

@media (min-width: 48rem) {
  .aisle-grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

@media (min-width: 64rem) {
  .aisle-grid {
    grid-template-columns: repeat(4, 1fr);
  }
}

.aisle-card {
  /* Square corners on purpose (pr3-shape-brief.md §3: "fichas cuadradas y
     boxy") — no `border-radius` here: combined with the bottom accent
     border on the SAME element, a rounded corner visually clashes where the
     thin side border meets the thicker accent border (impeccable
     border-accent-on-rounded). */
  display: block;
  padding: var(--space-2);
  border: 2px solid var(--color-border);
  border-bottom: 3px solid var(--nachon-blue);
  background: var(--color-paper);
  color: inherit;
  text-decoration: none;
}

.aisle-card:hover,
.aisle-card:focus-visible {
  border-color: var(--nachon-blue);
}

.aisle-card-name {
  display: block;
  font-weight: 700;
  color: var(--nachon-blue-dark);
  margin-bottom: 0.3em;
}

.aisle-card-desc {
  display: block;
  color: var(--color-ink-soft);
  font-size: 0.875rem;
}

/* One short section per subcategory, anchored at the aisle card's #id. */
.dept-section {
  padding-top: var(--space-3);
  padding-bottom: var(--space-3);
  border-top: 1px solid var(--color-border);
}

.dept-section:first-of-type {
  border-top: none;
}

.dept-section .door-chip-list {
  margin-top: var(--space-2);
}

/* ---------- Reviews (F2 Task 3: more breathing room) ---------- */

.reviews {
  background: var(--color-surface);
}

.reviews-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-4);
  margin-bottom: var(--space-3);
}

.review-card {
  margin: 0;
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-3);
  background: var(--color-paper);
}

.review-card blockquote {
  margin: 0 0 var(--space-2);
  font-size: 1.0625rem;
  line-height: 1.6;
}

.review-card figcaption {
  font-weight: 700;
  color: var(--nachon-blue-dark);
}

.review-source {
  color: var(--color-ink-soft);
  font-weight: 400;
}

/* ---------- About: history + timeline (F2 Task 3) ---------- */

.history {
  display: grid;
  gap: var(--space-4);
}

@media (min-width: 48rem) {
  .history {
    grid-template-columns: minmax(220px, 320px) 1fr;
    align-items: start;
  }
}

.history-photo img {
  border-radius: var(--radius);
  width: 100%;
}

/* Vertical rail drawn as a pseudo-element (not a `border-left` on a boxed
   card — that pattern is the detector's "side-tab accent border" tell). A
   timeline connector behind a sequence of dated events is a different,
   functional pattern, but it's implemented here without the flagged
   property to stay out of that lookalike. */
.timeline {
  position: relative;
  margin-top: var(--space-3);
  padding-left: var(--space-3);
}

/* Pre-existing About-page timeline rail, unchanged by PR1 (confirmed via
   `git diff origin/main` on this exact block) — flagged as a NEW ratchet
   finding only because PR1's unrelated additions elsewhere in this file
   shifted the detector's snippet context. It's a functional timeline
   connector behind dated events, not a card accent border — see the
   comment above `.timeline` for why this was deliberately built as an
   absolute pseudo-element instead of a `border-left`, the actual
   side-tab tell. */
/* impeccable-disable-next-line side-tab: functional timeline connector rail, not a card accent border — see comment above */
.timeline::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.3em;
  bottom: 0.3em;
  width: 3px;
  background: var(--nachon-blue);
}

.timeline-item {
  position: relative;
  padding-bottom: var(--space-3);
}

.timeline-item:last-child {
  padding-bottom: 0;
}

.timeline-item::before {
  content: "";
  position: absolute;
  left: -28px;
  top: 0.3em;
  width: 11px;
  height: 11px;
  border-radius: 50%;
  background: var(--nachon-yellow);
  border: 2px solid var(--nachon-blue-dark);
}

.timeline-year {
  display: block;
  font-size: 0.8125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--nachon-blue-dark);
  margin-bottom: 0.25em;
}

.timeline-item h3 {
  margin-bottom: 0.2em;
}

.timeline-item p {
  margin-bottom: 0;
  color: var(--color-ink-soft);
}

/* ---------- Contact ---------- */

.phone-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) 0;
  border-bottom: 1px solid var(--color-border);
}

.phone-label {
  font-weight: 600;
  color: var(--color-ink-soft);
}

.contact-phones a,
.contact-email a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  font-weight: 700;
  text-decoration: none;
  border-bottom: 2px solid var(--nachon-blue);
}

.contact-address address {
  font-style: normal;
  margin-bottom: var(--space-2);
}

.contact-hours p {
  margin-bottom: 0.25em;
}

/* Contact card grid (F3 refresh): the 4 legacy contact sections stay as
   wrappers (their classes are pinned by tests + the rules above); each wraps
   a .contact-card. The wrapper is a flex box so same-row cards stretch to
   equal height. */
.contact-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}

@media (min-width: 48rem) {
  .contact-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

.contact-grid > section {
  display: flex;
}

.contact-card {
  flex: 1;
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-paper);
  padding: var(--space-3);
}

/* Scaled-down card title, modeled on .footer-col-title (uppercase,
   letterspaced) — overrides the big yellow-ruled `main h2`. */
.contact-card h2 {
  display: block;
  font-size: 1.1rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--nachon-blue-dark);
  padding-bottom: 0;
  margin-bottom: var(--space-2);
  border-bottom: 0;
}

.contact-visit {
  margin: var(--space-3) 0 0;
}

.contact-visit img {
  display: block;
  width: 100%;
  max-width: 100%;
  height: auto;
  border-radius: var(--radius);
}

/* ---------- Footer (F2 Task 3: 3-column — NAP · horarios · nav) ---------- */

.site-footer {
  background: var(--nachon-blue);
  color: #fff;
  margin-top: var(--space-4);
}

.footer-inner {
  max-width: var(--content-max);
  margin: 0 auto;
  padding: var(--space-4) var(--space-3);
}

.footer-tagline {
  font-size: 1.125rem;
  font-weight: 700;
  max-width: 32rem;
}

.footer-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
  margin: var(--space-2) 0 var(--space-3);
}

@media (min-width: 48rem) {
  .footer-grid {
    /* auto-fit, not a fixed repeat(3|4, 1fr): PR1 added a 4th column
       (Síguenos/Ace) — a hardcoded column count would either orphan it onto
       its own row or need updating every time a column is added/removed. */
    grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr));
    gap: var(--space-4);
  }
}

.footer-col-title {
  display: block;
  font-size: 0.8125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--nachon-yellow);
  margin-bottom: var(--space-1);
}

.footer-nap {
  font-style: normal;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.footer-nap a,
.footer-nav-list a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  text-decoration: underline;
  width: fit-content;
}

.footer-nap a:hover,
.footer-nav-list a:hover {
  color: var(--nachon-yellow);
}

.footer-nav-list {
  display: flex;
  flex-direction: column;
}

.footer-hours {
  display: flex;
  flex-direction: column;
  gap: 0.25em;
  margin: 0;
}

.footer-rights {
  margin-top: var(--space-2);
  color: #dfe4f5;
  font-size: 0.875rem;
}

/* ---------- PR1: footer social + Ace Hardware seal ---------- */

.footer-social-list {
  display: flex;
  flex-direction: column;
  gap: 0.25em;
  margin-bottom: var(--space-2);
}

.footer-social-list a {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  text-decoration: underline;
  width: fit-content;
}

.footer-social-list a:hover {
  color: var(--nachon-yellow);
}

.footer-ace {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.15em;
  min-height: var(--touch-target);
  padding: var(--space-1) var(--space-2);
  background: #fff;
  border-radius: var(--radius);
  text-decoration: none;
  color: var(--color-ink);
  font-size: 0.8125rem;
}

/* Official Ace Hardware mark (assets/img/logo-ace.svg, Wikimedia Commons
   "Ace Hardware Logo.svg", 309×190 viewBox). Nachón is an authorized dealer
   (owner decision 2026-09-10), so the brand mark is shown, not a text stand-in. */
.footer-ace-logo {
  display: block;
  height: 1.75rem;
  width: auto;
}

.footer-ace span:last-child {
  font-weight: 600;
}

.footer-ace:hover {
  background: var(--nachon-yellow);
}

.footer-ace-note {
  margin: 0.4em 0 0;
  font-size: 0.75rem;
  color: #dfe4f5;
}

/* ---------- PR1: fixed mobile call/text/directions bar ---------- */

.mobile-bar {
  display: none;
}

@media (max-width: 47.99rem) {
  .mobile-bar {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 15; /* below the skip-link (20), above the sticky header (10) */
    background: var(--nachon-blue-dark);
    padding-bottom: env(safe-area-inset-bottom);
  }

  /* Room at the bottom of every page so the fixed bar never covers content
     (the footer's own CTA/links in particular). */
  body {
    padding-bottom: calc(var(--touch-target) + env(safe-area-inset-bottom));
  }
}

.mobile-bar-link {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--touch-target);
  color: #fff;
  font-weight: 700;
  font-size: 0.8125rem;
  text-decoration: none;
  text-align: center;
  border-right: 1px solid rgba(255, 255, 255, 0.25);
}

.mobile-bar-link:last-child {
  border-right: none;
}

.mobile-bar-link:hover,
.mobile-bar-link:focus-visible {
  background: var(--nachon-blue);
}

@media print {
  .mobile-bar {
    display: none !important;
  }
}

/* ---------- Doors catalog (F2 Task 7 — /doors, /doors/<slug>) ---------- */

.doors-page-header p {
  max-width: 40rem;
  color: var(--color-ink-soft);
  margin-bottom: 0;
}

/* Decorative catalog banner (pages/doors.ts) — full content width, fixed
   crop band via object-fit:cover. */
.doors-banner {
  width: 100%;
  margin-top: var(--space-3);
}

.doors-banner img {
  width: 100%;
  height: clamp(140px, 22vw, 260px);
  object-fit: cover;
  border-radius: var(--radius);
  display: block;
}

/* Brand quick-filter chips — plain <a> links (pages/doors.ts), each a real
   GET navigation, no JS required. Unselected: outlined. Selected: solid
   fill — same accent-fill language as .btn-cta, never a gradient. */
.doors-brand-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}

.doors-brand-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.35em;
  min-height: var(--touch-target);
  padding: 0.4rem 0.9rem;
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  font-weight: 600;
  font-size: 0.9375rem;
  text-decoration: none;
  color: var(--color-ink);
  transition: border-color 120ms ease, background-color 120ms ease, color 120ms ease;
}

.doors-brand-chip:hover {
  border-color: var(--nachon-blue);
}

.doors-brand-chip.is-active {
  background: var(--nachon-blue);
  border-color: var(--nachon-blue);
  color: #fff;
}

.doors-brand-chip-count {
  color: inherit;
  opacity: 0.75;
  font-weight: 400;
}

/* Filter form (collection/style/top) — one <form method="get">, works
   without JS via the visible submit button; site.js only auto-submits on
   `change` as a convenience (never a requirement). */
.doors-filters {
  background: var(--color-surface);
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-3);
  margin-bottom: var(--space-3);
}

.doors-filters-heading {
  display: block;
  font-size: 0.8125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--color-ink-soft);
  margin-bottom: var(--space-2);
  /* Impeccable P2 (final review 2026-08-04): `main h2` (the page-title rule)
     redeclares display/font-size/font-weight/letter-spacing/margin-bottom
     above but not these two — by CSS specificity-per-property, `main h2`'s
     3px yellow underline still applied to this small uppercase filter label,
     visually out of scale with the rest of the type system. */
  border-bottom: none;
  padding-bottom: 0;
}

.doors-filters-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-2);
  margin-bottom: var(--space-2);
}

.doors-filter-field label {
  display: block;
  font-size: 0.8125rem;
  font-weight: 600;
  margin-bottom: 0.3em;
}

.doors-filter-field select {
  width: 100%;
  min-height: var(--touch-target);
  padding: 0.5rem 0.6rem;
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-paper);
  color: var(--color-ink);
  font: inherit;
}

.doors-filter-field select:focus-visible {
  outline: 3px solid var(--nachon-yellow);
  outline-offset: 1px;
}

.doors-filters-clear {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  font-weight: 600;
  text-decoration: underline;
  color: var(--nachon-blue-dark);
  margin-bottom: var(--space-2);
}

.doors-results-count,
.doors-results-empty {
  font-weight: 600;
  color: var(--color-ink-soft);
}

/* ---------- Door cards grid ---------- */

.doors-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.door-card {
  display: block;
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-paper);
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  transition: border-color 120ms ease;
}

.door-card:hover,
.door-card:focus-visible {
  border-color: var(--nachon-blue);
}

/* `contain`, nunca `cover`: las fotos del catálogo son recortes altos y
   estrechos de la hoja de la puerta — `cover` en un box 4:5 amplía el centro
   en una tira borrosa (visto en prod con PBI). Mismo patrón que el catálogo
   del kiosko de clientes (customer-kiosk/doors.tsx: `object-contain` en caja
   de altura fija). */
.door-card-image {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: contain;
  padding: var(--space-2);
  background: var(--color-surface);
}

.door-card-body {
  padding: var(--space-2);
  border-top: 3px solid var(--nachon-blue);
}

.door-card-body h3 {
  font-size: 0.9375rem;
  margin-bottom: 0.2em;
  color: var(--nachon-blue-dark);
}

.door-card-body p {
  margin-bottom: 0;
  color: var(--color-ink-soft);
  font-size: 0.875rem;
}

/* ---------- Pagination ---------- */

.doors-pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  margin-top: var(--space-4);
}

.doors-page-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: 0.4rem 1rem;
  border: 2px solid var(--nachon-blue);
  border-radius: var(--radius);
  font-weight: 700;
  text-decoration: none;
  color: var(--nachon-blue-dark);
}

.doors-page-link:hover {
  background: var(--nachon-blue);
  color: #fff;
}

.doors-page-link.is-disabled {
  border-color: var(--color-border);
  color: var(--color-border);
}

.doors-page-status {
  font-size: 0.875rem;
  color: var(--color-ink-soft);
  /* No `white-space: nowrap` (CSS audit, final review 2026-08-04): the
     container now wraps (.doors-pagination flex-wrap above), but the status
     text itself ("Page X of Y") should also be free to wrap on very narrow
     viewports instead of forcing a horizontal overflow of its own. */
}

/* ---------- Door detail (/doors/<slug>) ---------- */

.doors-back-link {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  max-width: var(--content-max);
  margin: var(--space-2) auto 0;
  padding: 0 var(--space-3);
  width: 100%;
  font-weight: 700;
  text-decoration: underline;
  color: var(--nachon-blue-dark);
}

.door-detail {
  display: grid;
  gap: var(--space-4);
}

@media (min-width: 56rem) {
  .door-detail {
    grid-template-columns: minmax(280px, 26rem) 1fr;
    align-items: start;
  }
}

.door-detail-image {
  width: 100%;
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
}

.door-detail-gallery {
  display: flex;
  gap: var(--space-1);
  margin-top: var(--space-2);
  overflow-x: auto;
}

/* `contain` por el mismo motivo que .door-card-image: los recortes altos de
   puerta se vuelven tiras irreconocibles con `cover` en un cuadrado de 72px. */
.door-detail-thumb {
  width: 72px;
  height: 72px;
  flex-shrink: 0;
  object-fit: contain;
  background: var(--color-surface);
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
}

.door-detail-specs {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.4em var(--space-2);
  margin: 0;
}

.door-detail-specs dt {
  font-weight: 600;
  color: var(--color-ink-soft);
}

.door-detail-specs dd {
  margin: 0;
  font-weight: 700;
}

.door-chip-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.door-chip {
  padding: 0.35rem 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--color-ink-soft);
}

/* PR 5 (catálogo Emtek, shape §8.2): a `.door-chip` that IS a live link
   (Emtek → /door-hardware, department.ts) gets the same interactive
   treatment as `.doors-brand-chip`/`.aisle-card` — border-color change on
   hover/focus, no new underline or icon. `:focus-visible`'s outline is
   already global (`a:focus-visible`, line ~149); this only adds the
   hover/focus border color specific to the chip shape. */
a.door-chip {
  text-decoration: none;
  cursor: pointer;
}

/* Fix pass (finding MEDIUM/HTML validity) — a linked chip is now
   `<li class="door-chip-link"><a class="door-chip">...</a></li>` (a `<ul>`
   may only contain `<li>` as a direct child; a bare `<a>` sibling of
   `<li class="door-chip">` was invalid). Every OTHER `.door-chip` is still a
   direct child of the `display: flex` `.door-chip-list` and gets blockified
   by the flex algorithm for free; the `<a>` here is now one level removed
   from that flex context, so `display: flex` here re-creates the same
   blockified box for it (this `<li>` has exactly one child), keeping its
   padding/border identical to its sibling chips instead of shrinking to an
   inline box's height. */
.door-chip-link {
  display: flex;
}

a.door-chip:hover,
a.door-chip:focus-visible {
  border-color: var(--nachon-blue);
}

.door-detail-cta {
  background: var(--color-surface);
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  padding: var(--space-3);
}

.door-detail-cta p {
  color: var(--color-ink-soft);
}

/* ============================================================
   §motion — ReactBits ports (2026-08-04)
   docs/superpowers/plans/2026-08-04-nachon-site-motion.md

   Progressive-enhancement contract (site.js's own header comment carries the
   JS half): every rule below is either (a) scoped under `html.js-motion` —
   the class site.js adds ONLY when
   `matchMedia('(prefers-reduced-motion: reduce)').matches` is false, so a
   no-JS or reduced-motion visitor never starts from a hidden/blurred state
   — or (b) SpotlightCard's plain CSS-only hover effect, which degrades to
   "nothing happens" without JS, never to "content missing". Every animated
   rule is ALSO wrapped in `@media (prefers-reduced-motion: no-preference)`
   (belt) on top of the global `prefers-reduced-motion: reduce` nuke already
   declared near the top of this file (suspenders — see "Focus & motion
   (global)" above). ============================================================ */

/* ---- 1. SplitText/BlurText (hero h1, home.ts) ---- */

@media (prefers-reduced-motion: no-preference) {
  html.js-motion .hero-word {
    display: inline-block;
    opacity: 0;
    filter: blur(8px);
    transform: translateY(0.4em);
    animation: hero-word-in 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  }

  /* One rule per word position. The PR1 heroTitle is 11 words in EN and
     ES (was 4); nachon-site-layout.test.ts asserts the delays cover every
     word of both titles so a longer copy can never leave a tail of words
     popping in with no stagger (PR1 review MEDIUM). */
  html.js-motion .hero-word:nth-child(1) { animation-delay: 0ms; }
  html.js-motion .hero-word:nth-child(2) { animation-delay: 60ms; }
  html.js-motion .hero-word:nth-child(3) { animation-delay: 120ms; }
  html.js-motion .hero-word:nth-child(4) { animation-delay: 180ms; }
  html.js-motion .hero-word:nth-child(5) { animation-delay: 240ms; }
  html.js-motion .hero-word:nth-child(6) { animation-delay: 300ms; }
  html.js-motion .hero-word:nth-child(7) { animation-delay: 360ms; }
  html.js-motion .hero-word:nth-child(8) { animation-delay: 420ms; }
  html.js-motion .hero-word:nth-child(9) { animation-delay: 480ms; }
  html.js-motion .hero-word:nth-child(10) { animation-delay: 540ms; }
  html.js-motion .hero-word:nth-child(11) { animation-delay: 600ms; }
  html.js-motion .hero-word:nth-child(12) { animation-delay: 660ms; }
  html.js-motion .hero-word:nth-child(13) { animation-delay: 720ms; }
  html.js-motion .hero-word:nth-child(14) { animation-delay: 780ms; }
}

@keyframes hero-word-in {
  to {
    opacity: 1;
    filter: blur(0);
    transform: translateY(0);
  }
}

/* ---- 2. CountUp (trust band numbers, home.ts) — no CSS: a plain JS text
   update via requestAnimationFrame, no visual state here to gate. ---- */

/* ---- 3. ScrollReveal/FadeContent (section headings + department cards +
   review cards on home; timeline items on about; contact blocks on
   contact) ---- */

@media (prefers-reduced-motion: no-preference) {
  html.js-motion .reveal {
    opacity: 0;
    transform: translateY(16px);
    transition: opacity 0.5s, transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  }

  html.js-motion .reveal.is-visible {
    opacity: 1;
    transform: none;
  }

  /* Specificity collision guard (fix round 1, code review): `.category-card`
     (~L487 above) has its own pre-existing
     `transition: border-color 120ms ease;`. Both selectors have equal
     specificity (0,2,0 vs 0,1,1 — the html+class combo below still wins on
     source order alone against a bare `.category-card`, but relying on
     cascade order here would be fragile), and the `transition` shorthand
     does NOT merge across rules — whichever declaration applies LAST wins
     for the whole property, dropping the other transition entirely. Without
     this rule, the 5 `.category-card.reveal.spotlight` cards would lose
     their border-color hover transition (instant snap) the moment
     `js-motion` is active. Re-declaring the full list here, once, on the
     combined selector keeps both transitions working together.
     server/__tests__/nachon-site-motion.test.ts pins this rule's presence. */
  html.js-motion .category-card.reveal {
    transition: opacity 0.5s, transform 0.5s cubic-bezier(0.22, 1, 0.36, 1), border-color 120ms ease;
  }
}

/* ---- 4. SpotlightCard (department cards, home.ts). Pure CSS hover +
   `--spot-x`/`--spot-y` custom properties — site.js sets them via
   pointermove, fine-pointer devices only. Degrades to a centered glow
   without JS (the var() fallbacks below), never gated by `js-motion`. The
   radial-gradient is a light OVERLAY on a photo card, not gradient TEXT and
   not a glow border. ---- */

.spotlight {
  position: relative;
  overflow: hidden;
}

.spotlight::after {
  content: "";
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.3s;
  pointer-events: none;
  background: radial-gradient(
    240px circle at var(--spot-x, 50%) var(--spot-y, 50%),
    rgb(255 255 255 / 0.14),
    transparent 65%
  );
}

@media (hover: hover) and (pointer: fine) {
  .spotlight:hover::after {
    opacity: 1;
  }
}

/* ---- 5. LogoLoop (brands strip, home.ts — between departments and
   reviews). Default state (no JS or reduced-motion): plain wrapped list,
   the duplicate `aria-hidden` track hidden entirely. Under `js-motion` +
   `no-preference`: the CSS keyframe marquee runs and the duplicate track
   becomes the second, looping copy; hover pauses it. ---- */

.brands {
  background: var(--color-surface);
  text-align: center;
}

.brands-marquee {
  overflow: hidden;
}

.brands-track {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  margin: 0;
  padding: var(--space-1) 0;
}

.brands-track a {
  display: inline-block;
  font-weight: 700;
  font-size: 0.9375rem;
  color: var(--color-ink-soft);
  text-decoration: none;
  white-space: nowrap;
}

.brands-track a:hover,
.brands-track a:focus-visible {
  color: var(--nachon-blue-dark);
}

.brands-track[aria-hidden="true"] {
  display: none;
}

@media (prefers-reduced-motion: no-preference) {
  html.js-motion .brands-marquee {
    display: flex;
  }

  html.js-motion .brands-track {
    flex-wrap: nowrap;
    animation: marquee 30s linear infinite;
  }

  html.js-motion .brands-track[aria-hidden="true"] {
    display: flex;
  }

  html.js-motion .brands-marquee:hover .brands-track {
    animation-play-state: paused;
  }
}

@keyframes marquee {
  to {
    transform: translateX(-100%);
  }
}

/* ---------- FAQ (home — compact paraphrases of the live-site FAQ) ---------- */

.faq-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  max-width: 42rem;
  margin: 0 auto;
}

.faq-item {
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  padding: 0;
}

.faq-item summary {
  cursor: pointer;
  list-style: none;
  font-weight: 700;
  padding: var(--space-2) var(--space-3);
  color: var(--color-ink);
}

.faq-item summary::-webkit-details-marker {
  display: none;
}

.faq-item summary::after {
  content: "+";
  float: right;
  font-weight: 700;
  color: var(--nachon-blue);
}

.faq-item[open] summary::after {
  content: "–";
}

.faq-item p {
  margin: 0;
  padding: 0 var(--space-3) var(--space-2);
  color: var(--color-ink-soft);
  font-size: 0.9375rem;
}

/* ============================================================
   PR 5 — Emtek hardware catalog (/door-hardware, /door-hardware/<slug>)
   docs/superpowers/specs/2026-09-24-door-hardware-shape.md

   Extension of the /doors world (shape §3), not a new one: same tokens,
   same border-top/border-bottom accent language (never border-left — the
   side-tab tell this file already avoids on .door-card-body above). The
   only two genuinely new low-level pieces are the finish swatch (a small
   square image variant, never circular — no new shape language) and the
   generic-photo note (plain secondary text, same treatment as
   .door-card-body p / .aisle-card-desc, no new badge/icon) — shape §3.
   ============================================================ */

/* ---------- /doors → /door-hardware cross-sell (rule 5, shape §8.1) ---------- */

.doors-hardware-cross-sell {
  margin-top: var(--space-4);
}

/* ---------- Hub filters: category + finish chips ----------
   Fix pass (UX audit 2026-09-25, P0): both containers used to render their
   own natural-width `<a>` rows (`.hardware-category-chip`/`.hardware-finish-
   chip`, deleted here) — the ragged-row bug the owner reported by name.
   `renderCategoryChips`/`renderFinishChips` (pages/hardware.ts) now emit the
   shared `.hardware-option-grid`/`.hardware-option-cell` markup instead;
   these two selectors are kept only as the grid's OWN container classes
   (`extraClasses` in `optionGrid()`) for the vertical rhythm below them —
   the display/layout rules that used to live here (and, for
   `.hardware-finish-chips`, silently lost the flex-vs-grid race against
   `.hardware-option-grid` by source order alone) are gone for good. */
.hardware-option-grid.hardware-category-chips,
.hardware-option-grid.hardware-finish-chips {
  margin-bottom: var(--space-3);
}

/* ---------- Hub grid ---------- */

.hardware-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 260px), 1fr));
  gap: var(--space-3);
  margin-top: var(--space-2);
}

.hardware-card {
  display: block;
  border: 2px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-paper);
  overflow: hidden;
  color: inherit;
  text-decoration: none;
  transition: border-color 120ms ease;
}

.hardware-card:hover,
.hardware-card:focus-visible {
  border-color: var(--nachon-blue);
}

/* Preserve the complete silhouette, including long handles and their backplates. */
.hardware-card-image {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: contain;
  padding: var(--space-2);
  background: var(--color-paper);
}

.hardware-card-body {
  padding: var(--space-2) var(--space-2) var(--space-3);
  border-top: 1px solid var(--color-border);
}

.hardware-card-body h3 {
  font-size: 1.0625rem;
  margin-bottom: 0.2em;
  color: var(--nachon-blue-dark);
}

.hardware-card-body p {
  margin-bottom: 0.5em;
  color: var(--color-ink-soft);
  font-size: 0.875rem;
}

/* Flat inline row, no background/border of its own (shape §6.7 — "nunca una
   tarjeta anidada dentro de la tarjeta"): same treatment as .door-chip-list
   but swatches instead of text chips. */
.hardware-card-swatches {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.3em;
}

.hardware-card-swatch {
  width: 20px;
  height: 20px;
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
  object-fit: cover;
}

.hardware-card-swatch-more {
  font-size: 0.75rem;
  font-weight: 600;
  color: var(--color-ink-soft);
}

/* Fix pass — a finish with no swatch image (nullable column, §5.1) still
   renders here as a text fallback instead of dropping the finish (regla dura
   #3): same box as .hardware-card-swatch, initials instead of a photo. */
.hardware-card-swatch-text {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border-radius: var(--radius);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  color: var(--color-ink-soft);
  font-size: 0.5625rem;
  font-weight: 700;
  line-height: 1;
}

/* ---------- Detail (/door-hardware/<slug>) ---------- */

.breadcrumb-nav {
  display: block;
  max-width: var(--content-max);
  margin: var(--space-2) auto 0;
  padding: 0 var(--space-3);
  width: 100%;
  font-size: 0.875rem;
  color: var(--color-ink-soft);
}

.breadcrumb-nav a {
  font-weight: 600;
  text-decoration: underline;
  color: var(--nachon-blue-dark);
}

.breadcrumb-nav [aria-current="page"] {
  font-weight: 700;
  color: var(--color-ink);
}

/**
 * Fix pass (finding MEDIUM/design): the Finish selector's label sits inside
 * `.door-detail-media`, which the composed markup renders BEFORE
 * `.door-detail-info`'s `<h1>` — a real `<h2>` here would land ahead of the
 * page's own h1 in document order, breaking the heading outline for screen
 * readers / axe. Mirrors `main h2`'s exact visual treatment on a `<p>`
 * instead of a heading tag, so the owner directive (Finish/Specifications/
 * Functions/style-tile labels never drift apart in weight) still holds —
 * only the semantics change, never the look.
 */
.hardware-detail-finish-label {
  display: inline-block;
  font-size: var(--text-h2);
  font-weight: 800;
  letter-spacing: -0.01em;
  padding-bottom: 0.3em;
  margin-bottom: var(--space-3);
  border-bottom: 3px solid var(--nachon-yellow);
}

/* Fix pass (UX audit 2026-09-25, P0): used to declare its own `display: flex`
   here — same "wins today by source-order luck alone" landmine as the hub's
   `.hardware-finish-chips` (see that rule's own comment). The grid layout
   now comes from `.hardware-option-grid` (via `optionGrid()`'s
   `extraClasses`); this selector only carries the container's vertical
   rhythm. */
.hardware-option-grid.hardware-detail-finishes {
  margin-top: var(--space-2);
}

/* §5.1 generic-photo note — same secondary-text treatment as
   .door-card-body p / .aisle-card-desc, no new badge/icon (shape §3.2). */
.hardware-finish-generic-note {
  margin-top: var(--space-1);
  color: var(--color-ink-soft);
  font-size: 0.875rem;
}

.hardware-detail-description {
  color: var(--color-ink-soft);
}

.hardware-detail-specs-html {
  margin-top: var(--space-2);
  color: var(--color-ink);
}

.hardware-detail-documents {
  list-style: none;
  padding: 0;
  margin: 0;
}

.hardware-detail-documents li {
  margin-bottom: 0.4em;
}

.hardware-detail-documents a {
  font-weight: 600;
  text-decoration: underline;
  color: var(--nachon-blue-dark);
}

.hardware-detail-back {
  display: inline-flex;
  align-items: center;
  min-height: var(--touch-target);
  font-weight: 600;
  text-decoration: underline;
  color: var(--nachon-blue-dark);
  margin-top: var(--space-2);
}

/* ---------- Option grid (PR 8, "paridad UX" — owner design directive
   2026-09-25; UX fix pass same day): shared equal-size selector grid for ALL
   SIX selection groups on the public site — hub category chips, hub finish
   chips, the ficha's finish selector, its Functions pills and its Rosettes /
   Knob & lever style tiles — one recipe instead of drifting per-group rows.
   Cells are plain siblings of a CSS grid, never a card nested inside another
   card. */
.hardware-option-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--space-1);
  margin: 0;
  padding: 0;
}

/* Finish chips (hub + detail selector) lay out as a ROW inside the cell:
   28px swatch, name, count at the right — the cell minimum leaves room for
   the longest finish name ("Brushed Stainless Steel") before the ellipsis. */
.hardware-option-grid--finish {
  grid-template-columns: repeat(auto-fill, minmax(13.5rem, 1fr));
}

.hardware-option-grid--finish .hardware-option-cell-inner {
  flex-direction: row;
  justify-content: flex-start;
  text-align: left;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2) var(--space-1) var(--space-1);
}

.hardware-option-grid--finish .hardware-option-cell-media {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
}

.hardware-option-grid--finish .hardware-option-cell-row,
.hardware-option-grid--finish .hardware-option-cell-label {
  width: auto;
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.875rem;
}

/* Category chips (hub) and Functions pills (ficha) — text-only, no media
   slot, wider cell than the finish/style grids so longer labels (e.g.
   "Front Door Entry Sets", "Smart Lock Single Cylinder") still fit one line
   before the ellipsis fallback below kicks in. */
.hardware-option-grid--pill {
  grid-template-columns: repeat(auto-fill, minmax(13.5rem, 1fr));
}

.hardware-option-grid--pill .hardware-option-cell-inner {
  text-align: left;
  padding-left: var(--space-2);
  padding-right: var(--space-2);
}

.hardware-option-grid--pill .hardware-option-cell-label {
  font-size: 0.9375rem;
}

/* Style / rosette tiles: names such as "L-Square Rosso Levanto Marble
   Lever" get two lines with a fixed two-line box, so every tile in a row
   keeps the same height whether its name needs one line or two. */
.hardware-option-grid--style .hardware-option-cell-label {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  white-space: normal;
  min-height: 2.4em;
}

.hardware-option-cell {
  display: flex;
}

.hardware-option-cell-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  width: 100%;
  min-height: var(--touch-target);
  padding: var(--space-1);
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  background: var(--color-surface);
  text-align: center;
  text-decoration: none;
  color: var(--color-ink);
  cursor: default;
}

a.hardware-option-cell-inner {
  cursor: pointer;
}

a.hardware-option-cell-inner:hover,
a.hardware-option-cell-inner:focus-visible {
  border-color: var(--nachon-blue);
}

/* Fix pass (UX audit 2026-09-25, P1): selected state used to grow the border
   from 1px to 2px, shifting the box by 1px against its unselected siblings
   and (with the `font-weight: 700` that used to sit here too) shifting the
   label's own ellipsis truncation point. One border weight everywhere now
   (1px, set above); the "selected" signal is this ring PLUS `aria-current`
   (link cells) — never colour/weight alone (shape §11's accessibility
   rule). `box-shadow` never affects layout, so the box stays byte-identical
   in size to its unselected neighbours. */
.hardware-option-cell-inner.is-active {
  border-color: var(--nachon-blue);
  box-shadow: 0 0 0 2px var(--nachon-blue);
}

/* Fix pass (UX audit 2026-09-25, P2): the migrated swatch/thumbnail media box
   used to inherit `.hardware-finish-chip-swatch`'s `border: 1px solid
   var(--color-border)` — dropped in the shared-grid migration, so a
   light-colored finish (Satin Nickel, Polished Chrome) or a light product
   photo rendered edgeless against the `--color-surface` cell background.
   Restored here so hub finish chips, ficha finish selector and style tiles
   all match the kiosk's own `SwatchDot`/`SwatchPlaceholder` (both keep a
   Tailwind `border` class). */
.hardware-option-cell-media {
  border: 1px solid var(--color-border);
  border-radius: var(--radius);
  object-fit: cover;
}

.hardware-option-grid--style .hardware-option-cell-media {
  width: 64px;
  height: 64px;
}

/* No image (nullable column) — same symmetric-padding fallback as the old
   `.hardware-finish-chip-no-swatch`: text-only, never a broken image element. */
.hardware-option-cell-no-media {
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}

/* Fix pass (UX audit 2026-09-25, P1): single-line label + ellipsis instead of
   `overflow-wrap: anywhere`, which used to wrap a long label (e.g. "L-Square
   Rosso Levanto Marble Lever") to 2-3 lines inside a fixed-width cell,
   breaking row-height symmetry with its shorter neighbours. `title=` (set by
   both `optionGridLinkCell`/`optionGridStaticCell`) still carries the full
   name. `width: 100%` makes the label claim the cell's full cross-axis width
   even though the parent centers its children (`align-items: center`),
   which is what lets the ellipsis have a fixed box to truncate against. */
.hardware-option-cell-label {
  display: block;
  width: 100%;
  min-width: 0;
  font-size: 0.8125rem;
  line-height: 1.2;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Fix pass (UX audit 2026-09-25, P2): label + count used to both be centered
   children stacked in the column flex above — "centered under a centered
   label", not the "right-aligned, atenuado" the owner asked for. A cell with
   a count (hub category chips, hub finish chips) wraps both in this row
   instead; a cell without one (ficha finish selector, Functions, Rosettes /
   style tiles) never emits it, so nothing changes there. */
.hardware-option-cell-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-1);
  width: 100%;
}

.hardware-option-cell-row .hardware-option-cell-label {
  width: auto;
  flex: 1 1 auto;
}

.hardware-option-cell-count {
  flex-shrink: 0;
  font-size: 0.75rem;
  color: var(--color-ink-soft);
}

/* ---------- Knob & lever style tabs (PR 8 §7) — native <details>/<summary>
   accordion, no JS. No border/background on <details> itself: the tiles
   inside already have their own box (.hardware-option-cell-inner), and a
   second box around them would be a nested card. */
details[name="hardware-style-group"] {
  margin-bottom: var(--space-2);
}

details[name="hardware-style-group"] summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  min-height: var(--touch-target);
  padding: var(--space-1) 0;
  font-weight: 700;
}

details[name="hardware-style-group"] summary:focus-visible {
  outline: 3px solid var(--nachon-yellow);
}

/* Native consent shares the store's existing signage and contact controls. */
.contact-enrollment { max-width: 46rem; margin: 2.5rem auto 0; padding: 0 1rem 2.5rem; scroll-margin-top: 7rem; }
.consent-panel { border-top: 3px solid var(--nachon-blue); padding-top: 1.5rem; overflow-wrap: anywhere; }
.consent-header { border-bottom: 1px solid var(--color-border); padding-bottom: 1rem; }
.consent-brand { margin: 0; font-size: 1.5rem; }
.consent-purpose { margin-top: 1.5rem; }
.consent-purpose-heading { font-size: 1.125rem; margin: 0 0 .5rem; }
.consent-purpose-copy { line-height: 1.6; }
.consent-field { margin-top: 1rem; }
.consent-field-label { display: block; font-weight: 700; margin-bottom: .5rem; }
.consent-input { width: 100%; min-height: 2.75rem; border: 1px solid var(--color-border); border-radius: 3px; padding: .75rem; background: var(--color-paper); color: var(--color-ink); font: inherit; }
.consent-choice { display: flex; align-items: flex-start; gap: .75rem; margin-top: 1.5rem; line-height: 1.6; cursor: pointer; }
.consent-checkbox { flex: 0 0 auto; width: 1.25rem; height: 1.25rem; margin-top: .25rem; accent-color: var(--nachon-blue); }
.consent-optional { display: block; margin-bottom: .25rem; }
.consent-disclosure, .consent-legal-notice, .consent-business { color: var(--color-ink-soft); font-size: .875rem; line-height: 1.6; }
.consent-link, .consent-legal-links a { color: var(--nachon-blue-dark); text-decoration: underline; text-underline-offset: .2em; }
.consent-submit { margin-top: 1rem; min-height: 2.75rem; width: 100%; border: 2px solid var(--color-ink); border-radius: 3px; background: var(--nachon-yellow); color: var(--color-ink); padding: .75rem 1.5rem; font: inherit; font-weight: 700; cursor: pointer; }
.consent-submit:hover { background: var(--nachon-yellow-dark); }
.consent-input:focus-visible, .consent-checkbox:focus-visible, .consent-submit:focus-visible { outline: 3px solid var(--nachon-blue); outline-offset: 3px; }
.consent-business { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid var(--color-border); }
.contact-enrollment-receipt { max-width: 46rem; margin: 0 auto; padding: 2.5rem 1rem; }
.consent-receipt { line-height: 1.6; overflow-wrap: anywhere; }
