/* ─────────────────────────────────────────────────────────────────────────────
   layout-defence.css — drop into every LB Digital site, imported LAST.

   This is the floor, not the design. It makes the recurring faults structurally
   hard to reintroduce, so the audit has less to find and Elliot has nothing to
   report. Nothing in here sets a colour, a font family or a brand decision.

   Import after the site's own stylesheets:
     @import "layout-defence.css";
   ──────────────────────────────────────────────────────────────────────────── */

/* ── 1. Text never wraps into an ugly shape ────────────────────────────────── */

/* Headings get balanced lines; body copy gets a widow-avoiding last line.
   These are the browser's own typesetters — free, and they never regress. */
h1, h2, h3, h4, h5, h6,
blockquote, figcaption,
[class*="lede"], [class*="lead"], [class*="eyebrow"], [class*="kicker"] {
  text-wrap: balance;
}

p, li, dd, [class*="body"], [class*="copy"] {
  text-wrap: pretty;
}

/* A measure this wide is unreadable and wraps unpredictably. */
p, li, blockquote, [class*="lede"], [class*="lead"] {
  max-inline-size: 68ch;
}

/* ── 2. Buttons and labels hold one line ───────────────────────────────────── */

button, .btn, [class*="btn"]:not([class*="btn-group"]), [role="button"], a.cta {
  white-space: nowrap;
}

/* …unless the component opted out (accordion headers, card-shaped buttons). */
.btn--wrap, [data-wrap], button[aria-expanded], summary {
  white-space: normal;
}

/* ── 3. Nothing is ever cut off, overlapped, or off the page ───────────────── */

html, body {
  /* clip, not hidden — hidden breaks position: sticky */
  overflow-x: clip;
  max-inline-size: 100%;
}

/* The commonest source of horizontal overflow is a fixed-width child. */
img, svg, video, canvas, iframe, table, pre {
  max-inline-size: 100%;
}

/* Images keep their own shape. A stretched photo is always a bug. */
img, video {
  block-size: auto;
}
/* Always ship width+height (or an aspect-ratio) on <img>: the browser then
   reserves the box itself and the page cannot jump as images load. */
/* Any image told to fill a box must crop, never distort. */
[class*="media"] img, [class*="cover"] img, figure img, .card img {
  object-fit: cover;
}

/* Long unbroken strings (URLs, emails, IDs) must not push the layout wide. */
p, li, td, dd, h1, h2, h3, h4, [class*="card"] {
  overflow-wrap: anywhere;
}

/* ── 4. Scroll containers leave room for their children's shadows ──────────── */
/* A card shadow clipped by its rail is one of the faults that keeps recurring.
   Give every horizontal rail breathing room by default. */
[class*="rail"], [class*="carousel"], [class*="scroller"], [data-scroller] {
  padding-block: 1rem 1.5rem;
  scroll-padding-inline: var(--gutter, 1.25rem);
}

/* ── 5. A consistent gutter — text never kisses the bezel ──────────────────── */
:root {
  --gutter: clamp(1.25rem, 5vw, 2.5rem);
  /* marker: layout-audit.mjs reads this to confirm the defences are live */
  --layout-defence: on;
}
.wrap, .container, [class*="wrap"], [class*="container"] {
  padding-inline: max(var(--gutter), env(safe-area-inset-left), env(safe-area-inset-right));
  margin-inline: auto;
}

/* ── 6. Sections breathe symmetrically unless told otherwise ───────────────── */
/* Asymmetric top/bottom padding is the single most common "spacing is off"
   report. Make symmetry the default and asymmetry an explicit opt-in. */
section, [class*="section"] {
  padding-block: var(--section-pad, clamp(3.5rem, 8vw, 7rem));
}
[data-pad="tight"]  { --section-pad: clamp(2rem, 5vw, 3.5rem); }
[data-pad="loose"]  { --section-pad: clamp(5rem, 11vw, 9rem); }

/* ── 7. Repeated components are genuinely equal ────────────────────────────── */
/* Cards in a row stretch to a shared height; their footers stay pinned so a
   card with less copy doesn't look half-empty next to a fuller sibling. */
[class*="grid"], [class*="cards"], [class*="row"] {
  align-items: stretch;
}
[class*="card"] {
  display: flex;
  flex-direction: column;
}
[class*="card"] > [class*="foot"],
[class*="card"] > [class*="cta"],
[class*="card"] > :last-child:is(a, button, [class*="btn"]) {
  margin-block-start: auto;
}

/* ── 8. Every control is thumb-sized on a phone ────────────────────────────── */
@media (pointer: coarse) {
  a[class*="btn"], button, [role="button"], input, select, textarea,
  [class*="dot"], [class*="arrow"], [class*="close"], [class*="burger"] {
    min-block-size: 44px;
    min-inline-size: 44px;
  }
  /* Small visual controls (carousel dots) keep their look and grow their hit
     area instead of their body. */
  [class*="dot"], [class*="tick"] {
    min-block-size: 0;
    min-inline-size: 0;
    position: relative;
  }
  [class*="dot"]::after, [class*="tick"]::after {
    content: "";
    position: absolute;
    inset: 50% auto auto 50%;
    inline-size: 44px;
    block-size: 44px;
    transform: translate(-50%, -50%);
  }
}

/* ── 9. The page does not jump ─────────────────────────────────────────────── */
/* Reserve the scrollbar so opening a modal doesn't shift the layout, and stop
   inputs resizing their own container as you type. */
html { scrollbar-gutter: stable; }

/* Fixed/sticky bars sit under the notch correctly. Never add viewport-fit=cover
   to the meta tag — it is what puts content in the unpaintable zone on iOS 26. */
[class*="header"], [class*="nav"], [class*="sticky"] {
  padding-block-start: env(safe-area-inset-top, 0);
}

/* ── 10. Reveal animations never leave a hole ──────────────────────────────── */
/* If the observer never fires (or JS fails), content must still be visible.
   A blank section reads to Elliot as dead space, and he is right. */
@media (prefers-reduced-motion: reduce) {
  [class*="reveal"], [data-reveal] {
    opacity: 1 !important;
    transform: none !important;
  }
}
[class*="reveal"], [data-reveal] { transition-duration: .6s; }
