/* =========================================================
   BHA v2 — SHARED FOUNDATION
   Inherited by all three design directions. Contains only
   what must be identical across them: measured colour tokens,
   accessibility guarantees, and the no-JS safety net.
   Each direction supplies its own type scale, layout and motion.

   Contrast values below are COMPUTED (sRGB relative luminance),
   not asserted. See AUDIT_v1.md for the v1 failures this fixes.
   ========================================================= */

:root {
  /* --- Ground tones --- */
  --paper:      #F5F3EE;  /* base ground */
  --paper-2:    #ECE8E0;  /* alternate band */
  --ink:        #191813;  /* 16.03:1 on paper */

  /* --- Text --- */
  --body:       #2E2C28;  /* 12.57:1 on paper  — body copy */
  --label:      #6A6357;  /* 5.36:1 on paper, 4.86:1 on paper-2 — AA PASS
                             replaces v1 --stone #8F877A which was 3.20:1 FAIL */

  /* --- Lines --- */
  --border:     #918879;  /* 3.16:1 — AA PASS for UI component boundaries
                             (form fields, inputs). Replaces --concrete 1.63:1 FAIL */
  --rule:       #D8D3C8;  /* 1.35:1 — DECORATIVE hairlines ONLY.
                             Never a form border, never carries meaning. */
  --rule-2:     #C7C0B4;  /* slightly stronger decorative rule */

  /* --- On dark (ink ground) --- */
  --on-dark:        #F5F3EE;  /* 16.03:1 on ink */
  --on-dark-body:   #C7C0B4;  /* 9.84:1 on ink  — body copy on dark */
  --on-dark-label:  #9A9184;  /* 5.72:1 on ink  — labels on dark */
  --rule-dark:      #3A3833;  /* decorative only */

  /* --- Motion (research-verified curves) --- */
  --ease-out:  cubic-bezier(0.23, 1, 0.32, 1);
  --ease-expo: cubic-bezier(0.19, 1, 0.22, 1);  /* easeOutExpo — editorial reveals */
  --ease-io:   cubic-bezier(0.77, 0, 0.175, 1);

  /* --- Breakpoints, tokenised (v1 had 8 untokenised values) --- */
  --bp-lg: 1180px;
  --bp-md: 900px;
  --bp-sm: 640px;

  /* --- z-index scale, semantic (never arbitrary values) --- */
  --z-base: 1;
  --z-sticky: 50;
  --z-header: 100;
  --z-menu: 200;
  --z-modal: 300;
}

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body { margin: 0; }
h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
ul[class], ol[class] { list-style: none; margin: 0; padding: 0; }
img, svg, video, canvas { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; padding: 0; }
a { color: inherit; }

/* Long unbroken strings must never force horizontal scroll.
   v1 relied on body{overflow-x:hidden}, which can silently break
   position:sticky. Fix wrapping at the source instead. */
h1, h2, h3, h4, p, li, dd, figcaption { overflow-wrap: break-word; }

/* ---------- Accessibility ---------- */
:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
}
.on-ink :focus-visible { outline-color: var(--on-dark); }

.skip-link {
  position: absolute; left: -9999px; top: 0;
  z-index: var(--z-modal);
  background: var(--ink); color: var(--on-dark);
  padding: 0.85rem 1.25rem;
  text-decoration: none;
}
.skip-link:focus { left: 0; }

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

/* ---------- THE NO-JS SAFETY NET ----------
   v1 CRITICAL BUG: .reveal defaulted to opacity:0 and only became
   visible once JS ran. With JS blocked/slow/broken the entire site
   was blank (41 instances on the homepage alone).

   The inversion: content is VISIBLE BY DEFAULT. JS adds .js-ready to
   <html>, and ONLY THEN is the hidden-then-reveal behaviour allowed.
   No JS => everything simply renders, statically, immediately. */
.reveal { opacity: 1; transform: none; }

.js-ready .reveal {
  opacity: 0;
  transform: translateY(var(--reveal-shift, 14px));
  transition:
    opacity var(--reveal-dur, 650ms) var(--ease-expo),
    transform var(--reveal-dur, 650ms) var(--ease-expo);
}
.js-ready .reveal.is-in { opacity: 1; transform: none; }

/* ---------- MOBILE MOTION BUDGET (BUG-M2, 2026-08-13) ----------
   On a phone the reveal ran for 650ms and could carry up to 480ms of
   stagger delay on top (core.js: min(i,8) * 60ms). That is up to 1.13s
   between an element crossing the trigger line and finishing its arrival.
   A momentum fling covers a full viewport in far less than that, so the
   element completes its entrance well after the reader has scrolled past
   it — which is exactly what "transitions not in sync with the scroll"
   describes.

   A phone shows one column, so items arrive one at a time and stagger
   buys nothing anyway; the delay is removed outright below and the
   duration is cut to the 200-300ms standard-transition band. Desktop,
   where a stagger reads across a row, is untouched. */
@media (max-width: 900px) {
  .js-ready .reveal {
    --reveal-dur: 380ms;
    --reveal-shift: 10px;
    transition-delay: 0ms !important;   /* beats the inline delay core.js sets */
  }
}

/* ---------- STICKY-HEADER ANCHOR OFFSET ----------
   MEASURED 2026-08-13 in the WebKit engine at 390x844: following an
   in-page link (e.g. the nav's "#contact") scrolled the target flush to
   y=0, i.e. underneath the sticky header, clipping the first line of the
   section. Nothing in the codebase set scroll-margin, so every anchor on
   every page of all three directions landed short by the header height. */
[id] { scroll-margin-top: clamp(5rem, 14vw, 6.5rem); }

/* ---------- OPAQUE STICKY HEADER ON SMALL SCREENS ----------
   Directions A and B set `.site-header.is-solid` to rgba(...,0.94/0.95)
   plus backdrop-filter: blur(8px). On desktop that is a deliberate and
   good-looking effect. On iOS Safari a translucent, backdrop-filtered
   position:sticky element is a known repaint hazard during momentum
   scrolling: the backdrop snapshot can lag the scroller, so page content
   smears through the header for a frame or two.

   HONEST STATUS: this is a HARDENING measure, not a confirmed fix. The
   reported mobile text overlap (BUG-M1) was NOT reproduced here — a
   geometric scan of every text ink box across 3 directions x 3 page types
   x widths 320/360/375/390/430, ~25 scroll positions each, plus the
   menu-open, filter, order-by and sample-narrative states, returned zero
   text-on-text and zero text-on-image collisions once the sticky header's
   own (opaque, therefore covering) box was discounted. This removes the
   most plausible remaining iOS-only cause. It costs nothing on a phone,
   where an 8px backdrop blur behind a 60px bar is invisible anyway.

   !important is deliberate: foundation.css loads BEFORE each direction's
   main.css, so a plain rule here loses the cascade to all three. One rule
   that cannot drift beats three copies that can. */
@media (max-width: 900px) {
  .site-header.is-solid {
    background: var(--paper) !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
  }
}

/* ---------- Reduced motion (non-negotiable) ---------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js-ready .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ---------- Touch-target and input guarantees ----------
   MEASURED 2026-08-13: the rule below used to be `a, button, ... {
   min-height: 24px }` and was doing NOTHING for text links. min-height has
   no effect on a non-replaced INLINE box, so every footer and nav link was
   rendering at its line-box height (measured: 15px) while appearing to be
   covered by a guarantee. WCAG 2.2 AA (2.5.8 Target Size Minimum) wants
   24x24 CSS px, with an exception for links inline in a sentence.

   So: links that sit in navigation and link lists are made inline-block and
   padded to a real 24px. Links inside running prose are deliberately left
   alone, because they qualify for the inline exception and turning them into
   padded blocks would wreck the text flow of an editorial page. */
button, input, select, textarea, [role="button"] { min-height: 24px; }

nav a,
footer li > a,
.site-footer li > a {
  display: inline-block;
  min-height: 24px;
  padding-block: 0.3rem;
}

.tap { min-height: 44px; min-width: 44px; }

/* ---------- 44px on TOUCH pointers (added 2026-08-13, Bassem approved) ----------
   The 24px above is the WCAG 2.2 AA floor (2.5.8) and it is genuinely met.
   Apple's HIG and Material both want 44px, and a real device audit measured
   the brand link at 182x32 and every nav / contact-line link at ~33px on all
   three directions and all three engines. The two standards differ; a finger
   does not care which one we cite.

   Gated on `pointer: coarse` on purpose. A mouse is precise, and this project
   is actively REDUCING vertical space, so padding the desktop nav to 44px
   would spend the height we just reclaimed on a problem desktop does not have.

   `ul li > a` is the existing convention in this file for "link list, not
   prose": a direct-child anchor of a list item. That is what the contact
   lines (email / WhatsApp / phone / LinkedIn) are, so they are covered without
   a bespoke selector, and links inside running sentences keep the WCAG inline
   exception and their text flow. */
@media (pointer: coarse) {
  nav a,
  .nav a,
  .mobile-menu a,
  ul li > a,
  .site-footer li > a,
  a.brand,
  .brand {
    min-height: 44px;
    /* Both axes: a short word like "About" set at nav size is only 39px wide,
       so height alone still leaves an undersized target. The box grows to the
       right and text stays left-aligned (no justify-content), so nothing
       visibly shifts in a left-aligned footer list. */
    min-width: 44px;
    display: inline-flex;
    align-items: center;
  }
  button, [role="button"], .menu-toggle { min-height: 44px; min-width: 44px; }
}

input, select, textarea {
  font-size: 16px;           /* iOS zoom-on-focus guard */
  border-radius: 0;
  appearance: none;
  -webkit-appearance: none;
  background: var(--paper);
  border: 1px solid var(--border);   /* 3.16:1 — AA PASS */
  color: var(--body);
}
input:focus-visible, select:focus-visible, textarea:focus-visible {
  border-color: var(--ink);
}
input[aria-invalid="true"], textarea[aria-invalid="true"], select[aria-invalid="true"] {
  border-color: var(--ink);
  border-width: 2px;
}

::selection { background: var(--ink); color: var(--paper); }

/* ---------- Hatch conventions ----------
   Shared so all three directions use the identical convention. Fixed 1:1 with
   sector and derived from the sector string in JS, so it cannot drift the way
   v1's did (where only 1 of 6 hatches mapped consistently). */
.h-diagonal   { background-image: repeating-linear-gradient(45deg, var(--rule-2) 0 1px, transparent 1px 7px); }
.h-crosshatch { background-image: repeating-linear-gradient(45deg, var(--rule-2) 0 1px, transparent 1px 9px), repeating-linear-gradient(-45deg, var(--rule-2) 0 1px, transparent 1px 9px); }
.h-dots       { background-image: radial-gradient(var(--rule-2) 0.9px, transparent 1.2px); background-size: 8px 8px; }
.h-grid       { background-image: repeating-linear-gradient(0deg, var(--rule-2) 0 1px, transparent 1px 14px), repeating-linear-gradient(90deg, var(--rule-2) 0 1px, transparent 1px 14px); }
.h-vertical   { background-image: repeating-linear-gradient(90deg, var(--rule-2) 0 1px, transparent 1px 6px); }
.h-horizontal { background-image: repeating-linear-gradient(0deg, var(--rule-2) 0 1px, transparent 1px 6px); }
.h-reverse    { background-image: repeating-linear-gradient(135deg, var(--rule-2) 0 1px, transparent 1px 7px); }

/* Added when the photographed body of work brought three new sector families
   (Residential, Sports & Wellness, Civic & Religious). Seven patterns could not
   carry ten families without collisions, and a colliding hatch is exactly the
   v1 failure the audit named: a convention the data contradicts. */
.h-brick      { background-image: repeating-linear-gradient(0deg, var(--rule-2) 0 1px, transparent 1px 10px), repeating-linear-gradient(90deg, var(--rule-2) 0 1px, transparent 1px 20px); background-size: 20px 20px; }
.h-dash       { background-image: repeating-linear-gradient(90deg, var(--rule-2) 0 6px, transparent 6px 12px); background-size: 12px 5px; }
.h-fine       { background-image: repeating-linear-gradient(45deg, var(--rule-2) 0 1px, transparent 1px 4px); }

/* Hover effects must not fire on touch (v1 applied them unguarded) */
@media (hover: none) {
  .hover-only { transition: none !important; }
}
