/* ==========================================================================
   ALLA Arquitectura — WordPress-only corrections.

   alla.css is a byte-identical copy of the HTML template's stylesheet and must
   stay that way. Everything that exists ONLY because Elementor and Hello
   Elementor are in the room lives here instead.

   Loaded after alla.css. Every rule below carries the reason it exists.
   ========================================================================== */

/* --------------------------------------------------- 1. Elementor containers

   Elementor gives every Flexbox Container a default gap, expressed through
   --row-gap / --column-gap rather than the `gap` property. Setting `gap: 0`
   does nothing, because Elementor's own rule resolves the variables at a
   higher specificity a moment later. Zero the VARIABLES.

   Budget time for what the gap was hiding: every widget is a flex item, so a
   margin that used to collapse now sits inside the item. That is why the
   spacing rules further down exist at all.                                   */

.elementor-section.elementor-section-boxed > .elementor-container,
.e-con,
.e-con-inner {
  --row-gap: 0px;
  --column-gap: 0px;
}

/* Elementor's containers cap themselves at the kit's content width. The
   template's own .container already does the measuring, so the container
   must not impose a second, different one. */
.e-con > .e-con-inner {
  max-width: none;
  padding-inline: 0;
}

/* B6, round 3: the same "zero the variable" fix as the gap above, but for
   padding, and for a reason section 8.8's `:where(.e-con) { padding: 0 }`
   cannot reach.

   MEASURED live, port vs. the static HTML twin, same viewport (1568px):
   the whole `.hero__caption` row sits 20px further right in the port --
   84.11px from the left edge in the static build, 104.11px in the port --
   which is exactly enough to push the caption's text box 20px deeper into
   the diagonal band and lose its last word under it (B6's open half).
   Traced to two containers that carry NO padding rule of their own in
   alla.css at all -- `.hero` and `.hero__caption` are meant to run edge to
   edge -- so unlike `.container` (which sets its own literal
   `padding-inline: var(--gutter)` and rides straight over anything here),
   they fall through entirely to Elementor's raw default:
   `.e-con { --padding-left: var(--container-default-padding-left, 10px); ... }`,
   10px a side, 20px total on `.hero` (10) plus `.hero__caption` (10) nested
   inside it.

   `:where(.e-con) { padding: 0 }` (8.8) cannot fix this: it was written at
   ZERO specificity on purpose, so it can never beat Elementor's own
   `.e-con { ... }` rule (0,1,0) in the first place, regardless of load
   order -- it only ever affected inline per-element padding, which this
   build stopped writing when `con()` stopped setting padding at all (see
   that function's own comment). It has been doing nothing for either side
   of this bug.

   This rule wins the ONE side that matters -- the four per-side variables
   Elementor's own chain resolves from -- at Elementor's own specificity,
   relying on load order the same way the gap fix above already does. It
   cannot touch `.container`'s padding: `.container { padding-inline:
   var(--gutter) }` never reads `--padding-left`/`--padding-right` at all,
   so zeroing them has no path to it. Anything that DOES want padding on an
   `.e-con` element states it directly, in alla.css, the same way
   `.container` already does -- that rule then wins on load order exactly as
   it does today, untouched by this. */
.e-con {
  --padding-top: 0px;
  --padding-right: 0px;
  --padding-bottom: 0px;
  --padding-left: 0px;
}

/* --------------------------------------------------- 2. Hello Elementor

   The parent theme ships opinionated defaults that fight the design layer.
   `hello_elementor_enqueue_theme_style` is filtered off in functions.php;
   these are the leftovers that come from the reset it still prints.          */

body.elementor-page .site-main,
body .site-main {
  margin: 0;
  padding: 0;
  max-width: none;
}

.site-header,
.site-footer {
  display: none;   /* header and footer are Theme Builder templates */
}

/* ------------------------------------------------- 3. The class -> attribute
   bridge

   The template hangs behaviour on data- attributes: [data-reveal] for scroll
   reveals, and the hero/gallery roots. Elementor's custom HTML attributes are
   licence-gated, so on an unvalidated Pro install those attributes cannot be
   set from the panel. The workaround, proven on masana: the editor sets a CSS
   CLASS, and a small script in alla-wp.js copies it to the attribute at load.

   These rules make the class behave identically to the attribute so the page
   looks right in the editor too, where the script does not run.              */

html.js .js-reveal:not([data-reveal]) {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity var(--dur-reveal) var(--ease-out),
              transform var(--dur-reveal) var(--ease-out);
  animation: reveal-failsafe 0s linear 2500ms forwards;
}
html.js .js-reveal.is-in { opacity: 1; transform: none; }

/* Inside the Elementor editor nothing should ever be invisible: an editor that
   hides the thing you are editing is worse than an editor with no motion. */
.elementor-editor-active html.js [data-reveal],
.elementor-editor-active html.js .js-reveal,
body.elementor-editor-active [data-reveal],
body.elementor-editor-active .js-reveal {
  opacity: 1 !important;
  transform: none !important;
}

/* ----------------------------------------------------------- 4. Specificity

   The kit and the theme declare the same properties at the same weight, and
   the winner is decided by load order — which is OPPOSITE in the editor and
   on the front end. Elementor injects its CSS last in the editor; the theme
   loads last on the front end. Identical CSS, opposite winner.

   The fix is one extra class above the kit, kept BELOW the Style panel's own
   output so the client can still override everything from the panel. Body is
   that class. Anything added here must follow the same shape.

   Learned on masana: fifteen rules were at risk, nine of them colour, and nine
   had never been seen because nobody had opened those widgets in the editor.  */

body .btn { background-color: transparent; }
body .btn:hover { background-color: var(--bg-pure); color: var(--on-dark); }
body .section--dark .btn:hover { background-color: var(--on-dark); color: var(--bg-pure); }

/* The shorthand loses a same-weight tie against Elementor's own longhand, so
   button backgrounds are always stated as the longhand. */

/* ------------------------------------------------------ 5. Editor ergonomics

   A container the client cannot see is a container the client cannot select.
   Elementor already outlines empty containers; this only makes the site's own
   full-bleed sections legible while editing.                                  */

.elementor-editor-active .hero,
.elementor-editor-active .gallery,
.elementor-editor-active .showcase__band {
  outline: 1px dashed rgba(27, 27, 27, 0.25);
  outline-offset: -1px;
}

/* The menu overlay (#site-menu, container 6f897eb) is a fixed, hidden layer on
   the front end (alla.css §7), so in the editor it could be neither seen nor
   selected — Pixun, 2026-09-23: "SOLO QUIERO PODER EDITARLO". Shown here in
   flow, one screen tall, ONLY while its own header is the document being
   edited: Elementor puts `elementor-edit-area-active` on that document alone,
   so editing a page — whose header is also in the preview — leaves it hidden,
   and the front end never matches. Clipped so the diagonal bar, which starts
   10% above the overlay, does not cover the header bar while editing. The
   panel's own values on 6f897eb still win (they are three classes deep). */
.elementor-edit-area-active .menu {
  position: relative;
  inset: auto;
  min-height: 100vh;
  overflow: hidden;
  opacity: 1;
  visibility: visible;
  transition: none;
}

/* --------------------------------------------------------------- 6. Widgets

   Elementor's Icon List, Text Editor and Button widgets bring their own
   spacing that the template never had. Neutralised at one class above the
   kit, never with !important, so the panel still wins.                        */

body .elementor-widget:not(:last-child) { margin-block-end: 0; }
body .elementor-widget-text-editor p:last-child { margin-block-end: 0; }
body .elementor-icon-list-items { padding: 0; }

/* ------------------------------------------- 7. Layout inside a container

   Every Elementor container is `display: flex; flex-direction: column`, and
   that wins over the stylesheet's own rules for the same element. The effect
   is quiet and confusing: `align-items: flex-end` on a row was written to push
   the hero caption to the BOTTOM, and in a column it pushes it to the RIGHT
   instead — so the caption ends up top-right with nothing obviously broken.

   These re-state the direction the design assumes, one class above Elementor's
   `.e-con` so they win on specificity rather than on load order — which is the
   only thing that behaves the same in the editor and on the front end.        */

body .hero__inner.e-con {
  flex-direction: row;
  align-items: flex-end;
  height: 100%;
}

/* Unguarded, this also forced row+flex-end on a 375px viewport where
   alla.css's own `@media (max-width: 640px) { .hero__caption { flex-direction:
   column; align-items: flex-start; ... } }` wants the mark and the text
   stacked instead of squeezed side by side into the same width the desktop
   layout uses. MEASURED live: at 375px the port computed
   `flex-direction: row` while the static HTML twin, same viewport, computed
   `column` — the mark and text staying side-by-side is what pushed the
   wordmark's tail into the diagonal band. Scoped to match alla.css's own
   breakpoint so mobile falls through to it instead of being overridden. */
@media (min-width: 641px) {
  body .hero__caption.e-con {
    flex-direction: row;
    align-items: flex-end;
    flex-wrap: wrap;
  }
}
@media (max-width: 640px) {
  body .hero__caption.e-con {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* Pixun, 2026-08-30: the ball/heading/button now stack (alla.css's own
   `.showcase__head` — see its comment there), which is Elementor's own
   `.e-con` DEFAULT direction (column) — nothing to restate here any more.
   `align-items` still needs it, the same as `.intro__body.e-con` below: this
   file's own house rule is never to trust an Elementor default without
   checking it, and the intro's own identical column layout already needed
   this exact restatement. */
body .showcase__head.e-con {
  align-items: flex-start;
}
/* The dot+heading pair keeps its own inner stack too — scoped to this
   section only, because `.js-reveal` is a generic reveal hook used all over
   the site and a blanket override here would reach every other instance. */
body .showcase__head > .js-reveal.e-con {
  align-items: flex-start;
}

body .kit__inner.e-con {
  flex-direction: row;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
}

body .intro__body.e-con {
  align-items: flex-start;
}

/* The card is a link plus its caption; the media must not stretch. */
body .card-wrap.e-con {
  align-items: stretch;
}

/* Elementor wraps every widget in its own div, which becomes a flex item and
   inserts a box the static markup never had. Width auto stops those wrappers
   stretching to the full column and breaking the intended rhythm. */
body .hero__caption .elementor-widget,
body .showcase__head .elementor-widget,
body .kit__inner .elementor-widget {
  width: auto;
}

/* --------------------------------------- 8. What Elementor overrides, undone

   Every rule here was written after MEASURING the rendered page, not from
   reading the markup. Each one names the number that proved it.              */

/* 8.1  BLACK ON BLACK.

   Measured: the heading inside `.showcase__band` computed to rgb(27,27,27) on
   a rgb(27,27,27) ground. Elementor's kit applies its `text` global colour to
   every heading and text widget, which beats the band's inherited white.

   This is masana's most expensive bug in a different costume — there the kit
   held the wrong colour, here it holds the right one and applies it in the
   wrong place. Both are the same lesson: the kit and the theme declare the
   same property, and whoever wins is decided by specificity. So a widget on a
   dark ground inherits, rather than being told a colour.

   B6, added later, same bug, one section short: `.hero__text` computed
   rgb(27,27,27) too, over the hero's dark photograph. CONFIRMED by reading
   the matched rule directly (`document.styleSheets`), not guessed —
   `.elementor-widget-text-editor { color: var(--e-global-color-text) }` in
   Elementor's own generated post CSS is the only declared `color` on that
   element, and a declared rule beats plain inheritance regardless of
   specificity. `.hero__caption { color: var(--on-dark) }` in alla.css was
   never in the running — inheritance has none.

   The auditor's separate hypothesis for this same symptom — that the
   `hero__caption` container's `flex_gap: 0` setting makes Elementor's own
   `--column-gap` override `.hero__caption { gap: clamp(...) }` — was checked
   by computed style and DISCARDED: `.hero__caption` computes
   `gap: 60.16px` at 1280px and `67.68px` at 1440px, tracking
   `clamp(var(--sp-5), 4.7vw, 90px)` correctly in both the WordPress port and
   the static HTML twin (measured side by side, same viewport, same numbers).
   The gap was never the problem; the text colour was. */
body .section--dark .elementor-heading-title,
body .section--dark .elementor-widget-text-editor,
body .showcase__band .elementor-heading-title,
body .showcase__band .elementor-widget-text-editor,
body .showcase__band .elementor-widget-container,
body .footer .elementor-heading-title,
body .footer .elementor-widget-text-editor,
body .hero .elementor-heading-title,
body .hero .elementor-widget-text-editor {
  color: inherit;
}

/* 8.2  MASONRY NEEDS A BLOCK BOX.

   Measured: `.project-grid` computed `display: flex`, `column-count: 1`.
   Elementor makes every container a flex column, and CSS multi-column is
   simply ignored on a flex container — the masonry silently became a stack. */
body .project-grid.e-con,
body .figure-set.e-con {
  display: block;
}
/* .index-grid.e-con removed from the list above on 2026-09-07: 0cdc5d3 is
   now an Elementor Grid container (container_type: grid), and this rule
   forcing display:block would silently defeat that — the grid would never
   render. Do not put it back. */

/* 8.2b  NO SPACE BELOW THE PROJECT PHOTOGRAPHS (round 3).

   Multi-column has no row gap by design (see alla.css:635-638's own comment):
   the children carry a bottom margin instead, and that margin is what gives
   the horizontal gutter its vertical twin. `.figure-set`'s own children are
   raw Image widgets (`elementor-widget elementor-widget-image`) — confirmed
   live, `.figure-set > :first-child` — and Elementor ships
   `.e-con .elementor-widget.elementor-widget { margin-block-end: 0 }`,
   doubled specificity (0,3,0), against `.figure-set > * { margin-bottom:
   var(--col-gap) }` at (0,1,0). Measured before this fix: computed
   `margin-bottom: 0px` on every figure in the gallery, on the served page —
   photographs butted straight into the row below with no vertical gap, while
   the column-gap between them stayed correct. Restated at the matching
   weight, same value the static twin uses (`var(--col-gap)`, the SAME token
   as the column gap, so the two gaps read as one system rather than two
   numbers that happen to agree).

   `.project-grid` and `.index-grid` were checked too, since Pixun asked for
   "all": both wrap each item in its own Elementor CONTAINER (`card-wrap`,
   an `.e-con`), never a bare widget, so Elementor's widget-only reset never
   reaches them — measured live, both already carry their intended
   `margin-bottom` unmodified. Nothing to fix there. `.team` uses CSS Grid's
   `gap` property, not a margin, so this specific bug cannot reach it either
   — it has a real, separate rendering problem (see this round's notes) but
   it is not this one, and is not fixed here. */
body .figure-set > .elementor-widget.elementor-widget {
  margin-bottom: var(--col-gap);
}

/* 8.3  THE HERO WORDMARK — removed 2026-09-07.
   body .hero__mark img and --hero-mark-w are gone: the wordmark's width now
   lives in the Elementor panel (widget 337abb1's own width / width_tablet /
   width_mobile controls), and no element carries hero__mark on the live
   site anyway. See P2 of feedback-2026-09-07.md. */

/* 8.4  THE BUTTON IS AN OUTLINE, NOT A FILL.

   Measured: background rgb(27,27,27), text white — Elementor's own button skin.
   The design's button is a hairline rectangle with no fill and no radius. The
   rule sits one class above `.elementor-button` so the Style panel can still
   override it, per this build's second standing rule.

   THE TEXT SAT AT THE TOP OF THE BOX, NOT CENTRED (round 3). The static
   twin's `.btn` is `display: inline-flex; align-items: center;
   justify-content: center` (alla.css:205) — that is what centres a single
   line of text inside a box made tall by `min-height`. This rule already set
   `min-height` but never `display`/`align-items`/`justify-content`, so
   Elementor's own `.elementor-button{display:inline-block}` (frontend.min.css,
   specificity (0,1,0), never contested because this rule never touched
   `display`) stood unchallenged: an inline-block box with one line of text
   and no vertical centring puts the text at the top and the slack below it —
   which reads as a short button with a gap under the label, not a mis-sized
   one. Measured before this fix, 800px viewport: box height 44px (correct,
   `--btn-h`'s floor), text sitting flush with the top edge. Adding the three
   properties is the whole fix; no height value changes. */
body .btn .elementor-button,
body .elementor-widget-button.btn .elementor-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background-color: transparent;
  color: inherit;
  border: 1px solid currentColor;
  border-radius: 0;
  min-width: var(--btn-w);
  min-height: var(--btn-h);
  padding: var(--sp-3) var(--sp-6);
  font-size: var(--fs-btn);
  font-weight: 400;
  line-height: 1.2;
  fill: currentColor;
}
body .btn .elementor-button:hover {
  background-color: var(--bg-pure);
  color: var(--on-dark);
}
body .showcase__band .btn .elementor-button:hover {
  background-color: var(--on-dark);
  color: var(--bg-pure);
}

/* 8.5  THE CAROUSEL.

   Measured: slides 391x150 with the image at 150x150 and `object-fit: fill` —
   Elementor was serving the thumbnail size and squashing it square. The strip
   is a full-bleed band of cropped photographs, so the slide takes the band's
   height and the image covers it. */
body .gallery .swiper-slide,
body .gallery .swiper-slide figure {
  height: clamp(220px, 34.5vw, 663px);
  margin: 0;
}
body .gallery .swiper-slide img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
body .gallery .elementor-image-carousel-wrapper { margin: 0; }
body .gallery figure figcaption { display: none; }

/* 8.5b  SQUARE SLIDES FOR THE DRAWINGS CAROUSEL ONLY.

   Every drawing in docs/image-manifest.json is 1080x1080 (all 27, across all
   8 projects, no exception) and 8.5's landscape clamp — built for arbitrary-
   ratio photography — cover-crops a square plan hard: at the mobile 1-up
   breakpoint the slide is roughly viewport-wide but only 220px tall (the
   clamp's floor), so a 1:1 source scaled to cover crops ~40% off the top and
   bottom. For a floor plan that can remove a dimension line or a legend, so
   the plans carousel (`gallery gallery--square`) needs its own rule instead
   of 8.5's clamp — the plain `.gallery` (home page) carousel keeps 8.5
   completely untouched, because this selector only matches when BOTH
   classes are present.

   `aspect-ratio: 1 / 1` rather than a fixed px/vw height: Swiper sets each
   slide's WIDTH inline in px, recalculated by its own JS on every resize and
   on every Elementor responsive breakpoint (verified against the vendored
   `swiper.js`, `updateSlides()`: `slides[i].style.width = ...px` whenever
   `slidesPerView` is a number, which is the case at all three of this
   widget's settings — 3 desktop / 2 tablet / 1 mobile). It never sets an
   inline height in this configuration (no `autoHeight` control exists on
   Elementor's image-carousel widget, so Swiper's default, `autoHeight:
   false`, stands, and `.swiper-autoheight` — the only class that would make
   Swiper touch height via JS — is never applied; the one place `swiper.js`
   resets an inline slide height is `changeDirection()`, which only runs on
   an explicit horizontal/vertical toggle this widget never performs). So the
   height channel is free, and `aspect-ratio: 1 / 1` derives it FROM whatever
   width Swiper just set, at any breakpoint — the two can never disagree,
   because one is computed from the other; nothing jumps as the carousel
   resizes. `height: auto` has to be restated alongside it: a definite
   `height` always overrides `aspect-ratio` per spec, so adding `aspect-ratio`
   without also cancelling 8.5's `clamp()` would leave the clamp in force.

   Specificity: `body .gallery.gallery--square .swiper-slide` is (0,3,1) —
   against 8.5's own `body .gallery .swiper-slide` at (0,2,1): one class
   higher, on purpose, so an element carrying both `gallery` and
   `gallery--square` resolves `height` from THIS rule, and an element
   carrying `gallery` alone (the home carousel) still resolves it from 8.5,
   unchanged. No `!important`, matching this build's standing rule — nothing
   here is a property Elementor's own Style panel writes for this widget (no
   per-slide height control exists), so there is no (0,5,0)-class panel
   output to out-rank; the only real competitors are Swiper's own base
   library rule (`.swiper-slide { height: 100% }`, (0,1,0)) and 8.5 itself,
   and (0,3,1) clears both.

   `object-fit: cover` is left exactly as 8.5 already sets it
   (`body .gallery .swiper-slide img`, unscoped, so it already reaches the
   drawings carousel too) — with a 1:1 image inside a 1:1 box there is
   nothing to crop, cover and contain agree, and leaving it as the shared
   default means a future non-square drawing still gets a sane crop instead
   of distorting. */
body .gallery.gallery--square .swiper-slide,
body .gallery.gallery--square .swiper-slide figure {
  aspect-ratio: 1 / 1;
  height: auto;
}

/* 8.6  MAX-WIDTH ON A WIDGET.

   Measured: `.hero__text` carried `max-width: clamp(180px, 18vw, 22ch)` from
   alla.css and computed to `max-width: 100%`. Elementor's own
   `.elementor-widget { max-width: 100% }` has identical specificity (0,1,0),
   so the winner is decided by load order — the exact failure this file exists
   to prevent, and the reason nothing here should ever be left at one class.

   Any width the design sets on something that is ALSO an Elementor widget has
   to be re-stated here, one class up. */
/* Elementor's rule is
       `.elementor.elementor .e-con > .elementor-widget { max-width: 100% }`
   — specificity (0,4,0), inflated on purpose by repeating the class. A single
   `body .hero__text` is (0,1,1) and never had a chance; the measurement said
   `max-width: 100%` while the rule sat right there in the stylesheet, loaded.

   These match Elementor's own shape and add one more class, so they win by
   specificity in the editor and on the front end alike. Still no !important,
   so the Style panel — which outputs a per-element id selector — stays on top
   and the client can override any of it. */
/* Same bug as `.hero__caption.e-con` above, same fix: alla.css sets
   `.hero__text { max-width: none }` at <=640px so the caption can use the
   phone's full width once it is stacked instead of staying pinned to the
   desktop clamp. Unguarded, this rule fought that at every width. */
@media (min-width: 641px) {
  body .elementor .e-con > .elementor-widget.hero__text { max-width: clamp(180px, 18vw, 22ch); }
}
@media (max-width: 640px) {
  body .elementor .e-con > .elementor-widget.hero__text { max-width: none; }
}
body .elementor .e-con > .elementor-widget.lead { max-width: 18ch; }
body .elementor .e-con > .elementor-widget.prose { max-width: 62ch; }
body .elementor .e-con > .elementor-widget.team__bio { max-width: 40ch; }

/* ------------------------------------- 9. The header and footer, as rows

   Same cause as section 7: Elementor makes every container a flex COLUMN, so
   the header stacked its wordmark above the burger and the footer's three
   columns became three rows. Re-stated one class above `.e-con`.              */

body .header__inner.e-con {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
}

body .header__tools.e-con {
  flex-direction: row;
  align-items: center;
  gap: clamp(var(--sp-4), 2vw, var(--sp-12));
  width: auto;
}

body .header__inner .elementor-widget { width: auto; }

/* The footer is a grid in the design, and a grid is not a flex column. */
body .footer__inner.e-con {
  display: grid;
  grid-template-columns: 2.55fr 1.46fr 1fr;
  gap: var(--sp-10);
  align-items: start;
}

body .socials.e-con {
  flex-direction: row;
  gap: var(--sp-5);
  width: auto;
}

body .footer__legal.e-con {
  flex-direction: row;
  justify-content: space-between;
  flex-wrap: wrap;
}

/* Menu and footer links are Button widgets so the client can edit them; the
   design styles plain anchors, so the button skin is undone here. */
body .menu__nav .elementor-button,
body .footer__col .elementor-button,
body .socials .elementor-button {
  background-color: transparent;
  color: inherit;
  border: 0;
  border-radius: 0;
  padding: 0;
  min-width: 0;
  min-height: 0;
  font-size: inherit;
  font-weight: inherit;
  text-align: left;
}
body .menu__nav .elementor-button { font-size: clamp(2rem, 4.16vw, 5rem); line-height: 1.1; }
body .footer__col .elementor-widget-button,
body .socials .elementor-widget-button { width: auto; }
body .footer__col .elementor-widget-button + .elementor-widget-button { margin-top: var(--sp-2); }

@media (max-width: 1100px) {
  body .footer__inner.e-con { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 640px) {
  body .footer__inner.e-con { grid-template-columns: 1fr; }
}

/* 8.7  A HEADING WIDGET'S TAG IGNORES THE WRAPPER'S CLASS.

   Measured on /projectes/ at 1440:
       .card__name                          font-size 18px  (= --fs-h3)
       .card__name .elementor-heading-title font-size 32px  (= 2rem)

   Elementor puts the widget's class on the WRAPPER, never on the <h2> inside.
   The design's rule reaches the tag only by inheritance, and inheritance loses
   to any matching declaration — here hello-elementor's reset `h2 { font-size:
   2rem }`, which is only (0,0,1) but matches the element directly.

   So the tag is told to inherit from the wrapper it lives in. Same bug was on
   the home page's cards; this corrects both. */
body .elementor-widget-heading.card__name .elementor-heading-title,
body .elementor-widget-heading.lead .elementor-heading-title,
body .elementor-widget-heading.team__name .elementor-heading-title {
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
}

/* 8.8  CONTAINER PADDING BELONGS IN CSS, NOT IN EVERY ELEMENT'S SETTINGS.

   Elementor gives containers a default padding. Zeroing it per element wrote
   `padding: 0` into the per-page stylesheet at a specificity no class rule
   could beat, so `.page-head`'s top padding — the thing that clears the fixed
   header — was silently discarded and every inner page's title sat behind it.

   Reset once, here, at a specificity the design's own classes can override. */
:where(.e-con) { padding: 0; }
/* `:where()` carries ZERO specificity on purpose. A plain `.e-con { padding: 0 }`
   ties with `.page-head { padding-top: ... }` and wins on load order, which put
   every inner page's title back under the fixed header. A reset must be the
   easiest rule in the file to override, not the hardest. */

/* 8.9  THE HEADER WORDMARK.

   Measured: 855x256px inside an 89px-tall fixed header, on every page. An
   Elementor image fills its box, and §8.3 only ever fixed the hero's mark —
   the header's was left at full size and painted over the page title beneath
   it. `.page-head`'s 187px top padding clears an 89px header, not a 256px
   image. Same shape of fix, same reasoning. */
body .header__mark img,
body .footer__mark img {
  width: 100%;
  height: auto;
  display: block;
}
/* These have to out-specify §9's `body .header__inner .elementor-widget
   { width: auto }` (0,2,1) — two rules of mine were fighting, and `auto` won,
   so the mark rendered at the PNG's own 855px inside an 89px header. */
body .header__inner .elementor-widget.header__mark { width: clamp(96px, 8.85vw, 170px); }
body .footer__inner .elementor-widget.footer__mark { width: clamp(160px, 21.2vw, 407px); }

/* 8.10  THE PRO FORM WEARS ELEMENTOR'S SKIN.

   Every rule below replaces a measured default, and each is written to beat the
   specificity that currently wins — up to (0,5,0) for Elementor's own
   per-element output. Still no !important, so the Style panel stays on top.

     input   border 1px solid rgb(105,114,125), radius 3px, white fill,
             padding 8px 16px, min-height 40px
     label   15px, sentence case, rgb(27,27,27)
     submit  filled rgb(27,27,27), radius 3px, 95x40

   The design is a hairline under a transparent field, an uppercase meta label,
   and an outlined button. */

body .elementor .elementor-widget.form .elementor-field-group .elementor-field.elementor-field-textual {
  background-color: transparent;
  border: 0;
  border-bottom: 1px solid var(--border-faint);
  border-radius: 0;
  padding: var(--sp-3) 0;
  min-height: 0;
  font: inherit;
  color: inherit;
}
/* `outline: none` used to sit here, to beat Elementor Pro's own
   `.elementor-field-textual:focus` default outline (0,2,0) -- higher
   specificity than alla.css's site-wide `:focus-visible { outline: 2px
   solid currentColor; outline-offset: 3px }`, so Elementor's rule was
   winning regardless of load order. Suppressing it to `none` beat
   Elementor's unwanted default, but took the site's OWN focus indicator
   down with it: left with only a 1px border-bottom colour change
   (#e0e0e0 -> #1b1b1b) as the sole focus signal, which is thin against
   WCAG 2.4.11 (Focus Appearance).

   Restated instead of suppressed: same specificity fight, same winner,
   but the value now matches the site's own already-designed focus
   treatment verbatim -- black and white, nothing invented -- rather than
   erasing it. */
body .elementor .elementor-widget.form .elementor-field-group .elementor-field.elementor-field-textual:focus {
  border-bottom-color: var(--border);
  outline: 2px solid currentColor;
  outline-offset: 3px;
}

body .elementor .form .elementor-field-group > label {
  font-size: var(--fs-meta);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--text-secondary);
  margin-bottom: var(--sp-2);
}
/* The consent sentence is prose, not a field label — it stays sentence case. */
body .elementor .form .elementor-field-subgroup label {
  text-transform: none;
  letter-spacing: 0;
  font-size: var(--fs-meta);
  color: var(--text-primary);
}

body .elementor .form .elementor-form .elementor-button[type="submit"] {
  background-color: transparent;
  color: inherit;
  border: 1px solid currentColor;
  border-radius: 0;
  min-width: var(--btn-w);
  min-height: var(--btn-h);
  padding: var(--sp-3) var(--sp-6);
  font-size: var(--fs-btn);
  font-weight: 400;
}
body .elementor .form .elementor-form .elementor-button[type="submit"]:hover {
  background-color: var(--bg-pure);
  color: var(--on-dark);
}

body .elementor .form .elementor-field-group { margin-bottom: var(--sp-6); }

/* 8.11  THE KICKER LOST ITS MARGIN ON EVERY PAGE.

   Measured on /qui-som/: `.page-head__kicker` computes `margin-bottom: 0px`
   where the design sets `var(--sp-5)`. Elementor ships
   `.e-con .elementor-widget.elementor-widget { margin-block-end: 0 }` — the
   class deliberately doubled again, (0,3,0) — and the design's single class is
   (0,1,0).

   This was never about the 404 that surfaced it: it has been true on qui-som,
   contacte, projectes and all eight project pages since the first apply. Any
   margin the design puts on something that is also a widget has to be restated
   at this weight. */
body .elementor .e-con .elementor-widget.elementor-widget.page-head__kicker {
  margin-block-end: var(--sp-5);
}

/* 8.12  A PAINTED CLASS ON A WIDGET PAINTS THE WRAPPER.

   Measured on the 404: a 1px bordered box 1108px wide drawn around a 200px
   button. Elementor hangs a widget's CSS classes on its WRAPPER div, never on
   the <a> inside it — so `.btn` reached the wrapper, and `.btn` is a painted
   class: border, min-width, min-height, padding. 8.4 above dresses the real
   link correctly; nothing was undressing the box around it.

   It is on every button on the site, not just the 404's. It hides wherever the
   wrapper happens to be about as wide as the button (the intro's "Sobre
   nosaltres") and is obvious wherever the container is full width.

   The general lesson, and the one worth carrying to the next port: a class that
   PAINTS cannot be used as an Elementor hook. It has to either paint nothing,
   or be undone on the wrapper the way it is here. */
body .elementor .elementor-widget.btn {
  display: block;
  min-width: 0;
  min-height: 0;
  padding: 0;
  border: 0;
  transition: none;
}
body .elementor .elementor-widget.btn:hover {
  background: none;
  color: inherit;
}

/* 8.13  THE PAGE-HEAD'S RHYTHM WAS CARRIED BY INLINE STYLES.

   The static build spaces the page head with inline tokens on the elements
   themselves:

       <p class="prose" style="margin-top:var(--sp-6)">
       <p style="margin-top:var(--sp-8)"><a class="btn">

   Inline styles do not survive the port — the text becomes a widget and the
   attribute is gone — and Elementor then zeroes what is left, the same
   `.elementor-widget.elementor-widget { margin-block-end: 0 }` that took the
   kicker's margin in 8.11. Result: the body copy sat hard against the h1's
   descenders and the button against the copy.

   Restated here against the same tokens, so both builds still say --sp-6 and
   --sp-8 in one place. Scoped to `.page-head` deliberately: `.prose` is used on
   the project pages too, where a top margin would be wrong. */
body .elementor .page-head > .elementor-widget.elementor-widget.prose {
  margin-block-start: var(--sp-6);
}
body .elementor .page-head > .elementor-widget.elementor-widget.btn {
  margin-block-start: var(--sp-8);
}

/* 8.14  HELLO ELEMENTOR'S RESET PAINTS EVERY BARE BUTTON CRIMSON.

   `hello-elementor/assets/css/reset.css` is enqueued on every served page and
   ships (confirmed live, this exact text):

     [type=button],[type=submit],button{background-color:transparent;
       border:1px solid #c36;border-radius:3px;color:#c36; ... }
     [type=button]:focus,[type=button]:hover,[type=submit]:focus,
       [type=submit]:hover,button:focus,button:hover{
       background-color:#c36;color:#fff;text-decoration:none}

   Two separate leaks, confirmed by reading the live CSSOM (every rule that
   actually matches each element, not assumed from the stylesheet source):

   REST STATE — only `.burger` loses. `[type=button]` is (0,1,0); `.burger`
   sets `background`/`border-radius` (0,1,0) too and wins the tie only
   because alla.css loads after reset.css, but sets no `border` or `color` of
   its own, so reset's crimson 1px border and crimson text/icon colour stood
   unchallenged — measured live: `getComputedStyle(.burger).border` was
   `1px solid rgb(204, 51, 102)`. `.lb-trigger`, `.filters button` and the
   lightbox nav buttons already win their own rest-state tie (each sets
   `border`/`color` at (0,1,0) or higher), so only `.burger` needed this half.
   Worth noting: `color` also drives `:focus-visible { outline: 2px solid
   currentColor }` (alla.css:139) — the leaked crimson was making the
   burger's own keyboard-focus ring crimson too, on a palette whose own brief
   says "no red" (contacte's error copy, README).

   HOVER AND FOCUS — every bare button loses, `.burger` included. The reset's
   hover/focus selectors reach (0,2,0) via `[type=button]:hover` — higher
   than any single-class component rule in this file, and none of
   `.burger`, `.lb-trigger`, `.filters button` or `.lightbox__prev/__next/
   __close` had a `:hover`/`:focus` rule of their own to contest it (their
   REST rules do not carry over: cascade resolves hover-time conflicts by
   the specificity of what matches AT THAT MOMENT, and the always-matching
   rest rule is lower). Confirmed additional victims beyond this project's
   own carried-forward note (CHANGELOG 0.3.0 named `.burger`, `.lb-trigger`,
   `.filters button`, `.btn` on focus only): the lightbox `__prev`/`__next`/
   `__close` controls have exactly the same gap and were never named before.

   `body <selector>:hover` beats `[type=button]:hover` by the same margin
   this whole file already relies on elsewhere: equal or higher class count,
   plus the extra `body` element breaks the tie. Each component restates ITS
   OWN correct value rather than a blanket `color: inherit` — `inherit` would
   be wrong for the lightbox controls, whose text sits on a near-black
   overlay and is deliberately `var(--on-dark)`, not the page's ambient text
   colour.

   `.filters button[aria-pressed='true']` (already (0,2,1), unaffected by
   reset since it does not need `:hover` to apply) must keep winning over the
   neutralising rule below while a filter IS pressed — excluded with
   `:not([aria-pressed='true'])` rather than trusted to lose on specificity,
   because with the `body` prefix it would not: `body .filters
   button:hover` reaches (0,2,2), which beats the pressed rule's (0,2,1).

   ORDER IN THIS FILE IS LOAD-BEARING, not tidiness. `.burger` and
   `.lightbox__*` all carry `type="button"`, so `body [type="button"]:hover`
   in the generic net below is TIED at (0,2,1) with each component's own
   `body .burger:hover` / `body .lightbox__prev:hover` rule — same
   specificity, both real declarations. A tie is decided by which one comes
   LAST in the file, so the generic net has to be written FIRST and every
   named component AFTER it, or the net would win and silently erase the
   per-component colour it exists to protect (the lightbox controls losing
   their on-dark colour to the net's `inherit`, most visibly). */

/* A no-cost net for any bare button this file has not named individually —
   written first so every named component below can out-order it on a tied
   specificity. `color: inherit` is safe here specifically BECAUSE nothing
   unnamed is known to sit on a dark background the way the lightbox
   controls do; anything that later turns out to need its own colour on
   hover gets a component rule of its own, below, the same as the others. */
body button:hover,
body button:focus,
body [type="button"]:hover,
body [type="button"]:focus,
body [type="submit"]:hover,
body [type="submit"]:focus {
  background-color: transparent;
  color: inherit;
}

body .burger {
  border: 0;
  color: inherit;
}
/* The `body .burger:hover/:focus { background-color: var(--bg) }` rule that
   used to sit here re-asserted the white disc against this file's generic
   button-reset net. Removed 2026-09-07 (feedback package P4) along with the
   disc itself in alla.css — keeping it would have made the disc reappear on
   hover/focus only, worse than either extreme. */

body .lb-trigger:hover,
body .lb-trigger:focus {
  background-color: transparent;
  color: inherit;
}

body .filters button:hover:not([aria-pressed='true']),
body .filters button:focus:not([aria-pressed='true']) {
  background-color: transparent;
  color: inherit;
}

body .lightbox__prev:hover,
body .lightbox__prev:focus,
body .lightbox__next:hover,
body .lightbox__next:focus,
body .lightbox__close:hover,
body .lightbox__close:focus {
  background-color: transparent;
  color: var(--on-dark);
}

/* 8.15  THE SAME RESET ALSO PAINTS LINKS, NOT ONLY BUTTONS.

   `hello-elementor/assets/css/reset.css` ships `a{color:#c36}` at rest and
   `a:active,a:hover{color:#336}` on hover/active — both bare-element
   selectors, (0,0,1) and (0,1,1). Checked the live CSSOM again (same
   technique as 8.14, not assumed from source): REST is already safe
   everywhere, and only because of one thing already in this codebase —
   `alla.css:147`'s own `a { color: inherit }` ties reset's `a{color:#c36}`
   at identical specificity (0,0,1), and `alla.css` is enqueued at priority
   20, after Hello Elementor's stylesheet, so it wins that tie by load
   order on every page. Measured to confirm rather than trusted: `.breadcrumb
   a`'s rest colour is `rgb(90, 90, 90)` (`--text-secondary`, correct), not
   crimson, on every page checked.

   HOVER is a different story: reset's `a:hover` (0,1,1) is the ONLY rule in
   the whole cascade that sets `color` directly on a plain `<a>` in that
   state — alla.css has no site-wide hover-colour rule to tie against it,
   only per-component hover effects that never touch `color` at all
   (`.lang a:hover{opacity:1}`, `.menu__nav a:hover{transform:...}`,
   `.footer__col a:hover{opacity:.6}`, `.pager a:hover strong{text-decoration:
   underline}`). Checked every one of these against the live CSSOM: none of
   them sets `color`, so none of them can contest reset's `a:hover{color:
   #336}` (0,1,1) — it wins unopposed everywhere a link has no explicit
   hover colour of its own.

   Confirmed affected, beyond the breadcrumb already reported: `.footer__col
   a` and `.socials a` (the footer nav list and the social placeholders —
   both inherit `--on-dark` white from `.footer`'s own `color` at rest, both
   have nothing countering `a:hover` on hover), `.menu__nav a` (the full-
   screen menu overlay — inherits `--on-dark` white from `.menu`, same gap),
   and `.pager a` / `.office a` (previous/next project link, contact page
   office links — inherit the page's default text colour). All five would
   flash dark blue (`#336`) on hover instead of holding their real colour.
   Checked and clean: prose paragraphs never contain an inline link anywhere
   on the site (`grep` across every project description, the Qui som
   statement and all three legal pages returns none), so there is no
   currently-existing "prose link" instance to leak — noted because the
   brief asked, not because one exists to fix.

   NOT a blanket reset of every anchor's hover colour to one value — the
   design has no anchor anywhere that intentionally changes colour on hover
   (confirmed above: every real hover effect in this design is opacity,
   transform, or text-decoration, never a colour swap), so restating
   `color: inherit` on hover is not overriding a design decision, it is
   completing the same one `alla.css:147` already makes for rest state —
   extended to hover/focus/active because that is exactly where reset.css's
   OWN override lives. A future link that DOES want its own hover colour
   still wins here: it would set `color` on a rule scoped to its own class,
   which out-specifies this bare-element one.

   `a:hover` is (0,1,1) — the same weight as reset's own selector — so this
   ties rather than clearly wins, and the tie is resolved by load order
   exactly like the rest-state rule it extends: `alla-wp.css` loads after
   `alla.css`, which loads after `reset.css`, so this rule is unconditionally
   last regardless of which of the two rest-state contenders happens to sit
   where. Written as a plain `body`-prefixed rule anyway, matching this
   file's own house style of never trusting load order alone (README rule
   4) — the `body` element pushes it to (0,1,2), clear of any tie. */
body a:hover,
body a:focus,
body a:active {
  color: inherit;
}


/* 8.16  THE HEADER ROW NEVER BECAME A ROW.

   Measured live, port vs the static HTML twin, same viewport (1440px):
   `.header__inner` computed `top: 0px` in the port, `top: 11.66px` in the
   static twin — the header's ONE child (the row holding the mark and the
   tools) sat flush against the header's own top edge instead of centred in
   it, and every element inside inherited the same 11.66px upward shift.

   Root cause was not the mark, the burger or the lang switcher — it was
   `.header` itself. `.header { display: flex; align-items: center }` in
   alla.css never states `flex-direction`, so on the static twin it defaults
   to the CSS-initial `row`, and `align-items: center` centres `.header__inner`
   VERTICALLY, exactly as measured. On the port `.header` is an Elementor
   Container, whose own default is `flex-direction: column` — untouched here
   because section 9 above only ever restated `flex-direction: row` on
   `.header__inner` and `.header__tools`, never on `.header` itself (its
   single child made the bug easy to miss: a lone item in a column also
   starts on the block-axis leading edge, `justify-content: flex-start`,
   which reads the same as "sitting at the top" regardless of direction).
   With `.header` still a column, `align-items: center` was centring
   `.header__inner` HORIZONTALLY instead (no visible effect, `.header__inner`
   is already 100% wide) while its vertical position fell through to
   Elementor's column default — flush top. Confirmed live by toggling
   `flexDirection` to `row` on the actual element and re-measuring:
   `.header__inner`'s top moved from 0px to 11.66px, matching the static
   twin exactly, with no other property touched.

   Same shape as section 9's own fix, one level up the tree it forgot. */
body .header.e-con {
  flex-direction: row;
}

/* 8.17  THE HEADER WORDMARK'S WRAPPER WAS TALLER THAN ITS IMAGE.

   Measured live: `.header__mark`'s own box was 43.91px tall while the `<img>`
   inside it rendered at 38.16px — a 5.75px difference with no padding,
   margin or border anywhere in the chain (checked directly: all three are
   `0px` on the `<img>`, the `<a>` Elementor wraps it in, and `.header__mark`
   itself). The image is `display: block` (8.9 already sets that), but the
   `<a>` between it and `.header__mark` is `display: inline-block` — an
   atomic inline-level box, `vertical-align: baseline` by default — and
   `.header__mark` computes `line-height: 21.75px` at its own `font-size:
   15px`. A block box containing ONLY an inline-level child still lays out
   an invisible "strut" the height of its own line box, and a baseline-
   aligned inline-block can leave part of that strut's leading exposed below
   it — the classic "phantom gap under an inline image" — confirmed by
   temporarily setting `line-height: 0` on the live element: the wrapper
   dropped from 43.91px to 38.16px, matching the `<img>` exactly (and,
   combined with 8.16 above, put the mark within 0.04px of the static twin's
   own measured position, 18.05px vs 18.09px, at 1440).

   The PNG asset itself is not the problem: `alla_blanc.png` (855x256) has
   ZERO transparent padding — `Image.getbbox()` returns `(0, 0, 855, 256)`,
   the full canvas — so there is no baked-in whitespace to re-export around;
   this was purely the strut, not the asset.

   `line-height: 0` collapses the strut without touching anything the Style
   panel might reasonably want to change (the image's own size is governed
   by 8.9's `width`/`height`, untouched here). */
body .header__mark.elementor-widget {
  line-height: 0;
}

/* 8.18  THE PROJECT CARD'S TITLE SAT FLUSH AGAINST ITS PHOTOGRAPH.

   Measured live on the home page: `.card__media`'s wrapper computed
   `margin-bottom: 0px`, so `.card__name` began at the exact pixel
   `.card__media` ended on (bottom 3071.3px, top 3071.3px, same number) —
   no gap at all between the image and the title Pixun's screenshot shows
   with a clear space before it.

   Confirmed by reading the matched rules directly (`element.matches(...)`
   against every stylesheet), not assumed: THREE rules apply to
   `.card__media`'s wrapper —

     .elementor-widget:not(:last-child)                 margin-block-end: 20px   (0,2,0)  Elementor kit default
     .e-con .elementor-widget.elementor-widget           margin-block-end: 0px    (0,3,0)  Elementor's own override
     body .elementor-widget:not(:last-child)             margin-block-end: 0      (0,2,1)  this file, section 6

   The middle one wins — (0,3,0) beats both (0,2,0) and (0,2,1) — and it is
   the exact same class of bug this file has fixed four times already
   (8.2b, 8.11, 8.13): Elementor's own container/widget reset outranks a
   single-class design rule. `.card__media` was simply never added to that
   list before now.

   `var(--sp-4)` — not an invented value: it is the SAME token
   `.card__caption { padding-top: var(--sp-4) }` (alla.css:918) already uses
   for this identical image-to-caption relationship on the projects index
   page's always-visible card variant, so the home page's stacked title
   now reads on the same rhythm as the index page's, rather than a new
   number nobody chose.

   Selector shape and specificity match 8.11's own fix for the same
   competing rule: `body .elementor .e-con .elementor-widget.elementor-
   widget.<class>` is (0,5,1) against the competitor's (0,3,0) — clears it
   with room to spare, still no `!important`, so the Style panel stays on
   top. */
body .elementor .e-con .elementor-widget.elementor-widget.card__media {
  margin-block-end: var(--sp-4);
}

/* 8.19  THE CARD PHOTO SHRANK TO ITS OWN FILE SIZE, LEAVING A GAP EITHER SIDE.

   Reported by Pixun from a screenshot of the Elementor editor: the card's
   box was wider than the photo inside it, on the projects index
   (/projectes/, /es/proyectos/, /en/projects/). Measured live, all three,
   1920: `.card__media`'s widget box computed 839.52px; the `<img>` computed
   its own served width exactly (600 or 800px, whichever the photo's file
   is) — never 839.52, however wide the box was.

   NOT the same bug as `.figure-set` on the project detail pages (`image_size`
   was checked and ruled out there first): the card's Image widget carries a
   Link (`link_to: custom`, pointing at the project), so Elementor wraps the
   `<img>` in an `<a>`. That anchor computes `display: inline-block` with no
   width of its own, so it shrink-wraps to the image's natural size instead of
   filling the widget box — and `.card__media img { width: 100% }` (alla.css)
   cannot rescue it, because the percentage resolves against the ANCHOR's own
   width, which is exactly the undefined thing in question. Classic circular
   percentage, confirmed by reading `getComputedStyle` on the live anchor, not
   assumed: `display: inline-block`, `width: 800px` (== the image's own served
   width, not 100% of anything).

   Scoped to `.card__media` specifically — the same class 8.18 already
   targets, carried by the Image widget on both places this card pattern is
   used, the projects index AND the home page's own project showcase
   (`.project-grid`, `elementor_pages.py::page_home`'s `cards`) — because both
   wrap their photo in the identical link. Deliberately NOT a bare
   `.elementor-widget-image a` selector: that also matches the header
   wordmark's own anchor (`.header__mark`), which is currently sized to its
   own content on purpose and would stretch to fill the whole header if this
   rule reached it. Verified before shipping: the wordmark's anchor stayed at
   its own image width, unchanged, on a page in each language. */
body .card__media.elementor-widget-image a {
  width: 100%;
}

/* -------------------------------------------- 10. The English home header's
   logo flashes at full opacity for ~500ms on load

   Pixun, 2026-08-30: "el logo aparece por medio segundo y desaparece...
   Creo que se puede solucionar solo haciendo con css que se cargue con
   opacidad 0" -- hard rule 13's own stated exception, explicitly granted
   here for CSS. This is HALF of that fix; the other half -- the same flash
   on the way back UP to scrollY 0, a different cause (a stray CSS
   transition, not a paint-before-JS race) -- lives in alla-wp.js, next to
   the rest of this header's scroll-linked writes, because neutralising a
   transition needs an inline `!important`, which only JS can write.

   THE CAUSE, confirmed live not assumed: Motion Effects (elementor-pro)
   drives this logo's opacity from a scroll band (0 below ~16.4% of the
   page, 1 above ~17.6% -- invisible while the transparent header sits over
   the hero) but only WRITES that inline `opacity: 0` once its own frontend
   JS has booted. Before that script runs, the browser paints the widget at
   its untouched default, opacity 1 -- Pixun's own read of the cause was
   correct.

   SCOPED to `.elementor-1206` -- Elementor's own wrapper class for THIS
   header document specifically (confirmed live on the rendered wrapper's
   own class list: `elementor elementor-1206 elementor-location-header`,
   not assumed from the generated per-page CSS alone) -- so 1512's logo,
   which carries no motion-fx settings at all (confirmed via
   probe-header-states.php: no `motion_fx_*` key exists on 1512's copy of
   this same widget) and never fades, is never touched by this rule.

   GATED on `html.js` (section 3's own gate, reused rather than re-solved):
   a visitor whose JS never arrives must still SEE the logo, never lose it
   permanently. Once real JS runs, Motion Effects' own inline write governs
   completely regardless of this rule -- a declared inline style always
   beats a plain (non-`!important`) external rule -- so this only ever
   governs the handful of frames before that script boots, at any scroll
   position, not only at the top of the page. */
html.js .elementor-1206 [data-id="3c6b501"] {
  opacity: 0;
}

/* ------------------------------------------------------------------ 11.
   MOBILE: THE HEADER STACKED THE LOGO AND THE TOOLS ON TWO LINES (Catalan,
   Spanish and English alike -- see the SCOPED paragraph below for when
   Catalan/Spanish joined).

   Pixun's report, 2026-08-30: "arregla el header en la version de movil, no
   se quedan en linea. Se estan montando uno encima del otro."

   MEASURED at a true 390px viewport (same-origin iframe -- `resize_window`
   still cannot give a real mobile viewport here, T8):

     available content width      333.2px  (375 minus 2 x 20.92 padding)
     logo #3c6b501                109.0px
     tools #cdd1ab4               333.2px  <- the whole row, on its own
       nav menu #f3ad40a          198.5px
       burger  #3abf17e            33.6px

   Two compounding causes, both confirmed by measurement, not inferred:

   1. `#cdd1ab4` is stretched to 100% at mobile. Its desktop `width: auto`
      never reaches this breakpoint because the container Width control has
      no working mobile level on this install -- already recorded in
      HANDOFF from the conversion round ("no tiene nivel movil funcional...
      probado con clamp() y con px fijos: no genera CSS"). Shrinking its
      CONTENTS changed nothing at all: measured 333.2px whatever the menu
      inside it did. The box has to be told to stop growing.

   2. Even content-sized it did not fit: each language link carries
      `padding: 13px 20px` -- 40px of horizontal padding around a
      two-letter label -- so three items cost 198.5px, of which 120px is
      padding. At 8px it costs 126.5px, and the row then sums to 323.5px
      against 333.2 available.

   WHY CSS AND NOT THE PANEL (hard rule 13, said out loud as the rule
   requires): cause 1's own control is the broken one, so there is no panel
   route to it -- that is the whole reason this rule exists. The other two
   declarations could live in the panel and SHOULD move there when the
   header is next opened for editing; they are here only to keep this fix
   in one reviewable place rather than split across two mechanisms.

   SCOPED to `html[lang="en-GB"]`, `html[lang="ca"]` and `html[lang="es-ES"]`
   together (widened 2026-08-30, ROUND 3, Pixun's own instruction -- "los
   efectos del header de la version en ingles han de estar en la version en
   español i la de catalan tambien"). The Catalan and Spanish headers (1149,
   1203, plus each language's own new default-header sibling) share these
   very same Elementor element ids -- confirmed LIVE this round by fetching
   both directly, not taken on an earlier report alone -- and by this round
   they are ALSO panel-native, same `min_height: 140`, same broken mobile
   Width control on this install, so the identical fix is needed for the
   identical cause, not merely permitted by shared markup. This paragraph
   used to end "...but they still lay their header out from alla.css's own
   `.header` rules and must not be touched" -- true when written, not true
   any more: a separate agent's panel-native conversion of the Catalan and
   Spanish headers is what makes this widening correct now. Each selector
   below repeats the full compound selector once per language rather than
   adding bare `html[lang="ca"], html[lang="es-ES"]` to the list -- a
   selector-list comma binds at the TOP level, so appending a bare
   `html[lang="ca"]` would match the whole `<html>` element itself, not
   `<html lang="ca">`'s own copy of `.elementor-element-cdd1ab4` -- reasoned
   through the CSS spec before writing a single line, not discovered by
   shipping it broken. */
@media (max-width: 767px) {
  html[lang="en-GB"] .elementor-element-cdd1ab4,
  html[lang="ca"] .elementor-element-cdd1ab4,
  html[lang="es-ES"] .elementor-element-cdd1ab4 {
    width: auto !important;
    flex: 0 0 auto !important;
  }
  html[lang="en-GB"] .elementor-element-4dfbe58,
  html[lang="ca"] .elementor-element-4dfbe58,
  html[lang="es-ES"] .elementor-element-4dfbe58 {
    align-items: center !important;
    /* 3. At 375 the row was still 3.77px short: content box 319.76 against
       323.53 needed (logo 109 + gap 24 + tools 190.53). Measured, not
       estimated -- 390 and 414 already fitted, 375 did not, and 375 is an
       ordinary iPhone width. Dropping the gap to 12px leaves 8.2px of
       slack there and costs nothing visually at this size. */
    gap: 12px !important;
  }
  html[lang="en-GB"] .elementor-element-f3ad40a .elementor-nav-menu--main a,
  html[lang="ca"] .elementor-element-f3ad40a .elementor-nav-menu--main a,
  html[lang="es-ES"] .elementor-element-f3ad40a .elementor-nav-menu--main a {
    padding-left: 8px !important;
    padding-right: 8px !important;
  }
}

/* 11b. NARROW PHONES (<= 374px). The block above fixes 375 and up; measured
   at 360 the row is still 6.53px short (content box 305 against 311.53
   needed) and 320 is short by much more. Rather than squeeze 375 -- where
   the design is fine -- these three go further only where they have to:
   tighter language links, a tighter gap between them and the burger, and a
   smaller wordmark. Verified at 320 and 360, both real widths (Galaxy S is
   360, iPhone SE 1st gen is 320). Measured on English only at the time --
   widened to Catalan/Spanish 2026-08-30 (ROUND 3) on the reasoning this
   file's own section 11 comment now gives; the language switcher shows the
   same three two-letter labels regardless of which language is current, so
   the content width driving every number above is identical across all
   three -- re-verified live at 320/360/375/390/414 across all three
   languages this round rather than assumed, see the final report for the
   actual readings. */
@media (max-width: 374px) {
  html[lang="en-GB"] .elementor-element-f3ad40a .elementor-nav-menu--main a,
  html[lang="ca"] .elementor-element-f3ad40a .elementor-nav-menu--main a,
  html[lang="es-ES"] .elementor-element-f3ad40a .elementor-nav-menu--main a {
    padding-left: 4px !important;
    padding-right: 4px !important;
  }
  html[lang="en-GB"] .elementor-element-cdd1ab4,
  html[lang="ca"] .elementor-element-cdd1ab4,
  html[lang="es-ES"] .elementor-element-cdd1ab4 {
    gap: 8px !important;
  }
  html[lang="en-GB"] [data-id="3c6b501"],
  html[lang="en-GB"] [data-id="3c6b501"] img,
  html[lang="ca"] [data-id="3c6b501"],
  html[lang="ca"] [data-id="3c6b501"] img,
  html[lang="es-ES"] [data-id="3c6b501"],
  html[lang="es-ES"] [data-id="3c6b501"] img {
    width: 80px !important;
  }
}

/* ------------------------------------------------------------------ 12.
   THE HERO'S BACKGROUND OVERLAY WAS PAINTING BEHIND THE PHOTOGRAPH.

   Pixun set a Background Overlay gradient on the project template's hero
   container (#8f1a2c3, template 1534) — transparent to #000 from 69%, at
   0.49 opacity — to make the hero title readable over bright photographs.
   It saved correctly and Elementor generated it correctly; it was simply
   invisible.

   MEASURED live on /en/projects/jrc-habitatge/:

     ::before (the overlay)   position: absolute   z-index: auto
     .project-hero__media     position: absolute   z-index: auto

   Both positioned, both `z-index: auto`, so painting order falls back to DOM
   order — and the media is a real element that comes AFTER the generated
   ::before. The photograph covered the gradient completely.

   WHY THE PANEL CANNOT FIX THIS, which is the part worth remembering:
   Elementor's Background Overlay is designed to sit above the container's
   own BACKGROUND and below its CHILDREN. Here the photograph is not the
   container's background — it is a child Image widget, because that is what
   the parallax binds to (`js-parallax` -> `data-parallax`). So the overlay
   is structurally below it and no panel control can lift it.

   The alternative was moving the photo to the container's background via the
   Featured Image dynamic tag, which would have made the overlay work with no
   CSS at all — rejected because it would break the parallax, which is a
   worse trade than one rule here.

   Scoped to `.project-hero` so it cannot affect the homepage hero, which has
   its own slideshow and no overlay. Only the two z-indexes are stated: the
   gradient's colour, its 69% stop and its 0.49 opacity all stay in the
   panel, Pixun's to change. */
.project-hero > .project-hero__media {
  z-index: 0;
}
.project-hero::before {
  z-index: 1;
}
