/* ==========================================================================
   Electricitat Industrial Masana
   Derived from northbridge 1.0.0 — token NAMES are the catalog-wide set,
   every VALUE is this project's own (see docs/brief.md).
   ========================================================================== */

/* ---- fonts ---------------------------------------------------------------
   Montserrat variable, self-hosted (OFL, see fonts/OFL-montserrat.txt).
   latin-ext is listed first so the browser resolves the wider range for the
   Catalan interpunct and the client names; both are preloaded in <head>. */
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/montserrat-var-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020,
    U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Montserrat";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../fonts/montserrat-var-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193,
    U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ---- tokens ------------------------------------------------------------- */
:root {
  /* Palette — sampled from logomasana - positive.png and the mockup raster.
     White on --accent is 2.16:1 and is NEVER used for text; --accent-strong
     (5.03:1 with white) carries every filled button instead. */
  --bg: #ffffff;
  --bg-pure: #ffffff;
  --bg-elevated: #f4f5f7;
  --bg-invert: #2C3360;

  --accent: #eea03a;
  --accent-strong: #a0600e;
  --accent-strong-hover: #8d550c;

  --text-primary: #2C3360;
  --text-secondary: #2C3360;
  --text-tertiary: #2C3360;
  --text-on-accent: #2C3360;
  --text-invert: #ffffff;

  --border: #e1e2e9;
  --border-strong: #c8cad6;

  /* Spacing — 4px scale, inherited from the fork */
  --sp-1: 0.25rem;
  --sp-2: 0.5rem;
  --sp-3: 0.75rem;
  --sp-4: 1rem;
  --sp-5: 1.25rem;
  --sp-6: 1.5rem;
  --sp-8: 2rem;
  --sp-10: 2.5rem;
  --sp-12: 3rem;
  --sp-16: 4rem;
  --sp-20: 5rem;
  --sp-24: 6rem;
  --sp-32: 8rem;

  --container: 1200px;
  --gutter: clamp(1rem, 3vw, 2rem);
  --header-h: 84px;
  /* Fixed, so the bar's height must be a known constant — #main's top
     padding is computed from it. The trail scrolls horizontally rather than
     wrapping, which is what keeps this number true at any width. */
  --crumb-h: 44px;

  --radius-card: 28px;
  --radius-sm: 12px;
  --radius-pill: 999px;

  --shadow-header: 0 2px 18px rgba(44, 51, 96, 0.1);
  --shadow-card: 0 10px 30px rgba(44, 51, 96, 0.12);

  /* Motion. The reveal is deliberately split into two durations: opacity
     lands well before the transform does, so text becomes readable while
     the element is still settling. That is what makes a slower travel read
     as composed rather than sluggish. */
  --dur-fast: 160ms;
  --dur-hover: 280ms;
  --dur-mid: 400ms;
  --dur-reveal: 760ms;
  --dur-hero: 1300ms;
  --reveal-step: 70ms;

  --ease-out-cubic: cubic-bezier(0.22, 0.61, 0.36, 1);
  /* The reveal curve. Gently eased at BOTH ends rather than a pure
     ease-out: an ease-out starts at full speed, which is what reads as a
     pop however short you make it. A shallow S spends its speed in the
     middle, so nothing ever visibly starts or stops. */
  --ease-smooth: cubic-bezier(0.38, 0.06, 0.22, 1);

  --reveal-y: 16px;
}

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

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

body,
h1, h2, h3, h4, p, ul, ol, figure, blockquote, dl, dd { margin: 0; }

ul[class], ol[class] { list-style: none; padding: 0; }

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

button, input, select, textarea { font: inherit; color: inherit; }

html.scroll-locked { overflow: hidden; }

body {
  background: var(--bg);
  color: var(--text-primary);
  font-family: "Montserrat", system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 1.0625rem;
  line-height: 1.65;
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
}

/* ---- typography --------------------------------------------------------- */
h1, h2, h3, h4 {
  font-weight: 800;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--text-primary);
}

h1 { font-size: clamp(1.875rem, 3.4vw, 2.625rem); }

h2 {
  font-size: clamp(1.625rem, 2.6vw, 2.25rem);
  margin-bottom: var(--sp-8);
}

h3 { font-size: clamp(1.125rem, 1.5vw, 1.375rem); font-weight: 700; }

p + p { margin-top: var(--sp-4); }

a { color: var(--accent-strong); }

strong, b { font-weight: 700; }

:focus-visible {
  outline: 3px solid var(--accent-strong);
  outline-offset: 3px;
  border-radius: 2px;
}

.site-footer :focus-visible,
.serveis :focus-visible,
.band-invert :focus-visible,
.cta-band :focus-visible,
.hero :focus-visible { outline-color: var(--text-invert); }

/* ---- layout ------------------------------------------------------------- */
.container {
  width: min(100% - (var(--gutter) * 2), var(--container));
  margin-inline: auto;
}

.section { padding-block: clamp(var(--sp-16), 7vw, var(--sp-24)); }

.section--tight { padding-block: clamp(var(--sp-12), 5vw, var(--sp-16)); }

.section-heading {
  text-align: center;
  margin-bottom: var(--sp-12);
}

.lede {
  font-size: clamp(1.0625rem, 1.4vw, 1.1875rem);
  color: var(--text-secondary);
  max-width: 62ch;
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  top: 0; left: 50%;
  transform: translate(-50%, -120%);
  z-index: 200;
  background: var(--bg-invert);
  color: var(--text-invert);
  padding: var(--sp-3) var(--sp-6);
  border-radius: 0 0 var(--radius-sm) var(--radius-sm);
  font-weight: 600;
  text-decoration: none;
  transition: transform var(--dur-fast) var(--ease-out-cubic);
}
.skip-link:focus-visible { transform: translate(-50%, 0); }

/* Visible placeholder — content that is deliberately not final. Never
   styled to blend in: a placeholder that looks like copy ships as copy. */
.ph {
  background: repeating-linear-gradient(
    -45deg, #fdf3e2, #fdf3e2 8px, #f8e6c8 8px, #f8e6c8 16px
  );
  color: var(--text-on-accent);
  border: 1px dashed var(--accent-strong);
  border-radius: 4px;
  padding: 0.05em 0.4em;
  font-weight: 600;
}

/* ---- buttons ------------------------------------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: 48px;
  padding: var(--sp-3) var(--sp-8);
  border: 2px solid transparent;
  border-radius: var(--radius-pill);
  background: #eea03a;
  color: #2C3360;
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease-out-cubic),
              transform var(--dur-hover) var(--ease-smooth),
              box-shadow var(--dur-hover) var(--ease-smooth);
}
.btn:hover {
  background: #db8a1e;
  transform: translate3d(0, -2px, 0);
  box-shadow: 0 8px 20px rgba(44, 51, 96, 0.22);
}
.btn:active { transform: none; box-shadow: none; }

.btn--ghost {
  background: transparent;
  border-color: currentColor;
  color: var(--text-primary);
}
.btn--ghost:hover { background: var(--text-primary); color: var(--text-invert); }

.btn--on-invert { }
.btn--on-accent {
  background: var(--bg-invert);
  color: var(--text-invert);
}
.btn--on-accent:hover { background: #2C3360; }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
  justify-content: center;
}

/* ---- header ------------------------------------------------------------- */
.accent-rule {
  position: fixed;
  inset: 0 0 auto 0;
  height: 6px;
  background: var(--accent);
  z-index: 101;
}

.site-header {
  position: fixed;
  inset: 6px 0 auto 0;
  z-index: 100;
  background: var(--bg-pure);
  transition: box-shadow var(--dur-fast) var(--ease-out-cubic);
}
.site-header.is-scrolled { box-shadow: var(--shadow-header); }

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-6);
  min-height: var(--header-h);
}

.wordmark { display: block; flex-shrink: 0; }
.wordmark img { width: 190px; height: auto; }

.primary-nav ul {
  display: flex;
  align-items: center;
  gap: clamp(var(--sp-4), 2vw, var(--sp-8));
  margin: 0; padding: 0;
  list-style: none;
}

.primary-nav a {
  position: relative;
  color: var(--text-primary);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  padding-block: var(--sp-2);
  transition: color var(--dur-fast) var(--ease-out-cubic);
}

/* The underline wipes in from the left instead of fading a border on. It is
   a transform, so it composites; a border-colour transition repaints the
   element every frame for the same visual result. */
.primary-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--dur-hover) var(--ease-smooth);
}
.primary-nav a:hover { color: var(--accent-strong); }
.primary-nav a:hover::after { transform: scaleX(1); }

.primary-nav a[aria-current="page"] { color: var(--accent-strong); }
.primary-nav a[aria-current="page"]::after { transform: scaleX(1); }

/* The CTA inside the nav list is a button, not a nav link. Without this it
   inherits `.primary-nav a` (specificity 0,1,1, which outranks `.btn`'s
   0,1,0) and renders dark-on-orange at 3.66:1 — and orange-on-orange on
   contacte.html, where it also matches the aria-current rule above. Measured,
   not theorised. Must stay after both rules it corrects. */
.primary-nav .nav-cta a,
.primary-nav .nav-cta a:hover,
.primary-nav .nav-cta a[aria-current="page"] {
  color: var(--text-invert);
  border-bottom: 0;
  /* Same specificity trap as the colour above: `.primary-nav a` (0,1,1)
     outranks `.btn` (0,1,0), so without this the CTA inherits the nav
     link's colour-only transition and its lift and shadow snap instead of
     animating. */
  transition: background var(--dur-fast) var(--ease-out-cubic),
              transform var(--dur-hover) var(--ease-smooth),
              box-shadow var(--dur-hover) var(--ease-smooth);
}
/* The CTA is a pill button, not a nav link — the wipe underline would draw
   an accent bar straight across it. */
.primary-nav .nav-cta a::after { display: none; }

.header-cta { flex-shrink: 0; }

/* The CTA lives in the nav list so the mobile overlay can show it inside the
   panel. Above the breakpoint .header-cta is the one on screen, so the list
   copy must be hidden — otherwise "Contacte" renders twice and the pair
   pushes the header past its own container. */
.primary-nav .nav-cta { display: none; }

.nav-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 48px; height: 48px;
  padding: 0;
  background: none;
  border: 2px solid var(--border-strong);
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.nav-toggle .bars,
.nav-toggle .bars::before,
.nav-toggle .bars::after {
  display: block;
  width: 20px; height: 2px;
  background: var(--text-primary);
  border-radius: 2px;
}
.nav-toggle .bars { position: relative; }
.nav-toggle .bars::before,
.nav-toggle .bars::after { content: ""; position: absolute; left: 0; }
.nav-toggle .bars::before { top: -6px; }
.nav-toggle .bars::after { top: 6px; }
.nav-toggle .label-close { display: none; }

/* ---- breadcrumbs ---------------------------------------------------------
   Fixed directly under the header, as a second band of the same chrome. It
   must sit immediately after </header> in the DOM: the sibling selector
   below moves the scrolled-state shadow down onto it, so the shadow lands at
   the bottom of the whole fixed stack instead of in the middle of it. */
.breadcrumb {
  position: fixed;
  top: calc(var(--header-h) + 6px);
  left: 0;
  right: 0;
  z-index: 99;
  height: var(--crumb-h);
  display: flex;
  align-items: center;
  background: var(--bg-elevated);
  border-bottom: 1px solid var(--border);
  transition: box-shadow var(--dur-fast) var(--ease-out-cubic);
}

body:has(.breadcrumb) .site-header.is-scrolled { box-shadow: none; }
.site-header.is-scrolled ~ .breadcrumb { box-shadow: var(--shadow-header); }

.breadcrumb ol {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 0.8125rem;
  white-space: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
}
.breadcrumb ol::-webkit-scrollbar { display: none; }

.breadcrumb li { display: flex; align-items: center; gap: var(--sp-2); }
.breadcrumb li + li::before { content: "/"; color: var(--text-tertiary); }

.breadcrumb a {
  color: var(--text-secondary);
  text-decoration: none;
  padding-block: var(--sp-2);
}
.breadcrumb a:hover { color: var(--accent-strong); text-decoration: underline; }
.breadcrumb [aria-current="page"] { color: var(--text-primary); font-weight: 600; }

/* ---- main --------------------------------------------------------------- */
#main { padding-top: calc(var(--header-h) + 6px); }
body:has(.breadcrumb) #main { padding-top: calc(var(--header-h) + 6px + var(--crumb-h)); }

/* ---- hero --------------------------------------------------------------- */
.hero {
  --hero-pad-x: clamp(0.75rem, 1.5vw, 1.25rem);
  padding-inline: var(--hero-pad-x);
}

.hero-media {
  position: relative;
  /* `width: 100%` is load-bearing, not decoration. With `aspect-ratio` and a
     `min-height` that wins, the box resolves its WIDTH from the ratio
     (340 × 2.7 = 918px) instead of stretching to its container, which
     overflows the viewport horizontally anywhere under ~1000px. Measured at
     901px: 918px wide against 858px available. */
  width: 100%;
  border-radius: var(--radius-card);
  overflow: hidden;
  background: var(--bg-invert);
  aspect-ratio: 2.7 / 1;
  min-height: 340px;
  max-height: 620px;
}

.hero-media img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
}

/* The mockup sets the H1 in white directly on the photograph. The photo is
   pale on its left third, so the headline carries its own gradient ground
   rather than relying on the image staying dark. */
.hero-media {
  /* Held as a token, not inlined on the pseudo: a later layer stacks a
     white gradient on top of this one from the hero container's own
     Custom CSS, and it must reuse this exact value instead of copying it. */
  --hero-shade: linear-gradient(
    100deg,
    rgba(44, 51, 96, 0.72) 0%,
    rgba(44, 51, 96, 0.5) 34%,
    rgba(44, 51, 96, 0.08) 62%,
    rgba(44, 51, 96, 0) 100%
  );
}

.hero-media::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--hero-shade);
}

.hero-copy {
  position: absolute;
  z-index: 1;
  /* Aligned with the site's content column (.container), not with
     .hero-media's own wider edge. .container's own left inset resolves to
     max(--gutter, (100vw - --container) / 2) — but that's `100vw`, which
     INCLUDES the scrollbar (~15px in Chrome/Windows), while `.hero-copy`
     is positioned inside `.hero-media`, whose own width resolves against
     `100%` of its containing block, which does NOT include the scrollbar.
     Using `100vw` here landed the copy 7.5px (half the scrollbar) right of
     the nav and the card below it. `.hero-media`'s content-box width is
     `100% + 2 * var(--hero-pad-x)` of `.hero-copy`'s own containing block
     (`.hero` pads it in by `--hero-pad-x` on each side), so that's the
     substitution for `100vw` that keeps the same alignment math without
     the scrollbar's phantom width. */
  left: calc(max(var(--gutter), (100% + 2 * var(--hero-pad-x) - var(--container)) / 2) - var(--hero-pad-x));
  right: calc(max(var(--gutter), (100% + 2 * var(--hero-pad-x) - var(--container)) / 2) - var(--hero-pad-x));
  bottom: clamp(var(--sp-8), 7vw, var(--sp-16));
  /* Not a `ch` value: `ch` here would resolve against .hero-copy's own
     inherited body size, not the h1's clamp()ed display size, and the
     headline would collapse to a ~225px column. */
  max-width: min(31rem, 48%);
}
.hero-copy h1 { color: var(--text-invert); }

/* Hero entrance. Deliberately a CSS animation and not a `data-reveal`: the
   headline is the first thing on the page, and it must never depend on a
   script having run to become visible. `both` fill holds the opening frame
   during the delay and the closing frame afterwards, so the end state is
   correct even if the animation is stripped — which is exactly what the
   reduced-motion block below does.

   The image scales DOWN into place rather than up. Scaling up crops the
   frame mid-animation and reads as a slow zoom; scaling down settles. */
.hero-media img {
  animation: hero-settle var(--dur-hero) var(--ease-smooth) both;
}
.hero-copy {
  animation: hero-rise 900ms 160ms var(--ease-smooth) both;
}

@keyframes hero-settle {
  from { transform: scale(1.055); }
  to   { transform: scale(1); }
}
@keyframes hero-rise {
  from { opacity: 0; transform: translate3d(0, 20px, 0); }
  60%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}

/* ---- qui som (split) ---------------------------------------------------- */
.split {
  display: grid;
  grid-template-columns: minmax(0, 0.95fr) minmax(0, 1fr);
  gap: clamp(var(--sp-8), 6vw, var(--sp-20));
  align-items: center;
}

/* The media always starts at the top of the row. The grid stays
   `align-items: center` so the shorter column still centres where that reads
   better — but a photo floating half-way down beside a long text column
   looks like a mistake, so the picture opts out of it. Applies to every
   split on the site: Qui som, and all five service pages. */
.split-media { align-self: start; }

.split-media img {
  width: 100%;
  /* `height: auto` is required, not tidiness: the width/height HTML
     attributes map to CSS presentational hints, so height stays definite
     at the intrinsic pixel value and `aspect-ratio` is ignored entirely.
     Without it these render at their natural height (800px) regardless. */
  height: auto;
  border-radius: var(--radius-card);
  aspect-ratio: 1.17 / 1;
  object-fit: cover;
}

.split-body h2 { margin-bottom: var(--sp-6); }
.split-body .btn { margin-top: var(--sp-8); }

/* ---- serveis band ------------------------------------------------------- */
.serveis,
.band-invert {
  background: var(--bg-invert);
  color: var(--text-invert);
}
.serveis h2,
.band-invert h2,
.band-invert h3 { color: var(--text-invert); }
.serveis .lede,
.band-invert p,
.band-invert li { color: var(--text-invert); }
.band-invert .tick-list li::before { background: var(--e-global-color-accent, var(--accent)); }

.serveis-grid {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--sp-6);
}

.servei-card {
  position: relative;
  flex: 0 1 calc((100% - (var(--sp-6) * 2)) / 3);
  min-width: 260px;
  aspect-ratio: 1.16 / 1;
  border-radius: var(--radius-card);
  overflow: hidden;
  text-decoration: none;
  color: var(--text-invert);
  background: #2C3360;
  transition: transform var(--dur-hover) var(--ease-smooth),
              box-shadow var(--dur-hover) var(--ease-smooth);
}

.servei-card img {
  position: absolute;
  inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  transition: transform 620ms var(--ease-smooth);
}

/* Departure from the mockup, recorded in the brief: the mockup's white card
   titles sit straight on the photograph, which is unreadable over the pale
   wood of "Manteniment elèctric" and the bright cabling of "Quadres
   elèctrics". This scrim is the least invasive fix that keeps the
   composition — it darkens only the band the title occupies. */
.servei-card::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(44, 51, 96, 0.9) 0%,
    rgba(44, 51, 96, 0.74) 26%,
    rgba(44, 51, 96, 0.28) 55%,
    rgba(44, 51, 96, 0.05) 100%
  );
}

.servei-card h3 {
  position: absolute;
  z-index: 1;
  left: var(--sp-6);
  right: var(--sp-6);
  bottom: var(--sp-6);
  margin: 0;
  color: var(--text-invert);
  font-size: clamp(1.25rem, 1.9vw, 1.625rem);
  font-weight: 700;
  line-height: 1.2;
}

.servei-card:hover,
.servei-card:focus-visible { transform: translate3d(0, -6px, 0); box-shadow: var(--shadow-card); }
.servei-card:hover img,
.servei-card:focus-visible img { transform: scale(1.05); }

.serveis .btn-row { margin-top: var(--sp-12); }

/* ---- blog cards --------------------------------------------------------- */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--sp-8);
}

.post-card {
  text-align: center;
  position: relative;
  transition: transform var(--dur-hover) var(--ease-smooth);
}
.post-card:hover { transform: translate3d(0, -4px, 0); }
.post-card img { transition: transform 620ms var(--ease-smooth); }
.post-card:hover img { transform: scale(1.03); }

/* The whole card is clickable, but the accessible link name is only the
   title — the stretched pseudo-element gives the hit area without wrapping
   the image and body text inside the link, which would make a screen reader
   announce the entire card as the link's name. */
.post-card-link::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Deliberately NOT border-radius'd. A radius here clips the hit area to
     the rounded shape, so the four corners stop hit-testing and clicks fall
     through to the image and body text beneath. The focus ring is drawn on
     .post-card, so the overlay never needs a radius of its own. */
}
.post-card-link:focus-visible { outline: none; }
.post-card:has(.post-card-link:focus-visible) {
  outline: 3px solid var(--accent-strong);
  outline-offset: 6px;
  border-radius: var(--radius-card);
}
.post-card:hover h3,
.post-card:has(.post-card-link:focus-visible) h3 { color: var(--accent-strong); }

.post-card img {
  width: 100%;
  height: auto;
  border-radius: var(--radius-card);
  aspect-ratio: 1 / 1;
  object-fit: cover;
  margin-bottom: var(--sp-6);
}

.post-card h3 { margin-bottom: var(--sp-3); font-weight: 800; }
.post-card p { color: var(--text-secondary); font-size: 0.9375rem; }
.post-card a { text-decoration: none; color: inherit; }
.post-card a:hover h3 { color: var(--accent-strong); }

.blog .btn-row { margin-top: var(--sp-12); }

/* ---- single post (blog-post.html) --------------------------------------- */
.post-meta {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--sp-4);
}

.post-hero {
  width: 100%;
  height: auto;
  aspect-ratio: 2 / 1;
  object-fit: cover;
  border-radius: var(--radius-card);
  margin-bottom: clamp(var(--sp-10), 5vw, var(--sp-16));
}

.post-body { margin-inline: auto; }
/* The article body runs the full container width. `.prose` is declared after
   `.post-body` in this file and caps at 68ch, so the override needs both
   classes to outrank it. */
.prose.post-body { max-width: none; }

/* The gap under a page head was a full section's padding — nearly twice the
   air inside the band itself, so the title block read as detached from the
   content it introduces. It now matches the page head's own bottom padding,
   the same value on both sides of the band's edge. One rule for every inner
   page, including the article hero. */
.page-head + .section,
.page-head + * > .section { padding-top: clamp(var(--sp-12), 5vw, var(--sp-20)); }
.post-body h2 { margin-top: var(--sp-12); }


/* ---- prose / inner pages ------------------------------------------------ */
.page-head {
  background: var(--bg-elevated);
  padding-block: clamp(var(--sp-12), 5vw, var(--sp-20));
  border-bottom: 1px solid var(--border);
}
.page-head p { color: var(--text-secondary); margin-top: var(--sp-4); max-width: 62ch; }

/* The inner pages' counterpart to the hero entrance. Same keyframe, same
   curve, so a visitor moving from the home page to any other one sees the
   same gesture rather than two different ideas of how the site opens.

   CSS animations rather than `data-reveal` for the same reason the hero is:
   this is the first content on the page and it must never wait on a script.
   `.post-meta` is declared after `.page-head p` deliberately — it is also a
   `p`, and it needs the earlier delay, so it has to win the cascade. */
.page-head h1 { animation: hero-rise 820ms 120ms var(--ease-smooth) both; }
.page-head p  { animation: hero-rise 820ms 220ms var(--ease-smooth) both; }
.page-head .post-meta { animation: hero-rise 820ms 40ms var(--ease-smooth) both; }

.prose { max-width: 68ch; }
.prose p { color: var(--text-secondary); }
.prose p + p { margin-top: var(--sp-5); }

.tick-list { display: grid; gap: var(--sp-3); margin-top: var(--sp-6); }

/* A paragraph that follows a tick list had nothing between them — the list
   has a 24px margin above it and none below, so the closing line ran
   straight into the last bullet. Mirrored, so the list sits in equal air. */
.tick-list + p { margin-top: var(--sp-6); }
.tick-list li {
  position: relative;
  padding-left: var(--sp-8);
  color: var(--text-secondary);
}
.tick-list li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.55em;
  width: 10px; height: 10px;
  border-radius: 2px;
  background: var(--e-global-color-accent, var(--accent));
}

/* Service detail blocks (serveis.html) */
.servei-detail {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: clamp(var(--sp-8), 5vw, var(--sp-16));
  align-items: center;
  padding-block: clamp(var(--sp-10), 5vw, var(--sp-16));
  border-top: 1px solid var(--border);
}
.servei-detail:first-of-type { border-top: 0; }
.servei-detail-media { align-self: start; }
.servei-detail img {
  width: 100%;
  height: auto;
  border-radius: var(--radius-card);
  aspect-ratio: 1.4 / 1;
  object-fit: cover;
}
.servei-detail h2 { margin-bottom: var(--sp-4); }
.servei-detail .btn { margin-top: var(--sp-8); }
.servei-detail:nth-of-type(even) .servei-detail-media { order: 2; }

/* "Altres serveis" — the sideways links at the foot of each service page */
.related-serveis {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-3);
  margin-top: var(--sp-6);
  padding: 0;
  list-style: none;
}
.related-serveis a {
  display: inline-block;
  padding: var(--sp-2) var(--sp-5);
  border: 2px solid var(--accent-strong);
  border-radius: var(--radius-pill);
  color: var(--accent-strong);
  font-size: 0.875rem;
  font-weight: 600;
  text-decoration: none;
  transition: border-color var(--dur-fast) var(--ease-out-cubic),
              color var(--dur-fast) var(--ease-out-cubic);
}
.related-serveis a:hover { background: var(--accent-strong); color: var(--text-invert); }

/* Clients (clients.html) */
.client-list {
  columns: 3 220px;
  column-gap: var(--sp-8);
  margin-top: var(--sp-10);
}
.client-list li {
  break-inside: avoid;
  padding: var(--sp-2) 0 var(--sp-2) var(--sp-6);
  border-bottom: 1px solid var(--border);
  position: relative;
  color: var(--text-secondary);
  font-size: 0.9375rem;
}
.client-list li::before {
  content: "";
  position: absolute;
  left: 0; top: 1.05em;
  width: 8px; height: 8px;
  border-radius: 2px;
  background: var(--e-global-color-accent, var(--accent));
}

/* ---- client logo marquee (clients.html) ----------------------------------
   Auto-scrolling, so it needs a real pause control: WCAG 2.2.2 asks for a
   mechanism to stop anything that moves for more than five seconds, and
   hover-to-pause is not that mechanism — it does nothing for keyboard or
   touch. The button is the mechanism; hover and focus-within are extras.

   The track holds the same group twice and translates by exactly -50%. That
   only lands seamlessly because each group carries its own trailing
   `padding-right` instead of a gap sitting between the two groups — with a
   gap there, half the track is not one group and the loop visibly jumps. */
.logo-marquee-wrap { margin-top: var(--sp-10); }

.logo-marquee {
  position: relative;
  overflow: hidden;
  /* Fades the two edges so tiles enter and leave instead of being chopped. */
  -webkit-mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(to right, transparent, #000 6%, #000 94%, transparent);
}

.logo-marquee-track {
  display: flex;
  width: max-content;
  animation: logo-scroll 45s linear infinite;
}

.logo-marquee:hover .logo-marquee-track,
.logo-marquee:focus-within .logo-marquee-track,
.logo-marquee.is-paused .logo-marquee-track { animation-play-state: paused; }

@keyframes logo-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }
}

.logo-marquee-group {
  display: flex;
  align-items: stretch;
  gap: var(--sp-6);
  padding-right: var(--sp-6);
  margin: 0;
  list-style: none;
}

.client-logo {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 210px;
  min-height: 96px;
  padding: var(--sp-4);
  background: var(--bg-pure);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  text-align: center;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  line-height: 1.35;
  color: var(--text-secondary);
}

.marquee-controls {
  display: flex;
  justify-content: center;
  margin-top: var(--sp-6);
}

.marquee-toggle {
  padding: var(--sp-2) var(--sp-6);
  min-height: 44px;
  background: transparent;
  border: 2px solid var(--border-strong);
  border-radius: var(--radius-pill);
  color: var(--text-primary);
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out-cubic),
              color var(--dur-fast) var(--ease-out-cubic);
}
.marquee-toggle:hover { border-color: var(--accent); color: var(--accent-strong); }

/* ---- legal pages -------------------------------------------------------- */
.legal-prose { max-width: 78ch; }
.legal-prose h2 {
  font-size: clamp(1.25rem, 1.8vw, 1.5rem);
  margin-top: clamp(var(--sp-12), 5vw, var(--sp-16));
  margin-bottom: var(--sp-5);
}
/* `:first-child`, not `:first-of-type`. The avís legal opens with two
   paragraphs before its first h2, and :first-of-type stripped that heading's
   top margin even though it was not at the top of the page — the gap above
   "Identificació" collapsed while every other heading kept its spacing. */
.legal-prose > h2:first-child { margin-top: 0; }
.legal-prose p,
.legal-prose li { color: var(--text-secondary); }
.legal-prose p + p { margin-top: var(--sp-4); }
.legal-prose ul {
  display: grid;
  gap: var(--sp-2);
  margin: var(--sp-4) 0 0;
  padding-left: var(--sp-6);
  list-style: disc;
}
.legal-prose ul ul { margin-top: var(--sp-2); }

/* ---- prose lists wear the brand marker ----------------------------------
   A plain `ul` was falling back to the browser's disc, which reads as
   unstyled beside everything else on the page. Every list in running text
   now gets the same orange square `.tick-list` uses. `.tick-list` keeps its
   own rules — this is the same marker, not a redefinition of it, so the two
   can never drift apart visually without someone changing both on purpose.

   Nested lists get a hollow marker at the same size, so a sub-point still
   reads as subordinate without introducing a second colour. */
.prose ul:not(.tick-list),
.legal-prose ul,
.post-body ul {
  display: grid;
  gap: var(--sp-3);
  list-style: none;
  padding-left: 0;
  /* Measured before this line: these lists had no margin at all, so they sat
     flush against the paragraph above and the one below. 24px is not a new
     number — it is the rhythm `.tick-list` already keeps (its own 24 above,
     and 24 below via `.tick-list + p`), so every list on the site now
     breathes the same. */
  margin-block: var(--sp-6);
}
.prose ul:not(.tick-list) > li,
.legal-prose ul > li,
.post-body ul > li {
  position: relative;
  padding-left: var(--sp-8);
}
.prose ul:not(.tick-list) > li::before,
.legal-prose ul > li::before,
.post-body ul > li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  background: var(--e-global-color-accent, var(--accent));
}
/* A sub-list sits in from its parent's text, and its marker is outlined
   rather than filled. */
/* A sub-list belongs to the line above it, so it stays tight — the 24px
   would read as a break between two separate lists. */
.legal-prose ul ul,
.prose ul:not(.tick-list) ul,
.post-body ul ul { margin-left: var(--sp-6); margin-block: var(--sp-2); }
.legal-prose ul ul > li::before,
.prose ul:not(.tick-list) ul > li::before,
.post-body ul ul > li::before {
  background: none;
  box-shadow: inset 0 0 0 2px var(--e-global-color-accent, var(--accent));
}
/* 2026-09-15: every list marker on the site is now tied to the kit's
   "accent" global (orange), so this and the default rule above share the
   same value — no separate lighter tone is needed here any more. Kept as
   its own selector since `.band-invert` may need to diverge again later. */
.band-invert .prose ul:not(.tick-list) > li::before,
.band-invert .legal-prose ul > li::before { background: var(--e-global-color-accent, var(--accent)); }
/* Selector must out-specify the reset's `ol[class]` (0,1,1), which would
   otherwise strip the numbering off a list whose whole point is being
   numbered — the source document numbers these four legal bases 1–4. */
.legal-prose ol.legal-ol {
  display: grid;
  gap: var(--sp-2);
  margin: var(--sp-4) 0 0;
  padding-left: var(--sp-8);
  list-style: decimal;
  color: var(--text-secondary);
}

/* Tables carry their own scroll container so a wide table never widens the
   page — the body must not scroll sideways because of one element. The
   container is `tabindex="0"` in the markup, because a scrollable region
   that is not focusable cannot be scrolled by keyboard in Chrome at all
   (WCAG 2.1.1) — which is why it needs a visible focus ring too. */
.table-scroll {
  overflow-x: auto;
  margin-top: var(--sp-6);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.table-scroll:focus-visible {
  outline: 3px solid var(--accent-strong);
  outline-offset: 3px;
}
.legal-table {
  width: 100%;
  min-width: 640px;
  border-collapse: collapse;
  font-size: 0.9375rem;
}
.legal-table th,
.legal-table td {
  padding: var(--sp-4);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--border);
  color: var(--text-secondary);
}
.legal-table thead th {
  background: var(--bg-elevated);
  color: var(--text-primary);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}
.legal-table tbody th {
  color: var(--text-primary);
  font-weight: 700;
  white-space: nowrap;
}
.legal-table tr:last-child th,
.legal-table tr:last-child td { border-bottom: 0; }
.legal-table ul { margin-top: 0; }

.legal-updated {
  margin-top: clamp(var(--sp-12), 5vw, var(--sp-16));
  padding-top: var(--sp-6);
  border-top: 1px solid var(--border);
  font-size: 0.875rem;
  color: var(--text-tertiary);
}

/* CTA band */
.cta-band {
  background: var(--bg-invert);
  color: var(--text-invert);
  text-align: center;
}
.cta-band h2 { color: var(--text-invert); margin-bottom: var(--sp-4); }
.cta-band p { color: var(--text-invert); margin-bottom: var(--sp-8); }

/* ---- contact ------------------------------------------------------------ */
.contact-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(var(--sp-10), 6vw, var(--sp-20));
  align-items: start;
}

.contact-details dl { display: grid; gap: var(--sp-6); margin-top: var(--sp-8); }
.contact-details dt {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-tertiary);
  margin-bottom: var(--sp-1);
}
.contact-details dd { margin: 0; color: var(--text-secondary); }
.contact-details a { color: var(--accent-strong); }

/* Form — the fork's 4-signal error pattern (border + inset rail + glyph +
   text). Colour is never the only signal: WCAG 1.4.1. */
.form-field { margin-bottom: var(--sp-6); }

.form-field label {
  display: block;
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  margin-bottom: var(--sp-2);
}

.form-field input,
.form-field select,
.form-field textarea {
  width: 100%;
  padding: var(--sp-3) var(--sp-4);
  background: var(--bg-pure);
  border: 2px solid var(--border-strong);
  border-radius: var(--radius-sm);
  transition: border-color var(--dur-fast) var(--ease-out-cubic);
}
.form-field textarea { min-height: 150px; resize: vertical; }
.form-field input:focus,
.form-field select:focus,
.form-field textarea:focus { border-color: var(--accent-strong); }

.form-field.has-error input,
.form-field.has-error select,
.form-field.has-error textarea {
  border-color: var(--text-primary);
  border-left-width: 8px;
}
.form-field.is-valid input,
.form-field.is-valid select,
.form-field.is-valid textarea { border-color: var(--accent-strong); }

.field-error {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-2);
  margin-top: var(--sp-2);
  font-size: 0.875rem;
  font-weight: 600;
}
.field-error .glyph { flex-shrink: 0; }
.field-error .glyph::before { content: "\26A0\FE0E"; }
.form-field.is-valid .field-error .glyph::before { content: "\2713"; }
.form-field.is-valid .field-error { color: var(--accent-strong); }

.consent {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--sp-3);
  align-items: start;
}
.consent input { width: 20px; height: 20px; margin-top: 0.25em; }
.consent label { font-weight: 400; font-size: 0.9375rem; letter-spacing: 0; }

.honeypot {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

.form-status {
  margin-bottom: var(--sp-6);
  padding: var(--sp-4);
  border: 2px solid var(--text-primary);
  border-left-width: 8px;
  border-radius: var(--radius-sm);
  background: var(--bg-elevated);
  font-weight: 600;
}

#form-body { transition: opacity var(--dur-fast) var(--ease-out-cubic); }
#form-body.is-leaving { opacity: 0; }

.form-confirm {
  opacity: 0;
  transition: opacity var(--dur-mid) var(--ease-out-cubic);
}
.form-confirm.is-visible { opacity: 1; }
.form-confirm h2 { margin-bottom: var(--sp-3); }

/* ---- footer ------------------------------------------------------------- */
.site-footer {
  background: var(--accent);
  color: var(--text-on-accent);
  padding-block: clamp(var(--sp-12), 5vw, var(--sp-16));
}

.footer-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) repeat(2, minmax(0, 1fr));
  gap: clamp(var(--sp-8), 4vw, var(--sp-16));
}

.footer-logo img { width: 240px; height: auto; }
.footer-logo { display: block; margin-bottom: var(--sp-6); }

.site-footer h2 {
  font-size: 0.75rem;
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-on-accent);
  margin-bottom: var(--sp-4);
}

.site-footer p,
.site-footer li,
.site-footer address {
  color: var(--text-on-accent);
  font-size: 0.9375rem;
  font-weight: 500;
  font-style: normal;
}

/* The global reset only strips list styling from `ul[class]`, and the footer
   lists carry no class — so without this they keep the browser's default
   disc markers and 40px indent. */
.site-footer ul {
  display: grid;
  gap: var(--sp-2);
  list-style: none;
  padding: 0;
}

.site-footer a {
  color: var(--text-on-accent);
  text-decoration: none;
}
/* Underline only on hover/focus. Removing it at rest is safe here because
   the footer's links are grouped in lists of their own rather than sitting
   inside a paragraph — nothing needs to be told apart from surrounding
   body text (WCAG 1.4.1). Keeping it on interaction preserves the
   affordance. */
.site-footer a:hover,
.site-footer a:focus-visible { text-decoration: underline; }
.footer-logo:hover { text-decoration: none; }

.footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4) var(--sp-8);
  align-items: center;
  justify-content: space-between;
  margin-top: clamp(var(--sp-10), 4vw, var(--sp-12));
  padding-top: var(--sp-6);
  border-top: 1px solid rgba(44, 51, 96, 0.28);
  font-size: 0.875rem;
}
.footer-bottom ul { display: flex; flex-wrap: wrap; gap: var(--sp-6); }

/* ---- reveal motion ------------------------------------------------------ */
/* ---- inverted surfaces ---------------------------------------------------
   Every rule that recolours text for a dark ground lives HERE, at the end of
   the component styles, and is written at descendant specificity (0,2,1).

   Both halves matter. `.band-invert p` and `.prose p` are both (0,1,1), and
   `.prose` is declared later in this file, so it silently won — the copy on
   the indigo bands was rendering at --text-secondary on --bg-invert, about
   1.3:1. It read as a styling preference and was in fact unreadable text.
   Anything that inverts a surface belongs in this block from now on. */
.band-invert p,
.band-invert li,
.band-invert .prose p,
.band-invert .prose li,
.band-invert .tick-list li,
.band-invert .lede,
.serveis .lede,
.cta-band p,
.cta-band .lede { color: var(--text-invert); }

.band-invert a,
.cta-band a { color: var(--text-invert); }

/* ---- reveal motion -------------------------------------------------------
   Keyframe animations, not class-toggled transitions, and the stagger is an
   `animation-delay` rather than a chain of timers. Both choices are about
   the Elementor port: Elementor's entrance animations are exactly this —
   a named keyframe plus a per-widget inline `animation-delay` — so the port
   becomes an override of its keyframes instead of a reimplementation. It is
   also simply smoother, because a delay is scheduled by the compositor
   while a setTimeout chain drifts under load.

   Opacity completes at 55% of the run so text is legible while the travel is
   still settling underneath it. That is what stops a longer, gentler
   duration from reading as sluggish.

   `translate3d` rather than `translateY` keeps the element on its own layer
   for the duration without a permanent `will-change`, which would otherwise
   hold a compositor layer for every revealable element on the page. */
.js [data-reveal] { opacity: 0; }

.js [data-reveal].is-visible {
  animation: reveal-up var(--dur-reveal) var(--ease-smooth) both;
}

@keyframes reveal-up {
  from { opacity: 0; transform: translate3d(0, var(--reveal-y), 0); }
  55%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}

/* Media panels carry a touch of scale as well as travel — the image settles
   into its frame rather than sliding into it. Kept to 1.5%, because more
   than that resamples visibly on a photograph. */
.js [data-reveal="media"].is-visible { animation-name: reveal-media; }

@keyframes reveal-media {
  from { opacity: 0; transform: translate3d(0, var(--reveal-y), 0) scale(1.015); }
  55%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}

/* For a column with a natural side to arrive from. Never a default. */
.js [data-reveal="right"].is-visible { animation-name: reveal-right; }

@keyframes reveal-right {
  from { opacity: 0; transform: translate3d(20px, 0, 0); }
  55%  { opacity: 1; }
  to   { opacity: 1; transform: none; }
}

/* ==========================================================================
   Responsive
   ========================================================================== */

@media (max-width: 1060px) {
  .split { grid-template-columns: 1fr; }
  .split-media img { aspect-ratio: 1.6 / 1; }
  .servei-detail { grid-template-columns: 1fr; }
  .servei-detail:nth-of-type(even) .servei-detail-media { order: 0; }
  .contact-grid { grid-template-columns: 1fr; }
  .footer-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

@media (max-width: 900px) {
  .servei-card { flex-basis: calc((100% - var(--sp-6)) / 2); }
  /* Blog cards stack full width for the whole mobile range. Left to
     auto-fit they paired up into two ~50% columns between roughly 660 and
     900px — the same range that already runs the hamburger nav. */
  .card-grid { grid-template-columns: 1fr; }
}

/* ---- mobile overlay menu -------------------------------------------------
   Modal, not a disclosure panel: focus is trapped inside the header, `main`
   and the footer go `inert`, Esc closes and returns focus, body scroll is
   locked. The 900px cutoff is measured, not guessed: the logo (190px), the five
   nav links (359px) and the CTA (153px) plus two 24px gaps need a 750px
   container, which a 901px viewport supplies with 87px to spare. Change
   this number and js/main.js's matchMedia together — same boundary. */
@media (max-width: 900px) {
  .nav-toggle { display: inline-flex; }
  .header-cta { display: none; }

  /* The panel arrives from the right rather than fading in place. It is laid
     out full-size at all times and parked off-screen, so only `transform`
     and `visibility` change and the travel stays on the compositor.

     Enter is slower with a long tail-out, so the panel decelerates into
     place; exit is quicker and closer to linear, because a panel you have
     just dismissed should leave rather than linger. `visibility` is delayed
     to the end of the exit so the panel stays painted while it travels, and
     it keeps the closed menu out of the tab order, which `pointer-events`
     would not. */
  .primary-nav {
    position: fixed;
    inset: calc(var(--header-h) + 6px) 0 0 0;
    background: var(--bg-pure);
    transform: translate3d(100%, 0, 0);
    visibility: hidden;
    pointer-events: none;
    will-change: transform;
    box-shadow: -18px 0 40px rgba(44, 51, 96, 0.10);
    transition: transform 320ms cubic-bezier(0.4, 0, 0.7, 0.2),
                visibility 0s linear 320ms;
    overflow-y: auto;
  }

  .site-header.nav-open .primary-nav {
    transform: translate3d(0, 0, 0);
    visibility: visible;
    pointer-events: auto;
    transition: transform 520ms cubic-bezier(0.16, 1, 0.3, 1),
                visibility 0s;
  }

  /* The rows follow the panel in, a beat behind it. The delays are on the
     OPEN state only: on close everything leaves with the panel, and a
     staggered exit would leave rows trailing off the screen edge. */
  .primary-nav li {
    opacity: 0;
    transform: translate3d(12px, 0, 0);
    transition: opacity 260ms var(--ease-out-cubic),
                transform 260ms var(--ease-out-cubic);
  }
  .site-header.nav-open .primary-nav li { opacity: 1; transform: none; }
  .site-header.nav-open .primary-nav li:nth-child(1) { transition-delay: 130ms; }
  .site-header.nav-open .primary-nav li:nth-child(2) { transition-delay: 175ms; }
  .site-header.nav-open .primary-nav li:nth-child(3) { transition-delay: 220ms; }
  .site-header.nav-open .primary-nav li:nth-child(4) { transition-delay: 265ms; }
  .site-header.nav-open .primary-nav li:nth-child(5) { transition-delay: 310ms; }
  .site-header.nav-open .primary-nav li:nth-child(6) { transition-delay: 355ms; }

  .primary-nav ul {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    /* 32px above the first row, matching the WordPress build, where the
       panel covers the full viewport and pads its list down past the
       header instead of starting beneath it. */
    padding: var(--sp-8) var(--gutter) var(--sp-16);
  }

  .primary-nav li { border-bottom: 1px solid var(--border); }

  .primary-nav a {
    display: block;
    font-size: 1.0625rem;
    padding-block: var(--sp-5);
    border-bottom: 0;
  }
  /* In the overlay each link is a full-width row with its own divider, so
     the wipe underline would read as a second, heavier rule. */
  .primary-nav a::after { display: none; }

  .primary-nav .nav-cta {
    display: block;
    margin-top: var(--sp-8);
    border-bottom: 0;
  }
  .primary-nav .nav-cta .btn { width: 100%; }

  .site-header.nav-open .nav-toggle .bars { background: transparent; }
  .site-header.nav-open .nav-toggle .bars::before { top: 0; transform: rotate(45deg); }
  .site-header.nav-open .nav-toggle .bars::after { top: 0; transform: rotate(-45deg); }
}

@media (max-width: 760px) {
  :root { --header-h: 72px; }
  .wordmark img { width: 158px; }
  /* Pixun, 2026-09-08: "el hero de home en movil ha de tener mas margen y un
     poco mas grande". Measured at 375px before this: .hero-media sat 4px
     wider than .container (--hero-pad-x's 12px vs. the site gutter's 16px),
     so the panel visibly overran the card below it; and .hero-copy's own
     left/right formula left the headline only 4px off the panel's edge —
     pinned, not padded. Three fixes, all inside this ≤760px block only:
     --hero-pad-x takes the site --gutter so the panel sits flush with
     .container at every width (16px at 375 up to ~22.8px at 760, never a
     fixed value that would overshoot again on a wider phone); min-height
     grows 400 -> 440 for "un poco mas grande"; and .hero-copy gets a flat
     20px inset here instead of the desktop left/right formula, which was
     never meant to produce breathing room, only column alignment. */
  .hero { --hero-pad-x: var(--gutter); }
  .hero-media { aspect-ratio: 1.35 / 1; min-height: 440px; }
  .hero-copy {
    left: 20px;
    right: 20px;
    max-width: none;
    /* Elementor writes `width: var(--width)` (100%) onto this container.
       With `left`, `right` AND `width` all set, the box is over-constrained
       and the engine drops `right` — there's no right margin, the box runs
       4px past `.hero-media`'s own right edge (which clips via
       `overflow: hidden`), and the headline gets cropped. Overriding
       `width: auto` here lets `left`/`right` govern again, which is what
       actually keeps this aligned with `.container`. This is a direct
       declaration that wins over an Elementor-panel value on purpose (see
       CLAUDE.md exception C-2): no panel control can express "position this
       absolutely against the photo", so this geometry stays hand-written —
       restricted to this ≤760px block, desktop is untouched. */
    width: auto;
  }
  .servei-card { flex-basis: 100%; aspect-ratio: 1.5 / 1; }
  .client-list { columns: 2 160px; }
  .footer-grid { grid-template-columns: 1fr; }
  .footer-bottom { justify-content: flex-start; }
}

@media (max-width: 420px) {
  .wordmark img { width: 138px; }
  .client-list { columns: 1; }
  .btn { padding-inline: var(--sp-6); }
}

/* ==========================================================================
   Reduced motion — neutralises every animation defined above. Nothing in
   this product depends on a transition to become visible or usable.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  /* The mobile panel still opens and closes; it simply stops travelling and
     the rows stop staggering. These are transitions, not animations, so
     zeroing the durations leaves the end states untouched. */
  .primary-nav,
  .primary-nav li {
    transform: none;
    transition-duration: 0s;
    transition-delay: 0s;
  }
  .primary-nav { transition: visibility 0s; }

  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  /* Every reveal now runs on keyframes, so the animation is cancelled
     outright rather than merely shortened — and the resting `opacity: 0`
     must be undone explicitly, because with no animation there is nothing
     left to bring it back. Listing the variants too: a cancelled animation
     on a scaled or side-shifted element would otherwise strand it mid-pose.
     `animation-delay` is zeroed as well, since JS still writes the stagger
     and a delay with no animation is harmless but confusing to debug. */
  .js [data-reveal],
  .js [data-reveal="media"],
  .js [data-reveal="right"],
  .js [data-reveal].is-visible {
    animation: none !important;
    animation-delay: 0ms !important;
    opacity: 1;
    transform: none;
  }

  .hero-media img,
  .hero-copy,
  .page-head h1,
  .page-head p,
  .page-head .post-meta { animation: none !important; opacity: 1; transform: none; }

  .btn:hover,
  .servei-card:hover,
  .post-card:hover { transform: none; box-shadow: none; }
  .servei-card:hover img,
  .post-card:hover img { transform: none; }
  .servei-card:hover { box-shadow: var(--shadow-card); }

  /* The nav underline stays a state indicator, not an animation: current
     page keeps its bar, hover simply shows one with no wipe. */
  .primary-nav a::after { transition: none; }

  /* The logo marquee stops being a marquee: one static wrapped row, the
     duplicate group removed, and the pause control hidden because there is
     no longer anything to pause. */
  .logo-marquee { -webkit-mask-image: none; mask-image: none; }
  .logo-marquee-track {
    animation: none;
    width: 100%;
    flex-wrap: wrap;
    justify-content: center;
  }
  .logo-marquee-group {
    flex-wrap: wrap;
    justify-content: center;
    padding-right: 0;
  }
  .logo-marquee-group[aria-hidden="true"] { display: none; }
  .marquee-controls { display: none; }
}
