/* Nocturne — single stylesheet for MonoKromatik (ADR-003). Tokens live in
   tokens.css, linked before this file in every layout; this file consumes
   them and never defines a color, size, spacing, radius, shadow, duration or
   z-index outside a custom property. Inter is self-hosted (see
   wwwroot/fonts/) instead of loaded from Google Fonts. */

/* All four faces below are subset to Latin + typographic punctuation (UX-PRF-005/RNF-PRF-008 —
   "subconjuntos de caracteres"): every page on this site is in Spanish, so U+0000–00FF alone
   covers it (Basic Latin plus Latin-1 Supplement, which already includes á/é/í/ó/ú/ñ/ü/¿/¡),
   plus the em/en dashes, curly quotes and ellipsis editorial copy uses, the euro sign, and the
   ← ↑ → ↓ / ✓ / ⚠ glyphs the panel and portfolio-viewer UI sets directly as text (round-2 quality
   and security review, US-022 — the original, un-subset Inter already covered these; a first pass
   at this subset dropped them, falling back to a system font with a visibly different weight/
   baseline next to the rest of the Inter text). Re-subset with:
     python -m fontTools.subset <file>.woff2
       --unicodes="U+0000-00FF,U+2013-2026,U+20AC,U+2190-2193,U+2713,U+26A0" --layout-features='*'
       --flavor=woff2 --output-file=<file>.woff2
   against the original files from the Inter 4.1 release (github.com/rsms/inter/releases) — not
   against the already-subset files in this folder, or the missing glyphs stay missing. */
@font-face {
  font-family: Inter;
  src: url("../fonts/InterVariable.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
  unicode-range: U+0000-00FF, U+2013-2026, U+20AC, U+2190-2193, U+2713, U+26A0;
}

/* Hinted static instances for the weights this stylesheet actually uses for running text (body
   copy at 300, headings at --font-heading-weight/500). For these exact weights both this face and
   InterVariable above match with zero distance, so the browser's tie-break applies: the LAST
   matching @font-face rule in source order wins, which is why these three are declared after the
   variable font — keep them below it. Needed because InterVariable ships without hinting, which
   Windows' DirectWrite renders visibly heavier than intended at paragraph size (found testing the
   legal pages in Edge/Windows; https://github.com/rsms/inter ships these pre-hinted under
   extras/woff-hinted/ for exactly this reason). Other weights (600, 700…) still fall back to the
   variable font above. */
@font-face {
  font-family: Inter;
  src: url("../fonts/Inter-Light.woff2") format("woff2");
  font-weight: 300;
  font-display: swap;
  unicode-range: U+0000-00FF, U+2013-2026, U+20AC, U+2190-2193, U+2713, U+26A0;
}

@font-face {
  font-family: Inter;
  src: url("../fonts/Inter-Regular.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
  unicode-range: U+0000-00FF, U+2013-2026, U+20AC, U+2190-2193, U+2713, U+26A0;
}

@font-face {
  font-family: Inter;
  src: url("../fonts/Inter-Medium.woff2") format("woff2");
  font-weight: 500;
  font-display: swap;
  unicode-range: U+0000-00FF, U+2013-2026, U+20AC, U+2190-2193, U+2713, U+26A0;
}

/* ══════════════════════════════════════════════════════════════════════════
   1. TOKENS AND BASE
   Resets, typography scale, focus, selection and reduced-motion handling.
   ══════════════════════════════════════════════════════════════════════ */

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

/* The `hidden` attribute must win over any layout rule a component sets on
   itself (.checkbox-row is display:flex, for instance), otherwise an element
   a script hides stays on screen. */
[hidden] { display: none !important; }

/* Same problem, for the native <dialog>: the user agent stylesheet's own
   `dialog:not([open]) { display: none }` loses to any author rule that sets
   display on .dialog (it must, so showModal()'s "flex when open" layout is
   possible at all), which left a <dialog class="dialog"> visible even
   without [open]. This restores "closed by default" before .dialog's own
   display rule can apply. */
dialog:not([open]) { display: none !important; }

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

  /* Single dark theme in phase 1, regardless of the visitor's own OS/browser preference
     (RNF-CMP-005 — "no depende de prefers-color-scheme"). Without this, native form chrome that
     this stylesheet cannot restyle with tokens — the type="date" calendar popup, scrollbars —
     falls back to whatever the browser's own light/dark preference is, which reads as broken
     against a page that is always dark (found testing Contacto's date field, US-014). */
  color-scheme: dark;

  /* Keyboard focus (and scrollIntoView) never lands under .wa-float, on any public page
     (UX-ACC-015) — generous enough to clear the button's own height plus its offset from the
     edge in both the mobile (above the tab bar) and desktop position. */
  scroll-padding-bottom: calc(var(--size-touch-lg) + var(--space-8));
}

body {
  margin: 0;
  background: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: var(--font-size-300);
  line-height: 1.5;
  font-weight: 400;
}

h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  line-height: 1.15;
  letter-spacing: -0.015em;
  margin: 0 0 var(--space-2);
}

h1 { font-size: var(--font-size-800); }
h2 { font-size: var(--font-size-700); }
h3 { font-size: var(--font-size-600); }
h4 { font-size: var(--font-size-500); }
h5 { font-size: var(--font-size-400); }

/* The "kicker + title + subtitle" header shared by every second-level public page (Servicios,
   Portafolio, Contacto, FAQ, Nosotros): every one of their prototypes (servicios.dc.html,
   nosotros.dc.html, ...) fluid-sizes this title instead of the generic, flat h1 rule above.
   The bounds are still tokens (UX-VIS-001) rather than the prototype's raw px, per ADR-003's
   own "se retunea la escala" adjustment. */
.page-title {
  font-size: clamp(var(--font-size-600), 4vw, var(--font-size-700));
}

.page-subtitle {
  color: var(--color-neutral-300);
  font-size: var(--font-size-200);
}

/* A second heading inside the same top-level section (e.g. Nosotros' "Cómo trabajamos" below
   the profile — nosotros.dc.html keeps it in one <section>, not a new banded one), smaller than
   the page title and with its own top margin instead of a section border/background. */
.subsection-title {
  margin: var(--space-9) 0 var(--space-4);
  font-size: var(--font-size-400);
}

h6 {
  font-size: var(--font-size-200);
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

p {
  margin: 0 0 var(--space-3);
}

/* One step dimmer and lighter than headings/UI labels (which keep --color-text at 400/500):
   plain running text reads visibly heavier than intended at the body default (--color-text's
   near-white plus weight 400 against the dark background — found on the legal pages'
   paragraphs), so long-form copy gets its own lighter weight and dimmer color. Scoped to
   `:not([class])` rather than every `<p>`: many single-purpose classes (.card-title,
   .dialog-title, .list-row-title, .portfolio-viewer-counter, .card-body, .dialog-body…) are
   applied to a bare <p> and rely on inheriting color/weight from an ancestor or being read at
   full --color-text; a plain `p { color; font-weight }` rule sat above their specificity and
   silently overrode that (round-1 quality review, US-022 — found on .publish-requirement-body p
   and several others). A classless <p> (legal text, FAQ answers, the empty-portfolio message…)
   still gets the dimmer treatment. */
p:not([class]) {
  color: var(--color-text-subtle);
  font-weight: 300;
}

.prose { max-width: 65ch; }

a {
  color: var(--color-accent);
  text-underline-offset: calc(var(--space-1) * 0.75);
}

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

figure { margin: 0; }

figcaption {
  font-size: var(--font-size-100);
  margin-top: var(--space-1);
  color: var(--color-text-muted);
}

button, input, textarea, select { font-family: inherit; }

/* Photographs sit inside .lighten so their dark backgrounds blend with the
   page ground (ADR-003 "Imágenes"). */
.lighten {
  mix-blend-mode: lighten;
  background-color: transparent;
}

.text-muted { color: var(--color-text-muted); }

.kicker, .card-kicker {
  font-size: var(--font-size-100);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-accent);
}

.visually-hidden {
  position: absolute;
  width: 0.0625rem;
  height: 0.0625rem;
  padding: 0;
  margin: -0.0625rem;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* "Saltar al contenido" — first focusable element, off-screen until it
   receives focus (UX-NAV-008). */
.skip-link {
  position: absolute;
  left: var(--space-2);
  top: -3.75rem;
  z-index: var(--z-dialog);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  text-decoration: none;
  font-size: var(--font-size-200);
  transition: top var(--duration-fast) ease-out;
}

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

:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }

/* Freestanding rules fade to transparent at both ends — a Nocturne
   signature; box outlines and control separators stay solid. */
.hr {
  height: var(--hairline);
  border: 0;
  margin: var(--space-4) 0;
  background: linear-gradient(to right,
    transparent, var(--color-divider) var(--space-12),
    var(--color-divider) calc(100% - var(--space-12)), transparent);
}

@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;
  }
}

/* ══════════════════════════════════════════════════════════════════════════
   2. COMPONENTS
   ══════════════════════════════════════════════════════════════════════ */

/* — buttons — */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  cursor: pointer;
  text-decoration: none;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--font-size-200);
  line-height: 1.2;
  color: var(--color-text);
  background: transparent;
  border: var(--hairline) solid transparent;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-md);
  min-height: var(--size-touch-sm);
  transition: background var(--duration-fast) ease-out;
}

.btn svg { display: block; }

.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

.btn-primary {
  color: var(--color-text);
  border-color: var(--color-neutral-200);
}

.btn-primary:hover { background: color-mix(in srgb, var(--color-neutral-200) 12%, transparent); }
.btn-primary:active { background: color-mix(in srgb, var(--color-neutral-200) 22%, transparent); }

.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }

.btn-ghost {
  color: var(--color-accent);
  padding-inline: var(--space-1);
}

.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }

/* round-7: a small breadcrumb above the <h1> (PortafolioProyecto.cshtml), not a button below
   it — quiet enough that it reads as "you are here, one level under Portafolio", not as a UI
   control competing with the title for attention. */
.breadcrumb-link {
  display: inline-block;
  margin-bottom: var(--space-1);
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
  text-decoration: none;
}

.breadcrumb-link:hover {
  color: var(--color-accent);
  text-decoration: underline;
}

.btn-icon {
  width: var(--size-touch-sm);
  height: var(--size-touch-sm);
  padding: 0;
}

.btn-block {
  width: 100%;
  margin-top: var(--space-2);
}

.btn.is-loading {
  color: transparent;
  pointer-events: none;
  position: relative;
}

.btn.is-loading::after {
  content: "";
  position: absolute;
  width: 1rem;
  height: 1rem;
  border-radius: var(--radius-full);
  border: 2px solid color-mix(in srgb, var(--color-text) 35%, transparent);
  border-top-color: var(--color-text);
  animation: btn-spin var(--duration-base) linear infinite;
}

@keyframes btn-spin {
  to { transform: rotate(360deg); }
}

/* — icons — */
.icon {
  width: 1em;
  height: 1em;
  vertical-align: -0.125em;
  fill: currentcolor;
}

/* — cards — */
.card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

/* Spacing between a card's own content comes only from the gap above (matching every card in
   servicios.dc.html, which zeroes each paragraph's own margin the same way): a <p> or heading
   used inside a card would otherwise keep the global p/h1-h6 bottom margin, which stacks with
   the flex gap instead of being replaced by it, opening a visibly bigger gap than intended. */
.card > * {
  margin: 0;
}

.card-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--font-size-400);
  line-height: 1.2;
}

.card-body {
  margin: 0;
  font-size: var(--font-size-200);
  opacity: 0.8;
  flex: 1;
}

.card-meta {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

/* The service card's own "desde" price / combined estimate line: unlike .card-meta (a muted
   secondary detail), this is the card's headline number and reads brighter than the work-types
   list above it, matching the prototype's own contrast between the two (servicios.dc.html). */
.service-price-from {
  margin: 0;
  font-size: var(--font-size-100);
  color: var(--color-neutral-200);
}

/* A rate card's duration ("1 h"): the prototype (servicios.dc.html) sets this at full text size
   and color, not muted like .card-meta — the tag sitting next to it is the small, quiet element
   here, not the duration itself. */
.rate-duration {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  font-family: var(--font-heading);
  font-size: var(--font-size-300);
}

.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }

/* — tags — */
.tag {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  font-size: var(--font-size-100);
  letter-spacing: 0.02em;
  padding: var(--space-1) var(--space-3);
  border-radius: calc(var(--radius-md) * 0.75);
}

.tag-accent {
  background: var(--color-accent-800);
  color: var(--color-accent-100);
}

.tag-neutral {
  background: var(--color-neutral-800);
  color: var(--color-neutral-100);
}

.tag-outline {
  border: var(--hairline) solid var(--color-accent);
  color: var(--color-accent);
}

.tag-success {
  background: var(--color-success-800);
  color: var(--color-success-300);
}

.tag-warning {
  background: var(--color-warning-800);
  color: var(--color-warning-300);
}

.tag-danger {
  background: var(--color-danger-800);
  color: var(--color-danger-300);
}

.tag-info {
  background: var(--color-info-800);
  color: var(--color-info-300);
}

/* — forms — */
.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

/* round-6 visual polish: <fieldset class="field"> (Tipo principal/Categorías/Visibilidad in
   PortafolioProyecto.cshtml, two groups in Servicios.cshtml) had no border of its own — .field
   never declares one — so what showed on screen was the raw, unstyled browser default fieldset
   border and padding, inverted by the site's forced dark color-scheme. Not a deliberate design
   at any point; this resets it to look like the rest of the flat, borderless form. Distinct from
   .service-block, which *is* deliberately bordered on purpose (its own comment explains why:
   a visible boundary between repeated, identical-looking service entries). */
fieldset.field {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: none;
}

fieldset.field legend {
  padding: 0;
  margin: 0 0 var(--space-1);
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

/* Two related short fields (e.g. nombre/apellido) side by side instead of stacked; collapses to
   one column under 30 rem so it never squeezes on a narrow panel viewport. */
.field-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-3);
}

@media (max-width: 30rem) {
  .field-row {
    grid-template-columns: 1fr;
  }
}

.field > label {
  display: block;
  font-size: var(--font-size-100);
  color: color-mix(in srgb, var(--color-text) 70%, transparent);
}

.input {
  width: 100%;
  min-height: var(--space-9);
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: var(--font-size-200);
  color: var(--color-text);
  caret-color: var(--color-accent);
  background: var(--color-surface);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
}

.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }

.input:focus-visible {
  border-color: var(--color-accent);
  outline-offset: 0;
}

textarea.input {
  min-height: var(--space-16);
  resize: vertical;
}

.field-help {
  margin: 0;
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

.field-error {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0;
  font-size: var(--font-size-100);
  color: var(--color-danger-300);
}

.field-error .icon { flex: none; }

/* The public site stays monochrome even for a validation error (ADR-003: the four feedback
   colors, including --color-danger-300 that .field-error uses everywhere else, are a Panel-only
   addition) — Contacto's own form (US-014) reads its error text in the brightest neutral
   instead, matching contacto.dc.html's own choice for this exact element. */
[data-contact-form] .field-error {
  color: var(--color-neutral-100);
}

/* A page-level status notice (e.g. "verify your email" under the panel header): its own
   breathing room and a divider, unlike .field-error, which is the tight inline message that
   sits under a single input (UX-VIS-004: proximity signals relationship). */
.panel-notice {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-3) 0;
  margin-bottom: var(--space-6);
  border-bottom: var(--hairline) solid var(--color-divider);
  font-size: var(--font-size-100);
  color: var(--color-danger-300);
}

/* Restores the color it's meant to inherit from .panel-notice (its danger-colored alert state) —
   the p:not([class]) rule above otherwise overrides that inheritance, same case as
   .publish-requirement-body p below. */
.panel-notice p {
  margin: 0;
  color: inherit;
}

/* A checkbox and its wording on one baseline: the whole row is the label, so the
   text is part of the hit target (UX-FRM-005). */
.checkbox-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  font-size: var(--font-size-200);
  cursor: pointer;
}

.checkbox-row input { margin-top: 0.15em; }

/* A <div>, not a <p>, so it never got the base `p { margin: 0 0 var(--space-3) }` rhythm every
   other block-level element already has — whatever followed it (Contacto and Inicio's own
   .card-meta line) sat flush against it with zero gap (found reviewing Contacto, US-014). */
.actions-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

/* .actions-row has no margin-top of its own, and .field-help's own margin is zeroed (see below) —
   the two sat flush with zero gap after "Importación terminada" (Importar.cshtml and
   ImportarCarpeta.cshtml's paso 3) and after paso 0's own instructions (ImportarCarpeta.cshtml,
   with a hidden .field-error banner sitting between the two in the markup — a general sibling
   selector so it still applies regardless of what's in between), found live testing the
   local-folder import with the user. */
.field-help ~ .actions-row {
  margin-top: var(--space-3);
}

/* A heading dropped into an actions-row (Portafolio's "Proyectos" beside its buttons) still
   carries h1-h6's own margin-bottom — align-items: center centers each item's margin box, so
   that margin alone was enough to visibly push the heading's text a few px above the buttons
   beside it (found reviewing this with the user via devtools). */
.actions-row h1,
.actions-row h2,
.actions-row h3 {
  margin-bottom: 0;
}

.radio {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  font-size: var(--font-size-200);
}

.radio input, .seg-opt input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}

.radio .dot {
  width: 1rem;
  height: 1rem;
  flex: none;
  border-radius: var(--radius-full);
  border: 1.5px solid var(--color-divider);
}

.radio:hover .dot { border-color: var(--color-accent); }

.radio input:checked + .dot {
  border-color: var(--color-accent);
  background: var(--color-accent);
  box-shadow: inset 0 0 0 4px var(--color-bg);
}

.radio input:focus-visible + .dot {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

.seg {
  display: inline-flex;
  overflow: hidden;
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
}

.seg-opt {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  font-size: var(--font-size-100);
  cursor: pointer;
}

.seg-opt + .seg-opt { border-left: var(--hairline) solid var(--color-divider); }

.seg-opt:has(input:checked) {
  color: var(--color-accent);
  box-shadow: inset 0 0 0 var(--hairline) var(--color-accent);
}

.seg-opt:has(input:focus-visible) {
  outline: 2px solid var(--color-focus);
  outline-offset: -2px;
}

.seg-opt:not(:has(input:checked)):hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }

/* — tables — */
.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--font-size-200);
  table-layout: fixed;
}

.table th {
  text-align: left;
  font-size: var(--font-size-100);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--color-text) 60%, transparent);
  padding: var(--space-2);
  border-bottom: var(--hairline) solid transparent;
}

.table td {
  padding: var(--space-2);
  border-bottom: var(--hairline) solid transparent;
}

.table thead tr {
  background: linear-gradient(to right,
    transparent, var(--color-divider) var(--space-12),
    var(--color-divider) calc(100% - var(--space-12)), transparent) no-repeat bottom / 100% var(--hairline);
}

.table tbody tr {
  background: linear-gradient(to right,
    transparent, color-mix(in srgb, var(--color-text) 8%, transparent) var(--space-12),
    color-mix(in srgb, var(--color-text) 8%, transparent) calc(100% - var(--space-12)), transparent) no-repeat bottom / 100% var(--hairline);
}

.table tbody tr:hover {
  background:
    linear-gradient(color-mix(in srgb, var(--color-text) 4%, transparent),
      color-mix(in srgb, var(--color-text) 4%, transparent)) no-repeat 0 0 / 100% 100%,
    linear-gradient(to right,
      transparent, color-mix(in srgb, var(--color-text) 8%, transparent) var(--space-12),
      color-mix(in srgb, var(--color-text) 8%, transparent) calc(100% - var(--space-12)), transparent) no-repeat bottom / 100% var(--hairline);
}

/* The rate table scrolls horizontally within its own container instead of the page
   (criterion 6, UX-LAY-003/UX-DAT-008): .panel-content already has min-width:0, so this
   wrapper is what actually contains the overflow. */
.table-scroll {
  /* Capped so the table does not stretch edge to edge just because .service-block is now
     allowed to grow wide (it needs room for the table itself, on a wide enough screen, to show
     every column without scrolling) — this table only ever has a handful of short cells. */
  max-width: min(100%, 44rem);
  overflow-x: auto;

  /* .service-block is a column flex container; a flex item defaults to
     min-width:auto, which lets its intrinsic content (the table's own
     min-width below) push this element — and the whole page — wider than
     the viewport instead of scrolling internally. This is what actually
     makes overflow-x:auto contain the table (criterion 6, UX-LAY-003). */
  min-width: 0;
}

.table-scroll > .table {
  min-width: 36rem;
}

/* A table of long, unrelated cells (the administration list, US-026): the columns size to their
   content instead of sharing the width equally, no cell wraps mid-address, and the wrapper uses the
   full container and scrolls on its own when the screen is narrower than the table. */
.table-scroll--wide {
  max-width: 100%;
}

.table-scroll--wide > .table {
  table-layout: auto;
  min-width: 60rem;
}

.table--roomy th,
.table--roomy td {
  padding: var(--space-3) var(--space-4);
  white-space: nowrap;
  vertical-align: middle;
}

.table .num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* Rate table column widths (_ServiceBlock.cshtml), set on the header cells themselves: without
   these the browser's auto table layout gives every column the same share, since each cell
   holds a full-width <input> with nothing else to measure — "Duración" (1-3 digits) ends up as
   wide as "Precio base" (6-7 digits). */
.col-rate-duration { width: 5.5rem; }
.col-rate-package { width: 8rem; }
.col-rate-price { width: 7.5rem; }
.col-rate-actions { width: 3rem; }

/* Every service is its own fieldset (UX-FRM-011); the surcharge column is always rendered
   and only hidden, so catalog.js can reveal it instantly on the "admite recargo" checkbox
   without rebuilding the row (RF-SVC-003). Bordered as its own card: every block repeats the
   exact same field labels, so without a visible boundary there is no cue for where one service
   ends and the next begins (also used, unnumbered, for #combined-service-fields). */
.service-block {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);

  /* Capped at the panel's own width, not at .form-narrow-width: on a wide enough screen the
     rate table (.table-scroll > .table, min-width 36rem) should get to show every column
     without scrolling, which a 520px cap forced even on desktop. The single-line fields
     (Name, Description, …) get their own narrower cap below instead, so they do not stretch
     edge to edge just because the block around them grew. */
  max-width: var(--container-panel);

  /* <fieldset> has a browser-imposed intrinsic minimum width driven by its own
     content (unlike a plain <div>), which ignores max-width above and would
     otherwise let the table below stretch this element — and the page — past
     the viewport instead of scrolling inside .table-scroll (criterion 6). */
  min-width: 0;
  padding: var(--space-4);
  margin: 0;
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
}

.service-block > .field,
.service-block > .checkbox-row {
  max-width: var(--form-narrow-width);
}

/* The <legend> is a real heading now (not visually-hidden): a plain <legend> ignores most box
   properties in some browsers unless it is also told to behave like a block first. */
.service-block-title {
  display: block;
  padding: 0 var(--space-1);
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--font-size-400);
}

.service-block + .service-block {
  margin-top: var(--space-6);
}

.no-surcharge .surcharge-col {
  display: none;
}

.no-combined-service {
  display: none;
}

/* — portfolio (US-017): categories and projects — */

/* Prototype: "Proyectos" and its two buttons sit at opposite ends of the row
   (justify-content:space-between) — .actions-row alone just packs items left, which is right for
   every other actions-row (button-only, no title to separate from), but wrong here where a title
   shares the row with its actions. */
[data-projects-header] {
  justify-content: space-between;
}

/* The two buttons need their own flex group so space-between above splits the row in two (title
   vs. actions) instead of three — spread across all of h2 + both buttons as flex siblings, the
   second button ends up pinned to the row's far edge while the first drifts to the middle
   (found reviewing this with the user). */
.button-group {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
}

/* Every 36px control below (chip, icon button) uses --space-9, not a new token: it already
   equals 36px, and Portfolio's own criteria ask for that exact size on a dense admin list —
   --size-touch-sm (44px) stays the default everywhere else (UX-VIS-001, reuse tokens over
   inventing new ones when the scale already has the value). */
.chip-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

/* One category: the visual tag itself (.chip, matching the prototype's own plain
   `<span class="tag tag-outline">` exactly — name + count, nothing else inside its border).
   .chip-item is its positioning anchor for the popup menu below (position: relative). Two
   earlier attempts put the rename/reorder/delete controls right next to or inside the same
   bordered pill as the tag — RF-PTF-001 requires those actions to exist, but the prototype never
   depicted any of them, and both attempts still read as a bloated button cluster next to the
   clean label (found reviewing this with the user). The chip itself is now the trigger: clicking
   it opens .chip-menu with the four actions as menu items; .chip-controls is the no-JS fallback,
   hidden once portfolio.js takes over. */
.chip-item {
  position: relative;
  display: inline-flex;
  align-items: center;
}

/* .chip is a <button> now (clicking it opens the popup menu below), unlike every other .tag in
   the app, which is a plain <span> — a browser's UA stylesheet gives <button> its own font
   shorthand, so `font-family: inherit` alone clears that without the `font: inherit` shorthand
   this first had, which would have wiped out .tag's own font-size along with it (found reviewing
   this with the user: the chip rendered at the body's 16px instead of .tag's 12px). */
.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--space-9);
  padding-inline: var(--space-3);
  cursor: pointer;
  font-family: inherit;

  /* Same reset the icon buttons in .chip-controls already needed via .btn (round-2 review with
     the user): a <button>'s UA stylesheet gives it its own solid grey background, and neither
     .tag nor .tag-outline overrides it (every other .tag in the app is a plain, backgroundless
     <span>) — without this, an inactive chip showed that grey fill instead of reading as
     outlined/transparent like the prototype. */
  background: transparent;
}

.chip:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }

/* The one category currently filtering the project list below (portfolio.js): a solid fill
   instead of .tag-outline's accent border, same treatment .tag-neutral already uses elsewhere,
   so the active filter reads as "on" at a glance among the rest of the outlined chips. */
.chip.is-active-filter {
  background: var(--color-neutral-800);
  border-color: transparent;
  color: var(--color-neutral-100);
}

.chip.is-active-filter:hover { background: var(--color-neutral-700); }

.chip-controls {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: var(--space-2);
}

/* Once portfolio.js takes over, every child of .chip-controls hides individually (see
   portfolio.js) so Editar can reveal just the rename form — but .chip-controls itself was never
   hidden, so its own margin-left kept reserving 8px of dead space next to every chip even with
   nothing visible inside it (found reviewing this with the user via devtools: .chip-item measured
   ~10px wider than its visible button). The no-JS fallback still needs that margin to separate
   its always-visible buttons from the chip. */
.chip-item.js-menu-active .chip-controls {
  margin-left: 0;
}

/* The popup menu portfolio.js builds once per chip, anchored by .chip-item's relative
   positioning. Closed (hidden) until the chip is clicked (UX-CMP-007-style: closes on Escape,
   on an outside click, and after choosing an item; focus starts on the first item). */
.chip-menu {
  position: absolute;
  top: calc(100% + var(--space-1));
  left: 0;
  z-index: var(--z-dialog);
  display: flex;
  flex-direction: column;
  min-width: 9rem;
  padding: var(--space-1);
  background: var(--color-surface);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
}

.chip-menu-item {
  display: block;
  width: 100%;
  min-height: var(--space-9);
  padding: 0 var(--space-3);
  text-align: left;
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--color-text);
  font: inherit;
  font-size: var(--font-size-100);
  cursor: pointer;
}

.chip-menu-item:hover,
.chip-menu-item:focus-visible {
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
}

.chip-menu-item.is-danger { color: var(--color-danger-300); }

.chip-inline-form {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.chip-inline-form .input {
  min-height: var(--space-9);
  width: 10rem;
}

/* Guardar/Cancelar need to match the 36px chip row, not .btn's default 44px touch target
   (btn-icon-sm doesn't fit either — it's a fixed 36x36 box for a single icon glyph, and clipped
   "Guardar"'s text when tried here, found reviewing this with the user). */
.chip-inline-form .btn {
  min-height: var(--space-9);
  padding: 0 var(--space-3);
}

.add-category-input {
  width: 16rem;
  max-width: 100%;
}

/* Closes out the whole Categorías block before "Proyectos" starts — the prototype wraps
   Categorías in its own <section style="margin-bottom:26px">, a break .actions-row's shared 12px
   doesn't reproduce on its own. */
[data-add-category-form] {
  margin-bottom: var(--space-6);
}

.btn-icon-sm {
  width: var(--space-9);
  height: var(--space-9);
  padding: 0;
}

.btn-danger {
  color: var(--color-danger-300);
  border-color: var(--color-danger-300);
}

.btn-danger:hover { background: color-mix(in srgb, var(--color-danger-300) 12%, transparent); }
.btn-danger:active { background: color-mix(in srgb, var(--color-danger-300) 22%, transparent); }

/* A project row of the panel list: a placeholder thumbnail (no MediaItem pipeline until
   US-018), title, meta line and its reorder/edit actions — deliberately its own class rather
   than reusing .card, which assumes a vertical stack, not a dense horizontal row. */
.list-row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.list-row + .list-row {
  margin-top: var(--space-2);
}

.list-row-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  flex: 1;
  min-width: 0;
}

.list-row-title {
  margin: 0;
  font-size: var(--font-size-200);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.list-row-meta {
  margin: 0;
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

.thumb-placeholder {
  flex: none;
  width: 3.625rem; /* 58px */
  height: var(--size-touch-sm); /* 44px */
  border-radius: var(--radius-sm);
  background: var(--color-surface);
  border: var(--hairline) solid var(--color-divider);

  /* Also renders as an <img> once a project has an effective cover (US-019): keeps the fixed
     58×44 box instead of stretching to the cover photo's own aspect ratio. No-op on the empty
     placeholder <div>, which has no replaced content for object-fit to apply to. */
  object-fit: cover;
}

/* A dashed-border block explaining what goes here and offering the way to fill it
   (UX-EST-003) — Servicios' own empty state is a single .field-help line because it has
   nothing to link to beyond the button already above it; Portfolio's project list needs its
   own actions repeated here since the empty state replaces the whole list, buttons included. */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-8) var(--space-4);
  border: var(--hairline) dashed var(--color-divider);
  border-radius: var(--radius-md);
  text-align: center;
}

.empty-state p {
  margin: 0;
  color: var(--color-text-muted);
}

/* — media upload (US-018) — */
.media-upload-zone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-8) var(--space-4);
  border: var(--hairline) dashed var(--color-divider);
  border-radius: var(--radius-md);
  text-align: center;
}

.media-upload-zone p {
  margin: 0;
}

.media-upload-zone.is-dragover {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 6%, transparent);
}

.media-upload-skipped {
  margin: var(--space-3) 0 0;
  padding: 0;
  list-style: none;
  color: var(--color-danger-300);
  font-size: var(--font-size-100);
}

.media-upload-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-3);
}

.media-upload-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.media-upload-row-name {
  margin: 0;
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--font-size-100);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Same track/fill tokens as the prototype's own import-progress card
   (panel-importar.dc.html): a 6px pill track in --color-neutral-800, filled with
   --color-neutral-300. */
.media-upload-progress-track {
  flex: 1 1 8rem;
  height: 0.375rem; /* 6px */
  border-radius: var(--radius-full);
  background: var(--color-neutral-800);
  overflow: hidden;
}

.media-upload-progress-fill {
  height: 100%;
  width: 0%;
  background: var(--color-neutral-300);
  transition: width var(--duration-base) ease-out;
}

/* Closes out "Medios" before "Eliminar proyecto" starts — same convention
   [data-add-category-form] uses to break Categorías from Proyectos on Portafolio.cshtml: base
   heading styles only ever add margin ABOVE themselves (h1-h6's own margin: 0 0 var(--space-2)
   has no top value), so a section actually ends with a bottom margin on its last element, not a
   top margin on the next heading. Found live in the browser: "Medios" grid sat flush against
   "Eliminar proyecto" with a literal 0px gap between them. */
.media-grid {
  display: grid;

  /* 11rem, not the original 9rem (round-3 visual polish): comfortable room for the overlay's
     four action icons without crowding them. */
  grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
  gap: var(--space-4);
  margin-top: var(--space-3);
  margin-bottom: var(--space-6);

  /* round-4 visual polish: the grid's own default (stretch) forced every card in a row to the
     height of the tallest one, which left a card with fewer actions (the cover, with no "Usar
     como portada" button) holding a large dead gap of its own background instead of just ending
     sooner. A card that only shows what it actually has reads as deliberate; a card padded out
     with empty space reads as a bug. */
  align-items: start;
}

.media-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  transition: box-shadow var(--duration-fast) ease-out;
  cursor: grab;
}

.media-card:hover {
  box-shadow: var(--shadow-md);
}

/* Dragging (US-019, media-upload.js's initMediaReorderDrag) — the card being moved dims instead
   of disappearing, so the professional keeps a visual anchor for where it is mid-drag. */
.media-card.is-dragging {
  opacity: 0.5;
}

/* Positioning context for the "Portada actual" tag and the action overlay, both laid on top of
   the thumbnail (round-5 visual polish, one of three mockups presented — see _MediaGrid.cshtml's
   own comment) rather than as bordered buttons below it: the image is the one thing on the card
   worth looking at, everything else is chrome around it. */
.media-card-media {
  position: relative;
}

.media-card-thumb {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  border-radius: var(--radius-sm);
  object-fit: cover;
  background: var(--color-neutral-800);
}

.media-card-cover-tag {
  position: absolute;
  top: var(--space-2);
  left: var(--space-2);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  box-shadow: var(--shadow-sm);
}

.media-card-cover-tag-icon {
  width: 0.75rem;
  height: 0.75rem;
}

.media-card-skeleton {
  animation: media-card-pulse 1.4s ease-in-out infinite;
}

@keyframes media-card-pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.5; }
}

.media-card-thumb-error {
  display: grid;
  place-items: center;
  color: var(--color-danger-300);
  font-size: var(--font-size-400);
  border: var(--hairline) solid var(--color-danger-300);
}

.media-card-status {
  margin: 0;
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

.media-card-status-error {
  color: var(--color-danger-300);
}

.media-card-retry {
  align-self: flex-start;
  padding-inline: 0;
  min-height: auto;
  font-size: var(--font-size-100);
}

/* US-019: the alt text field lives directly on the card, not in a form of its own — saved on
   blur by media-upload.js. Otherwise a standard .field/.input, same as every other field in the
   panel (round-6 visual polish: an earlier floating-label treatment, label overlapping the input
   and animating out of the way on focus, read as crowded once a real value was focused — plain
   consistency with the rest of the site reads better than a custom one-off). .is-saved is a
   brief, self-clearing confirmation (UX-FBK-001) instead of a page-wide toast, since the field
   itself is the thing that changed. */
.media-card-alt-field.is-saved .input {
  border-color: var(--color-accent);
  transition: border-color var(--duration-base) ease-out;
}

/* — project edit page (round-8, designer mockup): two-column layout with a sticky "Detalles"
   sidebar, same pattern as the Cotizador's "Resumen de tu proyecto" summary card. Below 1024 px
   there is no room for a second column, so the sidebar drops below the media grid instead of
   trying to stay pinned over content it would otherwise cover. */
.project-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.project-header-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--space-1);
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
  font-size: var(--font-size-100);
}

.project-header-meta-icon {
  width: 0.875rem;
  height: 0.875rem;
}

.project-layout {
  display: grid;
  gap: var(--space-6);
  margin-top: var(--space-6);
}

@media (min-width: 64rem) {
  .project-layout {
    grid-template-columns: 1fr 20rem;
    align-items: start;
  }
}

.project-main {
  min-width: 0;
}

.project-main-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-4);
  flex-wrap: wrap;
  margin-bottom: var(--space-4);
}

.project-sidebar {
  min-width: 0;
}

@media (min-width: 64rem) {
  .project-sidebar {
    position: sticky;

    /* Round-10 (found testing scroll behavior): .panel-header (this page's own sticky header) is
       taller than var(--space-6) alone cleared, so the top of the upload zone box was sitting
       behind it once both were pinned. Same idea as .quote-summary's own sticky offset (its
       comment explains clearing the public site's shorter header) — var(--space-13) is the
       header's own min-height, plus var(--space-6) so it doesn't sit flush against it. */
    top: calc(var(--space-13) + var(--space-6));
  }
}

.project-sidebar-card {
  padding: var(--space-4);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
}

.project-sidebar-card h2 {
  margin-top: 0;
}

/* Round-10 (mockup fidelity fix): lives at the top of the sticky sidebar, above "Detalles" — not
   next to the "Medios" heading in the main column — so it scrolls and pins together with the rest
   of that column instead of disappearing once the media grid grows past one screen. */
.media-upload-zone-sidebar {
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
}

.media-upload-zone-sidebar .field-help {
  margin-top: var(--space-1);
  text-align: center;
}

.media-upload-zone-prompt {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  cursor: pointer;
  color: var(--color-text-muted);
  font-size: var(--font-size-100);
}

.media-upload-zone-link {
  color: var(--color-text);
  text-decoration: underline;
}

/* Criterion 3 / RF-PTF-006's missing-alt-text notice, brought forward from US-021 per the
   designer mockup — a neutral bordered callout, not a warning-colored one: the mockup shows it as
   informational (an icon + count + action), not an error state. */
.notice-banner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-4);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  font-size: var(--font-size-100);
}

.notice-banner-icon {
  flex-shrink: 0;
  width: 1.25rem;
  height: 1.25rem;
  color: var(--color-text-muted);
}

.notice-banner span {
  flex: 1;
}

.notice-banner form {
  flex-shrink: 0;
  margin-left: auto;
}

.media-card-badge {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  display: grid;
  place-items: center;
  min-width: 1.5rem;
  height: 1.5rem;
  padding-inline: var(--space-1);
  border-radius: var(--radius-full);
  background: rgb(0 0 0 / 55%);
  color: var(--color-neutral-100);
  font-size: var(--font-size-100);
  font-variant-numeric: tabular-nums;
}

/* The action row now sits below the thumbnail (round-8, designer mockup) instead of overlaid on
   top of it (round-5's earlier treatment) — every action still works without JavaScript
   (UX-ACC-003, UX-ACC-016), only the position changed. Round-12 (mockup fidelity fix, corrects
   round-9's overcorrection): ←/→/★ grouped together on the left with a small gap, only "Eliminar"
   pushed to the far right — not all four spread edge to edge with equal gaps. */
.media-card-actions {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.media-card-delete-form {
  margin-left: auto;
}

.media-card-action-icon {
  width: 1rem;
  height: 1rem;
}

/* Round-10 (mockup fidelity fix): the mockup's four icon buttons (←, →, ★, 🗑) all share the same
   neutral style — Eliminar isn't singled out with a permanent red border like the sitewide
   .btn-danger text button is. Danger only shows up as a hover/focus affordance, consistent with
   the rest of the row looking like one uniform group until the professional actually reaches for
   it. */
.media-card-action-danger:hover,
.media-card-action-danger:focus-visible {
  color: var(--color-danger-300);
  border-color: var(--color-danger-300);
}


.media-card-alt-field-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

/* Round-9 (mockup fidelity fix): a plain <label> has no sitewide font-size rule of its own, so it
   rendered at the full body size next to the counter's small, muted text — a card this narrow
   needs the label small and muted too, same treatment as fieldset.field's own <legend>. */
.media-card-alt-field-header label {
  color: var(--color-text-muted);
  font-size: var(--font-size-100);
  font-weight: 400;
}

.media-card-alt-counter {
  color: var(--color-text-muted);
  font-size: var(--font-size-100);
  font-variant-numeric: tabular-nums;
}

/* — toggle buttons (Tipo principal / Visibilidad) and chip picker (Categorías): the mockup's
   visual treatment for radio/checkbox groups elsewhere rendered as plain .checkbox-row lists
   (round-8) — the inputs themselves are unchanged, so no-JS submission and validation still work
   exactly as before; :has() only repaints the label around a checked input. */
.toggle-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.toggle-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  cursor: pointer;
  font-size: var(--font-size-100);
  transition: border-color var(--duration-fast) ease-out, background var(--duration-fast) ease-out;
}

.toggle-btn input {
  position: absolute;
  width: 0.0625rem;
  height: 0.0625rem;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.toggle-btn:has(input:checked) {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 14%, transparent);
}

.toggle-btn:has(input:focus-visible) {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

.toggle-btn-icon {
  width: 1rem;
  height: 1rem;
}

.chip-group {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.chip-toggle {
  display: inline-flex;
  align-items: center;
  padding: var(--space-1) var(--space-3);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-full);
  cursor: pointer;
  font-size: var(--font-size-100);
  transition: border-color var(--duration-fast) ease-out, background var(--duration-fast) ease-out;
}

.chip-toggle input {
  position: absolute;
  width: 0.0625rem;
  height: 0.0625rem;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
}

.chip-toggle:has(input:checked) {
  border-color: var(--color-accent);
  background: color-mix(in srgb, var(--color-accent) 14%, transparent);
}

.chip-toggle:has(input:focus-visible) {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

.chip-toggle .icon {
  width: 0.875rem;
  height: 0.875rem;
  margin-right: var(--space-1);
}

.chip-toggle-plus {
  margin-right: var(--space-1);
  color: var(--color-text-muted);
}

/* The delete-project block at the bottom of the "Detalles" sidebar (round-9, mockup fidelity
   fix): its own bordered callout, not a bare heading + form, so it reads as a distinct,
   deliberately separated danger zone rather than just the last field in the list. */
.danger-callout {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: var(--hairline) solid var(--color-divider);
}

.danger-callout-title {
  margin: 0;
  font-weight: 600;
}

/* A field's label and its live character counter on the same line (round-9, mockup fidelity
   fix) — the counter is its own element like .media-card-alt-counter, not appended to the label
   text, so it can update independently. */
.field-label-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.field-counter {
  color: var(--color-text-muted);
  font-size: var(--font-size-100);
  font-variant-numeric: tabular-nums;
}

/* — dialog — */
.dialog-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-dialog);
  display: grid;
  place-items: center;
  padding: var(--space-4);
  background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
}

.dialog {
  width: min(var(--dialog-width), 100%);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg);
}

/* The native element carries its own UA border, padding and max-width; the
   class above supplies the look, this strips the defaults and styles the
   backdrop the browser draws for showModal(). */
dialog.dialog {
  border: 0;
  padding: var(--space-4);
  max-width: min(var(--dialog-width), calc(100% - var(--space-8)));
  color: var(--color-text);
}

dialog.dialog::backdrop {
  background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
}

.dialog-title {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--font-size-500);
}

.dialog-body {
  font-size: var(--font-size-200);
  opacity: 0.85;
}

.dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

/* — toast — */
.toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-6);
  transform: translateX(-50%);
  z-index: var(--z-toast);
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-full);
  background: var(--color-action);
  color: var(--color-on-action);
  box-shadow: var(--shadow-lg);
  font-size: var(--font-size-200);
}

/* — layout utilities — */
.container-1200 {
  max-width: var(--container-public);
  margin: 0 auto;

  /* A flat --space-4/--space-6 gutter still read as flush against the edge on wide screens: at
     max-width the container fills the whole viewport, and that gutter is the only thing
     separating content from it (found on the landing's card row, but this class is shared by
     every public page's header/footer/sections, so it applies everywhere consistently). A single
     fixed value can't be right at both a narrow and a very wide viewport, so this scales up at
     wider breakpoints instead of picking one compromise number. */
  padding: 0 var(--space-4);
}

@media (min-width: 48rem) {
  .container-1200 {
    padding: 0 var(--space-8);
  }
}

@media (min-width: 75rem) {
  .container-1200 {
    padding: 0 var(--space-12);
  }
}

.container-1180 {
  max-width: var(--container-panel);
  margin: 0 auto;
  padding: 0 var(--space-4);
}

/* Wider variant for panel pages whose content genuinely needs the extra room (round-9, mockup
   fidelity fix: the media-editing page's two-column layout read as having too much empty side
   margin at 73.75rem next to the designer's mockup) — opt-in per page via
   ViewData["PanelContainerClass"] in _PanelLayout.cshtml, so every other panel page keeps its
   narrower, unchanged width. */
.container-1440 {
  max-width: var(--container-panel-wide);
  margin: 0 auto;
  padding: 0 var(--space-4);
}

/* — public site sections (US-011, criterion 8): every top-level section on a public page gets
   the same vertical rhythm and a divider marking where the previous one ended; width comes from
   a co-existing class (.container-1200, .faq-section, ...) so .section only owns padding,
   background and the divider. .section--alt is added by hand to whichever sections should read
   as a distinct band, rather than an automatic nth-of-type alternation that would misfire once a
   section (like FAQ, when empty) is conditionally absent. */
.section {
  padding-block: var(--space-14);
  border-top: var(--hairline) solid var(--color-divider);
}

.section--alt {
  background: color-mix(in srgb, var(--color-surface) 45%, transparent);
}

/* — Inicio (criterion 7): two columns from 768 px, photo column stacked below the text on
   mobile. Without a chosen photo (RF-INI-003) it is a single text column. */

/* minmax(0, 1fr) and not the default: a column sizes itself to its content's minimum width, and the
   carousel's row of professional tabs (landing) is wider than a phone, which stretched the whole
   page sideways. The tabs scroll on their own; the column must not follow their width. */
.hero-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: var(--space-7);
}

@media (min-width: 48rem) {
  .hero-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: center;
  }
}

/* With a photo the frame sits flush under the header, as in the prototype: the section gives
   up its own vertical padding and the text column carries it instead, so the text stays
   vertically centred against the photo. */
.hero--photo {
  padding-block: 0;
  border-top: 0;
}

.hero--photo .hero-text {
  padding-block: var(--space-12);
}

/* The professional's chosen portfolio photo: a portrait frame (675:1200, as in the prototype)
   in the second column, rounded, never taller than the viewport allows. */
.hero-photo {
  justify-self: center;
  width: min(27.5rem, 100%, calc(min(78vh, 43.75rem) * 675 / 1200));
  aspect-ratio: 675 / 1200;
  overflow: hidden;
  border-radius: var(--radius-md);
}

.hero-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hero-title {
  font-size: clamp(1.875rem, 7vw, 3.75rem);
  margin-bottom: var(--space-3);
}

.hero-tagline {
  display: block;
  color: var(--color-text-muted);
  font-size: var(--font-size-500);
  margin-top: var(--space-2);
}

.hero-secondary {
  max-width: 34ch;
}

/* — Landing (US-022, RF-PLT-001): criterion caps the h1 at 22ch and the lead paragraph at 48ch,
   both narrower than .hero-tagline/.hero-secondary's own limits (used by the per-professional
   Inicio page), so this story gets its own two modifiers layered on the existing hero classes
   instead of changing those shared limits for every professional's site. */
.landing-hero-title {
  max-width: 22ch;
}

.landing-lead {
  max-width: 48ch;
}

/* Cierre (#h-fin): a diferencia de los demás leads, el prototipo no le pone max-width a este
   párrafo — es corto y cabe en una sola línea, así que .landing-lead's 48ch lo partiría sin
   necesidad. También usa su propio tamaño/color más apagado (el prototipo no reutiliza aquí el
   estilo de párrafo por defecto). */
.landing-lead-tight {
  max-width: none;
  font-size: 0.906rem;
  color: var(--color-neutral-300);
}

/* Zeroes .actions-row's own margin-bottom (meant for a button row followed by page content)
   so the header's brand and buttons stay vertically centered against each other — the row's
   align-items: center centers each child's full margin box, so a bottom-only margin here would
   otherwise nudge the buttons upward relative to the brand text next to them. */
.site-header .actions-row {
  margin-bottom: 0;
}

/* The three "Tu URL"/"Tus precios"/"Tu portafolio" cards (criterion 1): a single column on
   mobile, three across from tablet width up — same breakpoint .hero-grid already uses.
   No margin-block of its own: this class sits on the same <section class="section"> element in
   Index.cshtml, and .section's own padding-block already sets the vertical rhythm — adding a
   margin on top of it doubled the empty space above/below the grid (found reviewing with the
   user). padding-inline IS needed here though, for a different reason: .section--alt's tinted
   background starts flush with this element's own edge (no gap of its own), so the cards touched
   the band's edge directly — increasing .container-1200's outer padding (main → section) moved
   the whole band further from the viewport but never touched this inner gap, which is why that
   fix alone didn't change what the user was pointing at. */

/* No padding-inline here (unlike its pre-US-028 version): this grid now lives inside the
   section's own .container-1200 wrapper, which already supplies the side padding — an extra
   one here only pushed the cards inward relative to the h2 above them, misaligning the two. */
.landing-features {
  display: grid;
  gap: var(--space-4);
}

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

/* The "Ejemplo" block: a label, the demo URL and an "Abrir" button, set apart from the hero
   with its own card so it reads as a distinct, concrete proof rather than a fourth feature. */
.landing-example {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.landing-example-label {
  font-size: var(--font-size-100);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-400);
}

.landing-example-url {
  font-size: var(--font-size-300);
}

/* The landing's own section headings (criterion 6) — deliberately not `.subsection-title`
   (18px, meant for a secondary heading under a page's own bigger h1, e.g. Nosotros' "Cómo
   trabajamos"): these are the page's only headings per section, sized like the prototype's own
   inline clamp (26-36px) with no extra top margin — the section's own 56px top padding
   (`.section`) already provides that spacing, so an added margin here only doubled the gap. */
.landing-section-title {
  margin: 0 0 var(--space-6);
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: clamp(1.625rem, 3vw + 1rem, 2.25rem);
  max-width: 24ch;
}

/* #h-fin (cierre): el prototipo usa un max-width e text-wrap propios para este título en
   particular (22ch + balance, contra 24ch sin balance de los demás) — de otro modo envuelve
   como "…compártelo" / "hoy" en vez del reparto parejo del prototipo. */
#h-fin.landing-section-title {
  max-width: 22ch;
  text-wrap: balance;
}

/* — Plataforma · portada (US-028): formulario de nickname, carrusel de "Destacados de hoy",
   buscador, enlaces por especialidad y demo de "Cotizaciones". — */
.landing-claim-form {
  margin-top: var(--space-4);
  max-width: 30rem;
}

.landing-claim-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* The border/background lives on this box (prefix + input together), not on the input itself —
   mirrors the prototype's own structure: a single 1-1-15rem flex item that can shrink as a unit,
   with the submit button as its own flex:0-0-auto sibling, so the two stay on one line down to a
   narrower width than they would if the button were a third item inside the same bordered row. */
.landing-claim-box {
  flex: 1 1 15rem;
  min-width: 0;
  display: flex;
  align-items: center;
  min-height: var(--space-9);
  padding-left: var(--space-3);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.landing-claim-box:has(input:focus-visible) {
  border-color: var(--color-accent);
}

.landing-claim-row > .btn {
  flex: 0 0 auto;
}

/* Where the box and the button no longer fit side by side (the box asks for 15rem and the button
   for its own width) they sit on two lines: then the button takes the box's full width instead of
   hugging its left edge. */
@media (max-width: 27.5rem) {
  .landing-claim-row > .btn {
    flex: 1 1 100%;
  }
}

.landing-claim-prefix {
  font-size: var(--font-size-200);
  color: var(--color-neutral-400);
  white-space: nowrap;
}

.landing-claim-box input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  color: var(--color-text);
  font: inherit;
  font-size: var(--font-size-200);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-1);
}

.landing-highlights {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.landing-highlights-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.landing-highlights-empty {
  margin: 0;
}

.landing-highlights-carousel {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.landing-highlight-panels {
  display: grid;
}

.landing-highlight-panels > * {
  grid-area: 1 / 1;
}

.landing-highlight-card {
  display: block;
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface);
  text-decoration: none;
  color: inherit;
}

.landing-highlight-card:hover {
  border-color: var(--color-neutral-500);
}

.landing-highlight-browserbar {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  border-bottom: var(--hairline) solid var(--color-divider);
}

.landing-highlight-dot {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  background: var(--color-neutral-700);
}

.landing-highlight-url {
  flex: 1;
  min-width: 0;
  text-align: center;
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.landing-highlight-body {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  align-items: stretch;
}

.landing-highlight-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4);
  justify-content: center;
  min-width: 0;
}

.landing-highlight-name {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--font-size-400);
}

.landing-highlight-spec,
.landing-highlight-city {
  font-size: var(--font-size-100);
  color: var(--color-neutral-300);
}

.landing-highlight-city {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.landing-highlight-city .icon {
  width: 0.875rem;
  height: 0.875rem;
  color: var(--color-neutral-400);
}

.landing-highlight-actions {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.landing-highlight-action {
  font-size: var(--font-size-100);
  padding: var(--space-1) var(--space-2);
  border: var(--hairline) solid var(--color-neutral-500);
  border-radius: var(--radius-sm);
}

.landing-highlight-action--muted {
  border-color: var(--color-divider);
  color: var(--color-neutral-300);
}

.landing-highlight-cover {
  aspect-ratio: 3 / 4;
  overflow: hidden;
}

.landing-highlight-cover img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.landing-highlight-toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.landing-highlight-tabs {
  flex: 1;
  min-width: 0;
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.landing-highlight-tabs [role="tab"] {
  scroll-snap-align: start;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--space-9);
  padding: var(--space-1) var(--space-2);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
}

.landing-highlight-tabs [role="tab"][aria-selected="true"] {
  border-color: var(--color-neutral-300);
  background: color-mix(in srgb, var(--color-text) 8%, transparent);
}

.landing-highlight-tab-thumb {
  width: 2.25rem;
  height: 2.25rem;
  flex: none;
  border-radius: var(--radius-sm);
  overflow: hidden;
  background: var(--color-neutral-900);
}

.landing-highlight-tab-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.landing-highlight-tab-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  text-align: left;
}

.landing-highlight-tab-name,
.landing-highlight-tab-spec {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.landing-highlight-tab-spec {
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

@media (min-width: 48rem) {
  .landing-highlight-tabs {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr));
    overflow-x: visible;
    scroll-snap-type: none;
  }
}

.landing-search-form {
  display: flex;
  gap: var(--space-2);
  align-items: center;
  min-height: var(--space-9);
  padding: 0 var(--space-1) 0 var(--space-3);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.landing-search-form .icon {
  flex: none;
  width: 1.0625rem;
  height: 1.0625rem;
  color: var(--color-neutral-400);
}

.landing-search-form input {
  flex: 1;
  min-width: 0;
  border: 0;
  background: transparent;
  color: var(--color-text);
  font: inherit;
  font-size: var(--font-size-200);
  padding: var(--space-2) 0;
}

.landing-search-form:has(input:focus-visible) {
  border-color: var(--color-accent);
}

.landing-specialty-links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.landing-specialty-links a {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  min-height: var(--space-9);
  padding: 0 var(--space-3);
  border: var(--hairline) solid var(--color-divider);
  border-radius: 999px;
  font-size: var(--font-size-100);
  text-decoration: none;
  color: var(--color-neutral-200);
}

.landing-specialty-links a:hover {
  border-color: var(--color-neutral-400);
}

.landing-specialty-links a span {
  color: var(--color-neutral-500);
}

.landing-pillar-list {
  margin: 0;
  padding-left: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.landing-quote-privacy {
  max-width: 48ch;
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.landing-quote-demo-stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  max-width: 30rem;
}

.landing-quote-demo {
  gap: var(--space-3);
}

.landing-quote-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* Fully rounded and high-contrast when selected — this demo's own pills, deliberately not the
   same subtle .chip.is-active-filter treatment the portfolio's category filter uses elsewhere in
   the app: those are a document-style "filter is on" indicator, while this one is a single,
   mutually-exclusive choice inside a marketing demo, matching the prototype's own pill(on)
   styling (a solid, unmistakably "chosen" fill). */
.landing-quote-row .chip {
  min-height: var(--space-9);
  padding-inline: var(--space-3);
  border: var(--hairline) solid var(--color-divider);
  border-radius: 999px;
}

.landing-quote-row .chip[aria-pressed="true"] {
  background: var(--color-action);
  border-color: var(--color-action);
  color: var(--color-on-action);
}

.landing-quote-total-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  padding-top: var(--space-2);
  border-top: var(--hairline) solid var(--color-divider);
}

.landing-quote-total-label {
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.landing-quote-total {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--font-size-500);
}

.landing-quote-summary {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.landing-quote-arrow {
  align-self: center;
  font-size: var(--font-size-200);
  color: var(--color-neutral-500);
}

.landing-quote-bubble {
  align-self: center;
  max-width: 92%;
  padding: var(--space-3);
  border-radius: var(--radius-md) var(--radius-md) var(--space-1) var(--radius-md);
  background: var(--color-neutral-800);
  border: var(--hairline) solid var(--color-neutral-700);
}

.landing-quote-bubble-label {
  display: block;
  margin-bottom: var(--space-1);
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.landing-quote-bubble-text {
  margin: 0;
  font-size: var(--font-size-200);
  line-height: 1.55;
  color: var(--color-neutral-100);
}

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

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

/* Minimalist per the prototype: a top rule, no card background — unlike Nosotros' own
   "Cómo trabajamos" steps (a different page, its own established card style), the landing's
   marketing sections use this plainer treatment for both "Cómo funciona" and "Pensado para
   quien vende su trabajo visual". */
.landing-audiences > div {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-top: var(--space-4);
  border-top: var(--hairline) solid var(--color-neutral-700);
}

.landing-steps {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--space-3);
}

@media (min-width: 48rem) {
  .landing-steps {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 13.75rem), 1fr));
  }
}

.landing-step {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-top: var(--space-4);
  border-top: var(--hairline) solid var(--color-neutral-600);
}

.landing-quote-grid {
  display: grid;
  gap: var(--space-7);
  align-items: center;
}

@media (min-width: 48rem) {
  .landing-quote-grid {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  }
}

.landing-example-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

/* Below 640 px the title block and the link no longer fit on one line and the link wraps under it:
   it then spans the same width as the photo grid below instead of keeping its own. */
@media (max-width: 40rem) {
  .landing-example-head > .btn {
    flex: 1 1 100%;
  }
}

.landing-example-meta {
  margin: var(--space-1) 0 0;
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.landing-sample-grid {
  margin-top: var(--space-4);
}

.landing-sample {
  display: block;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: var(--radius-md);
  border: var(--hairline) solid var(--color-divider);
}

.landing-sample img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* — Legal pages (US-022, RF-PLT-002): /terminos and /privacidad get their own minimal layout
   (_LegalLayout) — a single back link, no site header/footer chrome, narrower than even
   .faq-section's own 820 px reading column, matching the prototype's 720 px column exactly. */
.legal-container {
  max-width: 45rem;
  margin: 0 auto;
  padding: var(--space-14) var(--space-4) var(--space-16);
}

.legal-back-link {
  display: inline-flex;
  margin-bottom: var(--space-6);
  padding-inline: 0;
}

/* — Nosotros "Cómo trabajamos" (criterion 9): column on mobile, row from 1024 px. */
.process-steps {
  display: grid;
  gap: var(--space-4);
  margin: 0;
  padding: 0;
  list-style: none;
}

@media (min-width: 64rem) {
  .process-steps {
    grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
  }
}

/* — FAQ (criterion 10): its own narrower container, not .container-1200. */
.faq-section {
  max-width: 51.25rem;
  margin: 0 auto;
  padding-inline: var(--space-4);
}

.faq-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.faq-item {
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.faq-item summary {
  min-height: var(--size-touch-sm);
  display: flex;
  align-items: center;
  cursor: pointer;
  padding: var(--space-3) var(--space-4);
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--font-size-200);
}

.faq-item p {
  margin: 0;
  padding: 0 var(--space-4) var(--space-4);
  font-size: var(--font-size-200);
  white-space: pre-line;
}

/* — Servicios (US-012): price tables. .rate-card and .service-card reuse .card and .btn as-is
   (min-height and the actions-row gap already meet the 44 px / 8 px touch targets of
   RF-SRV-002/003), adding only the border a card needs to "resaltar" once selected. The border
   alone is a color-only cue (UX-VIS-013 forbids that): .is-selected also thickens it and each
   selected card carries its own "Seleccionado" tag or aria-current, never color by itself. */
.rate-card,
.service-card {
  border: var(--hairline) solid var(--color-divider);
}

.rate-card.is-selected,
.service-card.is-selected {
  border-width: calc(var(--hairline) * 2);
  border-color: var(--color-accent);
}

.price-lg {
  font-size: var(--font-size-500);
  font-variant-numeric: tabular-nums;
}

/* The service card's work-types list reads as a compact, secondary detail (matching the
   prototype's smaller list text), not full body size like the description above it. */
.service-card ul.prose {
  font-size: var(--font-size-200);
}

/* "Qué incluye <recargo>"'s own list (servicios.dc.html): smaller and dimmer than the card
   title above it, not full body size/brightness. */
.closing-card-list {
  font-size: var(--font-size-200);
  color: var(--color-neutral-300);
}

/* The price tables sit in the same section as the service cards above them (servicios.dc.html
   keeps everything — cards, tables, closing cards — inside one <section>); this is spacing
   between that block and the next, not a section boundary, so no border or alternate
   background here (that band is reserved for the next distinct page section, e.g. #cotizador). */
.price-tables {
  margin-top: var(--space-9);
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
}

/* Anchor targets (#precios-<slug>) land below the 52 px sticky header instead of under it. */
.price-table {
  scroll-margin-top: var(--space-13);
}

.price-table-title {
  font-size: var(--font-size-400);
}

.price-table-note {
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.closing-cards {
  margin-top: var(--space-8);
}

/* — Cotizador (US-013): form on the left, sticky summary on the right from 768 px (the width
   .hero-grid already uses for its own two-column breakpoint), stacked on mobile. */
.quoter-grid {
  display: grid;
  gap: var(--space-7);
}

/* No align-items: start on purpose: a sticky child only has room to stick within its own
   parent's box, and align-items: start would shrink #quoter-summary down to the height of the
   card itself (no taller than its content), leaving .quote-summary nothing to stick within — it
   would just scroll normally instead (found testing US-013 with the user). The default
   `stretch` keeps #quoter-summary as tall as the row (the longer form column sets it), which is
   what actually gives the sticky card room to travel. */
@media (min-width: 48rem) {
  .quoter-grid {
    grid-template-columns: 1fr 1fr;
  }
}

.quoter-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

.quote-step {
  border: 0;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.quote-step legend {
  padding: 0;
  font-size: var(--font-size-200);
  color: var(--color-neutral-300);
  margin-bottom: var(--space-1);
}

.quote-options {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

/* role="radio" buttons (RF-COT-002): full width, 48 px tall, label left / hint right. The
   selected state is never color alone (UX-VIS-013) — the border also thickens, same rule
   .rate-card/.service-card's own .is-selected already follows. */
.quote-option {
  min-height: var(--size-touch-md);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  text-align: left;
  border-radius: var(--radius-md);
  border: var(--hairline) solid var(--color-divider);
  background: transparent;
  color: var(--color-text);
  font: inherit;
  font-size: var(--font-size-200);
  cursor: pointer;
}

.quote-option:hover {
  border-color: color-mix(in srgb, var(--color-text) 45%, transparent);
}

.quote-option.is-selected {
  border-width: calc(var(--hairline) * 2);
  border-color: var(--color-neutral-200);
  background: color-mix(in srgb, var(--color-text) 10%, transparent);
}

.quote-option-hint {
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
  white-space: nowrap;
}

/* Sticky at 70px from the top — clears the 52px header plus a little breathing room, same idea
   as .price-table's own scroll-margin-top just above. */
.quote-summary {
  border: var(--hairline) solid var(--color-divider);
  position: sticky;
  top: var(--space-13);
}

.quote-summary dl {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-1) var(--space-3);
  font-size: var(--font-size-200);
}

.quote-summary dt {
  color: var(--color-neutral-400);
}

.quote-summary dd {
  text-align: right;
}

.quote-total-row {
  border-top: var(--hairline) solid var(--color-divider);
  padding-top: var(--space-3);
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.quote-total-label {
  font-size: var(--font-size-200);
  color: var(--color-neutral-300);
}

.quote-total-value {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: clamp(1.1875rem, 3.4vw, 1.625rem);
  text-align: right;
}

.quote-submit {
  min-height: var(--size-touch-md);
  margin-top: var(--space-1);
}

/* Dimmed, not removed from the tab order or made unclickable (UX-CMP-006 wants the explanation
   reachable): the click handler on this state opens a toast instead of following the href. */
.quote-submit.is-incomplete {
  opacity: 0.45;
}

/* .card-body's own flex:1 assumes it is the last element before the button (the common case);
   a service card has the work-types list and price after it, so growing the description itself
   would open a gap above them instead of pushing just the button down (round of manual testing,
   US-012). */
.service-card .card-body {
  flex: none;
}

.service-card .btn {
  width: 100%;
  margin-top: auto;
}

/* Same fix as .service-card above: a rate card's own surcharge line (.card-body) is followed
   by the "Seleccionada" tag and the actions row, not the card's edge — growing the line itself
   would open a gap instead of evening out the row when one card in the grid gets a tag the
   others don't. */
.rate-card .card-body {
  flex: none;
}

.rate-card .actions-row {
  margin-top: auto;
}

/* Each button fills an equal share of the row (servicios.dc.html's own rate buttons are
   flex:1 1 auto) — a lone "Seleccionar" spans the whole row instead of sitting as a small pill
   at its own content width, and two surcharge buttons split it evenly. */
.rate-card .actions-row .btn {
  flex: 1 1 auto;
}

.form-narrow {
  max-width: var(--form-narrow-width);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Same rhythm as .form-narrow, without its width cap: a page that mixes narrow fields with a
   wider live-preview row (/panel/textos) applies the cap per field instead of on the form. */
.form-fields {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.field-narrow {
  max-width: var(--form-narrow-width);
}

/* — live text previews (/panel/textos): an example of how the block renders on the public
   site, next to the textarea that edits it. Stacked on mobile; side by side from 768 px, where
   there is room for both without either becoming too narrow to read. */
.field-preview-row {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: var(--container-panel);
}

/* Most rows hold a single .field; Contacto's preview (Título + Texto together, one visual
   unit) stacks two, so this side needs its own rhythm between them — a no-op everywhere else,
   since gap has nothing to do with a single child. */
.field-preview-input {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

@media (min-width: 48rem) {
  .field-preview-row {
    flex-direction: row;
    align-items: flex-start;
  }

  .field-preview-input,
  .field-preview-output {
    flex: 1 1 0;
    min-width: 0;
  }
}

.field-preview-output {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.preview-list {
  margin: 0;
  padding-left: 1.25rem;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.preview-steps {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 11.25rem), 1fr));
}

.preview-step-label {
  margin: 0;
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

.preview-faq-item {
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  padding: var(--space-3);
}

.preview-faq-item summary {
  cursor: pointer;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
}

.preview-faq-item p {
  margin: var(--space-2) 0 0;
  white-space: pre-line;
}

/* — live services preview (/panel/servicios): full width below the form (a rate table needs
   more room than a side-by-side column can spare), with its own top margin to separate it from
   "Guardar cambios" above. */
.services-preview-section {
  margin-top: var(--space-8);
  max-width: var(--container-panel);
}

.preview-services {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}

/* One service's own title, description, work-type list and rate cards: a flex column so the
   gap alone sets the rhythm between them, instead of each element's own default margin
   (which does not collapse against a flex gap and would otherwise stack on top of it). */
.preview-service {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.preview-service > * {
  margin: 0;
}

.grid-autofit {
  display: grid;
  gap: var(--space-4);
}
.grid-autofit--150 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 9.375rem), 1fr)); }
.grid-autofit--230 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 14.375rem), 1fr)); }
.grid-autofit--240 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); }
.grid-autofit--260 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 16.25rem), 1fr)); }
.grid-autofit--280 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17.5rem), 1fr)); }
.grid-autofit--300 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 18.75rem), 1fr)); }

/* ══════════════════════════════════════════════════════════════════════════
   3. LAYOUTS
   Mobile-first: base styles target 320px; media queries add capability at
   the platform's single breakpoint (48rem / 768px) with min-width, except
   the ones that hide desktop-only chrome below it.
   ══════════════════════════════════════════════════════════════════════ */

/* — public site chrome — */
.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  min-height: var(--space-13);
  backdrop-filter: blur(var(--blur-chrome));
  background: color-mix(in srgb, var(--color-bg) 88%, transparent);
  border-bottom: var(--hairline) solid var(--color-divider);
}

.site-header .container-1200 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--space-13);
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}

.site-brand {
  font-family: var(--font-heading);
  font-size: var(--font-size-200);
  letter-spacing: 0.18em;
  text-decoration: none;
  color: var(--color-text);
}

/* Landing header on phones (360 to 767 px): the brand and the two account buttons share one row
   instead of the buttons stacking under each other and doubling the header's height. It takes a
   smaller wordmark and tighter buttons (the touch height stays); below 360 px nothing fits in one
   row, so the default wrapping stays. */
@media (min-width: 22.5rem) and (max-width: 47.99rem) {
  .site-header .container-1200 {
    flex-wrap: nowrap;
    gap: var(--space-2);
  }

  .site-header .site-brand {
    font-size: var(--font-size-100);
    letter-spacing: 0.06em;
    white-space: nowrap;
  }

  .site-header .actions-row {
    flex: none;
    flex-wrap: nowrap;
    gap: 0.375rem;
  }

  .site-header .actions-row .btn {
    padding-inline: var(--space-2);
    font-size: var(--font-size-100);
    white-space: nowrap;
  }
}

.site-nav {
  display: none;
  align-items: center;
  gap: var(--space-1);
}

.site-nav a {
  padding: var(--space-2) var(--space-2);
  min-height: var(--size-touch-sm);
  display: flex;
  align-items: center;
  font-size: var(--font-size-200);
  text-decoration: none;
  color: var(--color-neutral-300);
  border-radius: var(--radius-sm);
}

.site-nav a:hover { background: color-mix(in srgb, var(--color-text) 8%, transparent); }

/* The current page reads brighter than the rest, not dimmer (docs/prototype/*.dc.html: every
   page's own nav item gets color: var(--color-neutral-100) while the others get neutral-300). */
.site-nav a[aria-current="page"] { color: var(--color-neutral-100); }

.site-menu-toggle {
  display: inline-flex;
}

.site-menu {
  border-top: var(--hairline) solid var(--color-divider);
  padding: var(--space-2) var(--space-4) var(--space-4);
  display: flex;
  flex-direction: column;
  background: var(--color-bg);
}

.site-menu[hidden] { display: none; }

.site-menu a {
  padding: var(--space-3) var(--space-1);
  min-height: var(--size-touch-sm);
  display: flex;
  align-items: center;
  font-size: var(--font-size-400);
  text-decoration: none;
  color: var(--color-neutral-300);
}

.site-menu a[aria-current="page"] { color: var(--color-neutral-100); }

.tab-bar {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  width: 100%;
  bottom: 0;
  z-index: var(--z-tab-bar);
  display: flex;
  background: color-mix(in srgb, var(--color-bg) 96%, transparent);
  backdrop-filter: blur(var(--blur-chrome));
  border-top: var(--hairline) solid var(--color-divider);
  padding-bottom: env(safe-area-inset-bottom);
}

.tab-bar > * {
  flex: 1;
  min-width: 0;
  min-height: var(--size-touch-lg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-1);
  border: 0;
  background: transparent;
  color: var(--color-neutral-400);
  font: inherit;
  font-size: var(--font-size-100);
  text-decoration: none;
  cursor: pointer;
}

.tab-bar > *[aria-current="page"] { color: var(--color-neutral-100); }

.tab-bar > * span:last-child {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

.wa-float {
  position: fixed;
  right: var(--space-4);
  bottom: calc(var(--size-touch-lg) + env(safe-area-inset-bottom) + var(--space-4));
  z-index: var(--z-float);
  min-height: var(--space-12);
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0 var(--space-4);
  border-radius: var(--radius-full);
  background: var(--color-action);
  color: var(--color-on-action);
  text-decoration: none;
  font-size: var(--font-size-200);
  font-family: var(--font-heading);
  box-shadow: var(--shadow-lg);
}

.site-footer {
  border-top: var(--hairline) solid var(--color-divider);
  background: color-mix(in srgb, var(--color-surface) 45%, transparent);
}

.site-footer--public {
  position: relative;
  padding-bottom: calc(var(--size-touch-lg) + env(safe-area-inset-bottom) + var(--space-4));
  border-top: 0;
}

/* Fading hairline instead of a full-width border, as in the prototype. */
.site-footer--public::before {
  content: "";
  display: block;
  height: var(--hairline);
  background: linear-gradient(90deg, transparent, var(--color-neutral-600) 12%, var(--color-neutral-600) 88%, transparent);
}

.site-footer--public .site-footer-inner {
  padding-top: clamp(2.5rem, 6vw, 4rem);
  padding-bottom: 0;
  gap: 0;
}

.site-footer-columns {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 2.25rem 3rem;
}

.site-footer-about {
  display: flex;
  flex: 2 1 17.5rem;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.875rem;
  min-width: 0;
  max-width: 26.25rem;
}

.site-footer-brand {
  font-family: var(--font-heading);
  font-size: clamp(1.375rem, 3vw, 1.75rem);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1;
}

.site-footer-about p {
  margin: 0;
  font-size: var(--font-size-200);
  line-height: 1.6;
  color: var(--color-neutral-300);
  text-wrap: pretty;
}

.site-footer-about .btn {
  min-height: 2.75rem;
  margin-top: var(--space-1);
  font-size: var(--font-size-200);
  color: var(--color-accent);
}

.site-footer-col {
  display: flex;
  flex: 1 1 9.375rem;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-1);
  min-width: 0;
}

.site-footer-col:last-child { flex-basis: 12.5rem; }

.site-footer-heading {
  margin-bottom: var(--space-2);
  font-size: 0.7813rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-neutral-100);
}

.site-footer-col a {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 2rem;
  transition: color var(--duration-fast) ease-out, transform var(--duration-fast) ease-out;
}

.site-footer-col a:hover { transform: translateX(0.1875rem); }

nav.site-footer-col a:hover {
  text-decoration: underline;
  text-underline-offset: 0.3125rem;
}

.site-footer-col a[aria-current="page"] { color: var(--color-neutral-100); }

.site-footer-col small {
  font-size: 0.7813rem;
  color: var(--color-neutral-400);
  overflow-wrap: anywhere;
}

.site-footer-col > a + a { margin-top: var(--space-2); }

nav.site-footer-col > a + a { margin-top: 0; }

.site-footer-legal {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.625rem 1.25rem;
  margin-top: clamp(2.25rem, 5vw, 3.5rem);
  padding: 1.125rem 0 1.75rem;
  border-top: var(--hairline) solid var(--color-divider);
}

.site-footer-legal p {
  margin: 0;
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.site-footer-legal .site-footer-made-with {
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.site-footer-made-with span {
  margin-left: 0.25rem;
  font-family: var(--font-heading);
  font-size: 0.6875rem;
  letter-spacing: 0.2em;
  color: var(--color-neutral-200);
}

.site-footer .container-1200 {
  padding-top: var(--space-8);
  padding-bottom: var(--space-8);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.site-footer nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
}

.site-footer a {
  font-size: var(--font-size-200);
  color: var(--color-neutral-300);
  text-decoration: none;
}

.site-footer a:hover { color: var(--color-neutral-100); }

/* Overrides the shared, stacked layout above — must come after it to win the cascade at equal
   specificity (2 classes each). A row on one line (label left, links right), matching the
   prototype's own footer; _PublicLayout/_PlatformLayout keep the stacked version above, since
   their footers carry more links than fit comfortably on one line. */
.site-footer--landing .container-1200 {
  flex-flow: row wrap;
  align-items: center;
  justify-content: space-between;
  padding-top: var(--space-5);
  padding-bottom: var(--space-5);
}

/* The shared .site-footer p/a sizing (14-16px) is sized for the richer public-site/panel
   footers; the prototype's own footer reads as a quieter, smaller caption line (12px) — matched
   here rather than in the shared rule, same reasoning as the row-layout override above. */
.site-footer--landing p {
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.site-footer--landing a {
  font-size: var(--font-size-100);
  color: var(--color-neutral-300);
}

/* — panel chrome — */
.panel-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  border-bottom: var(--hairline) solid var(--color-divider);
  background: color-mix(in srgb, var(--color-bg) 92%, transparent);
  backdrop-filter: blur(var(--blur-chrome));
}

/* Round-13 (found by the user, regression from round-9): PortafolioProyecto.cshtml swaps
   .container-1180 for .container-1440 via ViewData["PanelContainerClass"] on this exact page, and
   that swap also changed the class on the header's own inner div (_PanelLayout.cshtml reuses one
   variable for all three container spots) — this rule only ever matched .container-1180, so on
   that page the header's brand/actions flex layout silently stopped applying and everything
   collapsed to the left. Matching both classes keeps the header's layout intact regardless of
   which container width the page below it chose. */
.panel-header .container-1180,
.panel-header .container-1440 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  min-height: var(--space-13);
  padding-top: var(--space-2);
  padding-bottom: var(--space-2);
}

.panel-brand {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
}

.panel-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

/* The base .tag is align-self: flex-start; in this row it must center like the buttons beside it. */
.panel-actions .tag { align-self: center; }

.panel-title {
  display: block;
  min-width: 0;
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.panel-layout {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-6);
  align-items: flex-start;
  padding-top: var(--space-5);
  padding-bottom: var(--space-13);
}

.panel-nav {
  display: flex;
  gap: var(--space-1);
  max-width: 100%;
  flex: 1 0 100%;
  overflow-x: auto;
  scroll-snap-type: x proximity;
  flex-wrap: nowrap;
}

/* Prototype (docs/prototype/panel-portafolio.dc.html): only the active section gets a visible
   border and a dimmer neutral-300 label; every other item is plain text with a transparent
   border, not the outlined pill our first pass gave every item regardless of state. */
.panel-nav > * {
  scroll-snap-align: start;
  flex: none;
  white-space: nowrap;
  text-align: left;
  min-height: var(--size-touch-sm);
  padding: 0 var(--space-3);
  border-radius: var(--radius-md);
  border: var(--hairline) solid transparent;
  background: transparent;
  color: var(--color-neutral-300);
  font: inherit;
  font-size: var(--font-size-200);
  cursor: pointer;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}

.panel-nav > *:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }

.panel-nav > *[aria-current="page"] {
  border-color: var(--color-divider);
  background: var(--color-surface);
  color: var(--color-neutral-100);
}

.panel-content {
  flex: 1 1 20rem;
  min-width: 0;
}

/* Prototype panel headings (docs/prototype/panel-portafolio.dc.html) use their own, smaller
   scale (h1: 24px, h2: 16px) than the site-wide h1/h2 sized for public marketing pages
   (40px/32px) — the panel is a dense admin surface, not a landing page. Margins follow the same
   source: h1's own 4px (not h1-h6's shared 8px) and its subtitle's 20px gap below (found reviewing
   with the user: our subtitle used .field-help's zero margin, sitting flush against "Categorías"
   instead of the prototype's clear break). */
.panel-content h1 {
  font-size: var(--font-size-600);
  margin-bottom: var(--space-1);
}

.panel-content h1 + p {
  margin-bottom: var(--space-5);
}

.panel-content h2 {
  font-size: var(--font-size-300);
}

/* — Panel de inicio (US-021): cuatro tarjetas de estadística — */
.panel-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9.375rem), 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-6);
}

.panel-stat-card {
  padding: var(--space-4);
  gap: var(--space-1);
}

.panel-stat-label {
  font-size: var(--font-size-100);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-neutral-400);
}

.panel-stat-value {
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--font-size-600);
}

/* — Publicar (US-021): tarjeta de requisitos, acciones y despublicar — */
.publish-card {
  max-width: 35rem;
  margin-bottom: var(--space-6);
}

.publish-requirements {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

.publish-requirement {
  display: flex;
  gap: var(--space-2);
  align-items: flex-start;
  font-size: var(--font-size-200);
}

.publish-requirement-icon {
  flex: none;
}

/* The icon carries most of the met/unmet distinction visually, but color alone never does
   (UX-VIS-013) — the ✓/! glyph itself is the non-color signal for both this and the label. */
.publish-requirement.is-met { color: var(--color-neutral-300); }
.publish-requirement.is-unmet { color: var(--color-neutral-100); }

.publish-requirement-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}

.publish-requirement-body form { margin: 0; }

/* Round-2 quality review: this <p> is not a direct child of .card, so it kept the global
   p { margin: 0 0 var(--space-3) } rule, stacking with the flex gap above. Also restores the
   met/unmet color it's meant to inherit from .publish-requirement — the global `p` rule's own
   color (added to dim plain body copy) otherwise overrides that inheritance. */
.publish-requirement-body p {
  margin: 0;
  color: inherit;
}

/* UX-LAY-008: 24 px is the enforced minimum touch target (WCAG 2.5.8), even for a small
   secondary link nested under a requirement label. */
.btn-inline {
  padding-inline: 0;
  min-height: 1.5rem;
  font-size: var(--font-size-100);
}

.publish-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  border-top: var(--hairline) solid var(--color-divider);
  padding-top: var(--space-4);
}

.publish-actions form { margin: 0; }

.publish-footer-actions {
  max-width: 35rem;
  margin-top: var(--space-4);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
}

.publish-footer-actions form { margin: 0; }

/* — error pages (404/500, "Sitio en borrador" style) — */
.error-page {
  min-height: 70vh;
  max-width: var(--error-page-width);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: var(--space-3);
}

/* — Portafolio público (US-015): "Trabajos" — filter pills plus the project grid. — */

/* Criterion 1: 44 px pills, "Todo" plus one per category. Active: light background/dark text
   (never color alone, UX-VIS-013 — aria-current already carries the state for assistive tech). */
.portfolio-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block: var(--space-6);
}

.portfolio-filter-pill {
  display: inline-flex;
  align-items: center;
  min-height: var(--size-touch-sm);
  padding-inline: var(--space-4);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-full);
  color: var(--color-text);
  font-size: var(--font-size-100);
  text-decoration: none;
  transition: background var(--duration-fast) ease-out, color var(--duration-fast) ease-out;
}

.portfolio-filter-pill:hover {
  border-color: var(--color-action);
}

.portfolio-filter-pill[aria-current="true"] {
  background: var(--color-action);
  border-color: var(--color-action);
  color: var(--color-on-action);
}

/* Criterion 3: a dotted card instead of a bare line of text (UX-EST-003 — explains what's here,
   why, and gives an action: "Ver todo"). */
.portfolio-empty {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-8) var(--space-4);
  border: var(--hairline) dashed var(--color-divider);
  border-radius: var(--radius-md);
  color: var(--color-text-muted);
}

/* Criterion 2: auto-fill at a 240 px floor reflows on its own between the prototype's three named
   breakpoints (320/640/1024 px) without hardcoding any of them — the same pattern the panel's own
   media grid already uses. */
.portfolio-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
  gap: var(--space-6) var(--space-4);
  margin-block: var(--space-6);
}

.portfolio-project-card {
  /* Criterion 3's opacity transition: replays on its own whenever a card goes from
     display: none (via the `hidden` attribute public-portfolio.js toggles) back to visible —
     browsers restart a CSS animation from its start every time, no JavaScript timing needed.
     prefers-reduced-motion: reduce already clamps every animation sitewide (see this file's own
     rule near the top). */
  animation: portfolio-card-fade-in var(--duration-base) ease-out;

  /* US-016: the card is a real <a> (RF-POR-001, opens the viewer/player) — reset the browser's
     default link styling so it still reads as a plain card, not a text link. */
  display: block;
  color: inherit;
  text-decoration: none;
}

@keyframes portfolio-card-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.portfolio-project-cover {
  position: relative;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--radius-md);
  border: var(--hairline) solid var(--color-divider);
  transition: border-color var(--duration-fast) ease-out, transform var(--duration-fast) ease-out;
}

.portfolio-project-cover img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Criterion 4 (C, could): a subtle scale on the cover only — never the whole card, so the title/
   meta text below never jitters. */
.portfolio-project-card:hover .portfolio-project-cover {
  border-color: var(--color-neutral-100);
  transform: scale(1.05);
}

.portfolio-video-badge {
  position: absolute;
  bottom: var(--space-2);
  left: var(--space-2);
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--radius-full);
  background: rgb(0 0 0 / 55%);
  color: var(--color-neutral-100);
}

.portfolio-video-badge .icon {
  width: 0.875rem;
  height: 0.875rem;
}

.portfolio-project-title {
  margin: var(--space-3) 0 0;
  font-size: var(--font-size-200);
}

.portfolio-project-meta {
  margin: var(--space-1) 0 0;
  color: var(--color-text-muted);
  font-size: var(--font-size-100);
}

/* — US-016: photo viewer and video player — */

/* dialog:not([open]) { display: none !important } already applies (see this file's own rule near
   the top) — showModal() puts the dialog in the browser's top layer, so no z-index is needed here
   to sit above the header/tab bar. */
.portfolio-viewer {
  /* inset: 0 alone already sizes a fixed, un-widthed dialog to fill its containing block — 100vw/
     100vh double up on that and also count the desktop scrollbar's own width (cutting off the ›
     button) and the mobile browser chrome's height (the footer hides under it, UX-ACC-005 area). */
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: 100%;
  max-height: 100%;
  margin: 0;
  padding: var(--space-4);
  border: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  background: color-mix(in srgb, var(--color-neutral-900) 92%, transparent);
  color: var(--color-neutral-100);
  animation: portfolio-viewer-fade-in var(--duration-base) ease-out;
}

@keyframes portfolio-viewer-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

.portfolio-viewer-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.portfolio-viewer-counter {
  margin: 0;
  font-family: var(--font-heading);
  font-size: var(--font-size-200);
}

/* A project's own page (US-016, post-launch adjustment): every Ready medium as real page content,
   same grid rhythm as the "Trabajos" listing's own .portfolio-grid. */
.portfolio-project-media-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--space-4);
  margin-block: var(--space-6);
}

/* Same visual language as the panel's own .media-card (light surface, square rounded thumbnail,
   numbered badge) — the user asked for the public grid to read like that already-familiar card,
   just without any of its editing controls (arrows, cover star, alt text, delete). */
.portfolio-viewer-thumb {
  position: relative;
  padding: var(--space-2);
  border: 0;
  border-radius: var(--radius-md);
  background: var(--color-surface);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  transition: box-shadow var(--duration-fast) ease-out;
}

.portfolio-viewer-thumb:hover,
.portfolio-viewer-thumb:focus-visible {
  box-shadow: var(--shadow-md);
}

.portfolio-viewer-thumb img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  border-radius: var(--radius-sm);
  object-fit: cover;
  background: var(--color-neutral-800);
}

.portfolio-viewer-thumb-badge {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  display: grid;
  place-items: center;
  min-width: 1.5rem;
  height: 1.5rem;
  padding-inline: var(--space-1);
  border-radius: var(--radius-full);
  background: rgb(0 0 0 / 55%);
  color: var(--color-neutral-100);
  font-size: var(--font-size-100);
  font-variant-numeric: tabular-nums;
}

.portfolio-viewer-body {
  flex: 1;
  min-height: 0;
  display: flex;

  /* No align-items: center here on purpose — the default (stretch) is what gives
     .portfolio-viewer-image-wrap a definite height for its own height: 100% to resolve against.
     With center, the wrap sizes to its content instead, so max-height: 100% on a tall portrait
     photo has nothing to cap against and the image overflows past the viewport. The nav buttons
     keep their own fixed 44px size regardless (.btn-icon sets an explicit height). */
  gap: var(--space-3);
}

.portfolio-viewer-nav {
  flex: none;

  /* .portfolio-viewer-body no longer stretches everything via align-items: center (see its own
     comment) — the buttons keep their fixed 44px size either way, but need this to stay vertically
     centered instead of defaulting to the top of the row under plain stretch. */
  align-self: center;
}

.portfolio-viewer-image-wrap {
  flex: 1;
  height: 100%;

  /* Not display: grid — an implicit grid row with no explicit track size sizes itself to its
     content (the image), which makes the row indefinite for percentage-height purposes, so the
     image's own max-height: 100% below resolved against nothing and overflowed past this box on
     a tall (portrait) photo. A flex container's height, by contrast, is what its own height: 100%
     above already sets — a percentage on a flex *item* like .portfolio-viewer-image can resolve
     against that directly. */
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 0;
}

.portfolio-viewer-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
}

.portfolio-viewer-footer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-4);
  text-align: center;
}

.portfolio-viewer-help {
  margin: 0;
  color: color-mix(in srgb, var(--color-neutral-100) 70%, transparent);
  font-size: var(--font-size-100);
}

/* Centered, unlike the fullscreen viewer above — the reproductor is "a dialog", not a takeover of
   the whole screen (criterion 4: "diálogo centrado de máximo 940 px"). */
.portfolio-player {
  width: min(58.75rem, calc(100% - var(--space-8)));
  max-width: min(58.75rem, calc(100% - var(--space-8)));
  margin: auto;
  padding: var(--space-4);
  border: 0;
  border-radius: var(--radius-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  background: var(--color-surface);
  color: var(--color-text);
  animation: portfolio-viewer-fade-in var(--duration-base) ease-out;
}

.portfolio-player::backdrop {
  background: color-mix(in srgb, var(--color-neutral-900) 92%, transparent);
}

.portfolio-player-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.portfolio-player-counter {
  margin: 0 0 var(--space-1);
  color: var(--color-text-muted);
  font-size: var(--font-size-100);
}

.portfolio-player-title {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--font-size-500);
}

.portfolio-player-body {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

.portfolio-player-video {
  flex: 1;
  min-width: 0;
  width: 100%;

  /* height: auto is already <video>'s default — kept explicit as a guard, since what actually
     forces criterion 4's 16:9 box is aspect-ratio below, combined with portfolio-viewer.js no
     longer setting width/height attributes on the element (those would override aspect-ratio). */
  height: auto;
  aspect-ratio: 16 / 9;
  border-radius: var(--radius-md);
  background: var(--color-neutral-900);
}

.portfolio-player-help {
  margin: 0;
  color: var(--color-text-muted);
  font-size: var(--font-size-100);
}

/* — Importar carpeta (US-020): paso 1's grupos/proyectos detectados and paso 2's progress bar.
   margin-bottom: .card only ever spaces its OWN content via its internal `gap` — nothing spaces
   one card from the next sibling card, or from whatever follows the last one. Paso 1 renders one
   .import-group per detected root group, so consecutive groups (e.g. "Fotografía" then "Video")
   sat flush against each other, and the last one sat flush against the submit button below it
   (found live testing the local-folder import with the user). A first attempt used
   `.import-group:last-of-type`, which doesn't actually match here: `:last-of-type` matches by TAG
   only, and the submit button's own wrapping `<div class="actions-row">` right after the loop is
   itself the actual last `<div>` sibling — so the compound selector never matched anything. A
   plain margin-bottom on every card sidesteps that entirely: consecutive cards still show a
   single gap (nothing above adds its own margin-top to double it), and the last one is
   unconditionally followed by the same gap, regardless of what comes next. */
.import-group {
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.import-project {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  border-top: var(--hairline) solid var(--color-divider);
}

/* Same gap this .card was already missing everywhere else it borders another sibling block
   (.import-group, above) — here it's the paso 2 progress card sitting flush against the
   "Cancelar" form right below it (found live testing the local-folder import with the user). */
[data-import-progress] {
  margin-bottom: var(--space-4);
}

.progress-bar {
  height: var(--space-3);
  border-radius: var(--radius-full);
  background: var(--color-neutral-800);
  overflow: hidden;
}

.progress-bar-fill {
  width: var(--pct, 0);
  height: 100%;
  background: var(--color-accent);
  transition: width var(--duration-base) ease-out;
}

@media (min-width: 48rem) {
  .site-nav { display: flex; }
  .site-menu-toggle { display: none; }
  .site-menu { display: none; }
  .tab-bar { display: none; }
  .site-footer--public { padding-bottom: 0; }
  .wa-float { bottom: var(--space-4); }

  .panel-nav {
    flex: 0 0 15rem;
    flex-direction: column;
    align-self: start;
    position: sticky;
    top: var(--space-15);
    overflow-x: visible;
    scroll-snap-type: none;
  }

  .panel-nav > * { flex: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   Professionals directory (US-027)
   Search, filters and sort are one GET form. Reuses .landing-search-form, .seg, .chip-toggle,
   .tag-outline and .portfolio-empty; only what has no equivalent is defined here. Under 760 px
   (47.5rem) professionals-directory.js turns .directory-filters into a bottom sheet — without it
   the filters simply stay inline at every width.
   ══════════════════════════════════════════════════════════════════════════ */
.directory-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-block: var(--space-6);
}

.directory-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
}

/* The prototype's own toolbar controls are taller (48px search, 48px sort, 40px city) than the
   shared .landing-search-form/.input convention (36-37px) reused elsewhere — scoped overrides
   below instead of changing those shared classes, which the landing hero and every other form in
   the app still rely on at their own size. */
.directory-search {
  flex: 1 1 18rem;
  min-height: 3rem;
}

.directory-search input { padding-block: var(--space-3); }

#directory-orden { min-height: 3rem; }

#directory-ciudad { min-height: 2.5rem; }

.directory-search-clear {
  display: inline-flex;
  align-items: center;
  padding: var(--space-2);
  color: var(--color-neutral-400);
}

.directory-search-clear:hover { color: var(--color-text); }

.directory-search-clear .icon {
  width: 1rem;
  height: 1rem;
}

.directory-sort {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

.directory-filters {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-4) var(--space-6);
  padding-block: var(--space-4);
  border-block: var(--hairline) solid var(--color-divider);
}

.directory-filters-head { display: none; }

.directory-filter {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

.directory-filter legend,
.directory-filter label:not(.seg-opt, .chip-toggle) {
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

.directory-count-badge {
  color: var(--color-neutral-500);
  margin-left: var(--space-1);
}

/* The prototype's specialty pills are taller/wider than the shared .seg-opt default elsewhere
   (Dev/Styles.cshtml) — scoped here rather than resizing that shared class. */
.directory-filter .seg-opt {
  min-height: 2.5rem;
  padding-inline: var(--space-4);
}

.directory-work-type-check { display: none; }

.directory-work-type:has(input:checked) .chip-toggle-plus { display: none; }

.directory-work-type:has(input:checked) .directory-work-type-check {
  display: inline-flex;
  margin-right: var(--space-1);
}

.directory-work-type-check .icon {
  width: 0.875rem;
  height: 0.875rem;
}

/* Matches the prototype's own pill size for this control — taller and with a bigger side padding
   than the shared .chip-toggle default, so it lines up with the 40px specialty/city controls
   beside it instead of looking shrunk against them. */
.directory-work-type {
  min-height: 2.5rem;
  padding-inline: var(--space-3);
  font-size: 0.8125rem;
}

/* With the script, the controls submit on change, so the button is only needed inside the sheet. */
.directory-form[data-enhanced] .directory-apply { display: none; }

.directory-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-dialog);
  background: color-mix(in srgb, var(--color-neutral-900) 72%, transparent);
}

.directory-results-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.directory-count {
  margin: 0;
  color: var(--color-text-muted);
}

.directory-count strong { color: var(--color-text); }

.directory-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.directory-chip {
  gap: var(--space-1);
  text-decoration: none;
}

.directory-chip .icon {
  width: 0.75rem;
  height: 0.75rem;
}

.directory-empty .icon {
  width: 1.5rem;
  height: 1.5rem;
}

.directory-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 14.375rem), 1fr));
  gap: 0.875rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.directory-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  overflow: hidden;
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--duration-fast) ease-out, transform var(--duration-fast) ease-out;
}

.directory-card:hover {
  border-color: var(--color-neutral-500);
  transform: translateY(-0.125rem);
}

.directory-card:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

/* The image is taken out of flow: with a percentage height inside an aspect-ratio box, its
   natural size would otherwise grow the box past 4:3. */
.directory-card-cover {
  position: relative;
  display: block;
  overflow: hidden;
  aspect-ratio: 4 / 3;
  background: var(--color-neutral-800);
}

.directory-card-cover img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;

  /* A vertical photo (the Inicio photo) cropped to 4:3 keeps the upper part, where faces are,
     instead of the centre; landscape covers are not affected. */
  object-position: 50% 25%;
}

.directory-card-body {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-3);
}

.directory-card-name {
  overflow: hidden;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: var(--font-size-300);
  text-overflow: ellipsis;
  white-space: nowrap;
}

.directory-card-spec {
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

.directory-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin-top: var(--space-1);
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.directory-card-meta > span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
}

.directory-card-meta .icon {
  width: 0.875rem;
  height: 0.875rem;
}

.directory-card-price {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
  margin-top: auto;
  padding-top: var(--space-2);
  border-top: var(--hairline) solid var(--color-divider);
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

.directory-card-price strong {
  font-size: var(--font-size-200);
  color: var(--color-text);
}

.directory-more {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  margin-top: var(--space-6);
  color: var(--color-text-muted);
}

.directory-more p {
  margin: 0;
  font-size: var(--font-size-100);
}

@media (max-width: 47.4375rem) {
  .directory-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-2);
  }

  .directory-filters.is-sheet {
    position: fixed;
    inset: auto 0 0;
    z-index: calc(var(--z-dialog) + 1);
    display: none;
    flex-flow: column nowrap;
    gap: var(--space-4);
    max-height: 82vh;
    max-height: 82svh;
    overflow-y: auto;
    padding: var(--space-4);
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    background: var(--color-surface);

    /* The desktop divider strip (border-block on the base rule) would just be clutter floating
       inside this sheet's own bordered, elevated surface. */
    border-block: 0;
  }

  .directory-filters.is-sheet.is-open { display: flex; }

  .directory-filters.is-sheet .directory-filters-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
  }

  .directory-filters-title {
    margin: 0;
    font-size: var(--font-size-400);
  }

  .directory-form[data-enhanced] .directory-filters.is-sheet .directory-apply { display: inline-flex; }

  .directory-sort { margin-left: auto; }
}

body.directory-sheet-open { overflow: hidden; }

/* — Perfil: "Foto del inicio" picker (RF-INI-003) — */
.home-photo-none {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
}

.home-photo-group {
  margin-bottom: var(--space-3);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
}

.home-photo-group > summary {
  padding: var(--space-3);
  cursor: pointer;
}

.home-photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(6rem, 1fr));
  gap: var(--space-2);
  padding: 0 var(--space-3) var(--space-3);
}

.home-photo-option {
  position: relative;
  display: block;
  cursor: pointer;
}

.home-photo-option input {
  position: absolute;
  inset: 0;
  opacity: 0;
  cursor: pointer;
}

.home-photo-option img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border: 2px solid transparent;
  border-radius: var(--radius-md);
}

.home-photo-option input:checked + img { border-color: var(--color-neutral-100); }

.home-photo-option input:focus-visible + img {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

/* — Nosotros: the chosen photo (a 1:1 frame, at most 17.5rem wide) beside the presentation text
   from 768 px, stacked on mobile; without a photo it is a single text column (RF-NOS-001). */
.about-grid {
  display: grid;
  gap: var(--space-5);
  align-items: start;
}

@media (min-width: 48rem) {
  .about-grid--photo {
    grid-template-columns: 1fr 1fr;
  }
}

.about-photo {
  width: 100%;
  max-width: 17.5rem;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: var(--radius-md);
}

.about-photo img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.about-text {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* ══════════════════════════════════════════════════════════════════════════
   Panel home: activity, sharing, directory switch, suggestions (US-029)
   Reuses .card, .panel-stat-*, .seg/.seg-opt (period selector), .toast and .publish-card; only
   what has no equivalent is defined here. The two columns wrap on their own, no breakpoint.
   ══════════════════════════════════════════════════════════════════════════ */
.panel-home {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-6);
  margin-top: var(--space-5);
}

.panel-home-main {
  display: flex;
  flex: 1 1 27.5rem;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

.panel-home-aside {
  display: flex;
  flex: 1 1 17.5rem;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}

/* Scoped to .panel-content so it outranks `.panel-content h2`, which would otherwise force 16px. */
.panel-content .panel-section-title {
  margin: 0;
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--color-neutral-400);
}

.panel-subsection-title {
  margin: 0;
  font-size: var(--font-size-200);
  font-weight: 500;
  color: var(--color-neutral-300);
}

/* — Qué hacer ahora — */
.suggestions-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
}

.suggestions-counter {
  margin: 0;
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

.suggestion-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
}

.suggestion-icon {
  display: inline-flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--radius-md);
  background: var(--color-neutral-800);
}

.suggestion-icon .icon {
  width: 1.25rem;
  height: 1.25rem;
}

.suggestion-body h3 {
  margin: 0 0 var(--space-1);
  font-size: var(--font-size-400);
}

.suggestion-body p {
  margin: 0 0 var(--space-3);
  font-size: var(--font-size-200);
  color: var(--color-text-muted);
}

.suggestion-body .actions-row { margin-bottom: 0; }

/* — Actividad — not a .card itself (unlike every other panel-home section): it is a plain group of
   individually-boxed pieces (each KPI tile, the chart, each ranking), same as the prototype. */
.activity {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.activity-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.activity-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
}

.activity-period-form {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

/* With the script, changing the period refreshes in place, so the button is only for the no-JS case. */
.activity-period-form[data-enhanced] [data-period-apply] { display: none; }

.activity-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 9.375rem), 1fr));
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.activity-kpi-card {
  padding: var(--space-3);
  gap: var(--space-1);
  border: var(--hairline) solid var(--color-divider);
}

/* Sentence case, not the uppercase/tracked .panel-stat-label style — matches the prototype's own
   KPI label (font-size:12px, no text-transform). */
.activity-kpi-label {
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.activity-kpi-variation {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.activity-kpi-variation .icon {
  width: 0.875rem;
  height: 0.875rem;
}

.activity-kpi-variation.is-up { color: var(--color-neutral-200); }

.activity-chart-block {
  margin: 0;
  border: var(--hairline) solid var(--color-divider);
}

.activity-chart-block figcaption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2);
}

.activity-peak {
  margin: 0;
  font-size: var(--font-size-100);
  color: var(--color-text-muted);
}

.activity-chart {
  display: flex;
  align-items: flex-end;
  gap: 0.1875rem;
  height: 8rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.activity-chart.is-sparse { gap: 0.5rem; }

.activity-bar {
  flex: 1 1 0;
  min-width: 0;
  height: var(--pct, 0);
  min-height: 0.125rem;
  border-radius: 0.125rem 0.125rem 0 0;
  background: var(--color-neutral-600);
}

.activity-bar.is-peak { background: var(--color-neutral-100); }

.activity-axis {
  display: flex;
  justify-content: space-between;
  font-size: var(--font-size-100);
  color: var(--color-neutral-500);
}

.activity-rankings {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
  gap: var(--space-5);
}

.activity-ranking-card { border: var(--hairline) solid var(--color-divider); }

.activity-ranking {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: 0;
  padding: 0;
  list-style: none;
}

.activity-ranking li {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--space-1) var(--space-3);
  font-size: var(--font-size-200);
}

.activity-ranking-label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.activity-ranking-name {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-width: 0;
}

.activity-ranking-name .icon {
  flex: none;
  color: var(--color-text-muted);
}

.activity-ranking-value {
  color: var(--color-text-muted);
  white-space: nowrap;
}

.activity-ranking-bar {
  grid-column: 1 / -1;
  height: 0.25rem;
  border-radius: var(--radius-full);
  background: var(--color-neutral-800);
}

.activity-ranking-bar::after {
  display: block;
  width: var(--pct, 0);
  height: 100%;
  border-radius: inherit;
  background: var(--color-neutral-300);
  content: "";
}

.activity-privacy-note { max-width: 60ch; }

/* — Comparte tu sitio — */
.share-site-url {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 2.75rem;
  padding: 0 var(--space-1) 0 var(--space-3);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-bg);
  font-size: var(--font-size-200);
}

.share-site-url code {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.share-site-url .btn { flex: none; }

.share-site .actions-row { margin-bottom: 0; }

.share-site-qr {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding-top: var(--space-1);
}

.share-site-qr img {
  flex: none;
  width: 7rem;
  height: 7rem;
  border-radius: var(--radius-md);
}

.share-site-qr-body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}

.share-site-qr-body .field-help { margin: 0; }

.share-site-download {
  align-self: flex-start;
  font-size: var(--font-size-100);
}

/* — Directorio de profesionales — */
.directory-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
}

.directory-head-text { min-width: 0; }

.directory-head-text .field-help { margin: var(--space-1) 0 0; }

.switch {
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.switch:disabled {
  cursor: progress;
  opacity: 0.6;
}

.switch:focus-visible {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
  border-radius: var(--radius-full);
}

.switch-track {
  display: inline-flex;
  align-items: center;
  width: 3rem;
  height: 1.75rem;
  padding: 0.125rem;
  border-radius: var(--radius-full);
  background: var(--color-neutral-700);
  transition: background var(--duration-fast) ease-out;
}

.switch-thumb {
  width: 1.5rem;
  height: 1.5rem;
  border-radius: var(--radius-full);
  background: var(--color-neutral-100);
  transition: transform var(--duration-fast) ease-out;
}

.switch[aria-checked="true"] .switch-track { background: var(--color-accent); }

.switch[aria-checked="true"] .switch-thumb {
  background: var(--color-neutral-900);
  transform: translateX(1.25rem);
}

.directory-stats {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-2) var(--space-3);
  margin: 0;
  padding-top: var(--space-3);
  border-top: var(--hairline) solid var(--color-divider);
  font-size: var(--font-size-200);
}

.directory-stats dt { color: var(--color-text-muted); }

.directory-stats dd { margin: 0; }

.directory-eligible {
  display: inline-flex;
  align-items: center;
  gap: 0.3125rem;
}

.directory-see-link {
  align-self: flex-start;
  font-size: var(--font-size-100);
}

/* — Accesos rápidos — no outer .card: each link is its own bordered row, matching the prototype. */
.quick-links {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.quick-links-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

.quick-links-list a {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 3rem;
  padding: 0 var(--space-3);
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-neutral-100);
  font-size: var(--font-size-200);
  text-decoration: none;
}

.quick-links-list a:hover { border-color: var(--color-neutral-500); }

.quick-links-list a span { flex: 1; }

.quick-links-list a .icon:first-child {
  flex: none;
  width: 1.125rem;
  height: 1.125rem;
  color: var(--color-neutral-300);
}

.quick-links-list a .icon:last-child {
  flex: none;
  width: 1rem;
  height: 1rem;
  color: var(--color-neutral-500);
}

/* Landing anchors (nav: Cómo funciona / Cotizaciones / Ejemplo): the header is sticky, so
   without this the jump puts the section's top edge under it and hides its top padding. On narrow
   screens the header wraps onto two rows, so landing.js publishes its real height; the one-row
   height is only the fallback (no script). */
#como-funciona,
#cotizaciones,
#ejemplo {
  scroll-margin-top: var(--landing-header-height, var(--space-13));
}

/* — Panel · Apariencia (US-030): docs/prototype/panel-apariencia.dc.html — palette cards on the
   left, a miniature of the professional's own site on the right, both painted with the palette's
   custom properties (cards: --swatch-*; miniature: the palette's own tokens, set inline). The
   miniature is a picture (role="img"), so its type keeps the prototype's small sizes. — */
.appearance {
  container-type: inline-size;
}

.appearance h1 {
  font-size: clamp(1.375rem, 3cqw, 1.75rem);
}

/* Room under the content for the fixed pending-changes bar, so it never covers the last lines. */
.appearance:has([data-pending-bar]:not([hidden])) {
  padding-bottom: 7.5rem;
}

.appearance .appearance-lead {
  max-width: 60ch;
  margin-top: var(--space-2);
  font-size: 0.8438rem;
  line-height: 1.55;
  color: var(--color-neutral-300);
}

.appearance-layout {
  display: flex;
  flex-wrap: wrap;
  gap: 1.375rem;
  align-items: flex-start;
}

.appearance-palettes {
  flex: 1 1 20rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
}

.appearance-palettes-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.625rem;
}

.appearance h2 {
  margin: 0;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: 1.0625rem;
}

.appearance-count {
  font-weight: 400;
  color: var(--color-neutral-500);
}

.appearance .seg[hidden] {
  display: none;
}

.appearance .seg-opt {
  min-height: 2.25rem;
  padding-inline: var(--space-3);
}

.palette-grid {
  display: grid;
  gap: 0.625rem;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 9.375rem), 1fr));
}

.palette-card {
  position: relative;
  display: flex;
  flex-direction: column;
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
  color: var(--color-text);
  cursor: pointer;
  overflow: hidden;
}

.palette-card[hidden] {
  display: none;
}

.palette-card:hover {
  border-color: var(--color-neutral-400);
}

.palette-card:has(input:checked) {
  border-color: var(--color-neutral-200);
  box-shadow: 0 0 0 var(--hairline) var(--color-neutral-200);
}

.palette-card:has(input:focus-visible) {
  outline: 2px solid var(--color-focus);
  outline-offset: 2px;
}

.palette-card input {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.palette-card-swatch {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-2);
  height: 5.5rem;
  padding: 0.625rem;
  background: var(--swatch-bg);
}

.palette-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.375rem;
}

.palette-aa {
  font-family: var(--font-heading);
  font-size: 1.25rem;
  font-weight: var(--font-heading-weight);
  line-height: 1;
  color: var(--swatch-text);
}

.palette-check {
  display: none;
  align-items: center;
  justify-content: center;
  width: 1.375rem;
  height: 1.375rem;
  border-radius: var(--radius-full);
  background: var(--swatch-text);
  color: var(--swatch-bg);
  font-size: 0.8125rem;
}

.palette-card:has(input:checked) .palette-check {
  display: flex;
}

.palette-card-middle {
  display: flex;
  align-items: center;
  gap: 0.375rem;
}

.palette-cotizar {
  display: inline-flex;
  align-items: center;
  height: 1.125rem;
  padding-inline: var(--space-2);
  border: var(--hairline) solid var(--swatch-accent);
  border-radius: 0.3125rem;
  font-size: 0.5938rem;
  color: var(--swatch-accent);
}

.palette-cotizar-fill {
  flex: 1;
  height: 1.125rem;
  border-radius: 0.3125rem;
  background: var(--swatch-surface);
}

.palette-card-bars {
  display: flex;
  gap: var(--space-1);
}

.palette-card-bars > span {
  height: 0.1875rem;
  border-radius: 0.5625rem;
}

.palette-bar-strong {
  flex: 3;
  background: var(--swatch-text);
}

.palette-bar-muted {
  flex: 2;
  background: var(--swatch-muted);
}

.palette-bar-accent {
  flex: 1;
  background: var(--swatch-accent);
}

.palette-card-body {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  padding: 0.5625rem 0.625rem 0.625rem;
}

.palette-card-name {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.375rem;
  font-size: 0.8438rem;
}

.palette-card-title {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.palette-current {
  flex: none;
  align-self: auto;
  padding: 0 var(--space-2);
  font-size: var(--font-size-100);
}

.palette-card-note {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.appearance-note {
  margin: 0;
  font-size: var(--font-size-100);
  line-height: 1.5;
  color: var(--color-neutral-500);
}

.appearance-preview {
  position: sticky;
  top: 4.625rem;
  flex: 1.3 1 23.75rem;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.appearance-preview-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.625rem;
}

.appearance-preview-name {
  font-size: var(--font-size-100);
  color: var(--color-neutral-400);
}

.appearance-open {
  align-self: flex-start;
  font-size: 0.7813rem;
}

/* The miniature. Every color comes from the tokens the inline style sets on this element, so the
   whole subtree repaints when the palette changes. */
.preview-frame {
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-bg);
  color: var(--color-text);
  box-shadow: var(--shadow-lg);
  transition: background var(--duration-base);
}

.preview-browser {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 0.5625rem var(--space-3);
  border-bottom: var(--hairline) solid var(--color-divider);
  background: var(--color-surface);
}

.preview-dots {
  display: flex;
  gap: 0.3125rem;
}

.preview-dots > span {
  width: 0.5rem;
  height: 0.5rem;
  border-radius: var(--radius-full);
  background: var(--color-neutral-700);
}

.preview-url {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  text-align: center;
  font-size: 0.6875rem;
  color: var(--color-neutral-400);
}

.preview-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.625rem;
  padding: 0.625rem 0.875rem;
  border-bottom: var(--hairline) solid var(--color-divider);
}

.preview-brand {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  font-family: var(--font-heading);
  font-size: 0.7813rem;
  letter-spacing: 0.14em;
}

.preview-links {
  display: flex;
  gap: var(--space-3);
  font-size: 0.6875rem;
  color: var(--color-neutral-300);
}

.preview-link-current {
  color: var(--color-neutral-100);
}

.preview-hero {
  display: grid;
  grid-template-columns: 1.2fr 1fr;
  gap: 0.875rem;
  align-items: center;
  padding: 1.125rem 0.875rem;
}

.preview-hero-text {
  display: flex;
  flex-direction: column;
  gap: 0.5625rem;
  min-width: 0;
}

.preview-kicker {
  font-size: 0.5938rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--color-neutral-400);
}

.preview-title {
  overflow-wrap: anywhere;
  font-family: var(--font-heading);
  font-weight: var(--font-heading-weight);
  font-size: clamp(1.375rem, 3.4cqw, 2.125rem);
  line-height: 0.95;
  letter-spacing: -0.03em;
}

.preview-lead {
  font-size: 0.7188rem;
  line-height: 1.5;
  color: var(--color-neutral-300);
}

.preview-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.375rem;
  margin-top: 0.125rem;
}

.preview-btn {
  min-height: 1.875rem;
  padding: 0 0.625rem;
  font-size: 0.6875rem;
  pointer-events: none;
}

.preview-hero-photo {
  aspect-ratio: 3 / 4;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: var(--color-surface);
}

.preview-hero-photo img,
.preview-cover img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.preview-covers {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.375rem;
  padding: 0 0.875rem 0.875rem;
}

.preview-cover {
  aspect-ratio: 1 / 1;
  border-radius: 0.375rem;
  overflow: hidden;
  background: var(--color-surface);
}

.preview-rate {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.625rem;
  margin: 0 0.875rem 0.875rem;
  padding: 0.75rem;
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-md);
  background: var(--color-surface);
}

.preview-rate-text {
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
  min-width: 0;
}

.preview-rate-label {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  font-size: var(--font-size-100);
}

.preview-rate-tag {
  align-self: auto;
  padding: 0 0.375rem;
  font-size: 0.5625rem;
}

.preview-rate-price {
  font-family: var(--font-heading);
  font-size: 1rem;
  font-weight: var(--font-heading-weight);
}

.preview-chips {
  display: flex;
  gap: var(--space-1);
}

.preview-chip {
  display: inline-flex;
  align-items: center;
  height: 1.5rem;
  padding-inline: 0.5625rem;
  border: var(--hairline) solid var(--color-divider);
  border-radius: var(--radius-full);
  font-size: 0.6563rem;
  color: var(--color-neutral-200);
}

.preview-chip-active {
  border-color: var(--color-neutral-100);
  background: var(--color-neutral-100);
  color: var(--color-neutral-900);
}

.appearance-apply {
  margin-top: var(--space-5);
}

.appearance-apply[hidden] {
  display: none;
}

/* The pending-changes bar (docs/prototype: "Cambios sin aplicar"). */
.palette-pending {
  position: fixed;
  left: 50%;
  bottom: var(--space-6);
  transform: translateX(-50%);
  z-index: var(--z-float);
  width: min(calc(100% - 1.5rem), 45rem);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.625rem;
  padding: 0.625rem 0.75rem 0.625rem var(--space-4);
  border: var(--hairline) solid var(--color-neutral-600);
  border-radius: var(--radius-lg);
  background: var(--color-surface);
  box-shadow: var(--shadow-lg);
}

.palette-pending[hidden] {
  display: none;
}

.palette-pending-text {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  font-size: 0.8438rem;
}

.palette-pending-dot {
  flex: none;
  width: 0.4375rem;
  height: 0.4375rem;
  border-radius: var(--radius-full);
  background: var(--color-neutral-100);
}

.palette-pending-actions {
  display: flex;
  gap: var(--space-2);
}

.palette-pending-actions .btn {
  min-height: 2.5rem;
}

/* Landing on phones: the closing call to action is centred (the prototype left-aligns it at every
   width; on a narrow screen that read as misaligned). From 768 px it keeps the prototype's
   alignment. The quote demo's WhatsApp bubble is centred at every width (see above). */
@media (max-width: 47.99rem) {
  .landing-final {
    text-align: center;
  }

  .landing-final .landing-section-title {
    margin-inline: auto;
  }

  .landing-final .actions-row {
    justify-content: center;
  }
}

/* Content-Security-Policy support: the server hands a bar's size (progress, activity chart,
   rankings) to the stylesheet as one of these classes (see PercentClass), since the policy does not
   allow style="width: N%". */
.pct-0 { --pct: 0%; }
.pct-1 { --pct: 1%; }
.pct-2 { --pct: 2%; }
.pct-3 { --pct: 3%; }
.pct-4 { --pct: 4%; }
.pct-5 { --pct: 5%; }
.pct-6 { --pct: 6%; }
.pct-7 { --pct: 7%; }
.pct-8 { --pct: 8%; }
.pct-9 { --pct: 9%; }
.pct-10 { --pct: 10%; }
.pct-11 { --pct: 11%; }
.pct-12 { --pct: 12%; }
.pct-13 { --pct: 13%; }
.pct-14 { --pct: 14%; }
.pct-15 { --pct: 15%; }
.pct-16 { --pct: 16%; }
.pct-17 { --pct: 17%; }
.pct-18 { --pct: 18%; }
.pct-19 { --pct: 19%; }
.pct-20 { --pct: 20%; }
.pct-21 { --pct: 21%; }
.pct-22 { --pct: 22%; }
.pct-23 { --pct: 23%; }
.pct-24 { --pct: 24%; }
.pct-25 { --pct: 25%; }
.pct-26 { --pct: 26%; }
.pct-27 { --pct: 27%; }
.pct-28 { --pct: 28%; }
.pct-29 { --pct: 29%; }
.pct-30 { --pct: 30%; }
.pct-31 { --pct: 31%; }
.pct-32 { --pct: 32%; }
.pct-33 { --pct: 33%; }
.pct-34 { --pct: 34%; }
.pct-35 { --pct: 35%; }
.pct-36 { --pct: 36%; }
.pct-37 { --pct: 37%; }
.pct-38 { --pct: 38%; }
.pct-39 { --pct: 39%; }
.pct-40 { --pct: 40%; }
.pct-41 { --pct: 41%; }
.pct-42 { --pct: 42%; }
.pct-43 { --pct: 43%; }
.pct-44 { --pct: 44%; }
.pct-45 { --pct: 45%; }
.pct-46 { --pct: 46%; }
.pct-47 { --pct: 47%; }
.pct-48 { --pct: 48%; }
.pct-49 { --pct: 49%; }
.pct-50 { --pct: 50%; }
.pct-51 { --pct: 51%; }
.pct-52 { --pct: 52%; }
.pct-53 { --pct: 53%; }
.pct-54 { --pct: 54%; }
.pct-55 { --pct: 55%; }
.pct-56 { --pct: 56%; }
.pct-57 { --pct: 57%; }
.pct-58 { --pct: 58%; }
.pct-59 { --pct: 59%; }
.pct-60 { --pct: 60%; }
.pct-61 { --pct: 61%; }
.pct-62 { --pct: 62%; }
.pct-63 { --pct: 63%; }
.pct-64 { --pct: 64%; }
.pct-65 { --pct: 65%; }
.pct-66 { --pct: 66%; }
.pct-67 { --pct: 67%; }
.pct-68 { --pct: 68%; }
.pct-69 { --pct: 69%; }
.pct-70 { --pct: 70%; }
.pct-71 { --pct: 71%; }
.pct-72 { --pct: 72%; }
.pct-73 { --pct: 73%; }
.pct-74 { --pct: 74%; }
.pct-75 { --pct: 75%; }
.pct-76 { --pct: 76%; }
.pct-77 { --pct: 77%; }
.pct-78 { --pct: 78%; }
.pct-79 { --pct: 79%; }
.pct-80 { --pct: 80%; }
.pct-81 { --pct: 81%; }
.pct-82 { --pct: 82%; }
.pct-83 { --pct: 83%; }
.pct-84 { --pct: 84%; }
.pct-85 { --pct: 85%; }
.pct-86 { --pct: 86%; }
.pct-87 { --pct: 87%; }
.pct-88 { --pct: 88%; }
.pct-89 { --pct: 89%; }
.pct-90 { --pct: 90%; }
.pct-91 { --pct: 91%; }
.pct-92 { --pct: 92%; }
.pct-93 { --pct: 93%; }
.pct-94 { --pct: 94%; }
.pct-95 { --pct: 95%; }
.pct-96 { --pct: 96%; }
.pct-97 { --pct: 97%; }
.pct-98 { --pct: 98%; }
.pct-99 { --pct: 99%; }
.pct-100 { --pct: 100%; }

/* ASP.NET renders an empty validation summary as <div class="validation-summary-valid"> with a
   hidden <li style="display:none"> inside; the Content-Security-Policy blocks that inline style, so
   the empty summary is hidden here instead. */
div.validation-summary-valid {
  display: none;
}

/* A form that sits inline in a row of controls (panel header "Salir"). */
.inline-form {
  display: inline;
}
