/* Elko Chair Repairs — design system.
   Structure follows the Geeks Brisbane system; every colour is from
   chairrepairs/brand/dist/brand-sheet.html. Do not add colours here.

   Every `color: <white>` declaration below carries a same-line
   cr-allow-white: on-charcoal marker comment. This is required by the
   hardened test_contrast.py (fix round 1/5, 2026-07-29): once a file
   declares ANY tan background, EVERY white foreground in that file must
   opt in explicitly, because a tan-bg parent + white-fg child rule (no
   single block containing both) is otherwise an undetectable evasion of
   a same-block check. This file legitimately has both (tan buttons with
   charcoal text elsewhere, white text on charcoal/transparent grounds
   here) — none of these are white-on-tan, hence the opt-outs below.

   Fix round (2026-07-29, Task 5): this comment used to spell out the
   marker wrapped in actual CSS comment delimiters, which put a closing
   asterisk-slash mid-sentence. CSS comments do not nest, so that closed
   THIS comment early, right after "on-charcoal" a few lines up --
   turning the rest of this prose plus the whole :root block below into
   one unparseable qualified rule (confirmed with tinycss2, and against
   the live site: getComputedStyle(document.documentElement)
   .getPropertyValue('--cr-charcoal') came back empty). Every
   var(--cr-*) reference in this entire file was silently resolving to
   nothing. Never put an asterisk immediately followed by a slash
   anywhere inside this comment again. */
:root {
  --cr-charcoal: #1C1B1A;
  --cr-tan: #C8752A;
  --cr-tan-hover: color-mix(in srgb, #C8752A 80%, #1C1B1A 20%); /* darker tan for hover, derived from the two palette colours — no 6th colour */
  --cr-cream: #F5F0E8;
  --cr-stone: #8A8580;          /* logo descriptor only */
  --cr-text: #6B6560;
  --cr-radius: 12px;
  --cr-shadow: 0 4px 20px rgba(28, 27, 26, 0.10);
  --cr-shadow-hover: 0 8px 30px rgba(28, 27, 26, 0.15);
  --cr-transition: all 0.3s ease;
}
[class^="cr-"] *, [class*=" cr-"] * { box-sizing: border-box; }
[class^="cr-"], [class*=" cr-"] {
  font-family: 'Lato', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  color: var(--cr-text);
  line-height: 1.6;
}

/* --- hero: real photo + charcoal wash + animated weave ---
   Fix round 2 (2026-07-30, client feedback): the all-CSS hero (previous
   round) read well but the client wants the photographic hero back,
   fixed properly this time -- the earlier photo blurred because its
   source was only 1000x450, not because a photo was the wrong idea.
   Chosen replacement: live 784 "Woman repairing wooden chair with drill"
   (new id 115), natural 2560x1707 -- a genuine process shot, 2.5x the
   width of the old blurring image, audited against the other 19 images
   >=1400px wide in the library (see visual-fixes-report.md for the full
   comparison against the runners-up).
   Layer order (back to front): 1) the photo itself, `background-size:
   cover`; 2) a flat charcoal wash -- `linear-gradient(rgba(28,27,26,0.72),
   rgba(28,27,26,0.72))` stacked ahead of the photo `url()` in the same
   `background-image` list, since `background-color` alone paints BEHIND
   every `background-image` layer and can't sit "in front of" the photo --
   dark enough that white text stays comfortably legible even over a
   worst-case pure-white patch of the source photo (measured contrast in
   visual-fixes-report.md); 3) `::before`, the same animated cross-hatch
   weave from the previous round, now compositing over the photo instead
   of a flat ground -- kept because the client liked it and it still
   reads as woven fabric over any backdrop. The floating craft-icon frieze
   from the previous round (`::after`) is DROPPED here: a real photo
   already carries plenty of visual detail, and stacking a third layer of
   icon silhouettes on top of it read as cluttered rather than restrained
   -- weave-only is the calmer choice once there's a photograph doing the
   work the icons used to do. `::after` is free again; nothing on `.cr-hero`
   uses it now.
   The photo itself is responsive: the default (desktop/tablet) rule below
   references the 2048x1365 WordPress-generated size, not the 2560px
   original -- close enough to lossless on any realistic non-retina
   desktop viewport while shipping about 30% fewer bytes.
   `background-position: center 40%` (not plain center): the frame's real
   subject -- the drill, the hand and the screwed joint -- sits in the
   upper-middle band of this photo (roughly its top 10%-70%), with the
   lower ~30% being just chair legs and background fireplace. A dead-
   centre crop was trimming into the screws/joint at the old, much
   shorter box height; shifting the anchor up keeps that detail in frame
   at every height this box now takes (confirmed by looking at the actual
   file, not guessed).
   Fix round 3 (2026-07-30, client feedback): added `min-height` -- at the
   box's OLD height (padding + content only, no min-height) it measured
   1622x506 against this 2048x1365 photo, an aspect mismatch (3.20 vs
   1.50) so severe that `cover` only showed 47% of the photo's height.
   `min-height: min(clamp(420px, 48vw, 860px), 80vh)` targets ~72% visible
   at typical desktop widths (see visual-fixes-report.md for the derivation
   and measured results) -- comfortably past the client's 65% floor, with
   headroom for measurement variance:
     - 48vw is the fluid centre: photoAspect(1.5007) / target hero
       aspect(~2.09) ~= 0.48, i.e. height should track ~48% of viewport
       width to hit ~72% visible height at any desktop width.
     - 420px floor keeps the box from going anaemic on a narrow desktop
       window (48vw only clears 420px above ~875px width) -- doesn't
       change mobile in practice, since mobile's own padding+content
       height already exceeds it (see the max-width:480px block below).
     - 80vh outer cap (via the wrapping `min()`, which makes it an
       absolute ceiling even below the 420px floor, not just another
       clamp bound) stops the hero from eating the whole screen on a
       short-but-wide window -- "pushes all content below the fold" is
       its own problem the client explicitly didn't want traded for this
       one. This DOES mean a viewport that's wide relative to its own
       height (roughly wider than 5:3) can fall under the 65% target
       again, because the two goals directly conflict there; the report
       documents this trade-off with the actual numbers from this
       environment's own viewport, which hits exactly that edge case.
   `display: flex; flex-direction: column; justify-content: center;` was
   added so the h1/p/badges stack centres vertically in the now-taller
   box -- without it the content would hug the top padding and leave a
   dead gap of empty photo above the footer-side padding, since nothing
   previously stretched or centred children within the box's height.
   Fix round 5 (2026-07-30, client feedback): the animated cross-hatch
   weave (`::before`) is REMOVED from the hero here -- the client wants it
   gone now that a real photo sits behind the text, but explicitly asked
   to keep the same weave in the footer (see that section below), so the
   `cr-weaveDrift` keyframes and the footer's own `::before` stay; only
   the hero's copy of the pseudo-element is deleted. Before deleting it,
   its actual contribution to text-over-photo contrast was checked
   analytically (composited the same 0.05-alpha tan hairline the hero
   copy used on top of each background this file already had measured):
   it turned out to be a very slight NET NEGATIVE for contrast, not a
   positive one -- tan is lighter than the charcoal-washed backdrop
   everywhere it was measured, so a hairline pixel was very slightly
   LOWER contrast than a plain pixel (deltas of 0.33-0.86 across every
   measured element, worst case still 8.17:1, comfortably above every
   applicable AA threshold). Removing it does not require deepening the
   wash -- if anything the hero's worst-case contrast improves slightly
   with the weave gone, since every pixel now matches the (higher)
   off-hairline value instead of ~8.7% of the area dipping to the lower
   on-hairline one. Exact figures in visual-fixes-report.md. */
.cr-hero {
  background-color: var(--cr-charcoal);
  background-image:
    linear-gradient(rgba(28, 27, 26, 0.72), rgba(28, 27, 26, 0.72)),
    url("https://chairrepairs.com.au/wp-content/uploads/2026/07/woman-repairing-wooden-chair-with-drill-2048x1365.jpg");
  background-size: cover;
  background-position: center 40%;
  min-height: min(clamp(420px, 48vw, 860px), 80vh);
  padding: 80px 20px 120px;
  text-align: center;
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
@keyframes cr-weaveDrift {
  0%   { transform: translate(0, 0); }
  100% { transform: translate(46px, 46px); }
}
@media (prefers-reduced-motion: reduce) {
  /* Real accessibility requirement, not a nicety: stop the footer's
     weave dead rather than merely slowing it. (The hero no longer has
     an animated layer of its own -- see the removal note above.) */
  .cr-footer::before { animation: none; }
}
.cr-hero > * { position: relative; z-index: 1; }
.cr-hero h1 {
  color: #FFFFFF; /* cr-allow-white: on-charcoal */
  font-size: clamp(2rem, 5vw, 3rem);
  font-weight: 800;
  margin: 0 0 20px;
  max-width: 900px;
  margin-inline: auto;
}
.cr-hero h1 span { color: var(--cr-tan); }   /* tan on charcoal, large text — permitted */
.cr-hero p { color: rgba(255,255,255,0.9); max-width: 620px; margin: 0 auto 30px; font-size: 1.1rem; } /* cr-allow-white: on-charcoal */

/* --- trust badges --- */
.cr-badges { display: flex; justify-content: center; gap: 30px; flex-wrap: wrap; color: #FFFFFF; font-size: 0.95rem; } /* cr-allow-white: on-charcoal */
.cr-badges svg { width: 24px; height: 24px; vertical-align: middle; margin-right: 6px; }

/* --- buttons. Charcoal on tan: 4.94:1. NEVER white on tan (3.48:1, fails AA). --- */
.cr-btn {
  display: inline-block;
  background: var(--cr-tan);
  color: var(--cr-charcoal);
  font-weight: 700;
  padding: 16px 40px;
  border-radius: 8px;
  font-size: 1.1rem;
  text-decoration: none;
  transition: var(--cr-transition);
}
.cr-btn:hover {
  background: var(--cr-tan); /* fallback where color-mix() is unsupported */
  background: var(--cr-tan-hover);
  color: var(--cr-charcoal);
  transform: translateY(-2px);
}
.cr-btn-ghost {
  display: inline-block; background: transparent; border: 2px solid rgba(255,255,255,0.35);
  color: #FFFFFF; padding: 14px 32px; border-radius: 8px; text-decoration: none; /* cr-allow-white: on-charcoal */
  font-weight: 600; transition: var(--cr-transition);
}
.cr-btn-ghost:hover { background: rgba(255,255,255,0.15); color: #FFFFFF; } /* cr-allow-white: on-charcoal */

/* --- sections --- */
.cr-section { background: #FFFFFF; padding: 70px 20px; }
.cr-section-cream { background: var(--cr-cream); padding: 70px 20px; }
.cr-section-dark { background: var(--cr-charcoal); padding: 70px 20px; color: #FFFFFF; } /* cr-allow-white: on-charcoal */
.cr-inner { max-width: 1100px; margin: 0 auto; }
.cr-section h2, .cr-section-cream h2, .cr-section-dark h2 {
  text-align: center; font-size: clamp(1.5rem, 3vw, 2rem); font-weight: 700; margin: 0 0 15px;
}
.cr-section h2, .cr-section-cream h2 { color: var(--cr-charcoal); }
.cr-section-dark h2 { color: #FFFFFF; } /* cr-allow-white: on-charcoal */
.cr-sub { text-align: center; color: var(--cr-text); font-size: 1.1rem; max-width: 620px; margin: 0 auto 40px; }
.cr-section-dark .cr-sub { color: rgba(255,255,255,0.85); } /* cr-allow-white: on-charcoal */

/* --- cards ---
   minmax floor is 330px, not 250px: inside .cr-inner (max-width 1100px) with a
   25px gap, 4 x 250 + 3 x 25 = 1075 <= 1100, so a 250px floor let FOUR tracks
   fit at desktop width instead of the intended three -- stranding a card alone
   on its own row on every grid that isn't a multiple of 4 (the 9-card service
   grid rendered 4/4/1, the 6-photo proof band 4/2, the 5-card areas section
   4/1). At 330px: 3 x 330 + 2 x 25 = 1040 <= 1100 (three fit), 4 x 330 + 3 x 25
   = 1395 > 1100 (four don't) -- three is the true maximum at this container
   width. The same floor auto-adjusts smaller breakpoints correctly with no
   extra media query needed: at a ~728px tablet content width two columns fit
   (2 x 330 + 25 = 685) but not three (1040); below roughly 355px only one
   fits. Found live on the homepage (fix round 2/5, 2026-07-30) -- shared file,
   so this also re-flows every other page's card grids, which is the point. */
.cr-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 25px; }
.cr-card {
  background: var(--cr-cream); border-radius: var(--cr-radius); padding: 25px 20px;
  border: 2px solid transparent; transition: var(--cr-transition);
}
.cr-card:hover { border-color: var(--cr-tan); transform: translateY(-5px); box-shadow: var(--cr-shadow-hover); }
.cr-card h3 { color: var(--cr-charcoal); font-size: 1.15rem; font-weight: 700; margin: 0 0 10px; }
.cr-card img { width: 100%; height: 200px; object-fit: cover; border-radius: 8px; margin-bottom: 15px; }

/* --- process steps --- */
.cr-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(230px, 1fr)); gap: 25px; }
.cr-step { background: #FFFFFF; border-radius: var(--cr-radius); padding: 30px; box-shadow: var(--cr-shadow); }
.cr-step-n {
  width: 45px; height: 45px; background: var(--cr-tan); color: var(--cr-charcoal);
  border-radius: 50%; font-size: 1.2rem; font-weight: 700;
  display: flex; align-items: center; justify-content: center; margin-bottom: 15px;
}

/* --- FAQ accordion --- */
.cr-faq-item { background: var(--cr-cream); border-radius: var(--cr-radius); overflow: hidden; margin-bottom: 12px; }
.cr-faq-q {
  width: 100%; padding: 20px 25px; background: var(--cr-cream); border: none; text-align: left;
  font-size: 1.05rem; font-weight: 700; color: var(--cr-charcoal); cursor: pointer;
  display: flex; justify-content: space-between; align-items: center; font-family: inherit;
}
.cr-faq-a { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; padding: 0 25px; }
.cr-faq-item.active .cr-faq-a { max-height: 600px; padding-bottom: 20px; }

/* --- .cr-post: long-form blog content (Task 15, blog migration) ---
   The 19 migrated posts are raw client-authored HTML (h1-h5, p, ul/ol/li,
   strong/b, a, hr, table, occasional img) with none of the site's own
   cr-section/cr-card scaffolding around it -- before this block the only
   rule reaching it at all was the site-wide [class^="cr-"] reset (font,
   base text colour, line-height), so headings rendered at bare browser
   defaults, images had no cap on width, and there was no reading-width
   constraint. Every colour below is one of the five design-system tokens
   (charcoal/tan/tan-hover/cream/text) or #FFFFFF as a background (not a
   foreground on tan, so it needs no cr-allow-white opt-out -- see
   test_contrast.py's own file-level rule for what actually requires one). */
.cr-post {
  max-width: 760px;
  margin: 0 auto;
  padding: 70px 20px;
  background: #FFFFFF;
}
.cr-post h1, .cr-post h2, .cr-post h3, .cr-post h4, .cr-post h5, .cr-post h6 {
  color: var(--cr-charcoal);
  font-weight: 700;
  line-height: 1.3;
  margin: 40px 0 16px;
}
.cr-post h1:first-child, .cr-post h2:first-child { margin-top: 0; }
.cr-post h1 { font-size: 2rem; }
.cr-post h2 { font-size: 1.5rem; }
.cr-post h3 { font-size: 1.2rem; }
.cr-post h4, .cr-post h5, .cr-post h6 { font-size: 1.05rem; }
.cr-post p { margin: 0 0 20px; }
.cr-post a { color: var(--cr-tan); text-decoration: underline; }
.cr-post a:hover { color: var(--cr-tan-hover); }
.cr-post strong, .cr-post b { color: var(--cr-charcoal); font-weight: 700; }
.cr-post ul, .cr-post ol { margin: 0 0 20px; padding-left: 24px; }
.cr-post li { margin-bottom: 8px; }
.cr-post blockquote {
  margin: 24px 0; padding: 16px 24px; background: var(--cr-cream);
  border-left: 4px solid var(--cr-tan); border-radius: 0 var(--cr-radius) var(--cr-radius) 0;
  color: var(--cr-text); font-style: italic;
}
.cr-post hr { border: none; border-top: 1px solid var(--cr-cream); margin: 32px 0; }
.cr-post img { max-width: 100%; height: auto; border-radius: var(--cr-radius); margin: 16px 0; box-shadow: var(--cr-shadow); }
.cr-post table { width: 100%; border-collapse: collapse; margin: 0 0 20px; }
.cr-post th, .cr-post td { padding: 10px 14px; border: 1px solid var(--cr-cream); text-align: left; }
.cr-post th { background: var(--cr-cream); color: var(--cr-charcoal); font-weight: 700; }

@media (max-width: 768px) {
  .cr-hero { padding: 60px 15px 100px; }
  .cr-badges { gap: 15px; }
}
@media (max-width: 480px) {
  .cr-hero { padding: 50px 15px 90px; }
  .cr-badges { flex-direction: column; align-items: center; gap: 12px; }
}
/* Fix round 4 (2026-07-30, client feedback): below the width where the hero
   box itself turns portrait (narrower than it is tall), `cover`-fitting the
   LANDSCAPE process photo crops almost all of its width away (measured as
   low as ~40% of the photo's width visible at real phone sizes) -- the
   "zoomed in" look the client flagged, made worse by the round-3 mobile
   variant (768x512) being smaller than the box needs once phone DPR (2-3x)
   is accounted for, so it was also being upscaled, not just badly cropped.
   Two separate bugs, one fix: below the measured crossover, swap to a
   PORTRAIT source entirely (a portrait photo in a portrait box crops almost
   nothing) at a size generous enough to stay downscaled even at 3x DPR.
   Chosen photo -- "Two elegant wooden chairs side by side" (live 684) --
   over the other three portrait candidates in the library for the same
   "strong, clearly-lit finished piece" reasoning the client gave: it's a
   completed leather reupholstery with nailhead trim (no visible wear, unlike
   the vintage floral chair, which reads as a "before" shot), and the pair
   sits centred with margin on both sides (~78% of the frame width), so it
   survives side-cropping at narrow widths without losing either chair --
   unlike the couch or single wider pieces also in the library. Served at
   its full 1920x2560 (not a smaller WordPress size) specifically for DPR
   headroom: see visual-fixes-report.md for the exact scale figures at
   measured mobile box sizes. `background-position: center` -- the pair is
   already centred in its own frame with even margins either side, so no
   off-centre anchor is needed the way the desktop photo needed one.
   This carries the same alt-text intent ("two elegant wooden chairs") into
   the image choice even though a CSS background has no alt attribute of its
   own -- the copy alongside it still does the describing.
   The breakpoint itself (525px) is not a round number picked by eye -- it's
   the measured point where the hero box's own aspect ratio crosses 1.0
   (width = height), found with a same-origin iframe test harness (this
   environment's outer browser window won't go narrower than ~1350px, so
   media queries were probed by forcing iframe box widths instead -- same
   technique already used for the header's mobile breakpoints). Swept
   320-1622px in both a coarse and a fine (500-540px, 5px steps) pass:
   heroH holds flat at 525px across that whole fine range (padding+content-
   driven, unaffected by width in this band), so heroW alone decides the
   crossover -- 520px measured 0.9905 (still portrait), 525px measured
   1.0001, 530px measured 1.0096 (already landscape). 525px is that
   crossover to the pixel. */
@media (max-width: 525px) {
  .cr-hero {
    background-image:
      linear-gradient(rgba(28, 27, 26, 0.72), rgba(28, 27, 26, 0.72)),
      url("https://chairrepairs.com.au/wp-content/uploads/2026/07/two-elegant-wooden-chairs-side-by-side-scaled.jpg");
    background-position: center;
  }
}

/* --- global header: charcoal ground, reversed lockup, tan/charcoal CTA ---
   Fix round 1/5 (2026-07-30): two site-wide criticals from review, both now
   fixed here plus in the markup (chairrepairs/assets/header.html):
   1. The logo was stretched ~41% (200x56 = 3.571:1 vs the SVG's true 2.537:1)
      because the HTML pinned both width AND height. The brand sheet's minimum
      is width-only. Fix: drop the height attribute in the HTML, and pin
      `height: auto` here so width alone governs the box — verified live via
      getBoundingClientRect(), not just file identity.
   2. There was no mobile nav pattern — 8 links wrapped the header to 4-5 rows
      on a phone. Fix: a `.cr-nav-toggle` button (hidden ≥900px) collapses
      `.cr-nav` into a hidden panel below 900px; JS lives in mu-cr-assets.php,
      shared once, same pattern as the FAQ accordion. --- */
.cr-header {
  background: var(--cr-charcoal); display: flex; align-items: center; gap: 24px;
  padding: 14px 24px; flex-wrap: wrap;
}
.cr-header-logo img { display: block; min-width: 140px; height: auto; color: #FFFFFF; } /* cr-allow-white: on-charcoal — alt-text fallback colour; height:auto preserves the 2.537:1 ratio */
.cr-nav-toggle {
  display: none; background: transparent; border: 2px solid rgba(255,255,255,0.35);
  border-radius: 8px; width: 44px; height: 40px; padding: 0; cursor: pointer;
  flex-direction: column; align-items: center; justify-content: center; gap: 5px;
}
.cr-nav-toggle span { display: block; width: 22px; height: 2px; border-radius: 1px; background: #FFFFFF; } /* cr-allow-white: on-charcoal */
.cr-nav { display: flex; gap: 20px; margin-left: auto; flex-wrap: wrap; }
.cr-nav a { color: #FFFFFF; text-decoration: none; font-weight: 600; font-size: 0.95rem; } /* cr-allow-white: on-charcoal */
.cr-nav a:hover { color: var(--cr-tan); }                    /* tan on charcoal — permitted */
.cr-header-cta { padding: 10px 22px; font-size: 1rem; }
@media (max-width: 900px) {
  /* Fix round 1/5 (2026-07-30): the first pass only added margin-left:auto to
     push the toggle+CTA right — it never checked whether logo(200px) + toggle
     + CTA + gaps actually FIT the row at real phone widths. They didn't: at
     375px only ~301px of content width is available, but the desktop sizes
     needed ~435px, so the CTA silently wrapped onto its own row even with
     nav hidden. Measured empirically (a local iframe harness serving this
     exact CSS+markup, width forced via the iframe box, not the OS window)
     against 320/375/430/768/900 before locking these numbers in: logo
     shrinks to the brand sheet's 140px floor (never smaller), toggle and CTA
     both shrink modestly, gap and padding tighten — 375px and wider now fit
     with room to spare (349px available vs 302px needed at 375).
     320px (older/smallest phones, below this task's four required widths)
     still wraps by a few pixels — not chased further here since the brand
     minimum floor on the logo leaves no more slack to give without breaking
     the width-only constraint; flagged for a future pass if 320px traffic
     turns out to matter. */
  .cr-header { gap: 8px; padding: 10px; }
  .cr-header-logo img { width: 140px; }
  .cr-nav-toggle { display: flex; margin-left: auto; width: 36px; height: 34px; }
  .cr-header-cta { padding: 7px 10px; font-size: 0.82rem; }
  .cr-nav {
    display: none; order: 4; width: 100%; margin-left: 0; margin-top: 14px;
    flex-direction: column; gap: 0;
  }
  .cr-nav.is-open { display: flex; }
  .cr-nav a { padding: 12px 2px; border-top: 1px solid rgba(255,255,255,0.15); }
}

/* --- global footer: charcoal ground, matches header. Five tokens only:
   charcoal ground, #FFFFFF headings, rgba(255,255,255,.8) body, tan links.
   Fix round 2 (2026-07-30, client feedback), two additions:
   1. `border-top` -- a subtle tan hairline separating the footer from
      whatever dark section precedes it (on every page, the final section
      before the footer is `.cr-section-dark`, so without this the two
      charcoal blocks ran together with no visible seam). Client's stated
      first preference over a cross-hatch band, and simpler: one property,
      no extra element, works identically regardless of what precedes it.
   2. `::before` -- the SAME cross-hatch weave used in the hero (client
      explicitly asked for the hero's cross-lines to carry into the
      footer), at a LOWER alpha (0.04 vs the hero's 0.05) because footer
      legibility (NAP, service links) matters more than decoration here.
      Measured impact on text contrast is in visual-fixes-report.md --
      negligible: at 0.04 alpha a hairline shifts the charcoal backdrop by
      only a few RGB values, nowhere near enough to move either footer
      text colour across a WCAG threshold. `position: relative;
      overflow: hidden;` were added to `.cr-footer` so the oversized,
      animated ::before has a containing box and doesn't bleed past the
      footer edges (which would visually collide with the new border-top
      hairline above it). `.cr-footer-inner`/`.cr-footer-legal` get
      `position: relative; z-index: 1;` for the same reason `.cr-hero > *`
      already does -- a position:absolute pseudo-element at z-index:0
      paints AFTER normal in-flow content in CSS's stacking order, so
      without this the weave would sit on top of the footer text instead
      of behind it. */
.cr-footer {
  background: var(--cr-charcoal);
  padding: 60px 24px 30px;
  position: relative;
  overflow: hidden;
  border-top: 1px solid rgba(200, 117, 42, 0.2);
}
.cr-footer::before {
  content: "";
  position: absolute;
  z-index: 0;
  pointer-events: none;
  top: -20%; left: -20%; width: 140%; height: 140%;
  background-image:
    repeating-linear-gradient(45deg, rgba(200, 117, 42, 0.04) 0 2px, transparent 2px 46px),
    repeating-linear-gradient(-45deg, rgba(200, 117, 42, 0.04) 0 2px, transparent 2px 46px);
  animation: cr-weaveDrift 26s linear infinite;
}
.cr-footer-inner {
  max-width: 1100px; margin: 0 auto; display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 40px;
  position: relative; z-index: 1;
}
.cr-footer-inner img { display: block; min-width: 140px; height: auto; margin-bottom: 16px; color: #FFFFFF; } /* cr-allow-white: on-charcoal — alt-text fallback colour; height:auto preserves the 2.537:1 ratio */
.cr-footer-inner p { color: rgba(255,255,255,0.8); font-size: 0.95rem; margin: 0 0 14px; } /* cr-allow-white: on-charcoal */
.cr-footer-inner h3 { color: #FFFFFF; font-size: 1.05rem; font-weight: 700; margin: 0 0 16px; } /* cr-allow-white: on-charcoal */
.cr-footer-inner a { display: block; color: rgba(255,255,255,0.8); text-decoration: none; margin-bottom: 10px; font-size: 0.95rem; } /* cr-allow-white: on-charcoal */
.cr-footer-inner a:hover { color: var(--cr-tan); }           /* tan on charcoal — permitted */
.cr-rating a { color: var(--cr-tan); font-weight: 700; display: inline; } /* tan on charcoal — permitted */
.cr-footer-legal {
  max-width: 1100px; margin: 40px auto 0; padding-top: 24px;
  border-top: 1px solid rgba(255,255,255,0.15);
  color: rgba(255,255,255,0.6); font-size: 0.85rem; text-align: center; /* cr-allow-white: on-charcoal */
  position: relative; z-index: 1;
}
