/*
Theme Name: OberBuilt Child
Template: Divi
Version: 0.2.0
Description: OberBuilt child theme — brand tokens + custom Divi 5 modules.
Author: OberBuilt Inc.
Author URI: https://oberbuilt.ca
Text Domain: oberbuilt-child
*/

/* ============================================================
   OberBuilt Child Theme — global brand layer
   ------------------------------------------------------------
   Appended by Plan 01-03 (.planning/phases/01-brand-foundation-
   identity-pages/01-03-PLAN.md, Task 3).

   Layer order:
     1. @font-face — self-hosted woff2 (Saira, IBM Plex Sans,
        IBM Plex Mono). Zero requests to fonts.googleapis.com /
        fonts.gstatic.com on any front-end page (verified in
        Plan 01-03 SUMMARY via DevTools Network panel).
     2. :root — semantic token aliases as CSS custom properties.
        Lets raw CSS (e.g. module-scoped style.css files that
        don't go through Divi's Global Variables inlining) use
        var(--ob-*) names directly.
     3. Focus reset — global :focus-visible ring honouring
        UI-SPEC §Focus + RESEARCH Pitfall 1.

   Source of truth for token values:
   .planning/design-system/oberbuilt-design-system/project/
   divi5-tokens.json (and colors_and_type.css).
   ============================================================ */

/* ---------- 1. @font-face (self-hosted) ---------------------
   - font-display: swap → never block render on the font.
   - unicode-range: Latin (U+0000–00FF) + Latin-Ext (U+0100–017F)
     covers Canadian English + French accents per UI-SPEC §Typography.
     IBM Plex Mono is ASCII-only (per Gate 3 budget; mono usage is
     code / numeric data, not prose).
   - size-adjust / ascent-override / descent-override intentionally
     OMITTED. The Wave 0 subset pipeline (fonttools 4.63.0 +
     pyftsubset) used Google's reference VFs at default width axis;
     font.tools doesn't publish overrides for the resulting subset
     metrics, and hand-tuned values would risk drift from the
     mockups. CLS will be measured against the 0.1 budget on the
     Phase 1 Lighthouse pass; if it regresses, this is the place to
     re-introduce the descriptors (RESEARCH Pitfall 5).
   ------------------------------------------------------------ */

@font-face {
  font-family: 'Saira';
  src: url('./assets/fonts/Saira-500.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0100-017F;
}
@font-face {
  font-family: 'Saira';
  src: url('./assets/fonts/Saira-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0100-017F;
}
@font-face {
  font-family: 'Saira';
  src: url('./assets/fonts/Saira-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0100-017F;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('./assets/fonts/IBMPlexSans-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0100-017F;
}
@font-face {
  font-family: 'IBM Plex Sans';
  src: url('./assets/fonts/IBMPlexSans-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0000-00FF, U+0100-017F;
}
@font-face {
  font-family: 'IBM Plex Mono';
  src: url('./assets/fonts/IBMPlexMono-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  unicode-range: U+0020-007E;
}

/* ---------- 2. :root raw palette tokens ---------------------
   Restored 2026-05-22 — Plan 01-03 executor defined only the
   semantic layer (--ob-bg, --ob-fg-1, etc.) and forgot the raw
   palette (Color — raw group from global-variables.source.json).
   Header/footer + mobile popover CSS referenced --ob-paper /
   --ob-steel-* / --ob-cyan-* which resolved to `initial`, making
   the mobile popover transparent on first ship.

   Single source of truth is global-variables.source.json. If you
   edit a value here, also run:
     node scripts/convert-tokens-to-divi5.js
   to keep the Divi 5 import file in sync.
   ------------------------------------------------------------ */

:root {
  /* Paper (canonical white) */
  --ob-paper:        #FFFFFF;

  /* Steel — cool neutral ramp */
  --ob-steel-50:     #F2F6FA;
  --ob-steel-100:    #E4ECF3;
  --ob-steel-200:    #C7D4E0;
  --ob-steel-300:    #94A8BC;
  --ob-steel-400:    #6B8197;
  --ob-steel-500:    #4A6076;
  --ob-steel-600:    #344758;
  --ob-steel-700:    #233140;
  --ob-steel-800:    #15232F;
  --ob-steel-900:    #0F1B26;

  /* Ink */
  --ob-ink:          #0B1620;

  /* Blue — primary brand */
  --ob-blue-100:     #DCEBFA;
  --ob-blue-200:     #B3D2F2;
  --ob-blue-300:     #7CBAEE;
  --ob-blue-400:     #4A9BE6;
  --ob-blue-500:     #1B7AD6;
  --ob-blue-600:     #0E5FB3;
  --ob-blue-700:     #0B4A8F;
  --ob-blue-800:     #0A3A6E;

  /* Cyan — accent */
  --ob-cyan-200:     #B6F0F7;
  --ob-cyan-300:     #62E2EF;
  --ob-cyan-400:     #2DD4E8;
  --ob-cyan-500:     #00B8D4;
  --ob-cyan-600:     #008FA5;
  --ob-cyan-700:     #006A7C;

  /* Red — critical */
  --ob-red-100:      #FBE0DD;
  --ob-red-300:      #F0857C;
  --ob-red-500:      #E1352B;
  --ob-red-600:      #C32820;
  --ob-red-700:      #951C16;

  /* Yellow — warning */
  --ob-yellow-100:   #FCF3C5;
  --ob-yellow-300:   #F7D547;
  --ob-yellow-500:   #F2C300;
  --ob-yellow-600:   #C29B00;

  /* Green — success */
  --ob-green-100:    #D4ECDF;
  --ob-green-500:    #1F8E5B;
  --ob-green-600:    #16704A;
}

/* ---------- 3. :root semantic tokens ------------------------
   Mirrors the semantic layer from colors_and_type.css. Names use
   the `--ob-` prefix to match the Divi 5 Global Variables slugs
   (`ob-<token>`), so `var(--ob-bg-card)` in module CSS resolves
   regardless of whether Divi inlines the variable.
   ------------------------------------------------------------ */

:root {
  /* Brand & action */
  --ob-brand:         #0E5FB3;
  --ob-brand-hover:   #0B4A8F;
  --ob-brand-fg:      #FFFFFF;
  --ob-accent:        #00B8D4;

  /* Surfaces */
  --ob-bg:            #F2F6FA;
  --ob-bg-card:       #FFFFFF;
  --ob-bg-data:       #F2F6FA;
  --ob-bg-inset:      #E4ECF3;
  --ob-bg-overlay:    rgba(11, 22, 32, 0.6);
  --ob-bg-grid:       rgba(11, 22, 32, 0.04);
  --ob-bg-dark:       #0B1620;
  --ob-bg-dark-card:  #15232F;

  /* Foreground roles */
  --ob-fg-1:          #0B1620;
  --ob-fg-2:          #344758;
  --ob-fg-3:          #6B8197;
  --ob-fg-4:          #94A8BC;
  --ob-fg-on-dark-1:  #F4F8FB;
  --ob-fg-on-dark-2:  #B3C4D2;
  --ob-fg-on-dark-3:  #7B8E9F;
  --ob-fg-on-accent:  #FFFFFF;

  /* Borders */
  --ob-border:        #C7D4E0;
  --ob-border-strong: #94A8BC;
  --ob-border-dark:   #233140;
  --ob-border-focus:  #00B8D4;

  /* Status */
  --ob-critical:      #E1352B;
  --ob-critical-bg:   #FBE0DD;
  --ob-warning:       #F2C300;
  --ob-warning-bg:    #FCF3C5;
  --ob-success:       #1F8E5B;
  --ob-success-bg:    #D4ECDF;
  --ob-info:          #1B7AD6;
  --ob-info-bg:       #DCEBFA;

  /* Type families */
  --ob-font-display:  'Saira', 'Helvetica Neue', system-ui, sans-serif;
  --ob-font-sans:     'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --ob-font-mono:     'IBM Plex Mono', 'JetBrains Mono', ui-monospace, Menlo, monospace;

  /* Layout */
  --ob-container:     1280px;
  --ob-gutter:        24px;
  --ob-rail-w:        240px;
  --ob-topbar-h:      60px;

  /* Motion */
  --ob-ease:          cubic-bezier(0.2, 0, 0, 1);
  --ob-t-fast:        120ms;
  --ob-t-base:        160ms;
  --ob-t-slow:        240ms;

  /* Raw cyan accent retained for focus rings that reference the
     ramp value rather than the semantic alias (kept in step with
     UI-SPEC §5 `ob-cyan-500`). */
  --ob-cyan-500:      #00B8D4;

  /* ---------- Spacing scale (UI-SPEC §5 / global-variables.json) ----------
     Plan 01-05 Rule 2 auto-fix: spacing / radius / type-size tokens were
     present in design-variables/global-variables.json (Divi's runtime
     consumer) but never mirrored into :root CSS custom properties. Module-
     scoped CSS (BRAND-03, BRAND-04 onward) references these via var(--ob-*),
     so they must resolve in raw CSS independently of Divi's GVar inlining. */
  --ob-s-0:           0;
  --ob-s-1:           4px;
  --ob-s-2:           8px;
  --ob-s-3:           12px;
  --ob-s-4:           16px;
  --ob-s-5:           20px;
  --ob-s-6:           24px;
  --ob-s-7:           32px;
  --ob-s-8:           40px;
  --ob-s-9:           56px;
  --ob-s-10:          72px;
  --ob-s-11:          96px;
  --ob-s-12:          128px;

  /* Radii */
  --ob-r-none:        0;
  --ob-r-sm:          4px;
  --ob-r-md:          6px;
  --ob-r-lg:          12px;
  --ob-r-pill:        9999px;

  /* Type sizes */
  --ob-text-xs:       11px;
  --ob-text-sm:       13px;
  --ob-text-base:     15px;
  --ob-text-md:       16px;
  --ob-text-lg:       18px;
  --ob-text-xl:       22px;
  --ob-text-2xl:      30px;
  --ob-text-3xl:      36px;
  --ob-text-4xl:      48px;
  --ob-text-5xl:      64px;
  --ob-text-display:  88px;
}

/* ---------- 3. Global focus reset ---------------------------
   Per UI-SPEC §Focus + RESEARCH Pitfall 1. `:focus-visible`
   activates on every keyboard navigation (which is what the spec
   actually needs — "visible at all times" is a usability claim
   about discoverability, not about replacing mouse-focus styling).
   Pointer clicks still get the implicit browser-native focus,
   matching mockups.
   ------------------------------------------------------------ */

*:focus-visible {
  outline: 2px solid var(--ob-cyan-500) !important;
  outline-offset: 2px !important;
}

/* Divi override: higher specificity + hardcoded colour as fallback */
body a:focus-visible,
body button:focus-visible,
body [href]:focus-visible,
body [tabindex]:focus-visible {
  outline: 2px solid #00B8D4 !important;
  outline-offset: 2px !important;
}

/* ============================================================
   OberBuilt Global Chrome — header + footer styling
   ------------------------------------------------------------
   Appended by Plan 01-04 (.planning/phases/01-brand-foundation-
   identity-pages/01-04-PLAN.md, Task 4).

   Layers:
     4. .skip-link  — UI-SPEC §Keyboard: visually-hidden off-screen
                      by default, slides into the top-left corner on
                      :focus so keyboard users can jump past the
                      sticky header to <main id="content">.
     5. Active nav  — UI-SPEC §Color "Accent reserved-for" item 5:
                      current-menu-item gets a 2px cyan underline.
                      Selector accommodates both the WP-canonical
                      `.current-menu-item` and Divi-5.5.x's per-build
                      variants (`.current_page_item`); apply belt-and-
                      suspenders so the rule survives a Divi point-
                      release that flips the class.
     6. Sticky safe — RESEARCH Pitfall 1: Divi inlines cached styles
                      with higher specificity than a bare `.et_pb_section`
                      selector. Prefix with `body` to raise the rule
                      above Divi's defaults without resorting to
                      !important.
   ------------------------------------------------------------ */

/* ---------- 4. Skip-to-content link ------------------------- */
.skip-link {
  position: absolute;
  left: -9999px;
  top: -9999px;

  &:focus,
  &:focus-visible {
    left: 16px;
    top: 16px;
    z-index: 1000;
    padding: 8px 16px;
    background: var(--ob-paper);
    color: var(--ob-fg-1);
    outline: 2px solid var(--ob-cyan-500);
    outline-offset: 2px;
    text-decoration: none;
    font-family: var(--ob-font-sans);
    font-size: 14px;
    font-weight: 600;
    border-radius: 4px;
  }
}

/* ---------- 5. Active-page nav underline -------------------- */
.et_pb_menu_inner_container .current-menu-item > a,
.et_pb_menu_inner_container .current_page_item > a,
.ob-primary-nav .current-menu-item > a,
.ob-primary-nav .current_page_item > a {
  border-bottom: 2px solid var(--ob-cyan-500);
  padding-bottom: 4px;
}

/* ---------- 6. Sticky header safety ------------------------- */
body .et_pb_section.ob-global-header {
  position: sticky;
  top: 0;
  z-index: 100;
  backdrop-filter: blur(12px);
  background: rgba(255, 255, 255, 0.85);
  min-height: 60px;
  border-bottom: 1px solid var(--ob-border);
}

body .ob-global-header .ob-header-logo img {
  max-height: 28px;
  width: auto;
}

@media (max-width: 767px) {
  body .ob-global-header .ob-header-logo img {
    max-height: 24px;
  }
}

/* ---------- 7. Footer details ------------------------------- */
body .et_pb_section.ob-global-footer {
  background: var(--ob-bg-dark);
  color: var(--ob-fg-on-dark-1);
}

.ob-footer-col-title {
  font-family: var(--ob-font-display);
  font-size: 13px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ob-fg-on-dark-2);
  margin: 0 0 12px;
}

.ob-footer-list {
  list-style: none;
  padding: 0;
  margin: 0;

  & li {
    margin-bottom: 8px;
  }

  & a {
    color: var(--ob-fg-on-dark-1);
    text-decoration: none;
    font-size: 14px;
    line-height: 1.5;
    transition: color var(--ob-t-fast) var(--ob-ease);

    &:hover,
    &:focus-visible {
      color: var(--ob-cyan-500);
    }
  }
}

.ob-footer-tagline {
  margin: 0;
  color: var(--ob-fg-on-dark-2);
  font-family: var(--ob-font-sans);
  font-size: 14px;
  font-weight: 500;
}

.ob-footer-copyright {
  margin: 0;
  color: var(--ob-fg-on-dark-3);
  font-family: var(--ob-font-sans);
  font-size: 13px;
}

@media (max-width: 767px) {
  body .ob-global-footer .ob-footer-main .et_pb_column:nth-child(2) {
    margin-top: var(--ob-s-6);
  }

  .ob-footer-copyright {
    text-align: center;
  }
}

/* ────────────────────────────────────────────────────────────────────────
 * Plan 01-04 (REVISED) — child-theme PHP header.php / footer.php overrides.
 *
 * These rules style the markup emitted by child-theme/{header,footer}.php
 * directly (.ob-site-header / .ob-site-footer scopes). The earlier
 * .ob-global-header / .ob-footer-* rules above were authored against the
 * Theme Builder layout JSON shape and remain in place as documentation /
 * for the chance the operator ever re-imports a TB template — they don't
 * apply to the current .ob-site-* markup but cost nothing to keep.
 * ──────────────────────────────────────────────────────────────────────── */

/* Accessibility: visually-hidden helper for the skip link until focused. */
.screen-reader-text {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.ob-skip-link:focus,
.ob-skip-link:focus-visible {
  position: fixed;
  top: var(--ob-s-3);
  left: var(--ob-s-3);
  width: auto;
  height: auto;
  margin: 0;
  padding: var(--ob-s-2) var(--ob-s-4);
  overflow: visible;
  clip: auto;
  white-space: normal;
  z-index: 10000;
  background: var(--ob-paper);
  color: var(--ob-fg-1);
  border: 2px solid var(--ob-cyan-500);
  border-radius: var(--ob-r-sm);
  text-decoration: none;
  font-family: var(--ob-font-display);
  font-weight: 600;
}

/* ── Site header ───────────────────────────────────────────────────────── */
.ob-site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  height: 60px;
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--ob-steel-100);
}

.ob-site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ob-s-6);
  max-width: var(--ob-container);
  height: 100%;
  margin: 0 auto;
  padding: 0 var(--ob-s-5);
}

.ob-site-header__logo {
  display: inline-flex;
  align-items: center;
  text-decoration: none;
  outline-offset: 4px;
  border-radius: var(--ob-r-sm);
}

.ob-site-header__logo img {
  height: 36px;
  width: auto;
  display: block;
}

.ob-site-header__nav {
  display: block;
}

.ob-site-header__nav .ob-nav {
  display: flex;
  align-items: center;
  gap: var(--ob-s-6);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ob-site-header__nav .ob-nav > li > a,
.ob-site-header__nav .menu-item > a {
  display: inline-block;
  padding: 4px 0;
  font-family: var(--ob-font-display);
  font-size: 14px;
  font-weight: 600;
  color: var(--ob-fg-1);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  transition: color 160ms var(--ob-ease), border-color 160ms var(--ob-ease);
}

.ob-site-header__nav .ob-nav > li > a:hover,
.ob-site-header__nav .menu-item > a:hover,
.ob-site-header__nav .ob-nav > li > a:focus-visible,
.ob-site-header__nav .menu-item > a:focus-visible {
  color: var(--ob-cyan-500);
  border-bottom-color: var(--ob-cyan-500);
  outline: none;
}

.ob-site-header__nav .ob-nav__item--current > a,
.ob-site-header__nav .current-menu-item > a {
  border-bottom-color: var(--ob-cyan-500);
}

@media (max-width: 767px) {
  .ob-site-header__inner {
    padding: 0 var(--ob-s-4);
  }
  .ob-site-header__nav .ob-nav,
  .ob-site-header__nav ul {
    gap: var(--ob-s-4);
  }
  .ob-site-header__nav .ob-nav > li > a,
  .ob-site-header__nav .menu-item > a {
    font-size: 13px;
  }
}

/* ── Site footer ───────────────────────────────────────────────────────── */
.ob-site-footer {
  background: var(--ob-bg-dark);
  color: var(--ob-fg-on-dark-1);
  padding: var(--ob-s-11) 0 var(--ob-s-6);
  margin-top: 0; /* no gap — capabilities block abuts footer dark-on-dark */
}

.ob-site-footer__inner {
  display: grid;
  grid-template-columns: 1fr 2fr;
  gap: var(--ob-s-9);
  max-width: var(--ob-container);
  margin: 0 auto;
  padding: 0 var(--ob-s-5);
}

.ob-site-footer__brand {
  display: flex;
  flex-direction: column;
  gap: var(--ob-s-3);
}

.ob-site-footer__logo {
  height: 40px;
  width: auto;
  display: block;
}

.ob-site-footer__tagline {
  margin: 0;
  font-family: var(--ob-font-sans);
  font-size: 14px;
  font-weight: 500;
  color: var(--ob-fg-on-dark-2);
}

.ob-site-footer__nav {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--ob-s-6);
}

.ob-site-footer__col-title {
  margin: 0 0 var(--ob-s-3);
  font-family: var(--ob-font-display);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ob-fg-on-dark-2);
}

.ob-site-footer__col ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--ob-s-2);
}

.ob-site-footer__col a {
  display: inline-block;
  padding: 4px 0;
  font-family: var(--ob-font-sans);
  font-size: 15px;
  color: var(--ob-fg-on-dark-1);
  text-decoration: none;
  transition: color 160ms var(--ob-ease);
}

.ob-site-footer__col a:hover,
.ob-site-footer__col a:focus-visible {
  color: var(--ob-cyan-500);
}

.ob-site-footer__col a[aria-disabled="true"] {
  opacity: 0.5;
  cursor: not-allowed;
}

.ob-site-footer__legal {
  max-width: var(--ob-container);
  margin: var(--ob-s-9) auto 0;
  padding: var(--ob-s-5) var(--ob-s-5) 0;
  border-top: 1px solid var(--ob-steel-700, rgba(255, 255, 255, 0.08));
}

.ob-site-footer__legal p {
  margin: 0;
  font-family: var(--ob-font-sans);
  font-size: 13px;
  color: var(--ob-fg-on-dark-3);
}

@media (max-width: 767px) {
  .ob-site-footer__inner {
    grid-template-columns: 1fr;
    gap: var(--ob-s-7);
    padding: 0 var(--ob-s-4);
  }
  .ob-site-footer__nav {
    grid-template-columns: 1fr 1fr;
  }
  .ob-site-footer__legal {
    text-align: center;
    padding: var(--ob-s-5) var(--ob-s-4) 0;
  }
}

@media (max-width: 479px) {
  .ob-site-footer__nav {
    grid-template-columns: 1fr;
  }
}

/* ────────────────────────────────────────────────────────────────────────
 * Mobile header — hamburger toggle + Popover API mobile nav.
 *
 * Pattern: HTML5 Popover API (popovertarget + popover="auto"). The browser
 * manages show/hide, aria-expanded, Escape-to-close, light-dismiss, and
 * top-layer rendering — zero JS required. Tested support in 2026:
 *   Chrome 114+ · Edge 114+ · Firefox 125+ · Safari 17+
 *
 * Spec source: research after 01-04 pivot (2026-05-22) — see
 * .planning/phases/01-brand-foundation-identity-pages/01-04-SUMMARY.md
 * § Mobile menu pivot.
 * ──────────────────────────────────────────────────────────────────────── */

/* Hamburger toggle button — hidden on desktop, shown <768px */
.ob-site-header__toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--ob-r-sm);
  color: var(--ob-fg-1);
  cursor: pointer;
  outline-offset: 2px;
  transition: background 160ms var(--ob-ease);
}

.ob-site-header__toggle:hover,
.ob-site-header__toggle:focus-visible {
  background: var(--ob-steel-50);
  outline: 2px solid var(--ob-cyan-500);
  outline-offset: -2px;
}

/* Three-line hamburger icon — CSS-only, no SVG/JS deps */
.ob-hamburger {
  display: inline-flex;
  flex-direction: column;
  justify-content: space-between;
  width: 22px;
  height: 16px;
}

.ob-hamburger span {
  display: block;
  width: 100%;
  height: 2px;
  background: currentColor;
  border-radius: 1px;
}

/* ── Mobile nav popover (full-screen overlay) ──────────────────────────── */

/*
 * popover="auto" elements live in the top-layer when open. Browsers reset
 * lots of defaults on `[popover]`, so we override aggressively.
 *
 * 2026-05-22 redesign: switched from 360px right-edge slide-in to full-screen
 * centered overlay (Apple/Stripe/Linear pattern). Reasons:
 *   - Right-edge panel left awkward dead space on the left where the
 *     backdrop was visible behind a translucent panel
 *   - Centered items on a full overlay have larger, easier thumb targets
 *   - Cleaner reduced-motion handling (no slide direction to consider)
 *
 * `background-color: #FFFFFF !important` is intentional and belt-and-suspenders:
 *   - Defeats any browser-default `Canvas` system color
 *   - Overrides Divi parent's stray `[popover]` rules if any
 *   - Solves the first-paint translucency the operator reported
 */
.ob-mobile-nav {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  background-color: #FFFFFF !important;
  color: var(--ob-fg-1);
  border: none;
  overflow: hidden;
}

/* Backdrop is fully covered by the panel itself, but keep a fallback in case
   100vh becomes 100svh and there's a sliver of background visible. */
.ob-mobile-nav::backdrop {
  background: rgba(11, 22, 32, 0.6);
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
}

.ob-mobile-nav__head {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 60px; /* match .ob-site-header height */
  padding: 0 var(--ob-s-5);
  border-bottom: 1px solid var(--ob-steel-100);
}

.ob-mobile-nav__title {
  font-family: var(--ob-font-display);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ob-fg-2);
}

.ob-mobile-nav__close {
  position: absolute;
  top: 50%;
  right: var(--ob-s-3);
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  padding: 0;
  background: transparent;
  border: none;
  border-radius: var(--ob-r-sm);
  color: var(--ob-fg-1);
  cursor: pointer;
  outline-offset: 2px;
}

.ob-mobile-nav__close:hover,
.ob-mobile-nav__close:focus-visible {
  background: var(--ob-steel-50);
  outline: 2px solid var(--ob-cyan-500);
  outline-offset: -2px;
}

.ob-mobile-nav__list,
.ob-mobile-nav ul {
  list-style: none;
  margin: 0;
  padding: var(--ob-s-7) var(--ob-s-5);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: flex-start;
  gap: var(--ob-s-2);
  height: calc(100vh - 60px);
  overflow-y: auto;
  max-width: 480px;
  margin-inline: auto;
}

.ob-mobile-nav__list li,
.ob-mobile-nav .menu-item {
  display: block;
}

.ob-mobile-nav__list li > a,
.ob-mobile-nav .menu-item > a {
  display: block;
  padding: var(--ob-s-4) var(--ob-s-4);
  font-family: var(--ob-font-display);
  font-size: 20px;
  font-weight: 600;
  color: var(--ob-fg-1);
  text-decoration: none;
  text-align: center;
  border-radius: var(--ob-r-md);
  border: 1px solid transparent;
  transition: background 160ms var(--ob-ease), color 160ms var(--ob-ease), border-color 160ms var(--ob-ease);
}

.ob-mobile-nav__list li > a:hover,
.ob-mobile-nav .menu-item > a:hover,
.ob-mobile-nav__list li > a:focus-visible,
.ob-mobile-nav .menu-item > a:focus-visible,
.ob-mobile-nav__list li > a:active,
.ob-mobile-nav .menu-item > a:active {
  background: var(--ob-steel-50);
  color: var(--ob-cyan-500);
  border-color: var(--ob-cyan-500);
  outline: none;
}

.ob-mobile-nav .current-menu-item > a,
.ob-mobile-nav .ob-nav__item--current > a {
  color: var(--ob-cyan-500);
  border-color: var(--ob-cyan-500);
}

/* Fade-in animation (transform-only) — no opacity flicker. */
@media (prefers-reduced-motion: no-preference) {
  .ob-mobile-nav {
    transition: transform 200ms var(--ob-ease);
  }
  @starting-style {
    .ob-mobile-nav:popover-open {
      transform: translateY(-8px);
    }
  }
  .ob-mobile-nav::backdrop {
    transition: background 200ms var(--ob-ease);
  }
  @starting-style {
    .ob-mobile-nav:popover-open::backdrop {
      background: rgba(11, 22, 32, 0);
    }
  }
}

/* Mobile breakpoint: hide inline desktop nav, show hamburger. */
@media (max-width: 767px) {
  .ob-site-header__nav {
    display: none;
  }
  .ob-site-header__toggle {
    display: inline-flex;
  }
}

/* ────────────────────────────────────────────────────────────────────────
 * Logo aspect-ratio enforcement
 *
 * Divi parent ships `img { max-width: 100%; height: auto; }` rules at
 * varying specificities. When .ob-site-footer was inside a non-Divi
 * container, the `height: auto` was winning and the browser computed
 * width from the intrinsic 940×250, then a higher-specificity selector
 * stretched the rendered width to fill the container.
 *
 * Fix: lock both height AND aspect-ratio on the logo elements with
 * !important. The aspect-ratio property forces the browser to compute
 * width from height regardless of which CSS competes.
 * ──────────────────────────────────────────────────────────────────────── */

.ob-site-header__logo img {
  display: block !important;
  height: 36px !important;
  width: auto !important;
  max-width: none !important;
  aspect-ratio: 773 / 124;
  object-fit: contain;
}

.ob-site-footer__logo {
  display: block !important;
  height: 40px !important;
  width: auto !important;
  max-width: none !important;
  aspect-ratio: 773 / 124;
  object-fit: contain;
}

/* ============================================================
   Plan 01-07 — Dyno-Graph Hero: full-bleed + 2-col centered
   ------------------------------------------------------------
   Three problems solved here:
   1. Divi .et_flex_section adds padding:56px 0 → white gap above hero.
   2. .et_pb_row caps at max-width:1080px → dark bg doesn't reach edges.
   3. Module CSS uses flex-direction:column → copy+chart stack vertically
      instead of sitting side-by-side (copy left, chart right).

   Fix:
   - Zero all Divi wrapper padding/max-width for .ob-hero-section.
   - Override .ob-hero to grid 2-col with the inset-padding centering
     trick: padding-inline: max(gutter, 50%−640px) keeps full-width
     dark background while centering content at ≤1280px.
   ============================================================ */

/* 1. Strip Divi section/row/column padding */
.ob-hero-section.et_pb_section,
.ob-hero-section.et_flex_section {
  padding: 0 !important;
  margin: 0 !important;
}

.ob-hero-section .et_pb_row,
.ob-hero-section .et_flex_row {
  max-width: 100% !important;
  width: 100% !important;
  padding: 0 !important;
  margin: 0 !important;
  --horizontal-gap: 0px;
}

.ob-hero-section .et_pb_column,
.ob-hero-section .et_flex_column {
  padding: 0 !important;
  margin: 0 !important;
}

/* 2. Two-column layout, centered within full-width dark section */
.ob-hero {
  display: grid !important;
  grid-template-columns: 1fr 1.25fr !important;
  align-items: center !important;
  gap: var(--ob-s-10) !important;
  /* padding-inline centers content at max 1280px while bg stays full-width */
  padding: var(--ob-s-11) max(var(--ob-gutter), calc(50% - 640px)) !important;
  flex-direction: unset !important; /* override module flex-direction:column */
}

/* Remove the old per-child max-width cap — columns manage their own width */
.ob-hero > * {
  max-width: none !important;
  min-width: 0;
}

/* Mobile: stack copy above chart */
@media (max-width: 900px) {
  .ob-hero {
    grid-template-columns: 1fr !important;
    padding: var(--ob-s-9) var(--ob-s-4) !important;
    gap: var(--ob-s-6) !important;
  }
}

/* ============================================================
   Plan 01-07 — "What we make" capability block (dark theme)
   (.ob-capabilities*)
   Matches Claude Design §04 — dark navy bg, cyan eyebrow,
   white Saira heading. Section gets id="capabilities" +
   class="ob-capabilities" via Divi htmlAttributes.
   ============================================================ */

/* Strip Divi's 56px section padding; own padding owns top/bottom only.
   No side padding here — row handles horizontal centering at ob-container. */
.ob-capabilities.et_pb_section,
.ob-capabilities.et_flex_section {
  background: var(--ob-bg-dark) !important;
  padding: var(--ob-s-12) 0 !important;
  align-items: stretch !important; /* row must fill full width */
}

/* Center row at 1280px (not Divi's 1080px default) */
.ob-capabilities .et_pb_row,
.ob-capabilities .et_flex_row {
  max-width: var(--ob-container) !important;
  width: 100% !important;
  padding: 0 var(--ob-gutter) !important;
  margin: 0 auto !important;
  --horizontal-gap: var(--ob-s-10); /* 72px column gap between text + image */
}

.ob-capabilities .et_pb_column,
.ob-capabilities .et_flex_column {
  padding: 0 !important;
  margin: 0 !important;
}

/* Keep generic selector for fallback if Divi class names ever change */
.ob-capabilities {
  background: var(--ob-bg-dark) !important;
}

/* Eyebrow label — <p class="ob-capabilities__eyebrow"> */
.ob-capabilities .ob-capabilities__eyebrow,
.ob-capabilities .et_pb_text_inner .ob-capabilities__eyebrow {
  font-family: var(--ob-font-display);
  font-weight: 600;
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ob-cyan-500) !important;
  margin-bottom: var(--ob-s-3);
  line-height: 1;
}

/* Heading */
.ob-capabilities h2,
.ob-capabilities .et_pb_text_inner h2 {
  font-family: var(--ob-font-display) !important;
  font-weight: 600 !important;
  font-size: clamp(2rem, 3.5vw, var(--ob-text-4xl)) !important;
  line-height: 1.15 !important;
  letter-spacing: -0.02em !important;
  color: var(--ob-fg-on-dark-1) !important;
  margin-bottom: var(--ob-s-5);
}

/* Body paragraphs (exclude eyebrow) */
.ob-capabilities p:not(.ob-capabilities__eyebrow),
.ob-capabilities .et_pb_text_inner p:not(.ob-capabilities__eyebrow) {
  font-family: var(--ob-font-sans);
  font-size: var(--ob-text-base);
  line-height: 1.6;
  color: var(--ob-fg-on-dark-2) !important;
  margin-bottom: var(--ob-s-4);
  max-width: 56ch;
}

/* Bullet list */
.ob-capabilities ul,
.ob-capabilities .et_pb_text_inner ul {
  list-style: none;
  padding-left: 0;
  margin: var(--ob-s-2) 0 0;
}

.ob-capabilities ul li,
.ob-capabilities .et_pb_text_inner ul li {
  font-family: var(--ob-font-sans);
  font-size: var(--ob-text-base);
  color: var(--ob-fg-on-dark-2) !important;
  line-height: 1.5;
  margin-bottom: var(--ob-s-2);
  padding-left: var(--ob-s-5);
  position: relative;
}

.ob-capabilities ul li::before,
.ob-capabilities .et_pb_text_inner ul li::before {
  content: '·';
  position: absolute;
  left: 0;
  color: var(--ob-cyan-500);
  font-weight: 700;
  font-size: 1.2em;
}

/* Image slot — dark placeholder until real photo is swapped in */
.ob-capabilities .et_pb_image,
.ob-capabilities .et_pb_image_wrap {
  aspect-ratio: 4/3;
  overflow: hidden;
  border-radius: var(--ob-r-md);
  background: var(--ob-bg-dark-card);
  display: block;
}

.ob-capabilities .et_pb_image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Remove Divi light-layout text overrides inside dark section */
.ob-capabilities .et_pb_text.et_pb_bg_layout_light,
.ob-capabilities .et_pb_module.et_pb_bg_layout_light {
  color: var(--ob-fg-on-dark-2) !important;
}

@media (max-width: 768px) {
  .ob-capabilities.et_pb_section,
  .ob-capabilities.et_flex_section {
    padding: var(--ob-s-9) 0 !important;
  }
  .ob-capabilities .et_pb_row,
  .ob-capabilities .et_flex_row {
    padding: 0 var(--ob-s-4) !important;
  }
}

/* ============================================================
   Plans 01-08 + 01-09 — /about-us/ and /contact-us/
   ============================================================ */

/* ── About page hero ─────────────────────────────────────────── */
.ob-about-hero.et_pb_section,
.ob-about-hero.et_flex_section {
  background: var(--ob-bg) !important;
  padding: var(--ob-s-11) 0 !important;
}

.ob-about-hero .et_pb_row,
.ob-about-hero .et_flex_row {
  max-width: var(--ob-container) !important;
  width: 100% !important;
  padding: 0 var(--ob-gutter) !important;
  margin: 0 auto !important;
}

.ob-about-hero .et_pb_column,
.ob-about-hero .et_flex_column {
  padding: 0 !important;
}

.ob-about-h1 {
  font-family: var(--ob-font-display);
  font-weight: 600;
  font-size: clamp(2rem, 4vw, var(--ob-text-4xl));
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--ob-fg-1);
  margin-bottom: var(--ob-s-5);
}

.ob-about-sub {
  font-family: var(--ob-font-sans);
  font-size: var(--ob-text-lg);
  line-height: 1.6;
  color: var(--ob-fg-2);
  max-width: 60ch;
}

/* ── About page content sections ─────────────────────────────── */
.ob-about-section.et_pb_section,
.ob-about-section.et_flex_section {
  background: var(--ob-bg) !important;
  padding: var(--ob-s-11) 0 !important;
  border-top: 1px solid var(--ob-steel-100);
}

.ob-about-section .et_pb_row,
.ob-about-section .et_flex_row {
  max-width: var(--ob-container) !important;
  width: 100% !important;
  padding: 0 var(--ob-gutter) !important;
  margin: 0 auto !important;
  --horizontal-gap: var(--ob-s-10);
}

.ob-about-section .et_pb_column,
.ob-about-section .et_flex_column {
  padding: 0 !important;
  margin: 0 !important;
}

/* Image column — aspect-ratio 4:3 placeholder */
.ob-about-section .et_pb_image,
.ob-about-section .et_pb_image_wrap {
  aspect-ratio: 4/3;
  overflow: hidden;
  border-radius: var(--ob-r-lg);
  background: var(--ob-steel-100);
  display: block;
  width: 100%;
}

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

.ob-about-section h2 {
  font-family: var(--ob-font-display);
  font-weight: 600;
  font-size: clamp(1.75rem, 3vw, var(--ob-text-3xl));
  line-height: 1.25;
  letter-spacing: -0.02em;
  margin-bottom: var(--ob-s-5);
  color: var(--ob-fg-1);
}

.ob-about-section p {
  font-family: var(--ob-font-sans);
  font-weight: 400;
  font-size: var(--ob-text-base);
  line-height: 1.5;
  color: var(--ob-fg-2);
  margin-bottom: var(--ob-s-4);
}

@media (max-width: 768px) {
  .ob-about-hero.et_pb_section,
  .ob-about-hero.et_flex_section {
    padding: var(--ob-s-9) 0 !important;
  }
  .ob-about-hero .et_pb_row,
  .ob-about-hero .et_flex_row {
    padding: 0 var(--ob-s-4) !important;
  }
  .ob-about-section.et_pb_section,
  .ob-about-section.et_flex_section {
    padding: var(--ob-s-9) 0 !important;
  }
  .ob-about-section .et_pb_row,
  .ob-about-section .et_flex_row {
    padding: 0 var(--ob-s-4) !important;
    flex-direction: column !important;
  }
  /* Flipped section: on mobile image always comes after text */
  .ob-about-section--flipped .et_pb_column:first-child {
    order: 2;
  }
  .ob-about-section--flipped .et_pb_column:last-child {
    order: 1;
  }
}

/* ============================================================
   About page — premium depth layer (dad feedback: "very light,
   need background colors, motion, something expensive")
   ============================================================ */

/* ── 1. Dark hero ─────────────────────────────────────────────
   Override the existing --ob-bg hero with full ink background.
   !important beats the specificity of the block above.        */
.ob-about-hero.et_pb_section,
.ob-about-hero.et_flex_section {
  background: var(--ob-ink) !important;
}

.ob-about-h1 {
  color: var(--ob-paper) !important;
}

.ob-about-sub {
  color: var(--ob-steel-50) !important;
}

/* ── 2. Alternating section rhythm ───────────────────────────
   .ob-about-section (sections 1 & 3 — no --flipped modifier)
   get a very subtle steel-50 tint.  --flipped stays at --ob-bg.
   nth-of-type cannot target by class reliably across Divi's
   wrapper structure, so we use the modifier absence directly.  */
.ob-about-section:not(.ob-about-section--flipped).et_pb_section,
.ob-about-section:not(.ob-about-section--flipped).et_flex_section {
  background: var(--ob-steel-50) !important;
}

/* ── 3. Section 3 dark accent ("Decades of experience") ──────
   The last .ob-about-section before the footer.  Divi wraps
   sections in order, so :last-of-type on the section element is
   unreliable — instead we use a dedicated modifier class.  The
   HTML already carries .ob-about-section on section 3, so we
   stack a second class .ob-about-section--dark there via the
   theme builder (or hand-edit).  If the builder cannot add a
   second class easily, the :last-child rule below catches it
   inside the page wrapper.  Both selectors are written so
   whichever applies wins (specificity is equal).              */
.ob-about-section--dark.et_pb_section,
.ob-about-section--dark.et_flex_section {
  background: var(--ob-steel-800) !important;
  border-top: none !important;
}

/* Fallback: last .ob-about-section inside the main content div.
   Works when the section is literally the final sibling before
   the footer section, which is the current page structure.    */
.ob-about-section:not(.ob-about-section--flipped):last-of-type.et_pb_section,
.ob-about-section:not(.ob-about-section--flipped):last-of-type.et_flex_section {
  background: var(--ob-steel-800) !important;
  border-top: none !important;
}

/* Text on dark section 3 */
.ob-about-section--dark h2,
.ob-about-section:not(.ob-about-section--flipped):last-of-type h2 {
  color: var(--ob-paper) !important;
}

.ob-about-section--dark p,
.ob-about-section:not(.ob-about-section--flipped):last-of-type p {
  color: var(--ob-steel-50) !important;
}

/* ── 4. Cyan accent rule on dark sections ─────────────────────
   3 px left border with padding on h2 inside any dark bg section.
   Applied to section 3 (--dark / last-of-type) and to nothing
   else — the steel-50 tinted sections keep plain headings.    */
.ob-about-section--dark h2,
.ob-about-section:not(.ob-about-section--flipped):last-of-type h2 {
  border-left: 3px solid var(--ob-cyan-500);
  padding-left: 1.5rem;
}

/* ── 5. Fade-up entrance animation ───────────────────────────  */
@keyframes ob-fade-up {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.ob-about-h1 {
  animation: ob-fade-up 0.6s ease-out both;
}

.ob-about-sub {
  animation: ob-fade-up 0.6s ease-out 0.15s both;
}

.ob-about-section h2 {
  animation: ob-fade-up 0.6s ease-out both;
}

@media (prefers-reduced-motion: reduce) {
  .ob-about-h1,
  .ob-about-sub,
  .ob-about-section h2 {
    animation: none !important;
  }
}

/* ── Contact page ────────────────────────────────────────────── */
.ob-contact-hero.et_pb_section,
.ob-contact-hero.et_flex_section {
  background: var(--ob-bg) !important;
  padding: var(--ob-s-11) 0 !important;
}

.ob-contact-form-section.et_pb_section,
.ob-contact-form-section.et_flex_section {
  background: var(--ob-bg) !important;
  padding: var(--ob-s-10) 0 var(--ob-s-12) !important;
}

.ob-contact-hero .et_pb_row,
.ob-contact-hero .et_flex_row,
.ob-contact-form-section .et_pb_row,
.ob-contact-form-section .et_flex_row {
  max-width: var(--ob-container) !important;
  width: 100% !important;
  padding: 0 var(--ob-gutter) !important;
  margin: 0 auto !important;
}

.ob-contact-hero .et_pb_column,
.ob-contact-hero .et_flex_column,
.ob-contact-form-section .et_pb_column,
.ob-contact-form-section .et_flex_column {
  padding: 0 !important;
}

.ob-contact-h1 {
  font-family: var(--ob-font-display);
  font-weight: 600;
  font-size: clamp(2rem, 4vw, var(--ob-text-4xl));
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--ob-fg-1);
  margin-bottom: var(--ob-s-5);
}

.ob-contact-sub {
  font-family: var(--ob-font-sans);
  font-size: var(--ob-text-lg);
  line-height: 1.6;
  color: var(--ob-fg-2);
  max-width: 56ch;
}

/* Divi contact form input styling */
/* form fills its column naturally — no artificial width cap */
.ob-contact-form-section .et_pb_contact_form_container {
  width: 100%;
}

.ob-contact-form-section .et_pb_contact_field label {
  font-family: var(--ob-font-display);
  font-weight: 600;
  font-size: var(--ob-text-sm);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ob-fg-2);
  margin-bottom: var(--ob-s-1);
  display: block;
}

.ob-contact-form-section .et_pb_contact_field input,
.ob-contact-form-section .et_pb_contact_field textarea,
.ob-contact-form-section .et_pb_contact_field select {
  font-family: var(--ob-font-sans);
  font-size: var(--ob-text-base);
  color: var(--ob-fg-1);
  background: var(--ob-bg-card);
  border: 1px solid var(--ob-border);
  border-radius: var(--ob-r-md);
  padding: var(--ob-s-3) var(--ob-s-4);
  width: 100%;
  transition: border-color 160ms var(--ob-ease), box-shadow 160ms var(--ob-ease);
}

.ob-contact-form-section .et_pb_contact_field input:focus,
.ob-contact-form-section .et_pb_contact_field textarea:focus,
.ob-contact-form-section .et_pb_contact_field select:focus {
  border-color: var(--ob-cyan-500);
  box-shadow: 0 0 0 3px rgba(0, 184, 212, 0.15);
  outline: none;
}

/* Submit button — target the actual rendered class (et_pb_contact_submit, not _form_submit).
   !important on color + background to override Divi's dynamic inline styles (#2ea3f2 default). */
.ob-contact-form-section .et_pb_contact_submit,
.ob-contact-form-section .et_pb_contact_form_submit,
.ob-contact-form-section .et_contact_bottom_container input[type="submit"] {
  font-family: var(--ob-font-display) !important;
  font-weight: 600 !important;
  font-size: var(--ob-text-base) !important;
  background: var(--ob-brand) !important;
  color: #fff !important;
  border: none !important;
  border-radius: var(--ob-r-md) !important;
  padding: var(--ob-s-3) var(--ob-s-7) !important;
  cursor: pointer;
  transition: background 160ms var(--ob-ease);
}

.ob-contact-form-section .et_pb_contact_submit:hover,
.ob-contact-form-section .et_pb_contact_form_submit:hover,
.ob-contact-form-section .et_contact_bottom_container input[type="submit"]:hover {
  background: var(--ob-brand-hover) !important;
}

/* Honeypot field — visually hidden, kept out of tab order via CSS */
.et_pb_contact_field:has(input[name*="_website_"]) {
  display: none !important;
  visibility: hidden !important;
}

/* Contact info panel (right column of form section) */
/* --ob-cyan-500 (#00B8D4) on --ob-bg (#F2F6FA) = 2.19:1 — fails WCAG AA.
   Use --ob-brand (#0E5FB3) = ~9.8:1 contrast instead. */
.ob-contact-info-label {
  font-family: var(--ob-font-display);
  font-weight: 600;
  font-size: var(--ob-text-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ob-brand);
  margin-bottom: var(--ob-s-6);
}

.ob-contact-info-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: var(--ob-s-6);
}

.ob-contact-info-item {
  display: flex;
  flex-direction: column;
  gap: var(--ob-s-1);
}

.ob-contact-info-key {
  font-family: var(--ob-font-sans);
  font-weight: 600;
  font-size: var(--ob-text-sm);
  color: var(--ob-fg-1);
}

.ob-contact-info-val {
  font-family: var(--ob-font-sans);
  font-size: var(--ob-text-base);
  color: var(--ob-fg-2);
  text-decoration: none;
}

a.ob-contact-info-val:hover {
  color: var(--ob-cyan-500);
  text-decoration: underline;
}

/* ============================================================
   Contact page — premium dark treatment
   Plan 01-13: dark hero + dark info panel + motion
   ============================================================ */

/* --- 1. Dark hero section ---------------------------------- */
.ob-contact-hero.et_pb_section,
.ob-contact-hero.et_flex_section,
.ob-contact-hero {
  background: var(--ob-ink) !important;
}

.ob-contact-hero h1,
.ob-contact-hero h2,
.ob-contact-hero h3 {
  color: var(--ob-paper) !important;
}

.ob-contact-hero p,
.ob-contact-hero .et_pb_text,
.ob-contact-hero .et_pb_text_inner {
  color: var(--ob-steel-50) !important;
}

/* --- 2. Contact form inputs on dark ------------------------ */
.ob-contact-hero input[type="text"],
.ob-contact-hero input[type="email"],
.ob-contact-hero input[type="tel"],
.ob-contact-hero textarea,
.ob-contact-hero .et_pb_contact_field input,
.ob-contact-hero .et_pb_contact_field textarea,
.ob-contact-hero .et_pb_contact_field select {
  background: rgba(255, 255, 255, 0.07) !important;
  border: 1px solid rgba(255, 255, 255, 0.2) !important;
  color: var(--ob-paper) !important;
  border-radius: var(--ob-r-md) !important;
}

.ob-contact-hero input[type="text"]::placeholder,
.ob-contact-hero input[type="email"]::placeholder,
.ob-contact-hero input[type="tel"]::placeholder,
.ob-contact-hero textarea::placeholder {
  color: rgba(255, 255, 255, 0.45) !important;
}

.ob-contact-hero .et_pb_contact_field input:focus,
.ob-contact-hero .et_pb_contact_field textarea:focus,
.ob-contact-hero .et_pb_contact_field select:focus {
  border-color: var(--ob-cyan-500) !important;
  box-shadow: 0 0 0 3px rgba(0, 184, 212, 0.2) !important;
  outline: none !important;
}

.ob-contact-hero .et_pb_contact_field label {
  color: var(--ob-steel-50) !important;
}

/* --- 3. Submit button on dark hero ------------------------- */
.ob-contact-hero .et_pb_contact_submit,
.ob-contact-hero .et_pb_contact_form_submit,
.ob-contact-hero .et_contact_bottom_container input[type="submit"],
.ob-contact-hero button[type="submit"] {
  background: var(--ob-cyan-500) !important;
  color: var(--ob-ink) !important;
  border: none !important;
  font-family: var(--ob-font-display) !important;
  font-weight: 600 !important;
  border-radius: var(--ob-r-md) !important;
  transition: background 160ms var(--ob-ease) !important;
}

.ob-contact-hero .et_pb_contact_submit:hover,
.ob-contact-hero .et_pb_contact_form_submit:hover,
.ob-contact-hero .et_contact_bottom_container input[type="submit"]:hover,
.ob-contact-hero button[type="submit"]:hover {
  background: var(--ob-cyan-600) !important;
}

/* --- 4. Dark info panel (form section) -------------------- */
.ob-contact-form-section.et_pb_section,
.ob-contact-form-section.et_flex_section,
.ob-contact-form-section {
  background: var(--ob-steel-800) !important;
}

.ob-contact-form-section h1,
.ob-contact-form-section h2,
.ob-contact-form-section h3,
.ob-contact-form-section h4 {
  color: var(--ob-paper) !important;
}

.ob-contact-form-section p,
.ob-contact-form-section .et_pb_text,
.ob-contact-form-section .et_pb_text_inner {
  color: var(--ob-steel-50) !important;
}

.ob-contact-form-section .ob-contact-info-key {
  color: var(--ob-paper) !important;
}

.ob-contact-form-section .ob-contact-info-val {
  color: var(--ob-steel-50) !important;
}

/* Form labels on dark panel */
.ob-contact-form-section .et_pb_contact_field label {
  color: var(--ob-steel-50) !important;
}

/* Form inputs on dark panel */
.ob-contact-form-section .et_pb_contact_field input,
.ob-contact-form-section .et_pb_contact_field textarea,
.ob-contact-form-section .et_pb_contact_field select {
  background: rgba(255, 255, 255, 0.07) !important;
  border: 1px solid rgba(255, 255, 255, 0.2) !important;
  color: var(--ob-paper) !important;
}

.ob-contact-form-section .et_pb_contact_field input::placeholder,
.ob-contact-form-section .et_pb_contact_field textarea::placeholder {
  color: rgba(255, 255, 255, 0.45) !important;
}

/* Submit button in form section on dark panel */
.ob-contact-form-section .et_pb_contact_submit,
.ob-contact-form-section .et_pb_contact_form_submit,
.ob-contact-form-section .et_contact_bottom_container input[type="submit"] {
  background: var(--ob-cyan-500) !important;
  color: var(--ob-ink) !important;
  border: none !important;
}

.ob-contact-form-section .et_pb_contact_submit:hover,
.ob-contact-form-section .et_pb_contact_form_submit:hover,
.ob-contact-form-section .et_contact_bottom_container input[type="submit"]:hover {
  background: var(--ob-cyan-600) !important;
}

/* Hover link colour on dark */
.ob-contact-form-section a.ob-contact-info-val:hover {
  color: var(--ob-cyan-500) !important;
}

/* --- 4a. Cyan eyebrow on info label ----------------------- */
.ob-contact-form-section .ob-contact-info-label,
.ob-contact-info-label {
  color: var(--ob-cyan-500) !important;
  letter-spacing: 0.08em !important;
}

/* --- 5. Subtle entrance motion ---------------------------- */
@keyframes ob-contact-fade-up {
  from {
    opacity: 0;
    transform: translateY(20px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.ob-contact-hero h1 {
  animation: ob-contact-fade-up 0.5s ease-out both;
  animation-delay: 0s;
}

.ob-contact-hero p {
  animation: ob-contact-fade-up 0.5s ease-out both;
  animation-delay: 0.1s;
}

.ob-contact-form-section {
  animation: ob-contact-fade-up 0.5s ease-out both;
  animation-delay: 0.2s;
}

@media (prefers-reduced-motion: reduce) {
  .ob-contact-hero h1,
  .ob-contact-hero p,
  .ob-contact-form-section {
    animation: none !important;
  }
}

/* ============================================================
   End contact premium dark treatment
   ============================================================ */

@media (max-width: 768px) {
  .ob-contact-hero.et_pb_section,
  .ob-contact-hero.et_flex_section {
    padding: var(--ob-s-9) 0 !important;
  }
  .ob-contact-hero .et_pb_row,
  .ob-contact-hero .et_flex_row,
  .ob-contact-form-section .et_pb_row,
  .ob-contact-form-section .et_flex_row {
    padding: 0 var(--ob-s-4) !important;
  }
}

/* ============================================================
   Contact + About page — background treatment (v0.1.5)

   Source: Claude Design handoff "Contact Background Treatment"
   Opacities dialled back ~25% from original spec ("less techno").

   Contact page layer map:
     [A · base]    hero surface — ambient raking light
     [A · traces]  diagonal hairlines, masked → fade at seam
     [A/B · seam]  cyan hairline pinned to hero bottom edge
     [B · base]    form surface — ink→steel blend from seam
     [B · grid]    sensor dot-grid, masked → fade in from seam
     [B · traces]  continued hairlines, masked → fade from seam

   About page dark sections get the same base + traces treatment
   (no dot-grid — that's specific to the data-panel context).
   ============================================================ */

/* ── Shared blend token ──────────────────────────────────── */
.ob-contact-hero,
.ob-contact-form-section {
  --c-seam: #0F1B26;  /* midpoint blend at hero / form join */
}

/* ── [A · base] Hero surface ─────────────────────────────── */
.ob-contact-hero.et_pb_section,
.ob-contact-hero.et_flex_section,
.ob-contact-hero {
  position: relative !important;
  isolation: isolate;
  overflow: hidden !important;
  background:
    radial-gradient(125% 150% at 80% -25%, rgba(0,184,212,0.09), transparent 55%),
    radial-gradient(90%  130% at -5%  0%,  rgba(14,95,179,0.09), transparent 52%),
    linear-gradient(180deg, #0C1925 0%, var(--ob-ink) 60%),
    var(--ob-ink) !important;
}

/* ── [A · orb] Breathing blue orb — upper half of hero ──────
   Replaces the trace hairlines (0.038 opacity, imperceptible).
   The seam ::after stays completely untouched.               */
.ob-contact-hero::before {
  content: '';
  position: absolute;
  width: 72%;
  height: 90%;
  top: -5%;
  left: -5%;
  background: radial-gradient(ellipse 55% 55% at center,
    rgba(14, 95, 179, 0.48) 0%,
    transparent 70%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-a 26s ease-in-out infinite alternate;
  animation-delay: -17s;
  will-change: transform, opacity;
}

/* ── [A/B · seam] Machined join ────────────────────────────
   Cyan hairline pinned to hero bottom, glow burst + bevel.
   This is the deliberate design element that kills the
   accidental colour-delta line.                           */
.ob-contact-hero::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0;
  height: 2px; z-index: 1; pointer-events: none;
  background: linear-gradient(90deg,
    transparent                  0%,
    rgba(0,184,212,0.15)        14%,
    rgba(0,184,212,0.95)        50%,
    rgba(0,184,212,0.15)        86%,
    transparent                100%);
  box-shadow:
    0 0 46px 6px rgba(0,184,212,0.22),  /* glow burst  */
    0 0 14px 1px rgba(0,184,212,0.38),  /* tight core  */
    0  1px 0  0   rgba(244,248,251,0.08); /* milled bevel */
}

/* ── [B · base] Form section surface ────────────────────── */
.ob-contact-form-section.et_pb_section,
.ob-contact-form-section.et_flex_section,
.ob-contact-form-section {
  position: relative !important;
  isolation: isolate;
  overflow: hidden !important;
  margin-top: 0 !important;
  background:
    radial-gradient(120% 70% at 50% 0%, rgba(0,184,212,0.04), transparent 60%),
    linear-gradient(180deg,
      var(--c-seam)       0%,
      var(--ob-steel-800) 16%,
      var(--ob-steel-800) 86%,
      #122029            100%),
    var(--ob-steel-800) !important;
}

/* ── [B · grid] Sensor dot-grid, fade in from seam ──────── */
.ob-contact-form-section::before {
  content: "";
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background-image: radial-gradient(rgba(182,240,247,0.05) 1px, transparent 1.6px);
  background-size: 22px 22px;
  -webkit-mask-image: linear-gradient(180deg,
    rgba(0,0,0,0.80) 0%, rgba(0,0,0,0.14) 40%, transparent 82%);
          mask-image: linear-gradient(180deg,
    rgba(0,0,0,0.80) 0%, rgba(0,0,0,0.14) 40%, transparent 82%);
}

/* ── [B · orb] Breathing cyan orb — lower-right of form section
   Replaces the trace hairlines (0.028 opacity, imperceptible).
   The dot-grid ::before stays completely untouched.          */
.ob-contact-form-section::after {
  content: '';
  position: absolute;
  width: 65%;
  height: 80%;
  bottom: -5%;
  right: -5%;
  background: radial-gradient(ellipse 52% 52% at center,
    rgba(0, 184, 212, 0.36) 0%,
    transparent 68%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-b 28s ease-in-out infinite alternate;
  animation-delay: -9s;
  will-change: transform, opacity;
}

/* ── Row content above decorative layers ─────────────────── */
.ob-contact-hero .et_pb_row,
.ob-contact-hero .et_flex_row,
.ob-contact-form-section .et_pb_row,
.ob-contact-form-section .et_flex_row {
  position: relative;
  z-index: 1;
}

/* ── Privacy note — below the contact info list ─────────── */
.ob-contact-privacy {
  font-family: var(--ob-font-sans);
  font-size: var(--ob-text-sm);
  color: var(--ob-fg-on-dark-3);
  line-height: 1.5;
  margin: var(--ob-s-6) 0 0;
  padding-top: var(--ob-s-5);
  border-top: 1px solid rgba(255, 255, 255, 0.07);
}

/* ── About page dark sections — same base + traces ──────────
   No dot-grid (that reads as data-panel / instrument; the
   about page is people + story, not sensor readout).       */
.ob-about-hero,
.ob-about-section--dark,
.ob-about-section:not(.ob-about-section--flipped):last-of-type {
  position: relative !important;
  isolation: isolate;
  overflow: hidden !important;
}

.ob-about-hero::before,
.ob-about-section--dark::before,
.ob-about-section:not(.ob-about-section--flipped):last-of-type::before {
  content: "";
  position: absolute; inset: 0; z-index: 0; pointer-events: none;
  background: repeating-linear-gradient(
    -22deg,
    transparent              0px 47px,
    rgba(0,184,212,0.038)   47px 48px,
    transparent             48px 95px,
    rgba(255,255,255,0.014) 95px 96px
  );
  -webkit-mask-image: linear-gradient(180deg, rgba(0,0,0,0.70) 0%, rgba(0,0,0,0.20) 100%);
          mask-image: linear-gradient(180deg, rgba(0,0,0,0.70) 0%, rgba(0,0,0,0.20) 100%);
}

.ob-about-hero .et_pb_row,
.ob-about-hero .et_flex_row,
.ob-about-section--dark .et_pb_row,
.ob-about-section--dark .et_flex_row {
  position: relative;
  z-index: 1;
}

/* ============================================================
   Breathing gradient ambience — v0.1.6

   Dad's note: "series of blues that fade in and out and move
   around very slowly — like a screensaver but blurred and
   not as fast or prevalent."

   Technique: two large soft radial-gradient orbs per section,
   absolutely positioned, animated with transform + opacity on
   offset timers so they never fully synchronise. No filter:blur
   — large gradients with a 70% falloff achieve the same
   softness and are GPU-friendly.

   Applied to dark sections where pseudo-elements are free:
     ::before + ::after  → .ob-hero-section, .ob-capabilities
     ::after only        → .ob-about-hero, .ob-about-section--dark
       (::before is already the traces layer on about sections)

   Contact page sections keep Claude Design's static ambient
   gradient — adding motion there would compete with the seam
   treatment.
   ============================================================ */

/* ── Shared keyframes ────────────────────────────────────── */
@keyframes ob-breathe-a {
  0%   { transform: translate(0%,    0%)   scale(1.00); opacity: 0.55; }
  28%  { transform: translate(14%,   11%)  scale(1.10); opacity: 0.90; }
  58%  { transform: translate(-6%,   20%)  scale(0.94); opacity: 0.65; }
  100% { transform: translate(20%,  -10%)  scale(1.07); opacity: 0.80; }
}

@keyframes ob-breathe-b {
  0%   { transform: translate(0%,    0%)   scale(1.00); opacity: 0.45; }
  22%  { transform: translate(-16%,  -9%)  scale(1.12); opacity: 0.85; }
  62%  { transform: translate(9%,  -19%)   scale(0.91); opacity: 0.55; }
  100% { transform: translate(-13%,  16%)  scale(1.06); opacity: 0.75; }
}

/* ── Homepage hero (ob-hero-section) ─────────────────────── */
/* Both pseudo-elements free — use them for two orbs.         */
.ob-hero-section {
  position: relative;
  overflow: hidden;
  isolation: isolate;
}

/* Orb 1 — deep brand blue, upper-left drift */
.ob-hero-section::before {
  content: '';
  position: absolute;
  width: 70%;
  height: 90%;
  top: 5%;
  left: 5%;
  background: radial-gradient(ellipse 55% 55% at center,
    rgba(14, 95, 179, 0.50) 0%,
    transparent 70%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-a 30s ease-in-out infinite alternate;
  animation-delay: -8s;
  will-change: transform, opacity;
}

/* Orb 2 — cyan, lower-right counter-drift */
.ob-hero-section::after {
  content: '';
  position: absolute;
  width: 60%;
  height: 80%;
  bottom: 5%;
  right: 5%;
  background: radial-gradient(ellipse 52% 52% at center,
    rgba(0, 184, 212, 0.38) 0%,
    transparent 68%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-b 24s ease-in-out infinite alternate;
  animation-delay: -15s;
  will-change: transform, opacity;
}

/* Content above orbs */
.ob-hero-section .et_pb_row,
.ob-hero-section .et_flex_row,
.ob-hero-section .ob-hero {
  position: relative;
  z-index: 1;
}

/* ── Homepage capabilities (dark "What we make") ─────────── */
.ob-capabilities.et_pb_section,
.ob-capabilities.et_flex_section {
  isolation: isolate;
}

/* Orb 1 — mid-blue, top-right */
.ob-capabilities::before {
  content: '';
  position: absolute;
  width: 56%;
  height: 76%;
  top: 5%;
  right: 5%;
  background: radial-gradient(ellipse 50% 50% at center,
    rgba(27, 122, 214, 0.42) 0%,
    transparent 68%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-b 27s ease-in-out infinite alternate;
  animation-delay: -5s;
  will-change: transform, opacity;
}

/* Orb 2 — deep blue, lower-left */
.ob-capabilities::after {
  content: '';
  position: absolute;
  width: 52%;
  height: 72%;
  bottom: 5%;
  left: 5%;
  background: radial-gradient(ellipse 48% 48% at center,
    rgba(14, 95, 179, 0.40) 0%,
    transparent 70%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-a 22s ease-in-out infinite alternate;
  animation-delay: -19s;
  will-change: transform, opacity;
}

/* Ensure capabilities content stays above */
.ob-capabilities .et_pb_row,
.ob-capabilities .et_flex_row {
  position: relative;
  z-index: 1;
}

/* ── About hero (::after — ::before reserved for traces) ─── */
.ob-about-hero::after {
  content: '';
  position: absolute;
  width: 75%;
  height: 85%;
  top: 5%;
  right: 5%;
  background: radial-gradient(ellipse 55% 55% at center,
    rgba(14, 95, 179, 0.42) 0%,
    transparent 68%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-a 28s ease-in-out infinite alternate;
  animation-delay: -12s;
  will-change: transform, opacity;
}

/* ── About dark closer (::after — ::before reserved for traces) */
.ob-about-section--dark::after,
.ob-about-section:not(.ob-about-section--flipped):last-of-type::after {
  content: '';
  position: absolute;
  width: 65%;
  height: 80%;
  bottom: 5%;
  left: 5%;
  background: radial-gradient(ellipse 52% 52% at center,
    rgba(0, 184, 212, 0.36) 0%,
    transparent 68%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-b 25s ease-in-out infinite alternate;
  animation-delay: -7s;
  will-change: transform, opacity;
}

/* ============================================================
   Breathing gradient ambience — light sections (v0.1.8)

   Same orb technique as v0.1.6/0.1.7 but tuned for light
   backgrounds (--ob-steel-50 #F2F6FA / --ob-bg #F2F6FA).
   Brand blue reads darker-on-light, so 0.13–0.18 in the
   gradient gives a visible pearl-blue wash without muddying.
   Both pseudo-elements are free on these sections (traces /
   seam rules only target the dark variants).

   Applied to:
     .ob-about-section (non-dark, non-flipped)  — steel-50 bg
     .ob-about-section--flipped                 — --ob-bg
   ============================================================ */

/* Stacking context for all about content sections (dark ones
   already have this; redundant on them, required on light). */
.ob-about-section.et_pb_section,
.ob-about-section.et_flex_section {
  position: relative !important;
  isolation: isolate;
  overflow: hidden !important;
}

/* Row content stays above the orb layer */
.ob-about-section .et_pb_row,
.ob-about-section .et_flex_row {
  position: relative;
  z-index: 1;
}

/* ── Non-flipped light sections (e.g. "section 1") ────────── */
/* Orb 1 — deep brand blue, upper-left */
.ob-about-section:not(.ob-about-section--dark):not(.ob-about-section--flipped)::before {
  content: '';
  position: absolute;
  width: 72%;
  height: 95%;
  top: -8%;
  left: -8%;
  background: radial-gradient(ellipse 55% 55% at center,
    rgba(14, 95, 179, 0.26) 0%,
    transparent 70%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-a 32s ease-in-out infinite alternate;
  animation-delay: -22s;
  will-change: transform, opacity;
}

/* Orb 2 — cyan, lower-right */
.ob-about-section:not(.ob-about-section--dark):not(.ob-about-section--flipped)::after {
  content: '';
  position: absolute;
  width: 62%;
  height: 82%;
  bottom: -6%;
  right: -6%;
  background: radial-gradient(ellipse 50% 50% at center,
    rgba(0, 184, 212, 0.19) 0%,
    transparent 68%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-b 26s ease-in-out infinite alternate;
  animation-delay: -11s;
  will-change: transform, opacity;
}

/* ── Flipped section (e.g. "section 2") ─────────────────── */
/* Orb 1 — mid-blue, upper-right */
.ob-about-section--flipped::before {
  content: '';
  position: absolute;
  width: 68%;
  height: 90%;
  top: 5%;
  right: -8%;
  background: radial-gradient(ellipse 52% 52% at center,
    rgba(27, 122, 214, 0.22) 0%,
    transparent 70%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-b 29s ease-in-out infinite alternate;
  animation-delay: -18s;
  will-change: transform, opacity;
}

/* Orb 2 — deep blue, lower-left */
.ob-about-section--flipped::after {
  content: '';
  position: absolute;
  width: 58%;
  height: 78%;
  bottom: 5%;
  left: -6%;
  background: radial-gradient(ellipse 48% 48% at center,
    rgba(14, 95, 179, 0.18) 0%,
    transparent 68%);
  pointer-events: none;
  z-index: 0;
  animation: ob-breathe-a 23s ease-in-out infinite alternate;
  animation-delay: -4s;
  will-change: transform, opacity;
}

/* ── Kill all motion when user asks for it ───────────────── */
@media (prefers-reduced-motion: reduce) {
  .ob-hero-section::before,
  .ob-hero-section::after,
  .ob-capabilities::before,
  .ob-capabilities::after,
  .ob-about-hero::after,
  .ob-about-section--dark::after,
  .ob-about-section:not(.ob-about-section--flipped):last-of-type::after,
  .ob-about-section:not(.ob-about-section--dark):not(.ob-about-section--flipped)::before,
  .ob-about-section:not(.ob-about-section--dark):not(.ob-about-section--flipped)::after,
  .ob-about-section--flipped::before,
  .ob-about-section--flipped::after,
  .ob-contact-hero::before,
  .ob-contact-form-section::after {
    animation: none !important;
    opacity: 0.5 !important;
  }
}
