/* ==========================================================================
   Layout — top bar, header/nav, section rhythm, footer, back-to-top
   ========================================================================== */

/* ---------- Top bar ---------- */
#topbar {
  position: relative;
  background: var(--color-bg-alt);
  border-bottom: 1px solid var(--color-border);
  font-size: var(--fs-sm);
  color: var(--color-text-muted);
  /* Fixed height — the hero subtracts --topbar-h (and --safe-top) to end
     exactly at the fold. The topbar is the first thing on the page, so it is
     what sits under the iPhone status bar at scroll 0: its background runs
     to the screen edge and the safe-area padding keeps the contact links
     below the clock. */
  height: calc(var(--topbar-h) + var(--safe-top));
  padding-top: var(--safe-top);
}

/* Hairline of brand color along the top edge of the page — below the status
   bar, or it would be hidden under the clock on iPhone */
#topbar::before {
  content: "";
  position: absolute;
  inset: var(--safe-top) 0 auto 0;
  height: 2px;
  background: var(--grad-accent);
  opacity: 0.85;
}

#topbar .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  height: 100%;
}

#topbar .contact-info {
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

#topbar .contact-info a,
#topbar .contact-info span {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--color-text-muted);
}
#topbar .contact-info a:hover { color: var(--color-accent); }
#topbar .contact-info svg { width: 15px; height: 15px; color: var(--color-accent); flex-shrink: 0; }
/* The WhatsApp mark is filled, so it reads a shade heavier than the line icons
   beside it at the same box size — trim it back to match. */
#topbar .contact-whatsapp svg { width: 14px; height: 14px; color: #25D366; }

#topbar .topbar-actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

/* Theme switch — a pill that slides a knob between sun and moon.
   It is a real <button> with aria-pressed, so it is keyboard and SR usable.

   The geometry is derived, not guessed, so the knob lands concentric with
   whichever icon it covers. With border-box sizing the padding box is
   (--switch-w − 2 × border) wide; the knob starts --switch-gap in and
   travels the remainder, and space-between drops each icon's centre on a
   knob centre. Change one variable and the rest stays aligned. */
.theme-toggle {
  --switch-w: 58px;
  --switch-h: 28px;
  --switch-gap: 2px;   /* knob inset from the pill's padding box */
  --switch-knob: 22px;
  --switch-icon: 14px;
  /* Padding box width (−2px for the borders) − knob − a gap at each end */
  --switch-travel: calc(var(--switch-w) - 2px - var(--switch-knob) - 2 * var(--switch-gap));
  /* Puts each icon's centre on a knob centre, given justify-content:space-between */
  --switch-pad: calc(var(--switch-gap) + (var(--switch-knob) - var(--switch-icon)) / 2);

  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  width: var(--switch-w);
  height: var(--switch-h);
  padding: 0 var(--switch-pad);
  border-radius: var(--radius-pill);
  border: 1px solid var(--color-border);
  background: var(--color-surface);
  cursor: pointer;
  flex-shrink: 0;
  transition: border-color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.theme-toggle:hover {
  border-color: var(--color-accent);
  box-shadow: var(--glow-accent);
}

.theme-toggle svg {
  width: var(--switch-icon);
  height: var(--switch-icon);
  position: relative;
  z-index: 1;
  transition: color var(--dur-base) var(--ease-out),
              opacity var(--dur-base) var(--ease-out);
}
.theme-toggle .icon-sun { color: var(--color-text-muted); opacity: 0.55; }
.theme-toggle .icon-moon { color: var(--color-accent); opacity: 1; }

:root[data-theme="light"] .theme-toggle .icon-sun { color: var(--color-accent); opacity: 1; }
:root[data-theme="light"] .theme-toggle .icon-moon { color: var(--color-text-muted); opacity: 0.55; }

/* The sliding knob sits behind the icons */
.theme-toggle::after {
  content: "";
  position: absolute;
  top: 50%;
  left: var(--switch-gap);
  width: var(--switch-knob);
  height: var(--switch-knob);
  margin-top: calc(var(--switch-knob) / -2);
  border-radius: 50%;
  background: var(--color-accent-tint);
  border: 1px solid var(--color-accent);
  transform: translateX(var(--switch-travel));
  transition: transform var(--dur-base) var(--ease-back);
}
:root[data-theme="light"] .theme-toggle::after { transform: translateX(0); }

@media (prefers-reduced-motion: reduce) {
  .theme-toggle::after { transition: none; }
  .header-ig,
  .nav-follow-photo { transition: none; }
}

#topbar .social-links {
  display: flex;
  gap: var(--space-xs);
}
#topbar .social-links a {
  color: var(--color-text-muted);
  display: inline-flex;
}
#topbar .social-links a:hover { color: var(--color-accent); }
#topbar .social-links svg { width: 16px; height: 16px; }

/* Mobile: the bar goes icon-only. The labels are clipped rather than
   display:none so screen readers still announce the address and number,
   and the roomier gap keeps the targets tappable. The Instagram link stays
   visible at every width — phones are exactly where it hands off to the app. */
@media (max-width: 720px) {
  #topbar .social-links svg { width: 18px; height: 18px; }

  #topbar .contact-info { gap: var(--space-lg); }
  #topbar .contact-info .label {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  #topbar .contact-info svg { width: 18px; height: 18px; }
  #topbar .contact-whatsapp svg { width: 17px; height: 17px; }
}

/* ---------- Header ---------- */
#header {
  position: sticky;
  /* Sticks below the status bar so the logo and hamburger never sit under
     the notch; the frosted plate below extends up to cover that strip. */
  top: var(--safe-top);
  z-index: 60;
  height: var(--header-h);
  display: flex;
  align-items: center;
  /* The tint lives HERE, on the sticky element itself, not on the plate
     below. iPhone Safari (26+) never gives the page the status-bar strip
     in browser mode (safe-area-inset-top is 0, viewport-fit=cover only
     matters for home-screen apps). Instead it tints that strip by sampling
     the background-color of the fixed/sticky element touching the top
     edge, and it ignores pseudo-elements. With a transparent header Safari
     found no chrome and composited raw page pixels under the clock; with
     the colour here the strip becomes an extension of the header. A plain
     colour does not make a containing block, so the drawer inside stays
     viewport-sized. */
  background-color: transparent;
  transition: background-color var(--dur-base) var(--ease-out);
}
#header.is-scrolled { background-color: var(--color-header-bg); }

/* The frosted BLUR lives on a pseudo-element and NEVER on #header itself:
   backdrop-filter makes an element a containing block for position:fixed
   descendants, which would shrink the mobile nav drawer and its backdrop
   from the viewport down to this 76px-tall box the moment the header
   picked up .is-scrolled. Fading opacity also animates cleanly, which
   backdrop-filter does not. The colour is on #header (see above) so Safari
   can sample it; this plate carries only the blur and the bottom hairline.

   The plate reaches --safe-top above the header box: while the header is
   stuck that puts its top edge at the very top of the screen, so on iPhone
   the status-bar strip is part of the header's material and scrolling
   content blurs behind it instead of showing raw under the clock — the
   native navigation-bar behaviour. */
#header::before {
  content: "";
  position: absolute;
  inset: calc(-1 * var(--safe-top)) 0 0 0;
  z-index: -1;
  backdrop-filter: blur(16px) saturate(140%);
  -webkit-backdrop-filter: blur(16px) saturate(140%);
  box-shadow: var(--shadow-sm), inset 0 -1px 0 var(--color-border);
  opacity: 0;
  /* visibility, not just opacity: an opacity-0 backdrop-filter is still a
     live backdrop layer that iOS re-blurs every frame. Hidden it costs
     nothing; the delayed flip keeps the fade-out playing on the way back. */
  visibility: hidden;
  transition: opacity var(--dur-base) var(--ease-out),
              visibility 0s linear var(--dur-base);
}

#header.is-scrolled::before {
  opacity: 1;
  visibility: visible;
  transition-delay: 0s, 0s;
}

/* Reading progress along the header's bottom edge. --scroll-progress is
   written by initScrollProgress(); scaleX is compositor-only, so this costs
   nothing per frame. */
#header::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--grad-accent);
  transform: scaleX(var(--scroll-progress));
  transform-origin: 0 50%;
  opacity: 0;
  transition: opacity var(--dur-base) var(--ease-out);
}
#header.is-scrolled::after { opacity: 1; }

#header .header-inner {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
}

/* The wordmark is solid black artwork, so it is inverted for the default dark
   theme and returned to its natural black in light mode. */
#header .logo img {
  height: 42px;
  width: auto;
  display: block;
  filter: invert(1) drop-shadow(0 2px 8px rgba(0, 0, 0, 0.5));
  transition: transform var(--dur-base) var(--ease-out);
}
#header .logo a:hover img { transform: scale(1.03); }

:root[data-theme="light"] #header .logo img { filter: none; }

/* --- Logo squeegee reveal ---
   One slow pass on load: the wordmark is uncovered left to right behind a
   drawn professional squeegee (#icon-squeegee in the sprite: steel channel,
   black rubber, fan head, green handle), and the pass lands on a double
   light-flash with a four-point sparkle — squeaky clean. Pure CSS and
   self-terminating: the wipe ends fully revealed and fills backwards only,
   so the logo can never be left hidden (it plays and completes even with
   scripting disabled; under reduced motion none of it exists and every prop
   is invisible at rest). The tool animates `left` in % on the same curve as
   the clip, so its rubber edge stays glued to the reveal line at any logo
   width — a one-shot pass on a 55x80px box is negligible paint. */
#header .logo a {
  position: relative;
  display: inline-block;
}

/* Real elements rather than pseudos so the tool drawing can take theme
   tokens through the sprite. Invisible at rest — only the animations below
   ever show them. */
.logo-squeegee,
.logo-sparkle {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}
.logo-squeegee {
  top: 50%;
  left: 0;
  width: 55px;    /* 110x160 viewBox at 0.5 — the slim channel spans the header, 76px against the 42px wordmark */
  height: 80px;
  margin-top: -40px;
  z-index: 2;
  /* A soft cast shadow rather than an accent halo: the tool is steel, rubber
     and green now, not a tinted glyph. Reads on both header tones. */
  filter: drop-shadow(0 2px 5px rgba(0, 0, 0, 0.45));
  rotate: 3deg;   /* hand-held, not mechanical */
}
.logo-sparkle {
  top: -11px;
  right: -13px;
  width: 20px;
  height: 20px;
  z-index: 3;
}

/* Flash plate for the double pulse as the pass completes. White core with
   an accent halo so it registers on the dark and light header alike. */
#header .logo a::before {
  content: "";
  position: absolute;
  inset: -10px;
  z-index: 1;
  background: radial-gradient(closest-side at 60% 50%,
    rgba(255, 255, 255, 0.9), rgba(126, 208, 222, 0.35) 45%, transparent 78%);
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  #header .logo img {
    animation: logo-wipe 1700ms var(--ease-out) 500ms backwards;
  }
  .logo-squeegee {
    animation: logo-squeegee-sweep 1700ms var(--ease-out) 500ms both;
  }
  /* 2200ms = wipe delay + duration: the flash fires as the tool exits */
  #header .logo a::before {
    animation: logo-flash 900ms var(--ease-out) 2200ms both;
  }
  .logo-sparkle {
    animation: logo-sparkle-pop 900ms var(--ease-out) 2200ms both;
  }
}
@keyframes logo-wipe {
  from { clip-path: inset(-6px 100% -6px 0); }
  to   { clip-path: inset(-6px -6px -6px 0); }
}
/* -3px = the rubber's working edge inside the 55px-wide tool drawing (x=6 of 110) */
@keyframes logo-squeegee-sweep {
  0%   { left: calc(-2% - 3px); opacity: 0; }
  6%   { opacity: 1; }
  94%  { opacity: 1; }
  100% { left: calc(101% - 3px); opacity: 0; }
}
/* Double pulse, second flash the brighter one */
@keyframes logo-flash {
  0%   { opacity: 0; }
  14%  { opacity: 0.85; }
  34%  { opacity: 0.05; }
  52%  { opacity: 1; }
  100% { opacity: 0; }
}
@keyframes logo-sparkle-pop {
  0%   { opacity: 0; scale: 0.2; }
  14%  { opacity: 1; scale: 1; }
  34%  { opacity: 0.1; scale: 0.4; }
  52%  { opacity: 1; scale: 1.2; }
  100% { opacity: 0; scale: 0.3; }
}

.nav-menu ul {
  /* Positioning context for the sliding .nav-indicator */
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

.nav-menu ul a {
  color: var(--color-text);
  font-family: var(--font-display);
  font-stretch: var(--wdth-display);
  font-weight: 600;
  font-size: var(--fs-sm);
  letter-spacing: 0.01em;
  padding: var(--space-2xs) var(--space-xs);
  border-radius: var(--radius-pill);
  position: relative;
  transition: color var(--dur-fast) var(--ease-out),
              background var(--dur-fast) var(--ease-out);
}

/* The underline now grows from the centre out and carries the gradient —
   same idea as before, read at a glance as deliberate rather than default. */
.nav-menu ul a::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 2px;
  width: 0;
  height: 2px;
  border-radius: 2px;
  background: var(--grad-accent);
  transform: translateX(-50%);
  transition: width var(--dur-base) var(--ease-out);
}

.nav-menu ul a:hover,
.nav-menu ul a.active { color: var(--color-accent); }
.nav-menu ul a:hover { background: var(--color-accent-tint); }
.nav-menu ul a:hover::after,
.nav-menu ul a.active::after { width: calc(100% - 2 * var(--space-xs)); }

/* --- Sliding active indicator ---
   One shared bar that glides between links as the active section changes,
   replacing the per-link underline on desktop (the drawer keeps the old
   style — see the 900px block below). Created by initNav(), which writes
   --ind-x/--ind-s; a fixed 80px basis scaled with scaleX keeps every move
   compositor-only, and a 2px gradient bar tolerates the scaling distortion.
   .is-settled arrives after the first placement so the bar never glides in
   from x=0 on load. */
.nav-indicator {
  position: absolute;
  bottom: 2px;
  left: 0;
  width: 80px;
  height: 2px;
  border-radius: 2px;
  background: var(--grad-accent);
  pointer-events: none;
  transform: translateX(var(--ind-x, 0px)) scaleX(var(--ind-s, 0));
  transform-origin: 0 50%;
}
@media (prefers-reduced-motion: no-preference) {
  .nav-menu.is-settled .nav-indicator { transition: transform 380ms var(--ease-out); }
}
.nav-menu.has-indicator ul a::after { display: none; }

.nav-toggle {
  display: none;
  background: none;
  border: none;
  padding: var(--space-2xs);
  cursor: pointer;
  color: var(--color-text);
}
.nav-toggle svg { width: 26px; height: 26px; }

/* Drawer-only pieces — see the 900px block. .header-actions must be
   display:none here, not just empty: #header .header-inner is
   space-between, and an empty third flex item would pull the nav bar off
   the right edge. */
.header-actions,
.nav-follow { display: none; }

/* One button, two glyphs — the open state shows the close icon */
.nav-toggle .icon-close { display: none; }
.nav-toggle[aria-expanded="true"] .icon-open { display: none; }
.nav-toggle[aria-expanded="true"] .icon-close { display: block; }

@media (max-width: 900px) {
  .nav-toggle {
    display: inline-flex;
    /* Above the drawer, so the same button also closes it. Only works
       because .nav-open lifts #header's own z-index past the drawer. */
    position: relative;
    z-index: 75;
  }

  .nav-menu {
    position: fixed;
    top: 0;
    bottom: 0;
    right: 0;
    left: auto;
    width: min(320px, 82vw);
    z-index: 70;
    background: var(--color-surface);
    border-left: 1px solid var(--color-border);
    box-shadow: var(--shadow-lg);
    transform: translateX(100%);
    transition: transform var(--dur-base) var(--ease-out);
    /* Top clears the status bar + header, bottom clears the home indicator
       / iOS 26 URL pill so the follow foot's CTA stays tappable. */
    padding: calc(var(--safe-top) + var(--header-h) + var(--space-lg))
             var(--space-lg)
             calc(var(--space-lg) + var(--safe-bottom));
    overflow-y: auto;
    /* the follow foot bleeds into the padding, never past it — no sideways scroll */
    overflow-x: hidden;
    overscroll-behavior: contain;
    /* column so the follow foot can sit at the bottom via margin-top:auto */
    display: flex;
    flex-direction: column;
  }
  .nav-menu.is-open { transform: translateX(0); }

  .nav-menu ul {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2xs);
    flex-shrink: 0;   /* short viewports scroll the drawer rather than squash the list */
  }

  /* --- Drawer foot: Tim, the wordmark as a stamp, and the Instagram CTA ---
     margin-top:auto pins it to the drawer's bottom edge while there is room
     and lets it flow after the links when there isn't. Negative margins
     equal to the drawer's padding bleed it to the drawer's edges; its own
     padding restores the text inset. isolation + an explicit surface
     background give the photo's blend a backdrop of its own — without that
     background the blend would have nothing to blend with and the photo
     would render unblended. */
  .nav-follow {
    position: relative;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-end;
    flex-shrink: 0;
    margin: auto calc(-1 * var(--space-lg)) calc(-1 * (var(--space-lg) + var(--safe-bottom)));
    min-height: 250px;
    /* bottom padding re-adds the safe area the negative margin just ate */
    padding: var(--space-xl) var(--space-lg) calc(var(--space-lg) + var(--safe-bottom));
    background: var(--color-surface);
    color: var(--color-text);
    overflow: hidden;
    -webkit-tap-highlight-color: transparent;
  }
  .nav-follow:focus-visible { outline-offset: -4px; } /* ring inside — the block reaches the drawer edge */

  /* Blend, mask and fade live on the <picture>, not the <img>: z-index:-1
     makes the picture its own stacking context, so an <img> blending inside
     it would have nothing but transparency to blend with and render at full
     colour. On the picture, the blend meets .nav-follow's surface. */
  .nav-follow-photo {
    position: absolute;
    inset: 0;
    z-index: -1;            /* behind stamp + CTA, above the block's own background */
    display: block;
    pointer-events: none;
    opacity: 0.4;
    mix-blend-mode: luminosity;  /* dark: his tones, the surface's hue */
    -webkit-mask-image: linear-gradient(to bottom, transparent 0, rgba(0, 0, 0, 0.6) 40%, #000 72%);
            mask-image: linear-gradient(to bottom, transparent 0, rgba(0, 0, 0, 0.6) 40%, #000 72%);
    transition: opacity var(--dur-base) var(--ease-out);
  }
  .nav-follow-photo img {
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: 50% 20%;   /* Tim is high in his frame (cf. .team-member .pic img) */
  }
  :root[data-theme="light"] .nav-follow-photo {
    mix-blend-mode: multiply;    /* light: ink on paper */
    filter: grayscale(0.65);
    opacity: 0.3;
  }
  .nav-follow:hover .nav-follow-photo,
  .nav-follow:focus-visible .nav-follow-photo { opacity: 0.55; }
  :root[data-theme="light"] .nav-follow:hover .nav-follow-photo,
  :root[data-theme="light"] .nav-follow:focus-visible .nav-follow-photo { opacity: 0.42; }

  /* The wordmark as a stamp: rotated, slightly translucent, inverted for the
     dark theme exactly like the header logo (#header .logo img above). */
  .nav-follow-logo {
    position: relative;
    width: 64%;
    max-width: 200px;
    height: auto;
    margin-bottom: var(--space-md);
    rotate: -6deg;
    opacity: 0.9;
    filter: invert(1) drop-shadow(0 2px 6px rgba(0, 0, 0, 0.45));
  }
  :root[data-theme="light"] .nav-follow-logo { filter: none; opacity: 0.85; }

  .nav-follow-cta {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--space-xs);
    font-family: var(--font-display);
    font-stretch: var(--wdth-display);
    font-weight: 700;
    font-size: var(--fs-sm);
    letter-spacing: 0.01em;
    color: var(--color-accent);
  }
  /* Same recipe as .follow-mark in the footer (components.css), on surface tokens */
  .nav-follow-mark {
    display: inline-flex;
    width: 38px; height: 38px;
    align-items: center; justify-content: center;
    flex-shrink: 0;
    border-radius: var(--radius-pill);
    background: var(--color-accent-tint);
    border: 1px solid var(--ring-accent);
    color: var(--color-accent);
    transition: background var(--dur-fast) var(--ease-out),
                color var(--dur-fast) var(--ease-out),
                box-shadow var(--dur-fast) var(--ease-out);
  }
  .nav-follow-mark svg { width: 17px; height: 17px; }
  .nav-follow:hover .nav-follow-mark,
  .nav-follow:focus-visible .nav-follow-mark {
    background: var(--grad-accent);
    border-color: transparent;
    color: var(--color-on-accent);
    box-shadow: var(--glow-accent);
  }

  /* --- Header Instagram link, shown once the top bar has scrolled away ---
     Absolutely positioned to the LEFT of the toggle, so it takes no layout
     space (the hamburger never moves) and the show/hide is opacity + scale
     only. No z-index on purpose: the open drawer (z 70) covers it, and the
     drawer carries its own follow block. */
  .header-actions {
    display: flex;
    align-items: center;
    position: relative;
  }
  .header-ig {
    position: absolute;
    right: 100%;
    top: 50%;
    translate: 0 -50%;
    margin-right: var(--space-2xs);
    width: 40px;
    height: 40px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: var(--radius-pill);
    color: var(--color-text-muted);
    opacity: 0;
    scale: 0.6;
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--dur-base) var(--ease-out),
                scale var(--dur-base) var(--ease-back),
                color var(--dur-fast) var(--ease-out),
                background var(--dur-fast) var(--ease-out),
                visibility 0s linear var(--dur-base);
  }
  .header-ig svg { width: 22px; height: 22px; }
  #header.topbar-out .header-ig {
    opacity: 1;
    scale: 1;
    visibility: visible;
    pointer-events: auto;
    transition-delay: 0s;
  }
  .header-ig:hover,
  .header-ig:focus-visible {
    color: var(--color-accent);
    background: var(--color-accent-tint);
  }

  .nav-menu ul a {
    font-size: var(--fs-lg);
    display: block;
    padding: var(--space-2xs) var(--space-sm);
  }
  .nav-menu ul a::after { left: var(--space-sm); transform: none; }
  .nav-menu ul a:hover::after,
  .nav-menu ul a.active::after { width: 28px; }

  /* The drawer keeps the per-link underline; the sliding bar is desktop-only
     (its geometry is meaningless in a vertical list). */
  .nav-indicator { display: none; }
  .nav-menu.has-indicator ul a::after { display: block; }

  .nav-backdrop {
    position: fixed;
    inset: 0;
    z-index: 65;
    background: var(--color-overlay);
    backdrop-filter: blur(3px);
    -webkit-backdrop-filter: blur(3px);
    opacity: 0;
    /* THE mobile-only crash: test2 added this blur, and at opacity 0 the
       element still owned a live, full-viewport backdrop layer that iPhone
       Safari re-blurred on every frame of every animation on the page, from
       first paint, whether or not the drawer was ever opened. visibility
       takes the layer out of the tree while closed; the delayed flip lets
       the closing fade finish before it goes. */
    visibility: hidden;
    pointer-events: none;
    transition: opacity var(--dur-base) var(--ease-out),
                visibility 0s linear var(--dur-base);
  }
  .nav-backdrop.is-open {
    opacity: 1;
    visibility: visible;
    pointer-events: auto;
    transition-delay: 0s, 0s;
  }

  /* #header is a stacking context at z-index 60, so the drawer inside it
     can only outrank page chrome if the header itself is lifted first. */
  body.nav-open { overflow: hidden; }
  body.nav-open #header { z-index: 80; }
  body.nav-open .back-to-top,
  body.nav-open .float-whatsapp {
    opacity: 0;
    pointer-events: none;
  }
}

/* ---------- Section rhythm ---------- */
/* Scoped to content sections so #topbar / #hero keep their own sizing.
   overflow:hidden clips the decorative artwork that deliberately bleeds past
   the section edges, so it can never widen the document. Note this makes each
   section a scroll container — anything that needs to escape one (a sticky
   sidebar, a popover) would be silently clipped. */
main > section {
  padding-block: var(--section-pad);
  position: relative;
  overflow: hidden;
}

/* Keep content above the decorative layers */
main > section > .container {
  position: relative;
  z-index: 1;
}

section[id] { scroll-margin-top: var(--header-h); }

/* "Home" (nav, logo, footer) targets #hero. Its margin also reserves the
   topbar's height, which lands the scroll at exactly y = 0 — so going Home
   brings the contact strip back into view instead of stopping 42px short. */
#hero { scroll-margin-top: calc(var(--header-h) + var(--topbar-h)); }

.section-bg { background: var(--color-bg-alt); }

/* Soften the hard edge where a tinted band meets the page background, and
   mark it with a hairline that catches the accent at its centre. */
.section-bg::before,
.section-bg::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 64px;
  pointer-events: none;
  z-index: 0;
}
.section-bg::before {
  top: 0;
  background:
    var(--grad-accent-fade) top / 100% 1px no-repeat,
    linear-gradient(to bottom, var(--color-bg), transparent);
  opacity: 0.9;
}
.section-bg::after {
  bottom: 0;
  background:
    var(--grad-accent-fade) bottom / 100% 1px no-repeat,
    linear-gradient(to top, var(--color-bg), transparent);
  opacity: 0.9;
}

/* ---------- Footer ---------- */
#footer {
  position: relative;
  background: var(--color-footer-bg);
  color: var(--color-footer-text);
  /* Clips the .deco--footer water-line, which bleeds past both side edges */
  overflow: hidden;
}
#footer::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: var(--grad-accent-fade);
}

#footer .footer-top {
  padding-block: var(--space-xl);
  border-bottom: 1px solid var(--color-footer-rule);
}

/* The 4th column now holds the follow card rather than a feed widget. Its
   handle is one unbreakable token, so that track gets the same weight as the
   address column — at the narrow end of this 4-col band (just past 900px) a
   1.2fr track left it a few pixels short. The two link lists wrap freely and
   are unaffected by the reweighting. */
#footer .footer-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1.4fr;
  gap: var(--space-lg);
}

@media (max-width: 900px) {
  #footer .footer-grid {
    grid-template-columns: 1fr 1fr;
  }
}
@media (max-width: 560px) {
  #footer .footer-grid { grid-template-columns: 1fr; }
}

#footer h3, #footer h4 {
  color: var(--color-footer-text);
}

#footer .footer-info h3 {
  font-size: var(--fs-xl);
  font-weight: 800;
  letter-spacing: -0.02em;
}

#footer h4 {
  font-size: var(--fs-base);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  margin-bottom: var(--space-sm);
  padding-bottom: var(--space-2xs);
  border-bottom: 1px solid var(--color-footer-rule);
}

#footer p, #footer li { color: var(--color-footer-muted); }

#footer .footer-links li {
  display: flex;
  align-items: center;
  gap: 4px;
  margin-bottom: var(--space-2xs);
}
#footer .footer-links li > svg {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  color: var(--color-accent);
  transition: transform var(--dur-fast) var(--ease-out);
}
#footer .footer-links li:hover > svg { transform: translateX(3px); }
#footer .footer-links a {
  color: var(--color-footer-muted);
}
#footer .footer-links a:hover { color: var(--color-footer-accent); }

#footer .social-links {
  display: flex;
  gap: var(--space-xs);
  margin-top: var(--space-sm);
}
#footer .social-links a {
  display: inline-flex;
  width: 38px; height: 38px;
  align-items: center; justify-content: center;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.08);
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: var(--color-footer-text);
  transition: background var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
#footer .social-links a:hover {
  background: var(--color-accent);
  transform: translateY(-2px);
  box-shadow: var(--glow-accent);
}
#footer .social-links svg { width: 16px; height: 16px; }

#footer .footer-bottom {
  padding-block: var(--space-sm);
  text-align: center;
  font-size: var(--fs-sm);
  color: var(--color-footer-muted);
}

/* ---------- Back to top ----------
   The ring is the document's reading progress; the disc inside it is the
   button proper. One element, two jobs, no extra markup. */
.back-to-top {
  position: fixed;
  /* insets keep both discs clear of the home indicator / URL pill */
  right: calc(var(--space-md) + var(--safe-right));
  bottom: calc(var(--space-md) + var(--safe-bottom));
  z-index: 80;
  width: 50px;
  height: 50px;
  padding: 3px;
  border-radius: var(--radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    conic-gradient(from -90deg,
      var(--color-accent-2) calc(var(--scroll-progress) * 360deg),
      rgba(255, 255, 255, 0.16) 0);
  color: #fff;
  box-shadow: var(--shadow-md);
  opacity: 0;
  transform: translateY(12px);
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out),
              transform var(--dur-base) var(--ease-out);
}
.back-to-top::before {
  content: "";
  position: absolute;
  inset: 3px;
  border-radius: 50%;
  background: var(--color-accent);
  transition: background var(--dur-fast) var(--ease-out);
}
.back-to-top.is-visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.back-to-top:hover { transform: translateY(-3px); }
.back-to-top:hover::before { background: var(--color-accent-strong); }
.back-to-top svg { width: 20px; height: 20px; position: relative; }

/* ---------- Floating WhatsApp ----------
   Same disc as the arrow, stacked directly above it and sharing its
   show/hide (initBackToTop toggles both). Brand green, not a theme token —
   the mark has to read as WhatsApp at a glance. The 60ms delay lets it
   follow the arrow in a small cascade rather than popping in lock-step. */
.float-whatsapp {
  position: fixed;
  right: calc(var(--space-md) + var(--safe-right));
  bottom: calc(var(--space-md) + 50px + var(--space-xs) + var(--safe-bottom));
  z-index: 80;
  width: 50px;
  height: 50px;
  border-radius: var(--radius-pill);
  display: flex;
  align-items: center;
  justify-content: center;
  background: #25D366;
  color: #fff;
  box-shadow: var(--shadow-md);
  opacity: 0;
  transform: translateY(12px);
  pointer-events: none;
  transition: opacity var(--dur-base) var(--ease-out) 60ms,
              transform var(--dur-base) var(--ease-out) 60ms,
              background var(--dur-fast) var(--ease-out);
}
.float-whatsapp.is-visible {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
.float-whatsapp:hover {
  transform: translateY(-3px);
  background: #1ebe5d;
}
.float-whatsapp svg { width: 24px; height: 24px; }
