/* Alza Estudio brand fonts (Dax Pro Condensed), per deseño/WEB_alza/Alza_WEB_info.pdf */
@font-face {
  font-family: "Dax Pro Condensed";
  src: url("../fonts/dax-pro-condensed-light.0c763f726768.woff2") format("woff2");
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Dax Pro Condensed";
  src: url("../fonts/dax-pro-condensed-regular.ba9dbe2df0f4.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Dax Pro Condensed";
  src: url("../fonts/dax-pro-condensed-regular-italic.1e9db2c2abe3.woff2") format("woff2");
  font-weight: 400;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Dax Pro Condensed";
  src: url("../fonts/dax-pro-condensed-bold.c242f7b191fa.woff2") format("woff2");
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Pixel-measured directly off a 1920px-wide render of 1_Alza.pdf (the
     earlier 108/124px values came from a ~1450px-wide export of the same
     file, extrapolated -- this is the real number at the project's own
     1920px reference, no rescaling guesswork). */
  --navbar-height: 153px;

  /* Alza Estudio brand palette, per deseño/WEB_alza/Alza_WEB_info.pdf */
  /* Reverted the earlier "deepened" #3f4e5a back to the original Pantone
     431 C: sampled this background (navbar + every section, excluding the
     footer's own lighter strip) across all five design PNGs/PDFs and it
     is the same #5c6771 (92,103,113) everywhere, within 1 unit of
     rounding noise -- not the darker, more saturated value that had been
     substituted in. */
  --alza-dark: #5c6771;
  --alza-light: #7c878e; /* 2. Suplementario - Gris clarito (Pantone 430 C) */
  /* 3. Suplementario - Sand (Pantone 465 C). Slightly warmer than the
     original #beac83 per the 2026 redesign -- same role everywhere it was
     already used (titles, links, accents), just a touch richer. */
  --alza-sand: #bcac88;
  --alza-sand-deep: #9a8d70; /* hover/active shade of the sand above, ~18% darker */

  --bs-font-sans-serif: "Dax Pro Condensed", "Archivo", sans-serif;
  /* Display face for headings only (h1-h6, nav wordmark) -- body copy
     stays on the brand sans above. Loaded from Google Fonts in base.html;
     falls back to the brand sans rather than a generic one so a slow/
     blocked font load still reads as "this site", not as unstyled text. */
  --font-display: "Oswald", "Dax Pro Condensed", "Archivo", sans-serif;
  /* Confirmed against the design PDFs (not Oswald, which had never
     actually been checked against them) -- section h2 only for now. */
  --font-title: "Yanone Kaffeesatz", "Oswald", "Dax Pro Condensed", "Archivo", sans-serif;

  /* Fluid type scale for the elements with their own visual identity
     (wordmark, section titles, hero headline). "1920 is the reference
     width" means sizes are pinned/maxed out by lg (992px) and hold flat
     from there up through 1920 and beyond, per this project's own rule
     that Bootstrap's grid (not a second fluid system) carries everything
     below lg. The vw-coefficient is chosen so each clamp hits its max
     exactly at 992px (max_px / 9.92), not at 1920px.

     A pixel cap-height measurement off the mockup, converted to font-size
     via an assumed cap-height/em ratio, overshot badly across the board --
     the ratio assumption does not hold for this condensed display face at
     this letter-spacing. All four of these are confirmed by rendering the
     real homepage (Django template, real content) with Playwright at
     1920px and comparing the screenshot directly against the mockup PNGs,
     not derived from a cap-height formula. --font-hero turned out to
     already be right before that formula pass touched them; only the
     wordmark pair actually needed to come down.

     --font-section-title's max was re-measured again switching the face
     to Yanone Kaffeesatz (see --font-title above): that face has a much
     shorter cap-height per em than Oswald, so matching the design's
     actual "Servicios" ink box (149x29px at this project's 1920
     reference, scaled down) needs a noticeably bigger font-size (51.78px/
     3.236rem, not 44px/2.75rem) -- found by rendering the same word
     across a font-size x letter-spacing grid and matching both
     dimensions at once. */
  --font-wordmark-lg: clamp(1rem, 6.92vw, 4.29rem);
  --font-wordmark-sm: clamp(0.6rem, 4.77vw, 2.96rem);
  --font-section-title: clamp(1.5rem, 5.22vw, 3.236rem);
  --font-hero: clamp(1.75rem, 7.258vw, 4.5rem);

  --bs-primary: var(--alza-sand);
  --bs-primary-rgb: 188, 172, 136;
  --bs-secondary: var(--alza-light);
  --bs-secondary-rgb: 124, 135, 142;
  --bs-dark: var(--alza-dark);
  --bs-dark-rgb: 92, 103, 113;
  --bs-link-color: var(--alza-dark);
  --bs-link-color-rgb: 92, 103, 113;
  --bs-link-hover-color: var(--alza-sand);
  --bs-link-hover-color-rgb: 188, 172, 136;
}

/* Compensates for the fixed navbar removing itself from normal document
   flow, per Bootstrap's own documented recipe for .fixed-top. */
body {
  padding-top: var(--navbar-height);
  font-weight: 700;
}

/* Every single-page section is <section id="{{ anchor }}">, and every nav
   link/locale-switcher entry points straight at one of those ids -- without
   this, jumping to #proxectos (click or direct link) lands the section's
   own top edge flush with the viewport top, which is exactly where the
   fixed navbar sits, so its heading renders hidden underneath it.
   scroll-margin-top pushes the landing spot down by the navbar's own
   height; browsers honour it on both a plain #hash navigation and
   Element.scrollIntoView(), so no JS scroll offset is needed. */
section[id] {
  scroll-margin-top: var(--navbar-height);
}

.navbar.fixed-top {
  min-height: var(--navbar-height);
}

/* The bug behind "the menu isn't touching the bottom of the navbar":
   .navbar's align-items:center centres its child .container-fluid INSIDE
   the taller min-height navbar, but .container-fluid's own height was
   only ever its content's natural height (~100-124px) -- never the full
   153px. Everything pinned absolute (toggler, nav-collapse) is positioned
   against *this* box, not <nav> itself, so "bottom: 0.1rem" landed 0.1rem
   off a box that already stopped well short of the navbar's real bottom
   edge. Giving .container-fluid the same min-height makes it (and
   therefore the pinned children's coordinate space) actually span the
   full navbar. */
/* calc(...) - 1rem, not the full --navbar-height: Bootstrap's own .navbar
   padding (0.5rem top + 0.5rem bottom = 1rem) sits *outside* this box, on
   <nav> itself. Setting this to the full --navbar-height stacked that 1rem
   on top of it, so <nav> rendered 1rem taller than the mockup's measured
   153px (confirmed: 170px instead of 153px). This way the padding plus
   this box add back up to exactly --navbar-height. */
.navbar.fixed-top > .container-fluid {
  min-height: calc(var(--navbar-height) - 1rem);
}

/* Toggler and nav-collapse pinned to the right edge of the navbar with
   plain position:absolute, not flex ordering + margin-auto -- a fixed
   coordinate relative to .container-fluid.position-relative, so it cannot
   lose to some other rule's specificity/!important on the flex row.
   right:3.3rem (~53px at 1920) is pixel-measured directly -- "ES" and the
   hamburger both end there, well past the small default container-fluid
   gutter (0.75rem) that right:0 would have given them. */
.navbar-toggler-pin {
  position: absolute;
  top: 50%;
  right: 3.3rem;
  transform: translateY(-50%);
}

/* Only pinned from lg up, where the collapse is an inline element sitting
   beside the logo. Below lg it must stay a normal block (Bootstrap's own
   full-width mobile dropdown) -- position:absolute there would pull it out
   of flow and break that panel entirely. */
@media (min-width: 992px) {
  .navbar-collapse-pin {
    position: absolute;
    right: 1rem;
    bottom: -1.1rem;
  }
}

/* Centered "alza / estudio" wordmark, matching Alza_boceto.pdf -- true
   centering on the navbar regardless of how wide the logo/nav-links either
   side are (flexbox space-between alone would only center it when both
   sides happen to be equal width). Requires .navbar .container to be
   position:relative (set inline in base.html). */
/* Pixel-measured off the mockup: "alza"+"estudio" together are NOT
   vertically centred in the navbar -- "alza" sits close to the top edge
   and "estudio"'s own baseline lands almost exactly on the navbar's
   vertical midpoint (the logo, separately, IS centred -- these are two
   different anchoring rules coexisting). top:0.2rem with no Y-transform
   reproduces that; only X stays centred via translateX. */
.navbar-wordmark {
  /* Values confirmed directly against the live page. text-align: left
     (not center) is what keeps "alza" and "estudio" sharing a left edge
     ("a" above "e") -- centered, each line was centring independently
     within whatever width the other line's content forced the block to,
     so the shorter line drifted sideways relative to the longer one. */
  position: absolute;
  top: -1.1rem;
  left: 50%;
  transform: translateX(-50%);
  text-align: left;
  line-height: 0.8;
  color: #fff;
  white-space: nowrap;
}
.navbar-wordmark-main,
.navbar-wordmark-sub {
  /* Dax Pro Condensed, the brand's own body font (--bs-font-sans-serif),
     not --font-display (Oswald) -- confirmed by the client as the real
     header typeface. Letter-spacing below is an absolute gap measured
     off the design, not tied to any particular typeface's own glyph
     widths, so it holds unchanged across this swap. */
  font-family: var(--bs-font-sans-serif);
  display: block;
}
.navbar-wordmark-main {
  font-weight: 700;
  font-size: var(--font-wordmark-lg);
  /* 86px, not the 82px pixel-measured off 1_Alza.pdf: that figure was
     measured against Oswald, and switching the wordmark to Dax Pro
     Condensed changed "alza"'s own glyph widths enough that 82px left
     its ink 13px short of "estudio"'s own ink extent below it (345px vs
     358px, confirmed on the live render) -- a visible gap under "alza"'s
     own right edge that the client flagged. 86px closes that gap
     (357px, within 1px of "estudio"'s 358px) by re-solving the same way
     the letter-spacing itself was originally arrived at: render, measure
     the live ink extent, adjust, repeat -- not a formula. */
  letter-spacing: 86px;
  /* CSS letter-spacing adds a gap AFTER every character, including the
     last one -- that trailing 86px is invisible (nothing follows it)
     but still counts toward this element's own rendered width, which
     is what .navbar-wordmark's left:50%/translateX(-50%) centers on.
     Left in, the wordmark's actual *ink* ("alza"'s and "estudio"'s
     visible glyphs, confirmed matched in width above) sat ~46px left
     of true centre -- confirmed on the live render, and visibly
     off-centre to the eye. Cancelling it with an equal negative
     margin-right removes it from that width calculation entirely,
     without affecting the letter-spacing between the actual
     characters. */
  margin-right: -86px;
}
.navbar-wordmark-sub {
  font-weight: 300;
  font-size: var(--font-wordmark-sm);
  /* Confirmed directly against the live page. */
  letter-spacing: 40px;
  /* Same trailing-letter-spacing correction as .navbar-wordmark-main
     above, with this line's own letter-spacing value. */
  margin-right: -40px;
}

/* Gold line under the fixed navbar, 2026 redesign. */
.navbar.fixed-top {
  border-bottom: 1px solid var(--alza-sand);
}


/* Headings carry the display face; body copy (p, rich text, labels...)
   stays on --bs-font-sans-serif. h5/h6 included because several titles
   that read visually as "h3" (project card title, service card title,
   team member name) are styled with Bootstrap's .h5 utility class but are
   still real h3 elements underneath -- a type selector catches them
   without having to chase every utility-classed heading individually. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
}

/* Each section's own <h2> (Estudo, Proxectos, Servizos, Equipo,
   Contacto...) rides the same fluid scale as the hero/wordmark rather
   than Bootstrap's fixed h2 size, so it keeps proportion with them at any
   viewport width down from the 1920px reference. Gold, matching the
   mockups -- it was inheriting plain white from .section-bg-dark. */
/* font-family + letter-spacing confirmed+measured against 3_Servicios.png
   at native PDF resolution (not the 1920px rescale, to rule out resampling
   artifacts): Yanone Kaffeesatz 700 at this font-size needs 0.16em of
   tracking to match the design's own ink width exactly (150x29px
   rendered vs. 149x29px measured) -- plain Yanone with no letter-spacing
   reads noticeably tighter than the design. */
section h2 {
  font-size: var(--font-section-title);
  font-family: var(--font-title);
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--alza-sand);
}

/* Pixel-measured off 2_Proyectos.pdf at 1920px: the intro copy's cap-height
   is 24px, which this hits exactly -- plain Bootstrap body text (1rem)
   was rendering it at roughly two thirds that size. */
.section-intro {
  font-size: 2.25rem;
}

/* Pixel-measured off 5_Contacto.png at 1920px: the agent names/address/
   phone list's cap-height is 22px, same as .service-text's own confirmed
   1.875rem -- it had no font-size rule of its own and was falling back to
   plain Bootstrap body text (1rem). */
.contact-info-list {
  font-size: 1.875rem;
}

/* "Datos de contacto", off the same 5_Contacto.png: same size as the list
   above, but set regular so the bold names stand out from their
   registration numbers ("María Rodríguez GA-264") -- at body's own 700 the
   two would look the same. Names in sand, the rest (address, phones) in
   the section's own text colour, phone/email links included, as in the
   mockup; a blank line's worth of space between pieces. */
.contact-details {
  font-size: 1.875rem;
  font-weight: 400;
  line-height: 1.2;
}
.contact-details strong,
.contact-details b {
  font-weight: 700;
}
.contact-details p {
  margin-bottom: 1.2em;
}
.contact-details > :last-child,
.contact-details > :last-child > p:last-child {
  margin-bottom: 0;
}
.contact-details-names {
  margin-bottom: 1.2em;
  color: var(--alza-sand);
}
.contact-details-names p {
  margin-bottom: 0;
}
.contact-details a:not(.btn) {
  color: inherit;
  text-decoration: none;
}

/* Pixel-measured off 3_Servicios.pdf at 1920px: "Comercial"'s cap-height is
   24px and the description's is 20px -- both were plain Bootstrap defaults
   (h5 at 1.25rem, body text at 1rem) and the title was also inheriting
   white from .section-bg-dark instead of the brand gold. */
.service-text h3 {
  color: var(--alza-sand);
  font-size: 1.9rem;
}
/* Side padding pixel-measured off 3_Servicios.pdf: noticeably more than
   the px-4 (1.5rem, fixed at every width) the template had -- matches the
   same breakpoint scale already used for .title-intro-row, not a fixed
   utility value. Weight inherits from body's own 700. */
.service-text {
  font-size: 1.875rem;
  padding: 1.5rem;
}
@media (min-width: 768px) {
  .service-text {
    padding-left: 3rem;
    padding-right: 3rem;
  }
}
@media (min-width: 1200px) {
  .service-text {
    padding-left: 5rem;
    padding-right: 5rem;
  }
}

/* Hero heading: white, like the rest of the hero text (.hero carries text-white) */

/* Extra breathing room above/below the hero photo itself. The photo wrapper
   is position:absolute + inset:0 (Bootstrap's top-0/start-0/w-100/h-100),
   so percentage/offset values resolve against the section's padding box --
   padding on .hero alone would NOT create a visible gap here. Insetting the
   wrapper itself is what actually shows as space above/below the image;
   the section needs its own background so that gap isn't just white. */
.hero {
  background-color: var(--alza-dark);
  /* Clips .hero-decor below to the section's own box instead of spilling
     into whatever comes after it in the scroll. */
  overflow: hidden;
}
.hero > .position-absolute {
  top: 3rem;
  bottom: 5rem;
  height: auto;
}

/* Hero headline: fluid size matching the wordmark/section-title scale,
   and the brand gold rather than plain white -- it is the one headline on
   the page sitting directly on a photo/slider/video rather than a flat
   section background, so it is allowed to stand out more than the h2s. */
.hero h1 {
  font-size: var(--font-hero);
  letter-spacing: 0.03em;
  color: var(--alza-sand);
}

/* Decorative corner shape, independent of whatever media (photo, slider
   or video) is behind it -- a plain circle rather than an SVG arc, clipped
   to a quarter by the section's own overflow:hidden above and its own
   position half outside the bottom-left corner. z-index sits with the
   media layer (0) and below the text layer (1, see .hero-content below),
   and after the media wrapper in source order so it paints on top of the
   photo/slider but still under the heading. */
/* Pixel-measured off 1_Alza.pdf: only a small sliver of this circle shows
   in the corner (~90px of a 1450px-wide frame, ~6%), not the large
   quarter-circle this used to be (288px, ~20%). */
/* Re-measured against the correctly-sized 1920px render (the previous
   8rem came from the earlier un-rescaled ~1450px export). */
/* Confirmed by rendering the real homepage with Playwright at 1920px and
   comparing the screenshot directly against 1_Alza.pdf: 11rem read as a
   large, prominent shape next to the mockup's subtle corner sliver. */
.hero-decor {
  position: absolute;
  left: -2rem;
  bottom: -2rem;
  width: 6rem;
  height: 6rem;
  border-radius: 50%;
  background-color: #fff;
  pointer-events: none;
}

/* Subtle brightness/saturation lift on the hero photo itself (not an
   overlay -- that was the flat 50%-black filter that got removed). */
.hero img,
.hero video {
  filter: brightness(1.08) saturate(1.12);
}

/* Nav links: white by default, sand on hover/focus/current section,
   matching Alza_boceto.pdf ("Sobre nós" shown in the sand accent). */
/* Pixel-measured off 1_Alza.pdf: "Proyectos Servicios Equipo Contacto"
   is set noticeably larger and bolder than Bootstrap's own 1rem default
   nav-link size -- close to 1.5rem. Dax Pro Condensed (--bs-font-sans-
   serif), not --font-display (Oswald) -- confirmed by the client as the
   real header typeface, same as the wordmark above. Only 300/400/700
   are self-hosted (see @font-face at the top of this file), so 700
   (bold) replaces the old 600 -- there is no 600 weight to fall back
   to. */
.navbar-nav .nav-link {
  color: #fff;
  font-family: var(--bs-font-sans-serif);
  font-size: 1.5rem;
  font-weight: 700;
}
.navbar-nav .nav-link:hover,
.navbar-nav .nav-link:focus,
.navbar-nav .nav-link.active {
  color: var(--alza-sand);
}

/* Bootstrap's compiled CSS hardcodes its default blue on most components
   (buttons, form focus rings, checked checkboxes) via component-scoped
   variables (e.g. --bs-btn-bg) rather than reading the root --bs-primary/
   --bs-dark overrides above -- those only reach utility classes like
   .bg-dark/.text-primary. Re-pointing each affected component here. */
/* 2026 redesign: buttons/CTAs move to the gold accent. Text set explicitly
   to the brand dark rather than left at Bootstrap's compiled default
   (white) -- white-on-gold falls well under WCAG's 4.5:1 text contrast,
   where dark-on-gold lands at the same ~3.9:1 this site already ships for
   sand-on-dark elsewhere (nav links, project titles). */
.btn-primary {
  --bs-btn-bg: var(--alza-sand);
  --bs-btn-border-color: var(--alza-sand);
  --bs-btn-color: var(--alza-dark);
  --bs-btn-hover-bg: var(--alza-sand-deep);
  --bs-btn-hover-border-color: var(--alza-sand-deep);
  --bs-btn-hover-color: var(--alza-dark);
  --bs-btn-active-bg: var(--alza-sand-deep);
  --bs-btn-active-border-color: var(--alza-sand-deep);
  --bs-btn-active-color: var(--alza-dark);
  --bs-btn-focus-shadow-rgb: 188, 172, 136;
}

:root {
  --bs-focus-ring-color: rgba(92, 103, 113, 0.25);
}

.form-check-input:checked {
  background-color: var(--alza-dark);
  border-color: var(--alza-dark);
}

/* Undoes a distortion caused by Bootstrap's own card rule. The {% picture %}
   tag emits width/height attributes on the <img> (deliberately -- they let the
   browser reserve the right box and avoid layout shift). Those attributes act
   as CSS presentational hints. Bootstrap's

       .card-img, .card-img-bottom, .card-img-top { width: 100%; }

   overrides the width hint but leaves the height one alone, so the image ends
   up at the column's width with the rendition's *pixel* height -- squashed
   horizontally. Setting height:auto drops that hint and lets the browser
   derive the height from the intrinsic aspect ratio again. */
.card-img,
.card-img-top,
.card-img-bottom {
  height: auto;
}

/* Now that the project cards are keyboard-reachable (tabindex in
   projects_block.html), the focus has to be visible -- a focus ring you
   cannot see is the same as no focus at all for the person relying on it.
   :focus-visible, not :focus, so it never shows on a mouse click. */
.project-card:focus-visible {
  outline: 3px solid var(--alza-sand);
  outline-offset: 3px;
}

/* Footer band, 2026 redesign -- the mockups show it as a visibly lighter
   strip than the dark sections above it, not the same --alza-dark. White
   text on this lighter grey follows the same pairing already accepted for
   .about-highlights below. */
.site-footer {
  background-color: var(--alza-light);
}

/* "Sobre nós" / "Estudo" section. The highlighted quotes keep their
   Suplementario-gray band with white text -- that pairing already follows
   the site's own rule (white text on a grey background). */
.about-highlights {
  background-color: var(--alza-light);
  color: #fff;
}

/* ---------------------------------------------------------------------- *
 * Per-section background: every section built on AnchoredStructBlock
 * (Proxectos, Estudo, Servizos, Equipo, Contacto, Galería -- not Hero,
 * which always has its own photo/video behind the text) picks one of these
 * three from an editor field. "dark" is the default, so every section
 * reads as this same grey unless someone chooses otherwise -- matching the
 * navbar's own bg-dark exactly, per the client's earlier request for
 * Proxectos, now the shared default everywhere. Cards (Bootstrap's own
 * white .card) are left alone on purpose: a light card floating on a dark
 * section is the intended look, not a bug to fix.
 * ---------------------------------------------------------------------- */
.section-bg-dark {
  background-color: var(--alza-dark);
  color: #fff;
}
.section-bg-light {
  background-color: #fff;
  color: var(--alza-dark);
}
.section-bg-sand {
  background-color: var(--alza-sand);
  color: var(--alza-dark);
}

/* Bootstrap's .text-muted assumes a light background (it is a dark grey);
   on a dark or sand section that reads as almost invisible, so it is
   repointed to a translucent white instead. !important matches the
   specificity of Bootstrap's own .text-muted rule, which also carries one. */
.section-bg-dark .text-muted {
  color: rgba(255, 255, 255, 0.65) !important;
}

/* Links inherit the browser/Bootstrap blue by default, which all but
   disappears on this grey -- repointed to the brand's sand accent, the
   same colour the navbar and hero already use for exactly this reason. */
.section-bg-dark a:not(.btn),
.section-bg-sand a:not(.btn) {
  color: var(--alza-sand);
}
.section-bg-dark a:not(.btn):hover,
.section-bg-dark a:not(.btn):focus,
.section-bg-sand a:not(.btn):hover,
.section-bg-sand a:not(.btn):focus {
  color: var(--alza-sand);
  text-decoration-color: currentColor;
}


/* Proxectos/Servizos title+intro row, 2026 redesign: full-bleed (see
   title-intro-row in the two templates), its own padding standing in
   for the .container/.container-fluid it used to sit inside -- same
   responsive 1.5rem/3rem/5rem scale already used for .projects's own
   former container-fluid padding, kept here for visual continuity. */
.title-intro-row {
  /* Neutralizes Bootstrap's own .row class, which this keeps only for
     its row-cols/gutter-var plumbing elsewhere on the site -- its
     default -0.75rem negative margins assume a padded .container
     cancelling them back out. With no container here, they were
     pulling the row's own box 12px past each edge of the viewport,
     throwing off the calc(50% - 219px) wordmark alignment below (which
     needs this box to be exactly 0 to 100% of the viewport). */
  margin-left: 0;
  margin-right: 0;
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}
@media (min-width: 768px) {
  .title-intro-row {
    padding-left: 3rem;
    padding-right: 3rem;
  }
}

/* The INTRO paragraph's own left edge (not the title's) is pinned to
   match the navbar wordmark's left edge above it ("alza"/"estudio" both
   start there) -- confirmed against the design PDFs too: "Nuestro
   objetivo..."/"Si los que buscas..." starts at x=794-799 there, within
   a few px of the wordmark's own x=789-795, while the title sits to its
   left ending with a gutter before that point. The title keeps its
   original right-aligned-within-col-lg-5 position; only the intro is
   repinned here.

   grid, not flex: with the intro's position fixed independent of the
   title's own rendered width, a flex item's margin-left would stack on
   top of the title item's width instead of resolving to an absolute
   viewport position. A grid column's WIDTH can be that absolute value
   instead -- column 1 (calc(50vw - 219px - 2rem)) holds the title,
   column 2 (2rem) is a plain gutter, and column 3 (1fr, i.e. "whatever
   is left") holds the intro, which therefore always starts exactly at
   the sum of columns 1+2 = 50vw - 219px, regardless of the title's own
   text length.

   calc(50vw - 219px): the wordmark centers itself on the full viewport
   width via left:50%/transform:translateX(-50%) (see .navbar-wordmark);
   219px is half its own rendered width (438px, confirmed via the live
   render), fixed at this value from the lg breakpoint up because its
   clamp()'d font-size is too. vw, not %: a %-based column track resolves
   against the grid's own content-box width, which this row's
   padding-right (5rem at xl) makes narrower than, and off-centre
   within, the true viewport (confirmed: 50% measured 40px short of the
   wordmark on the live render) -- vw is anchored to the viewport
   itself, independent of any ancestor's padding, same as the wordmark's
   own left:50% implicitly is (its own container-fluid carries no side
   padding, so it never hit this). */
@media (min-width: 992px) {
  .title-intro-row {
    display: grid;
    grid-template-columns: calc(50vw - 219px - 2rem) 2rem 1fr;
    padding-left: 0;
  }
  /* Bootstrap's own ".row > *" rule (its grid gutter mechanism) puts
     0.75rem of padding on every direct row child, Bootstrap grid column
     or not -- left unset, that padding pushed the intro's visible text
     12px right of x=741 (the grid column's own, exact, edge), reading
     as a stray gap rather than a flush start. Zeroed on both columns so
     each one's text sits exactly on its grid track's boundary. */
  .title-intro-row .col-lg-5 {
    grid-column: 1;
    width: auto;
    padding-left: 0;
    padding-right: 0;
    text-align: right;
  }
  .title-intro-row .col-lg-7 {
    grid-column: 3;
    width: auto;
    /* calc(50vw + 117.41px), not a flat rem value: a fixed width only
       ever matched "Contacto"'s own right edge at the one viewport
       width it was measured at (1920px) -- confirmed broken at every
       other width (off by 21.6px at 1600/1440/1366px, by a full 320px
       at 2560px), because the two edges this spans don't scale at the
       same rate. "Contacto"'s own right edge tracks the viewport at a
       flat 100vw - 101.59px (confirmed constant across 1200-2560px);
       this element's left edge (see .col-lg-5's calc(50vw - 219px)
       sibling column above) tracks at 50vw - 219px. The gap between
       them -- this max-width -- is therefore (100vw - 101.59px) -
       (50vw - 219px) = 50vw + 117.41px, confirmed to land exactly on
       "Contacto" at every width tested (1200-2560px), not just one. */
    max-width: calc(50vw + 117.41px);
    padding-left: 0;
    padding-right: 0;
  }
}

@media (min-width: 1200px) {
  .title-intro-row {
    padding-right: 5rem;
  }
}

section .mb-4.text-center {
  max-width: 65ch;
  margin-left: auto;
  margin-right: auto;
}

/* Language switcher styled as plain italic sand text, matching
   Alza_boceto.pdf ("GL"), not a bordered button -- min-width/height keeps
   a comfortable touch target (WCAG 2.5.5) despite the minimal look. */
/* Pixel-measured off 1_Alza.pdf: "ES" is 19px tall against "Contacto"'s own
   20px cap-height -- essentially the same size as the nav links, not a
   small badge. font-size here matches .navbar-nav .nav-link closely
   (1.5rem) rather than the browser default it was quietly falling back
   to. min-width/height still keeps the WCAG 2.5.5 touch target. */
/* "ES" was sitting noticeably higher than "Contacto" -- both start at the
   same top, but the button's own min-height (44px, kept for the WCAG
   touch target) makes it taller than the plain nav-link text next to it,
   so their bottoms fell apart. align-self: flex-end lines this one flex
   item's bottom edge up with the row's, matching the rest of the menu. */
.nav-item-lang {
  align-self: flex-end;
}
/* Values confirmed directly against the live page. */
@media (min-width: 992px) {
  .lang-switcher {
    margin-left: 1rem;
    margin-right: 1.6rem;
  }
}
.lang-switcher-toggle {
  min-width: 44px;
  min-height: 44px;
  font-size: 1.4rem;
  color: var(--alza-sand);
  font-style: italic;
  font-weight: 700;
  background: none;
  border: none;
}
.lang-switcher-toggle:hover,
.lang-switcher-toggle:focus,
.lang-switcher-toggle:active {
  color: var(--alza-sand);
}
.lang-switcher-toggle::after {
  display: none; /* hide the Bootstrap dropdown caret */
}

/* ---------------------------------------------------------------------- *
 * Project cards: the title sits ON the cover photo, centred, and the
 * description is no longer printed here at all -- it moved to the first
 * slide of the project's own gallery (see .project-intro-* below).
 * ---------------------------------------------------------------------- */

/* The card is now nothing but the photo, so it must not add a white
   border/background of its own around it, and it has to establish a
   containing block for the absolutely-positioned overlay. Square corners
   (no border-radius) per the client's request -- .card-img-top below
   inherits this same 0 rather than needing its own override. */
.project-card {
  position: relative;
  overflow: hidden;
  background-color: var(--alza-dark);
  border-radius: 0;
}

.project-card .card-img-top {
  border-radius: inherit;
  transition: transform 0.4s ease;
}

/* A slow push-in on hover/focus, so a card that opens something reads as
   interactive. The .project-card-overlay tint below is what keeps the
   title readable, so the photo itself is not darkened separately -- doing
   both stacked would read as muddy. :active covers touch, same as the
   title fade below. */
.project-card:hover .card-img-top,
.project-card:focus-visible .card-img-top,
.project-card:active .card-img-top {
  transform: scale(1.04);
}

@media (prefers-reduced-motion: reduce) {
  .project-card .card-img-top {
    transition: none;
  }
  .project-card:hover .card-img-top,
  .project-card:focus-visible .card-img-top,
  .project-card:active .card-img-top {
    transform: none;
  }
}

/* Covers the whole photo, same treatment as .project-intro-overlay on the
   obra's own text slide (same brand-dark tint at the same opacity) --
   the client asked for this to shade the full image, not just a box behind
   the words, matching that existing pattern.

   Hidden by default and faded in on hover/focus, per the client's request:
   the card is meant to read as just the photo until you interact with it.
   :focus-visible covers keyboard use; :active is the best a touchscreen can
   do without JS, since touch has no real hover -- a tap shows the title
   for as long as the finger is down, which is a reasonable approximation
   but not a perfect equivalent (acknowledged as a known trade-off). */
.project-card-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1rem;
  background-color: rgba(92, 103, 113, 0.72);
  opacity: 0;
  transition: opacity 0.25s ease;
}

.project-card:hover .project-card-overlay,
.project-card:focus-visible .project-card-overlay,
.project-card:active .project-card-overlay {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .project-card-overlay {
    transition: none;
  }
}

.project-card-title {
  color: #fff;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* ---------------------------------------------------------------------- *
 * The first slide of a project's gallery: cover photo, project text on top.
 * ---------------------------------------------------------------------- */
.project-intro-slide {
  position: relative;
}

.project-intro-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem 3rem; /* clear of the carousel's prev/next controls */
  color: #fff;
  background-color: rgba(92, 103, 113, 0.72);
}

/* A long description must not overflow off the bottom of the photo, so the
   text itself scrolls inside the slide rather than the slide growing. The
   block stays centred in the slide. */
.project-intro-inner {
  max-height: 100%;
  overflow-y: auto;
  max-width: 60ch;
  /* Room between the text and the scrollbar. Without it the text runs
     straight into the bar, which reads as a layout error rather than as a
     scrollable area. */
  padding-right: 1.25rem;
  /* Reserve the bar's width whether or not it is showing, so a description
     long enough to scroll is not set to a narrower measure than a short
     one -- otherwise the text reflows the instant the overlay is drawn. */
  scrollbar-gutter: stable;
  /* The default bar is a light-on-light smear over the photo. */
  scrollbar-width: thin;
  scrollbar-color: var(--alza-sand) rgba(255, 255, 255, 0.15);
}

/* The same treatment for WebKit/Blink, which ignores scrollbar-color. */
.project-intro-inner::-webkit-scrollbar {
  width: 8px;
}

.project-intro-inner::-webkit-scrollbar-track {
  background-color: rgba(255, 255, 255, 0.15);
  border-radius: 4px;
}

.project-intro-inner::-webkit-scrollbar-thumb {
  background-color: var(--alza-sand);
  border-radius: 4px;
}

.project-intro-text :last-child {
  margin-bottom: 0;
}

.project-intro-text a {
  color: var(--alza-sand);
}

/* ---------------------------------------------------------------------- *
 * Hero slider
 * ---------------------------------------------------------------------- */

/* .carousel-item is display:block, which on its own would collapse to the
   image's height instead of filling the inset photo wrapper the hero uses. */
.hero .carousel,
.hero .carousel-inner,
.hero .carousel-item {
  height: 100%;
}

.hero-slide-caption {
  position: absolute;
  right: 15%;
  bottom: 1.5rem;
  left: 15%;
  padding: 0.5rem 1rem;
  color: #fff;
  text-align: center;
  background-color: rgba(92, 103, 113, 0.6);
  border-radius: 0.25rem;
}

/* The indicators otherwise sit under the photo wrapper's 5rem bottom inset,
   which puts them off the visible part of the slide. */
.hero .carousel-indicators {
  margin-bottom: 0.75rem;
}

/* Editor-set transition speed ("Duración do cambio"), carried in --slide-speed
   by _slider_attrs.html. Bootstrap hard-codes 0.6s: the slide's own
   transition, plus -- for fade -- a matching delay on the outgoing slide so it
   only drops out once the incoming one has fully covered it; both must move
   together or the fade flashes the background. Bootstrap's JS reads the
   computed duration back to know when a slide has finished, so overriding it
   here keeps the two in step. Inside no-preference so Bootstrap's own
   reduced-motion "transition: none" still wins for those who ask for it. */
@media (prefers-reduced-motion: no-preference) {
  .carousel .carousel-item {
    transition-duration: var(--slide-speed, 0.6s);
  }
  .carousel-fade .active.carousel-item-start,
  .carousel-fade .active.carousel-item-end {
    transition-delay: var(--slide-speed, 0.6s);
  }
}

/* "Só durante o cambio de imaxe": hidden from the first paint, straight off
   the data attribute -- hiding it from JS let it flash up on load and then
   fade away. The template only emits the attribute for a slider of two
   photos or more, and puts it back in a <noscript>, so the text is never
   hidden with nothing to bring it back. config.js adds .veil-on --veil-lead seconds before each photo change and
   removes it once the change is over, so the light veil over the photos and
   the text rise together over --veil-lead, stay full through the change and
   fall together over --veil-lead after it. One transition for both is what
   keeps them in step. */
.hero[data-hero-text="transition"] > .hero-content,
.hero-veil {
  opacity: 0;
  transition: opacity var(--veil-lead, 1s) ease-in-out;
}
.hero.veil-on > .hero-content,
.hero.veil-on .hero-veil {
  opacity: 1;
}
/* Inside the media wrapper's own stacking context, over the slides (the
   active fade slide is z-index 1; the veil comes later in the source, so it
   wins the tie) but under the arrows and dots -- see the rule after this. */
.hero-veil {
  position: absolute;
  inset: 0;
  z-index: 1;
  background-color: rgba(255, 255, 255, 0.6);
  pointer-events: none;
}
.hero[data-hero-text="transition"] .carousel-control-prev,
.hero[data-hero-text="transition"] .carousel-control-next {
  z-index: 2;
}


/* ---------------------------------------------------------------------- *
 * Team photos: one photo is a plain round portrait, several become a
 * slider -- in the same round frame. The carousel needs an explicit box
 * because its slides are absolutely positioned once it starts moving, so
 * there is no image left in flow to give the frame its height.
 * ---------------------------------------------------------------------- */
.team-photo-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 1 / 1;
  overflow: hidden;
  border-radius: 50%;
}

.team-photo-frame .carousel-item img {
  border-radius: 50%;
}

/* Full-size Bootstrap controls would cover most of a portrait this small. */
.team-photo-frame .carousel-control-prev,
.team-photo-frame .carousel-control-next {
  width: 25%;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.team-photo-frame:hover .carousel-control-prev,
.team-photo-frame:hover .carousel-control-next,
.team-photo-frame .carousel-control-prev:focus-visible,
.team-photo-frame .carousel-control-next:focus-visible {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .team-photo-frame .carousel-control-prev,
  .team-photo-frame .carousel-control-next {
    opacity: 1;
    transition: none;
  }
}

/* Member card name, 2026 redesign -- the one accent on an otherwise plain
   card (photo, role on .text-muted, bio in body text). */
.team-member-name {
  color: var(--alza-sand);
  letter-spacing: 0.03em;
}

/* Pixel-measured off 4_Equipo.png at 1920px: the intro copy sits 143px
   (7.45% of 1920) from the viewport edge, not Bootstrap's own .container
   gutter -- same breakpoint scale as .service-text's own padding. Its
   cap-height ("D" of "Después") is 16px against .service-text's own
   "A" at 22px for its confirmed 1.875rem, which at that same ratio
   lands here at 1.375rem -- noticeably smaller than the single-sentence
   .section-intro used in Proxectos/Servizos, since this is a multi-
   paragraph block of running prose instead. */
.team-intro-col,
.team-intro-text {
  font-size: 1.375rem;
}
.team-intro-col,
.team-intro-wrap {
  padding-left: 1.5rem;
  padding-right: 1.5rem;
}
@media (min-width: 768px) {
  .team-intro-col,
  .team-intro-wrap {
    padding-left: 3rem;
    padding-right: 3rem;
  }
}
@media (min-width: 1200px) {
  .team-intro-col,
  .team-intro-wrap {
    padding-left: 8.9375rem;
    padding-right: 8.9375rem;
  }
}

/* Pixel-measured off 4_Equipo.png: the photo's own frame is 957x962 --
   essentially square -- independent of however much text runs next to
   it (see team_block.html's comment). aspect-ratio is what keeps that
   shape fixed; position-relative/overflow-hidden pair with the
   template's own position-absolute media wrapper (top-0 start-0 w-100
   h-100), the same recipe as the Hero's own media, so the photo fills
   that fixed-ratio box exactly regardless of how its own height ends up
   being resolved. */
.team-intro-photo-col {
  aspect-ratio: 957 / 962;
}

/* Pixel-measured off 4_Equipo.png: the team intro photo is black and
   white, unlike every other photo on the site. */
.team-intro-media {
  filter: grayscale(100%);
}

/* Keeps the hero's heading and text above the photo behind them.

   Bootstrap gives the *active* slide of a .carousel-fade `z-index: 1`. The
   media wrapper is position:absolute with z-index:auto, which does NOT open
   a stacking context, so that 1 competed directly against the text layer
   (z-index:auto, i.e. 0) and won -- the text was invisible except for the
   instant mid-transition when the outgoing slide drops back to z-index: 0,
   which is exactly when it flickered into view.

   Giving the wrapper an explicit z-index opens a stacking context, trapping
   the slide's z-index inside it; the text layer then sits above the whole
   wrapper rather than above one slide. */
.hero > .position-absolute {
  z-index: 0;
}
.hero > .hero-content {
  z-index: 1;
}

/* ---------------------------------------------------------------------- *
 * Project gallery modal
 * ---------------------------------------------------------------------- */

/* The header band carries the project's name -- the only place it appears
   in the modal now. Brand grey rather than the default white, with the name
   in the sand accent, matching the section headings elsewhere. */
.project-modal-header {
  background-color: var(--alza-dark);
  border-bottom: none;
}

.project-modal-title {
  color: var(--alza-sand);
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 0;
}

/* Fullscreen toggle. Sits over the top-right of the gallery rather than in
   the header, because the header is not on screen once the carousel itself
   is fullscreen. z-index clears the carousel's own controls. */
.project-fullscreen-toggle {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 3;
  width: 2.75rem;
  height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 0.25rem;
  background-color: rgba(92, 103, 113, 0.65);
}

.project-fullscreen-toggle:hover,
.project-fullscreen-toggle:focus-visible {
  background-color: rgba(92, 103, 113, 0.9);
}

.project-fullscreen-toggle:focus-visible {
  outline: 3px solid var(--alza-sand);
  outline-offset: 2px;
}

/* Four corner brackets pointing outwards: the "expand" idiom. Drawn in CSS
   rather than loaded as an icon font for one glyph. */
.project-fullscreen-icon {
  position: relative;
  display: block;
  width: 1.1rem;
  height: 1.1rem;
}

.project-fullscreen-icon::before,
.project-fullscreen-icon::after {
  content: "";
  position: absolute;
  width: 0.45rem;
  height: 0.45rem;
  border: 2px solid #fff;
}

.project-fullscreen-icon::before {
  top: 0;
  left: 0;
  border-right: none;
  border-bottom: none;
}

.project-fullscreen-icon::after {
  right: 0;
  bottom: 0;
  border-left: none;
  border-top: none;
}

/* Once fullscreen, the same brackets point inwards to read as "collapse". */
.project-fullscreen-toggle.is-fullscreen .project-fullscreen-icon::before {
  top: 0;
  left: 0;
  border: 2px solid #fff;
  border-left: none;
  border-top: none;
}

.project-fullscreen-toggle.is-fullscreen .project-fullscreen-icon::after {
  right: 0;
  bottom: 0;
  border: 2px solid #fff;
  border-right: none;
  border-bottom: none;
}

/* The gallery has two jobs, and they want opposite things from the same
   photo -- so the cropping is done here, in CSS, and not baked into the
   rendition. The <img> is always served uncropped (max-, see
   projects_block.html); what changes is how it is fitted.

   In the modal: one fixed 4:3 box for every slide, filled edge to edge
   (cover). Moving between slides then never jumps the dialog's height,
   whatever shape each photograph happens to be.

   Baking a cropped rendition in instead would have been simpler and wrong:
   the cropped pixels would be all that exists, so blowing the photo up
   would show the crop rather than the photograph. */
/* modal-lg's own 800px, a bit wider per the client's request -- overriding
   the same CSS variable Bootstrap's .modal-lg sets, so everything else
   .modal-lg already does (margins, centering) is untouched. */
@media (min-width: 992px) {
  .project-modal-dialog {
    --bs-modal-width: 1000px;
  }
}

.project-carousel .carousel-item {
  aspect-ratio: 4 / 3;
  background-color: #000;
}

/* 4:3 assumes a landscape screen with room to spare above and below the
   dialog. On a phone in portrait it does the opposite: the dialog's own
   width is already capped to the viewport, so a wide/short box then only
   fills roughly a third of the screen's height, reading as too small
   however good the photo itself is. Taller than wide instead, on the same
   phone breakpoint used elsewhere on the page (the navbar wordmark) --
   still the same fixed-box-plus-cover technique above, just a box shaped
   for the screen it is actually on. */
@media (max-width: 575.98px) {
  .project-carousel .carousel-item {
    aspect-ratio: 3 / 4;
  }
}

.project-carousel .carousel-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.project-carousel .project-intro-slide {
  height: 100%;
}

/* "Amosar fotos completas na galería": swaps cover for contain in that same
   4:3 box (.project-carousel .carousel-item, above) -- the box never
   changes size, only whether a photo that does not match its ratio gets
   cropped or letterboxed. The box's own background-color: #000 (above) is
   what shows through the letterboxing.

   `> picture > img`, not a plain descendant selector, is what keeps this
   off the intro slide: {% picture %} always wraps its <img> in a <picture>
   element, and that <picture> is a direct child of .carousel-item for the
   cover-as-photo and gallery slides, but one level deeper (inside
   .project-intro-slide) for the intro slide -- so this selector simply
   never matches it, without needing to name it. */
.project-carousel-uncropped .carousel-item > picture > img {
  object-fit: contain;
}

/* Fullscreen: the constraint comes off. Real proportions, centred, with
   black down the sides (or top and bottom) where the photograph does not
   reach -- contain rather than cover, so nothing is cut off. */
.project-carousel:fullscreen {
  width: 100vw;
  height: 100vh;
  background-color: #000;
}

.project-carousel:fullscreen .carousel-inner,
.project-carousel:fullscreen .carousel-item {
  height: 100%;
  aspect-ratio: auto;
}

.project-carousel:fullscreen .carousel-item img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The one exception, in both states: the first slide is a backdrop for the
   project's text, not a photograph being examined. Letterboxing it would
   put the words on black bars, so it stays full-bleed (cover). */
.project-carousel:fullscreen .project-intro-slide img {
  object-fit: cover;
}

.project-carousel:fullscreen .project-intro-inner {
  max-width: 80ch;
  font-size: 1.15rem;
}

/* Service card images. A fixed 2:3 (portrait) frame per the 2026
   redesign -- was 3:2 landscape -- so a row of service cards keeps its
   headings and text on the same lines whatever shape each photo is, and
   so the carousel has a height at all, since its slides go
   position:absolute the moment it starts moving. The single-image
   (non-slider) card uses a matching 2:3 rendition requested from
   services_block.html directly, since it has no fixed frame of its own
   to crop into. */
.service-media {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 3;
  overflow: hidden;
}

/* Full-size controls would swamp a card this narrow. */
.service-media .carousel-control-prev,
.service-media .carousel-control-next {
  width: 20%;
}

.service-media .carousel-indicators {
  margin-bottom: 0.5rem;
}

/* Language switcher dropdown, brought onto the brand palette.

   Bootstrap hardcodes its own blue on the active item
   (--bs-dropdown-link-active-bg: #0d6efd) and a light panel, neither of
   which reads the --bs-primary/--bs-dark overrides at :root -- those only
   reach utility classes. The component's own variables have to be set on
   the component, which is what this does: a dark panel matching the
   navbar it drops out of, white items, and the sand accent for the
   current language, the same signal the nav uses for the current section. */
.lang-switcher .dropdown-menu {
  --bs-dropdown-bg: var(--alza-dark);
  --bs-dropdown-border-color: rgba(255, 255, 255, 0.18);
  --bs-dropdown-link-color: #fff;
  --bs-dropdown-link-hover-color: var(--alza-sand);
  --bs-dropdown-link-hover-bg: rgba(255, 255, 255, 0.08);
  --bs-dropdown-link-active-color: var(--alza-sand);
  --bs-dropdown-link-active-bg: rgba(255, 255, 255, 0.08);
  --bs-dropdown-link-focus-bg: rgba(255, 255, 255, 0.08);
  --bs-dropdown-item-padding-y: 0.4rem;
}

/* Matches the toggle beside it (italic sand "GL"), so the open menu looks
   like part of the same control rather than a stock Bootstrap panel. */
.lang-switcher .dropdown-item {
  font-style: italic;
  letter-spacing: 0.06em;
}

.lang-switcher .dropdown-item.active {
  font-weight: 700;
}

.lang-switcher .dropdown-item:focus-visible {
  outline: 2px solid var(--alza-sand);
  outline-offset: -2px;
}


/* Skip link: off screen until it takes focus, then pinned below nothing --
   it must sit above the fixed navbar, or it would land behind it. */
.skip-link {
  position: absolute;
  top: -3rem;
  left: 0.5rem;
  z-index: 1040;
  padding: 0.6rem 1rem;
  color: #fff;
  background-color: var(--alza-dark);
  border-radius: 0 0 0.25rem 0.25rem;
  transition: top 0.15s ease;
}

.skip-link:focus {
  top: 0;
  color: #fff;
  outline: 3px solid var(--alza-sand);
  outline-offset: 2px;
}

/* The target takes focus programmatically (tabindex="-1"); nobody needs to
   see a focus ring around the entire page as a result. */
main:focus {
  outline: none;
}
