/* Finch Criminal Law: page CSS.
   Extracted verbatim from the <style> block in index.html on 14 August 2026 when
   about.html was added. Linked by every page, from the project ROOT so the one
   relative url() in here (assets/images/contact-office.webp) keeps resolving.
   It is linked at the same document position the <style> block occupied, after
   the design system styles.css, so the cascade order is unchanged. */
/* ============================================================
   Finch Criminal Law home page.
   Everything below is composed from the design-system tokens in
   _ds/.../tokens/. No new colours, radii, shadows or families.
   The shear (--shear-angle, the logo mark's -20deg) is the one
   geometric motif and is used here as accent bar and divider only.
   ============================================================ */

/* Semantic z-scale, so nothing ever reaches for 9999 */
:root{
  --z-base:0;
  --z-raised:1;
  --z-flyout:20;
  --z-sticky-cta:25;
  --z-sticky:30;
  --z-skip:100;
}
/* visually hidden, still read by assistive tech */
.vh{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}

html{scroll-padding-top:130px}
body{margin:0;background:var(--surface-page);color:var(--text-body);font-family:"Montserrat","Helvetica Neue",Arial,sans-serif;-webkit-font-smoothing:antialiased}
*{box-sizing:border-box}
a{color:var(--text-link)}a:hover{color:var(--text-link-hover)}
/* Container reclaims half the dead space each side. The design system's
   --container-max is 1200px; this gives (viewport + 1200) / 2, so the side
   gutter is exactly half what it was, and caps at 1560px so line lengths stay
   sane on very wide monitors (the cap only bites above 1920px, where the
   formula would have produced 1560px anyway).
   Uses % rather than vw so a classic Windows scrollbar cannot push it wide.
   Text measures are all ch-capped, so widening the shell does not lengthen
   any line of prose. */
.wrap{max-width:min(calc((100% + 1200px) / 2),1560px);margin:0 auto;padding:0 40px}
.skip{position:absolute;left:-9999px;background:var(--surface-card);color:var(--text-heading);padding:12px 20px;z-index:var(--z-skip);border-radius:var(--radius-xs)}
.skip:focus{left:16px;top:16px}

/* ---------- The brand mark ------------------------------------
   The firm's real icon mark, traced from the supplied artwork and
   drawn as SVG paths in home-sections-a.jsx, so it takes
   currentColor and stays crisp at any size. Used as a section
   accent and divider. Never on a content container.
   NOTE: --shear / --shear-angle (-20deg) remain the tokens for the
   small hover bars on nav links and service cards, which are
   genuine skewed rules, not the mark. */
.brandmark{display:block;flex:none}

/* ---------- Utility bar + header ------------------------------ */
.utilbar{background:var(--navy-900);color:var(--text-on-dark-muted);font-size:13px}
.ubin{min-height:40px;display:flex;align-items:center;justify-content:space-between;gap:24px}
.ubitem{display:inline-flex;align-items:center;gap:8px}
.ubright{display:flex;align-items:center;gap:28px}
.ubphone{display:inline-flex;align-items:center;gap:8px;color:var(--text-on-dark);font-weight:600;text-decoration:none}
.ubphone:hover{color:var(--maroon-300)}
/* The blur lives on a pseudo-element, not on .hdr itself. backdrop-filter on
   the header makes it a compositing group that traps its own children, which
   left the Services flyout painting BEHIND the hero headline. Same visual
   result, and the flyout now stacks correctly. */
.hdr{position:sticky;top:0;z-index:var(--z-sticky);box-shadow:var(--shadow-header)}
.hdr::before{content:"";position:absolute;inset:0;z-index:-1;background:var(--scrim-header);backdrop-filter:var(--blur-header);-webkit-backdrop-filter:var(--blur-header)}
.hdrin{min-height:88px;display:flex;align-items:center;justify-content:space-between;gap:32px}
.hdrlogo{flex:0 0 auto;line-height:0}
.nav{display:flex;align-items:center;gap:32px;font-size:12px;font-weight:600;letter-spacing:var(--tracking-eyebrow);text-transform:uppercase}
.nav a{position:relative;color:var(--text-muted);text-decoration:none;padding-bottom:6px;transition:color var(--dur-fast) var(--ease-out)}
/* underline grows from the left on hover: the shear's cousin, not a border swap */
/* scaleX, not width: composited, no layout thrash */
.nav a::after{content:"";position:absolute;left:0;bottom:0;height:2px;width:100%;background:var(--maroon-700);transform:var(--shear) scaleX(0);transform-origin:left center;transition:transform var(--dur-base) var(--ease-out)}
.nav a:hover{color:var(--text-heading)}
.nav a:hover::after{transform:var(--shear) scaleX(1)}
/* ---------- Services flyout -----------------------------------
   Lives inside the sticky header, which has no overflow clipping,
   so an absolutely positioned panel escapes cleanly. */
.navdd{position:relative;display:flex;align-items:center}
.navdd-t{display:inline-flex;align-items:center;gap:6px}
.navdd-t svg{transition:transform var(--dur-fast) var(--ease-out)}
.navdd-t[aria-expanded="true"] svg{transform:rotate(180deg)}
.navdd-t[aria-expanded="true"]{color:var(--text-heading)}
.navdd-t[aria-expanded="true"]::after{transform:var(--shear) scaleX(1)}
/* offset clears the full 88px header, not just the link box */
.navdd-m{position:absolute;top:calc(100% + 35px);left:-24px;z-index:var(--z-flyout);
  min-width:288px;padding:22px 24px 18px;background:var(--surface-card);
  border:1px solid var(--border-hairline);border-radius:var(--radius-card);
  box-shadow:var(--shadow-lg)}
.navdd-m[hidden]{display:none}
/* a hover bridge across the gap, so the pointer can travel to the panel */
.navdd-m::before{content:"";position:absolute;left:0;right:0;top:-35px;height:35px}
/* .navdd-mark removed 19 August with the flyout's brand mark. */
.navdd-m ul{list-style:none;margin:0;padding:0;display:grid;gap:2px}
.navdd-m a{display:block;padding:9px 10px;margin:0 -10px;font-size:13.5px;font-weight:500;
  letter-spacing:0;text-transform:none;color:var(--text-body);text-decoration:none;
  border-radius:var(--radius-xs);transition:background-color var(--dur-fast) var(--ease-out),color var(--dur-fast) var(--ease-out)}
.navdd-m a:hover{background:var(--navy-050);color:var(--text-heading)}
/* the nav's shear underline must not leak onto flyout items */
.navdd-m a::after{content:none}
/* NO ANIMATION ANYWHERE INSIDE THE FLYOUT. This is deliberate and load-bearing.
   Anything inside .navdd-m that gets a CSS animation is promoted to a composited
   layer, and inside this sticky, backdrop-filtered header Chrome then paints
   that layer wrong while still hit-testing it correctly. It has bitten twice:
   animating .navdd-m made the panel look half-transparent with the hero <h1>
   showing through; moving the animation to .navdd-m > * fixed that but later
   left the eight menu items invisible on an opaque panel. Both times
   getComputedStyle reported perfectly normal values.
   A/B confirmed each time: `animation: none` renders correctly, opacity-only
   keyframes do not help, so it is layer promotion itself, not the property.
   The flyout opens instantly. That is the cost, and it is worth paying. */

.hdract{display:flex;align-items:center;gap:20px;min-width:0}
.navcall{display:inline-flex;align-items:center;gap:8px;color:var(--text-heading);font-weight:700;font-size:15px;text-decoration:none;white-space:nowrap}
.navcall:hover{color:var(--maroon-700)}

/* ---------- Hero ----------------------------------------------
   The one dominant idea on the page. Type runs to the system's
   display scale and the portrait breaks the 1200px container to
   the viewport edge, so the composition is asymmetric rather than
   two equal columns. */
.hero{background:var(--gradient-brand);position:relative;overflow:hidden}
/* The whole hero must sit above the fold: viewport minus the 40px utility bar
   and the 88px header. Type is capped against BOTH width and viewport height so
   a short laptop screen shrinks the headline rather than pushing content out of
   view. The USP strip is now INSIDE this box: .hero owns the 100svh height and
   .herog flexes to fill whatever the strip leaves, so the strip sits on the
   bottom edge of the first screen at every viewport height. */
.hero{min-height:calc(100svh - 128px);display:flex;flex-direction:column}
/* width:100% is load-bearing. .herog is a .wrap, so it carries `margin:0 auto`,
   and .hero is a column flex container. A flex item with auto side margins is
   NOT stretched: it shrink-wraps to fit-content and the auto margins then
   centre it. That silently indented and centred the whole hero column at
   tablet widths (copy started at 181px instead of 40px at 900px wide) from the
   moment .hero became a flex container. Explicit width restores stretch. */
.herog{width:100%;flex:1 1 auto;min-height:0;padding-top:clamp(20px,3.6vh,44px);padding-bottom:clamp(20px,3.6vh,44px);display:grid;grid-template-columns:1.12fr .88fr;gap:56px;align-items:center}
.herocopy{min-width:0}
.heroimg{align-self:stretch;min-height:0;padding:0;position:relative}
/* bleed the portrait out of the container to the right viewport edge */
/* Pull the portrait out to the viewport edge. The offset must track the same
   two regimes as .wrap above: (100vw-1200)/4 below the cap, (100vw-1560)/2
   above it. .hero has overflow:hidden, so any scrollbar rounding is clipped
   rather than becoming document overflow. */
@media (min-width:1081px){
  /* The hero copy fills the CONTAINER, not the container's padded inset.
     The portrait bleeds to the right viewport edge, so a ~100px left gutter
     made the copy look pushed inward against a side with no gutter at all.
     Dropping the 40px padding moves the copy out to the container's own left
     edge, which is still a real page landmark, so the hero stays related to
     the grid instead of hugging the viewport (which diverges badly on wide
     screens: the container edge is 500px in at 2560, not 72px).
     The max() keeps a 40px floor below ~1360px, where the container is already
     flush to the viewport and zero padding would put text against the edge. */
  .herog{padding-left:max(0px, calc(40px - max(0px, (100vw - 1200px) / 4, (100vw - 1560px) / 2)));
    padding-right:0}
  /* bleed now only has to cover the container gutter, not gutter + padding */
  .heroimg{margin-right:calc(-1 * max(0px, (100vw - 1200px) / 4, (100vw - 1560px) / 2));
    margin-top:calc(-1 * clamp(20px,3.6vh,44px));margin-bottom:calc(-1 * clamp(20px,3.6vh,44px))}
  /* wider column, so let the copy actually use it rather than sitting in a
     narrow measure with dead navy to its right */
  .herocopy h1{max-width:23ch}
  .herocopy .lead{max-width:56ch}
  /* the USP strip keeps the shared container: it is page furniture, and it
     stays centred on the page, which was the point of the last fix */
}
/* margin-top 0: with the brand mark gone the h1 is the first thing in the
   column, and a top margin would hang it below the top of the image beside it */
h1{font-size:min(clamp(32px,4vw,54px),6.3vh);line-height:1.07;letter-spacing:-.022em;font-weight:700;color:var(--text-on-dark);margin:0 0 clamp(10px,1.7vh,18px);text-wrap:balance;max-width:18ch}
.lead{font-size:min(18px,2.3vh);line-height:1.65;max-width:46ch;margin:0 0 clamp(18px,2.8vh,30px);text-wrap:pretty}
.lead-dark{color:var(--text-on-dark-muted)}
/* the two CTAs wrap to stacked at every width the hero column allows;
   consistent-but-unequal button widths read as a bug, so pin them equal */
.ctarow{display:flex;gap:14px;flex-wrap:wrap}
.ctarow>a{min-width:324px}
@media (max-width:640px){.ctarow>a{min-width:0;width:100%}}
/* four USPs on a 2x2 so the block stays compact enough to clear the fold */
.usps{list-style:none;display:grid;grid-template-columns:repeat(2,auto);justify-content:start;gap:clamp(8px,1.4vh,14px) 32px;margin:clamp(18px,3vh,32px) 0 0;padding:clamp(16px,2.6vh,26px) 0 0;border-top:1px solid var(--border-on-dark)}
.usps li{display:inline-flex;align-items:center;gap:10px;color:var(--text-on-dark);font-size:min(14px,1.8vh);font-weight:600}
.usps li svg{color:var(--maroon-300);flex:none}

/* ---------- Google rating -------------------------------------- */
/* No box. It sat directly under the hero CTA and its border read as a second,
   weaker button. It is now a plain inline mark: Google glyph, stars, score. */
.grating{display:inline-flex;align-items:center;gap:9px;text-decoration:none;
  transition:var(--transition-control)}
.grating-g{display:flex;flex:none;line-height:0}
.grating-stars{display:flex;gap:2px;color:var(--maroon-500)}
.grating-stars svg{fill:currentColor;stroke:none}
.grating-txt{font-size:15px;white-space:nowrap}
.grating-txt b{font-weight:700;color:var(--text-heading);letter-spacing:-.01em}
.grating:hover .grating-stars{color:var(--maroon-700)}
.grating-dark .grating-stars{color:var(--maroon-300)}
.grating-dark .grating-txt b{color:var(--text-on-dark)}
.grating-dark:hover .grating-stars{color:#fff}
.grating-sm{gap:8px}
.grating-sm .grating-txt{font-size:14px}
.grating:focus-visible{outline:none;box-shadow:var(--ring-focus);border-radius:var(--radius-sm)}
.grating-dark:focus-visible{box-shadow:var(--ring-focus-dark)}

/* ---------- Hero credentials row ------------------------------- */
/* QLS accreditation and the Google rating share one line, so the two proof
   marks read as a single credentials band rather than two stacked badges. */
/* Sits well clear of the CTA: at the old clamp the credentials read as part of
   the button block rather than as separate proof. Separated from the CTA by a
   hairline as well, so the hero reads action / then evidence.
   Every value is vh-aware, because this block plus the USP strip has to fit
   inside 100svh on a 720px-tall laptop. */
.herocreds{display:flex;align-items:center;gap:clamp(16px,2.2vw,28px);flex-wrap:wrap;
  margin-top:clamp(20px,3vh,40px);padding-top:clamp(16px,2.2vh,28px);
  border-top:1px solid var(--border-on-dark)}
.herocreds-rule{width:1px;align-self:stretch;min-height:34px;background:var(--border-on-dark)}
/* The supplied mark is transparent with black ring text, so it needs a light
   ground to read on the navy. The disc changes; the mark itself never does. */
.qlschip{display:grid;place-items:center;flex:none;border-radius:50%;
  width:var(--qls);height:var(--qls);
  background:var(--bone-50,#FBFAF9);box-shadow:0 0 0 1px rgba(6,19,29,.06)}
.qlsmark{display:block;width:100%;height:100%;object-fit:contain;padding:5px}
/* the hero copy column must clear the fold on a 720px screen, so the badge
   shrinks with viewport height rather than pushing the strip under it */
.herocreds .qlschip{width:min(var(--qls),7.6vh);height:min(var(--qls),7.6vh)}
/* Mark plus words. The ring text on the artwork is unreadable much below
   100px, so at hero size the mark alone says nothing to anyone who does not
   already know it. Fallback (.qlstxt) is the same words with an icon, used
   only if the image fails to load. */
.qlslock,.qlstxt{display:inline-flex;align-items:center;gap:12px}
.qlstxt{gap:10px}
.qlstxt svg{color:var(--maroon-700);flex:none}
/* Words sized up 17 September 2026 (BugHerd 102) with the mark. */
.qlswords{display:flex;flex-direction:column;font-size:12.5px;line-height:1.32;
  letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;color:var(--text-muted)}
/* The post-nominal keeps QLS's own casing, AccS(Crim), inside an uppercase line. */
.qlspn{text-transform:none}
.qlswords b{font-size:15px;font-weight:700;letter-spacing:.03em;color:var(--text-heading)}
.qlstxt-dark svg{color:var(--maroon-300)}
.qlstxt-dark .qlswords{color:var(--text-on-dark-muted)}
.qlstxt-dark .qlswords b{color:var(--text-on-dark)}

/* ---------- USP strip, moved out of the hero -------------------- */
/* Reverted 5 August to the original bone band, on request. It still lives
   INSIDE .hero so it stays above the fold: flex:0 0 auto holds it at its
   natural height and .herog absorbs the rest of the 100svh box. */
.uspstrip{flex:0 0 auto;background:var(--surface-card);
  border-bottom:1px solid var(--border-hairline)}
/* flex, not a 4-column grid: equal columns left-aligned each item inside its
   own cell, so the set read as left-hung rather than centred, and the longest
   USP wrapped to two lines while the others sat on one. */
/* Centred on the page, not on the container's content box: .wrap is centred
   and symmetrically padded, so justify-content:center puts the midpoint of the
   set on the page midpoint. Asserted against the viewport centre, not the ul. */
/* margin:0 AUTO, not margin:0. This is a .wrap, and .wrap's `margin:0 auto`
   sits at the same specificity earlier in the sheet, so a bare `margin:0` here
   silently wins and hangs the whole list off the left edge. It did exactly
   that until 5 August. Measuring the items against the list's own box cannot
   detect it; assert against the VIEWPORT centre instead. */
.uspstrip-in{list-style:none;margin:0 auto;display:flex;flex-wrap:nowrap;justify-content:center;
  align-items:center;gap:clamp(20px,3.4vw,52px);
  padding-top:clamp(15px,2.1vh,24px);padding-bottom:clamp(15px,2.1vh,24px)}
.uspstrip-in li{display:flex;align-items:center;gap:10px;font-size:clamp(13px,1.02vw,14.5px);
  font-weight:600;line-height:1.4;color:var(--text-heading);white-space:nowrap}
.uspstrip-in svg{color:var(--maroon-700);flex:none}

/* ---------- Process -------------------------------------------- */
/* Deliberately plain. The previous version separated the cells with sheared
   2px rules; the skew threw them well outside their own cell and they read as
   diagonal slashes through the copy. Cards, a gap, a number. */
.procgrid{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(4,1fr);gap:20px}
.proc{background:var(--surface-card);border:1px solid var(--border-hairline);
  border-radius:var(--radius-card);padding:30px 26px 28px}
.proc-n{display:block;font-size:13px;font-weight:700;letter-spacing:var(--tracking-eyebrow);
  color:var(--maroon-700);margin-bottom:16px}
.proc h3{font-size:18.5px;font-weight:600;line-height:1.35;color:var(--text-heading);margin:0 0 10px}
.proc p{font-size:15px;line-height:1.65;color:var(--text-muted);margin:0}

/* ---------- Sticky CTA ------------------------------------------ */
.stickycta{position:fixed;left:0;right:0;bottom:0;z-index:var(--z-sticky-cta);
  background:var(--navy-900);border-top:1px solid var(--border-on-dark);
  box-shadow:0 -10px 30px rgba(6,19,29,.28);
  transform:translateY(100%);transition:transform var(--dur-base) var(--ease-out);
  visibility:hidden}
.stickycta.is-on{transform:none;visibility:visible}
.stickycta-in{display:flex;align-items:center;justify-content:space-between;gap:24px;
  padding-top:14px;padding-bottom:14px}
.stickycta-t{font-size:14.5px;line-height:1.45;color:var(--text-on-dark);margin:0;
  /* BugHerd 41: one line, with the original wording restored. The 52ch cap was
     what forced the wrap: the line is 84 characters, so 52ch guaranteed two.
     nowrap makes the single line a rule rather than a coincidence of viewport
     width, and the text is already display:none below 1081px, so this only has
     to hold on desktop. The breakpoint at which it still FITS is measured, not
     assumed, and set below. */
  white-space:nowrap;min-width:0}
.stickycta-a{display:flex;align-items:center;gap:20px;flex:none}
.stickycta-ph{display:inline-flex;align-items:center;gap:8px;color:var(--text-on-dark);
  font-weight:700;font-size:15px;text-decoration:none;white-space:nowrap}
.stickycta-ph:hover{color:var(--maroon-300)}
.stickycta-ph svg{color:var(--maroon-300)}

/* ---------- Footer memberships ---------------------------------- */
/* centred as a block: heading over a centred row of tiles */
.foot .faccred{padding-bottom:32px;text-align:center}
.foot .faccred h2{margin:0 0 18px}
/* flex-direction:row is load-bearing. The generic `.foot ul` further down sets
   flex-direction:column, and omitting it here stacks the tiles full-width. */
.foot .faccred ul{display:flex;flex-direction:row;flex-wrap:wrap;justify-content:center;
  gap:14px;list-style:none;margin:0;padding:0}
.foot .faccred li{flex:0 1 190px;min-width:150px}
/* The QLS tile now carries mark plus words (BugHerd 102), so it sizes to its
   content and its li is allowed to be wider than the placeholder tiles. */
.faccred-tile{display:grid;place-items:center;min-height:74px;padding:6px 14px;text-align:left}
.foot .faccred li:first-child{flex:0 1 auto}
.faccred-tile .qlsmark{margin:0 auto}
.foot .fabout+.flegal{margin-bottom:22px}

/* ---------- Media placeholders -------------------------------- */
.ph{background:var(--bone-100);border:1px solid var(--border-hairline);border-radius:var(--radius-image);display:grid;place-items:center;width:100%;overflow:hidden}
.ph span{font-size:12px;letter-spacing:var(--tracking-wide);text-transform:uppercase;color:var(--text-muted);text-align:center;padding:0 24px;line-height:1.6}
.ph-dark{background:rgba(251,250,249,.06);border-color:var(--border-on-dark)}
.ph-dark span{color:var(--text-on-dark-muted)}
.heroimg .ph{border-radius:0;border:none}
/* The source is 3:2 landscape and this slot is a tall portrait column, so
   object-fit:cover keeps only about 45% of the width. The subject sits
   centre-right (roughly 45-78% across), so the focal point is pushed right of
   centre to keep him framed instead of cropping his shoulder. */
.heroimg .ph img{object-position:70% center}
@media (max-width:1080px){.heroimg .ph img{object-position:64% center}}

/* ---------- Section rhythm ------------------------------------
   The system defines --section-y 96px and --section-y-lg 128px, and the page
   ran on those plus hand-set 112px in four places. It read as too airy: the
   sections were so far apart the page lost its through-line and every panel
   felt like a separate document.
   Retuned 5 August to a viewport-aware ramp roughly 25 to 30% tighter. The
   tokens are left alone; these are local overrides, so nothing else built on
   the design system moves. The pulse (normal / large / extra) is preserved,
   just at a smaller amplitude. */
:root{
  --sec-y:clamp(52px,6vw,76px);
  --sec-y-lg:clamp(64px,7.2vw,92px);
  --sec-y-xl:clamp(68px,7.8vw,96px);
  --shead-gap:clamp(26px,3.2vw,38px);
}
.sec{padding:var(--sec-y) 0}
.sec-white{background:var(--surface-card)}
/* the readme's rhythm is bone -> sunken bone -> gradient navy; this is the
   sunken step, and it keeps the FAQ distinct from white Locations above and
   bone Insights below */
.sec-sunken{background:var(--surface-sunken)}
#services{padding:var(--sec-y-xl) 0}
#about{padding:var(--sec-y-lg) 0}
#why{padding:var(--sec-y) 0}
/* trimmed from 112px: the panel was 842px tall, out of proportion to the
   three short quotes it holds */
#testimonials{padding:var(--sec-y) 0}
#testimonials .shead{margin-bottom:clamp(20px,2.4vw,28px)}
#locations{padding:var(--sec-y-lg) 0}
#faq{padding:var(--sec-y-xl) 0}
#insights{padding:var(--sec-y-xl) 0}

.shead{margin-bottom:var(--shead-gap);max-width:none}
/* the 20px used to sit on the h2 to clear the mark above it. With the mark now
   opt-in, the spacing moves onto the mark so headings without one start flush. */
.shead .brandmark{margin:0 0 16px}
/* Widened 20ch -> 34ch and 58ch -> 74ch so the heading and intro run further
   right above the card rows in Services, Why, Testimonials and Locations.
   About, FAQ and Contact are unaffected: their sheads sit inside narrower
   grid columns that constrain them well before these caps bite.
   74ch keeps the intro inside the 65-75ch readability band. */
.shead h2{font-size:clamp(30px,3vw,42px);line-height:1.12;letter-spacing:var(--tracking-heading);font-weight:600;color:var(--text-heading);margin:0;text-wrap:balance;max-width:34ch}
.sintro{font-size:17px;line-height:1.7;color:var(--text-muted);max-width:74ch;margin:18px 0 0;text-wrap:pretty}

/* ---------- Services: a hairline matrix, not six floating cards
   "Structure comes from hairlines, not boxes" (design system
   readme). Six identical bordered cards was the tell; this is one
   ruled block that reads as a single object. */
/* 8 practice areas as a scroll-snap carousel: 4 in view on desktop, 2 on
   tablet, 1 with a peek on mobile. Native scrolling means it stays swipeable
   and trackpad-scrollable; the arrows page it by exactly one card.
   The 2px gutters keep the run reading as one block. */
.svctrack{display:flex;gap:2px;overflow-x:auto;overflow-y:hidden;
  scroll-snap-type:x mandatory;overscroll-behavior-x:contain;
  border-radius:var(--radius-card);
  scrollbar-width:none;-ms-overflow-style:none}
.svctrack::-webkit-scrollbar{display:none}
.svctrack .scard{flex:0 0 calc((100% - 6px) / 4);scroll-snap-align:start}

/* The SERVICES INDEX shows the same eight cards as a static 4x2 grid instead.
   Same 2px gutters and same radius as the carousel track, so the run reads as
   one block either way, and the same .scard rules below style both: only the
   container differs. Deliberately NOT scoped to a page class, because the grid
   only exists where the markup does. */
/* Row gap is deliberately much larger than the column gap: the 2px gutter keeps
   each ROW reading as one continuous block, the way the carousel track does,
   while the rows themselves are separated so the grid does not read as one
   430px-tall slab of photography. Asked for on 19 August.
   overflow:hidden had to go with it. It was there to clip the cards to the
   container's radius, but with a row gap it also clipped the gap's background
   to the rounded box, which showed as the page colour cornering oddly between
   rows. The cards carry their own square corners, so nothing needs clipping. */
/* 3x3 SINCE 11 SEPTEMBER 2026: BugHerd 49 took the practice areas from eight
   to nine, and nine on a 4-up grid leaves one card alone on a third row. Three
   across fills three rows exactly, and each card is wider than it was at 4-up,
   so the "Homicide & serious crime" wrap noted below only improves. */
.svcgrid{display:grid;grid-template-columns:repeat(3,1fr);
  gap:clamp(18px,2vw,28px) 2px}

@media (max-width:1080px){
  /* 2x4. At 4-up the cards drop under ~250px wide and "Homicide & serious
     crime" wraps to three lines against a 430px-tall card. */
  .svcgrid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:640px){
  .svcgrid{grid-template-columns:1fr}
}

/* carousel arrows, sat top-right beside the section heading */
.svcnav{display:flex;gap:8px;flex:none}
.svcbtn{display:inline-flex;align-items:center;justify-content:center;width:46px;height:46px;
  background:transparent;color:var(--maroon-700);border:1px solid var(--border-default);
  border-radius:var(--radius-button);cursor:pointer;transition:var(--transition-control)}
.svcbtn:hover:not(:disabled){background:var(--navy-800);border-color:var(--navy-800);color:var(--bone-50)}
.svcbtn:disabled{background:var(--disabled-bg);border-color:transparent;color:var(--disabled-text);cursor:not-allowed}
.svcbtn:focus-visible{outline:none;box-shadow:var(--ring-focus)}
/* the icon set has no chevron-left, so the previous arrow is a rotated one */
.svcbtn-prev svg{transform:rotate(180deg)}

.scard{position:relative;display:flex;align-items:flex-end;min-height:430px;overflow:hidden;text-decoration:none;background:var(--navy-900);isolation:isolate}
.scardimg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:-2;
  filter:saturate(.72) contrast(1.02);transform:scale(1.02);
  transition:transform var(--dur-slow) var(--ease-out),filter var(--dur-slow) var(--ease-out)}
.scard:hover .scardimg{transform:scale(1.07);filter:saturate(.85) contrast(1.02)}
/* documented treatment for text over photography: a scrim, never a capsule.
   Held at .94 through the whole text zone so body copy clears AA on any frame. */
.scardscrim{position:absolute;inset:0;z-index:-1;
  background:linear-gradient(180deg,rgba(6,19,29,.34) 0%,rgba(6,19,29,.62) 34%,rgba(6,19,29,.94) 62%,rgba(6,19,29,.97) 100%);
  transition:opacity var(--dur-base) var(--ease-out)}
.scardbody{position:relative;display:flex;flex-direction:column;align-items:flex-start;padding:26px 24px 24px;width:100%}
/* icon and heading share a row; the row carries the two-line reserve so every
   description in a row still starts on the same baseline */
.scardhead{display:flex;align-items:flex-start;gap:13px;margin:0 0 12px;width:100%}
/* BugHerd 31, second half: flex-start, NOT center. The row reserves two lines,
   and centring inside that reserve means a one-line heading sits 12px lower
   than a two-line one. "Homicide & serious crime" is the only card that wraps,
   so it was the only heading out of line once the descriptions were equalised.
   Top-aligning makes every heading start at the same y whatever it wraps to,
   and it puts the icon beside the FIRST line of a wrapped heading, which is
   where it belongs. The practice area names are the client's exact wording and
   are not shortened to dodge this. */
@media (min-width:641px){.scardhead{min-height:52px}}
.scardicon{color:var(--maroon-300);flex:none;line-height:0;transition:transform var(--dur-base) var(--ease-out)}
.scard:hover .scardicon{transform:translateY(-3px)}
.scard h3{font-size:18.5px;font-weight:700;line-height:1.28;color:var(--text-on-dark);margin:0;letter-spacing:-.008em;min-width:0}
.scard p{font-size:14px;line-height:1.6;color:var(--text-on-dark-muted);margin:0 0 16px;max-width:32ch}
/* BugHerd 31: every card heading sits on the same line as the others.
   .scard is align-items:flex-end, so the whole body is anchored to the BOTTOM
   of the card and a longer description pushes its heading UP. While the
   descriptions were lorem they were all the same length and the headings lined
   up by accident; real copy of 4 to 7 lines spread them by 68px.
   Reserving five lines here makes the alignment structural rather than a
   property of the copy, so a future wording change cannot silently break it.
   Paired with descriptions tightened to a narrow character band. Desktop only:
   below 641px the cards are a one-per-view carousel and reserving space there
   would just add dead height. */
@media (min-width:641px){.scard p{min-height:112px}}
.scardgo{margin-top:auto;display:inline-flex;align-items:center;gap:6px;font-size:11.5px;font-weight:600;letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;color:var(--text-on-dark)}
.scardgo svg{transition:transform var(--dur-base) var(--ease-out)}
.scard:hover .scardgo svg{transform:translateX(4px)}
/* the shear mark wipes across the top edge on hover */
.scard::before{content:"";position:absolute;top:0;left:0;height:4px;width:64px;background:var(--maroon-500);transform:var(--shear) scaleX(0);transform-origin:left center;transition:transform var(--dur-base) var(--ease-out);z-index:var(--z-raised)}
.scard:hover::before{transform:var(--shear) scaleX(1)}
.scard:focus-visible{outline:none;box-shadow:var(--ring-focus-dark);z-index:var(--z-raised)}

/* ---------- Generic grids ------------------------------------- */
.grid3{display:grid;grid-template-columns:repeat(3,1fr);gap:32px}
/* TESTIMONIALS RAIL: three per view, scrolled, above the mobile breakpoint.
   Six real reviews replaced three placeholders on 8 September 2026 and the
   3-up grid wrapped to two rows, which is not what the design does anywhere
   else. This makes it a track instead.

   It only applies from 641px up. Below that .msnap already turns the same
   element into the one-per-view snap carousel every other rail uses, so the
   mobile behaviour and its assertions are untouched.

   THE CARD WIDTH SUBTRACTS TWO GAPS, NOT THREE. Three cards across means two
   gaps between them, so (100% - 2*32)/3. Subtracting three leaves a sliver of
   the fourth card showing, which reads as a rendering fault rather than as an
   invitation to scroll. */
@media (min-width:641px){
  .tstrack{display:flex;gap:32px;overflow-x:auto;overflow-y:hidden;
    scroll-snap-type:x mandatory;scroll-behavior:smooth;
    scrollbar-width:none;-ms-overflow-style:none;padding-bottom:4px}
  .tstrack::-webkit-scrollbar{display:none}
  .tstrack>*{flex:0 0 calc((100% - 64px) / 3);scroll-snap-align:start;min-width:0}
  /* The controls sit under the track and centred, the same placement BugHerd 24
     settled on for the services carousel and BugHerd 13/14 for the card rails,
     so all four rails on the site handle the same way. */
  #testimonials .msnav{display:flex;justify-content:center;gap:12px;margin-top:26px}
}
.grid4{display:grid;grid-template-columns:repeat(4,1fr);gap:32px}
.grid3 h3,.grid4 h3{font-size:19px;font-weight:600;color:var(--text-heading);margin:20px 0 10px}
.grid3 p,.grid4 p{font-size:15px;line-height:1.65;color:var(--text-muted);margin:0}
.card-ondark h3{color:var(--text-on-dark)}
.card-ondark p{color:var(--text-on-dark-muted)}
.cardlink{display:inline-flex;align-items:center;gap:6px;margin-top:18px;font-size:12px;font-weight:600;letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;color:var(--maroon-700);text-decoration:none}
.cardlink svg{transition:transform var(--dur-base) var(--ease-out)}
a.cardlink:hover{color:var(--maroon-500)}
a.cardlink:hover svg{transform:translateX(4px)}

/* ---------- About --------------------------------------------- */
.aboutg{display:grid;grid-template-columns:.92fr 1.08fr;gap:80px;align-items:center}
.aboutmedia{position:relative}
/* ---------- About tabs -----------------------------------------
   The DS Tabs component styles itself inline; these only add the
   spacing it leaves open and a fade as the panel content swaps.
   The panel is keyed on the tab value in JSX, so React remounts it
   and the animation re-runs on every change. */
/* width:fit-content does both jobs at once: the tablist shrinks to the two
   tabs so the DS component's inline border-bottom no longer spans the whole
   panel, and margin-inline:auto then centres it. */
.abouttabs{width:fit-content;margin:0 auto 44px}
.aboutg[role="tabpanel"]{animation:aboutIn var(--dur-base) var(--ease-out)}
@keyframes aboutIn{from{opacity:0}to{opacity:1}}
@media (prefers-reduced-motion:reduce){.aboutg[role="tabpanel"]{animation:none}}

/* The About photo is a 2:3 portrait. Its ratio prop is 4/5 rather than the
   original 4/3 so the stacked (mobile) crop stays portrait and does not slice
   the subject in half; on desktop the ratio is overridden to fill the column.
   Focal point sits above centre to keep his head in frame and hold the
   clasped hands. */
.aboutmedia .ph img{object-position:var(--about-focal,center 30%)}
/* let the photo carry the column instead of floating in whitespace.
   Ph writes aspect-ratio inline, so the override needs !important. */
@media (min-width:1081px){
  .aboutg{align-items:stretch}
  .aboutmedia{display:flex;min-height:560px}
  .aboutmedia .ph{aspect-ratio:auto!important;height:100%;width:100%}
}
/* the mark, oversized, tucked against the photo corner: brand
   signature doing structural work rather than decoration */
.aboutmark{position:absolute;left:-30px;bottom:30px;z-index:var(--z-raised)}
.body{font-size:16.5px;line-height:1.72;color:var(--text-body);max-width:56ch;margin:0 0 20px;text-wrap:pretty}
.creds{list-style:none;margin:10px 0 34px;padding:0;display:flex;flex-direction:column;gap:0}
.creds li{display:flex;align-items:center;gap:14px;font-size:15px;font-weight:600;color:var(--text-heading);padding:15px 0;border-top:1px solid var(--border-hairline)}
.creds li:last-child{border-bottom:1px solid var(--border-hairline)}
.creds svg{color:var(--maroon-700);flex:none}

/* ---------- Why ------------------------------------------------ */
.whygrid .why{border-top:2px solid var(--navy-800);padding-top:26px}
.whygrid .why svg{color:var(--maroon-700);flex:none}
/* icon and heading on one row, matching the service cards */
.whyhead{display:flex;align-items:center;gap:13px;margin:0 0 12px}
/* BugHerd 32: reserve two lines for every differentiator heading so the four
   cards' body copy starts on the same baseline. Three of the four wrap to two
   lines and "Fee certainty in writing" does not, which left its paragraph
   sitting 24px higher than its neighbours. Reserved rather than padded with
   longer wording, because the headings are the approved copy and stretching one
   to fit a layout is how copy quietly drifts. Desktop only: the grid is a
   single column below 900px, where there is nothing to line up against. */
@media (min-width:901px){.whyhead h3{min-height:50px}}
/* scoped two deep: .grid4 h3 is (0,1,1) and would win on source order */
.whygrid .why h3{font-size:18.5px;line-height:1.35;margin:0;min-width:0}

/* ---------- Stats: an evidence rail, not a metrics wall --------
   Big-number-plus-small-label repeated four times is the SaaS
   hero-metric template. Pulling the value down, pushing the label
   up and separating with the shear makes it read as evidence. */
.stats{background:var(--gradient-brand);padding:84px 0}
/* the rail is centred as a group and each of the three blocks is centred
   within its own track, so the whole thing reads symmetrically */
.statgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:0}
.stat{display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:4px 40px;position:relative}
.stat+.stat::before{content:"";position:absolute;left:0;top:6px;bottom:6px;width:2px;background:var(--border-on-dark);transform:var(--shear)}
/* tabular figures stop the number jittering while it counts up */
.statv{font-size:clamp(36px,3.8vw,52px);font-weight:700;letter-spacing:-.02em;color:var(--text-on-dark);line-height:1;font-variant-numeric:tabular-nums;font-feature-settings:"tnum" 1;position:relative}
.statl{font-size:14.5px;line-height:1.65;color:var(--text-on-dark-muted);max-width:26ch;margin-inline:auto}
/* the brand's mark closes off each stat */
.stat .brandmark{margin-top:8px}

/* ---------- Testimonials --------------------------------------- */
/* the Icon set is outline-only; an unfilled star row reads as zero stars */
/* the card is a flex column so the quote absorbs the slack; the divider,
   initials and meta then sit on the same line across all three cards
   regardless of quote length */
.tcard{display:flex!important;flex-direction:column}
.stars{display:flex;gap:3px;color:var(--maroon-500);margin:16px 0 0}
.stars svg{fill:currentColor;stroke:none}
/* `.tcard .quote`, not `.quote`: the cards sit inside .grid3, and `.grid3 p`
   sets margin:0 at (0,1,1) specificity, which silently beat a bare `.quote`
   rule at (0,1,0). That is why the gap under the stars stayed at 0px. */
/* TESTIMONIAL CARDS, reworked 8 September 2026 when the six real reviews landed.
   The three placeholders were all the same length, so the old flex:1 1 auto
   quote produced equal cards by accident. The real reviews run from about 200
   to 490 characters, which would have made one card nearly twice the height of
   another and pushed every name to a different line.

   FIXED HEIGHT, NOT max-height, and that is the whole trick. A max-height only
   caps the tall ones, so the short ones stay short and the names still land at
   different heights. A fixed height makes every quote block identical, so the
   divider and name sit on the same line on EVERY card, in both grid rows and
   across the mobile carousel. Long reviews scroll inside that block.

   160px IS MEASURED, NOT CHOSEN. It is the natural rendered height of the
   shortest review, Ben Keech's, which Wayde set as the visible height before
   scrolling. Measured at 1470px: Ben Keech 160, Mckenzie Brookins 160, Katie
   293, Vanessa Harper 320, Andrew Sutherland 320, Julia Shkolnikova 347. So the
   first two sit flush with no scrollbar and the other four scroll. If a review
   is ever swapped, re-measure with build/verify/quote-h.js rather than nudging
   this number until it looks right. */
.tcard .quote{flex:0 0 auto;height:160px;overflow-y:auto;overscroll-behavior:contain;
  font-size:15.5px;line-height:1.72;color:var(--text-body);margin:34px 0 22px;
  padding-right:10px;scrollbar-width:thin;scrollbar-color:var(--border-hairline) transparent}
/* A visible but quiet scrollbar. Without one, a review that continues below the
   fold looks like a review that ends mid sentence. */
.tcard .quote::-webkit-scrollbar{width:6px}
.tcard .quote::-webkit-scrollbar-track{background:transparent}
.tcard .quote::-webkit-scrollbar-thumb{background:var(--border-hairline);border-radius:3px}
.tcard .quote:hover::-webkit-scrollbar-thumb{background:var(--maroon-300)}
/* margin-top:auto still pins the block to the bottom of the card, which now
   matters only as a safety net: with the quote at a fixed height every card is
   already the same height. */
.qwho{margin-top:auto;display:flex;flex-direction:column;gap:2px;border-top:1px solid var(--border-hairline);padding-top:14px}
.qname{font-size:14px;font-weight:700;color:var(--text-heading)}
/* .qmeta is gone. The sub line under each name was removed on instruction when
   the real reviews landed, and the Google review data carries nothing to put
   there: a matter type would have to be inferred from the review text, which is
   guessing about a real person's matter. */

/* ---------- Locations: list left, map right ---------------------
   Replaces six identical cards. Hovering or focusing a row moves the
   map pin, and every row is a real link to its location page. */
.locg{display:grid;grid-template-columns:1fr 1.04fr;gap:56px;align-items:stretch}
.loclist{list-style:none;margin:0;padding:0;border-top:1px solid var(--border-default)}
/* background tint + chevron slide only. An earlier version animated
   padding-left, which thrashes layout; a left accent stripe would have been the
   other obvious fix but >1px side-stripes are a banned pattern. */
.locrow{position:relative;display:flex;align-items:flex-start;gap:16px;
  padding:22px 18px 22px 14px;border-bottom:1px solid var(--border-default);
  text-decoration:none;transition:background-color var(--dur-base) var(--ease-out)}
.locrow:hover,.locrow.is-active{background:var(--bone-100)}
.locrow-pin{color:var(--maroon-700);flex:none;margin-top:2px;line-height:0}
.locrow-body{display:flex;flex-direction:column;gap:5px;min-width:0;flex:1 1 auto}
.locrow-t{display:flex;align-items:center;gap:12px;flex-wrap:wrap;font-size:18.5px;font-weight:600;line-height:1.3;color:var(--text-heading);letter-spacing:-.008em}
.locrow-d{font-size:14.5px;line-height:1.6;color:var(--text-muted)}
.locbadge{font-size:10.5px;font-weight:600;letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;
  color:var(--maroon-700);background:var(--maroon-050);border-radius:var(--radius-pill);padding:4px 10px}
.locrow-go{color:var(--maroon-700);flex:none;align-self:center;transition:transform var(--dur-base) var(--ease-out)}
.locrow:hover .locrow-go,.locrow.is-active .locrow-go{transform:translateX(4px)}
.locrow:focus-visible{outline:none;box-shadow:var(--ring-focus);z-index:var(--z-raised)}

/* The map stretches to the list's height and the SVG fills it with `slice`
   (cover). The projection window carries ~20% margin around the outermost pins,
   so no realistic container aspect crops one out. */
.locmap{position:relative;border-radius:var(--radius-card);overflow:hidden;
  background:var(--navy-900);box-shadow:var(--shadow-md);height:100%;min-height:460px}
.locmap svg{display:block;width:100%;height:100%}
.locmap-sea{fill:var(--navy-900)}
/* 2-degree graticule: reads as a map rather than a silhouette */
.locmap-grid path{stroke:var(--navy-500);stroke-width:1;opacity:.30;fill:none}
.locmap-land path{fill:var(--navy-600);stroke:var(--navy-400);stroke-width:2.5;stroke-linejoin:round}
/* soft maroon wash centred on the Cairns office, reading as reach */
.locreach-a{stop-color:var(--maroon-500);stop-opacity:.26}
.locreach-b{stop-color:var(--maroon-500);stop-opacity:0}

.locpin-halo{fill:var(--maroon-500);opacity:0;transform-box:fill-box;transform-origin:center;
  transform:scale(.4);transition:opacity var(--dur-base) var(--ease-out),transform var(--dur-base) var(--ease-out)}
.locpin-dot{fill:var(--maroon-500);stroke:var(--bone-50);stroke-width:2.5;
  transform-box:fill-box;transform-origin:center;transition:transform var(--dur-base) var(--ease-out)}
.locpin-label{fill:var(--text-on-dark-muted);font-family:inherit;font-size:23px;font-weight:600;
  letter-spacing:-.005em;paint-order:stroke;stroke:var(--navy-900);stroke-width:6;stroke-linejoin:round;
  transition:fill var(--dur-base) var(--ease-out)}
.locpin.is-active .locpin-halo{opacity:.30;transform:scale(1)}
.locpin.is-active .locpin-dot{transform:scale(1.4)}
.locpin.is-active .locpin-label{fill:var(--bone-50)}

/* ---------- FAQ ------------------------------------------------- */
.faqg{display:grid;grid-template-columns:.82fr 1.18fr;gap:72px;align-items:start}
.faqside .shead{margin-bottom:32px}
.faqlist{margin:0}
/* Accordion styles its own rows inline; these only touch what it leaves open */
.faqlist button{font-size:18px!important;line-height:1.4!important;letter-spacing:-.005em}
.faqlist button:focus-visible{outline:none;box-shadow:var(--ring-focus)}
/* The DS Accordion collapses a panel to height 0 but leaves it visible to
   assistive tech, and wires no aria-controls. Screen readers therefore read
   every answer whether open or not, and any link inside a collapsed answer
   would be tabbable while invisible. visibility:hidden takes collapsed panels
   out of the a11y tree and the tab order. Fix it properly in the Claude Design
   project when convenient; this is the local guard. */
.faqlist button[aria-expanded="false"] + div{visibility:hidden}

/* ---------- Insights -------------------------------------------- */
/* align-items:flex-end aligns MARGIN boxes, so the 52px bottom margin that used
   to sit on .shead pushed the button exactly 52px below the heading. The margin
   belongs on the row. */
.secrow{display:flex;align-items:flex-end;justify-content:space-between;gap:32px;margin-bottom:var(--shead-gap)}
.secrow .shead{margin-bottom:0}

/* Blog posts are proper cards: white, hairline, 4px radius, shadow-sm, and the
   documented 2px hover lift to shadow-md. */
.post{display:flex;flex-direction:column;background:var(--surface-card);
  border:1px solid var(--border-hairline);border-radius:var(--radius-card);
  box-shadow:var(--shadow-sm);overflow:hidden;transition:var(--transition-surface)}
.post:hover{transform:var(--lift-hover);box-shadow:var(--shadow-md)}
.postmedia{overflow:hidden}
/* BugHerd 45: the post card's image is a link. Block so it fills the media box,
   and it inherits the card's existing hover treatment rather than adding a
   second one. */
.postmedia-a{display:block;text-decoration:none}

.postmedia .ph{border-radius:0;border:0;transition:transform var(--dur-slow) var(--ease-out)}
.post:hover .postmedia .ph{transform:scale(1.04)}
.postbody{display:flex;flex-direction:column;flex:1 1 auto;padding:22px 26px 26px}
/* scoped two levels deep: .grid3 h3 / .grid3 p sit at the same specificity as a
   single-class rule and would otherwise win on source order */
.post .postbody h3{font-size:19px;font-weight:600;line-height:1.35;margin:12px 0 10px}
.post .postbody h3 a{color:var(--text-heading);text-decoration:none;transition:color var(--dur-fast) var(--ease-out)}
.post .postbody h3 a:hover{color:var(--maroon-700)}
.post .postbody p{font-size:15px;line-height:1.65;color:var(--text-muted);margin:0 0 20px}
.post .postbody .cardlink{margin-top:auto}
.pmeta{display:flex;align-items:center;gap:8px;margin:0;font-size:13px;color:var(--text-muted)}
.pmeta svg{color:var(--maroon-700)}

/* ---------- Contact --------------------------------------------- */
/* Photographic ground for the closing CTA. The scrim mirrors --gradient-brand's
   own stops and angle (navy-700 -> navy-900 at 155deg) but with alpha, so the
   image reads as texture rather than as a picture. Documented treatment: text
   over photography sits on a gradient scrim, never a capsule or blur panel.
   The source is pre-darkened at build (brightness 0.62, saturation 0.58) and
   the scrim runs .82 to .89. Both were tuned against a measured contrast
   floor, not by eye: see CONTEXT.md. */
.contact{padding:var(--sec-y-lg) 0;
  background-image:linear-gradient(155deg,rgba(12,39,58,.82) 0%,rgba(6,19,29,.89) 100%),
    url("assets/images/contact-office.webp");
  background-size:cover;background-position:center;background-repeat:no-repeat}
.contactg{display:grid;grid-template-columns:1fr 1fr;gap:80px;align-items:start}
.cdetails{list-style:none;margin:44px 0 0;padding:0;display:flex;flex-direction:column;gap:0}
.cdetails li{display:flex;align-items:center;gap:16px;padding:18px 0;border-top:1px solid var(--border-on-dark);font-size:16px;color:var(--text-on-dark);min-width:0}
.cdetails li:last-child{border-bottom:1px solid var(--border-on-dark)}
.cdetails svg{color:var(--maroon-300);flex:none}
.cdetails a{color:var(--text-on-dark);font-weight:600;text-decoration:none;min-width:0;overflow-wrap:anywhere}
.cdetails a:hover{color:var(--maroon-300)}
/* ---------- Location detail page ----------------------------------
   Shared by all six location pages. The hero ground is set inline from the
   slug, so there is no per-location CSS here and adding a page needs none. */

/* The .introp centred-intro rules were removed on 19 August with the panel they
   styled: the services lead-in became an image-and-text panel and nothing else
   on the site centres its copy. The centring lesson they carried is in
   CONTEXT.md section 6, which is the durable place for it. */

/* Hero: copy left, map right. The background is the brand gradient ONLY.
   --hero-photo is repurposed as a SCRIM LAYER rather than a photo. .hero-sm
   declares `background-image: <navy gradient>, var(--hero-photo, <fallback>)`,
   so whatever goes in that slot paints beneath the navy. Setting it to the
   design system's --gradient-scrim-left darkens the LEFT, where the copy is,
   and fades to nothing on the RIGHT, where the map panel sits. Exactly the
   shape this layout needs, and it is an existing token rather than a new
   gradient invented here.

   It must be set to SOMETHING: left unset, the var() fallback paints the About
   page's boardroom photograph, which is also what this page's closing contact
   panel uses, so the same image would appear twice on one page.

   It was briefly `none`. That was measurably worse: with only the navy
   gradient the hero lead measured 4.86:1 at 390px, which passes AA's 4.5 floor
   but was the WEAKEST hero on the site (the others run 6.0 to 7.3, and the home
   hero measures 7.07:1 at the same width). Do not go back to `none` on the
   assumption that a flat brand gradient is automatically safe: it was not. */
/* EVERY INTERNAL BANNER IS THIS ONE CLASS NOW. It was .hero-location, six
   pages, until 20 August; About, Contact, Blog, the post template, the
   locations index, the services index and the practice-area page all carried a
   photograph as a full-bleed BACKGROUND under a navy scrim instead. They are
   all the split banner now: copy left on navy, photograph right.

   --hero-photo is a SCRIM LAYER here, not a photo, and it must be set to
   something. .hero-sm declares `background-image: <navy gradient>,
   var(--hero-photo, ...)`, so whatever sits in that slot paints beneath the
   navy. --gradient-scrim-left darkens the LEFT where the copy sits and fades to
   nothing on the right where the photo is: exactly this layout's shape, out of
   an existing token.

   It was briefly `none` on the location build and measuring caught that as a
   regression: with only the flat brand gradient the hero lead measured 4.86:1
   at 390px. That passes AA's 4.5 floor, so nothing failed, but it was the
   WEAKEST hero on the site. Do not go back to `none` on the assumption that a
   flat brand gradient is automatically safe. It is not: the copy sits over a
   different part of it. */
.hero-split{--hero-photo:var(--gradient-scrim-left)}

/* The two-column rule lives INSIDE a min-width query rather than being written
   as a more specific selector. .herog-sm{grid-template-columns:1fr} sits in the
   max-width:1080px block, and a bare `.hero-split .herog-sm` is (0,0,2,0)
   against that rule's (0,0,1,0), so it would win at EVERY width and the hero
   would stay two-column on a phone. That exact bug has already happened once on
   this hero; see CONTEXT.md section 6. */
/* --- Location banner: copy left on navy, photograph right ----------------
   Redesigned 20 August. It carried a generated MAP in a bordered panel until
   then, and the map was only ever a stand-in: nothing in the image library
   depicted any of these towns. Six photographs were supplied, so the banner
   became what it should have been, which is the home hero's shape.

   THE PHOTO USES .heroimg, THE HOME PAGE'S OWN CLASS. That is deliberate and it
   is what makes "mirrors the home page" true rather than approximately true:
   the stretch, square corners, object-position, wipe-in and the
   prefers-reduced-motion opt-out are all inherited. Only the bleed offsets are
   restated below, because they must cancel .herog-sm's padding, not .herog's.

   THE BANNER'S DIMENSIONS DO NOT CHANGE. Height still comes from the copy
   column plus .herog-sm's padding, exactly as it did with the map: 408px at
   1440. The photo stretches into that box and its negative block margins
   cancel the padding so it fills the band edge to edge. If you ever change
   .herog-sm's padding you must change these two margins with it, the same
   coupling the home hero's portrait has and for the same reason. */
@media (min-width:901px){
  /* stretch, not center: the row height comes from the COPY and the photo
     fills it. Two stretched boxes always agree, so if you ever need to prove
     which one drives the row, compare against content, not against a box.
     That mistake shipped once on this hero: see CONTEXT.md section 6. */
  .hero-split .herog-sm{grid-template-columns:1.04fr .96fr;
    gap:clamp(36px,4vw,64px);align-items:stretch}
  /* .heroimg{height:320px;max-height:320px} lands below 1080px and would pin
     the photo to a fixed height inside a stretched row at 901 to 1080. Two
     classes beat one, so this wins wherever it applies regardless of order,
     but it is written after the base rules anyway. */
  .hero-split .heroimg{height:auto;max-height:none}
}
@media (min-width:1081px){
  /* Same two regimes as .wrap and as the home hero: (100vw-1200)/4 below the
     cap, (100vw-1560)/2 above it. Copy leaves for the container's own left
     edge, photo bleeds to the right viewport edge. .hero has overflow:hidden so
     scrollbar rounding is clipped rather than becoming document overflow. */
  .hero-split .herog-sm{
    padding-left:max(0px, calc(40px - max(0px, (100vw - 1200px) / 4, (100vw - 1560px) / 2)));
    padding-right:0}
  .hero-split .heroimg{
    margin-right:calc(-1 * max(0px, (100vw - 1200px) / 4, (100vw - 1560px) / 2));
    margin-top:calc(-1 * clamp(64px,7.8vw,108px));
    margin-bottom:calc(-1 * clamp(64px,7.8vw,108px))}
}
@media (max-width:900px){
  /* THE FIRST LINE OF THIS COMMENT USED TO READ "Below the copy on a phone: the
     heading has to lead". THAT IS NO LONGER TRUE. BugHerd 17 moved the banner
     ABOVE the copy on 2 September, so the heading now follows the photograph.
     The margin-top:8px below was the separator between the copy and the image
     and has no job any more; it is zeroed in the stacked block near the bottom
     of this file, where it was showing as an 8px strip of navy between the
     header and the banner. The aspect ratio note is still live and still the
     reason this rule exists.
     16:9, WHICH IS THE SHAPE THE MAP PANEL HAD, and that is the point. The
     brief was to change the banner's design without changing its dimensions,
     and a fixed 200px box did change them: measured against the map build, the
     banner lost 187px at 768 and gained 25px at 390. Matching the old aspect
     ratio puts every width back where it was. Desktop was already unchanged,
     because there the height comes from the copy column either way.
     height:auto is needed because .heroimg carries height:320px below 1080. */
  .hero-split .heroimg{height:auto;max-height:none;aspect-ratio:16/9;margin-top:8px}
}

/* Contact panel: text left, form right. Distinct from BOTH the closing
   ContactPanel (navy over a photo, raised card) and the contact page's
   ContactMain (tiles). This one is a white surface with the form in a bone
   well, because the left column is prose rather than a list of details. */
.loccontactg{display:grid;grid-template-columns:1.06fr .94fr;gap:clamp(44px,4.6vw,76px);align-items:start}
.loccontact-b .body{margin-top:20px}
.loccontact-b .body:first-of-type{margin-top:26px}
.loccontact-d{list-style:none;margin:30px 0 0;padding:0;display:flex;flex-direction:column;gap:0}
.loccontact-d li{display:flex;align-items:center;gap:14px;padding:16px 0;
  border-top:1px solid var(--border-hairline);font-size:16px;color:var(--text-body);min-width:0}
.loccontact-d li:last-child{border-bottom:1px solid var(--border-hairline)}
.loccontact-d svg{color:var(--maroon-700);flex:none}
/* min-width:0 and overflow-wrap: a flex item defaults to min-width:auto, so a
   long value pushes the row wider than its column without them. */
.loccontact-d a{color:var(--text-heading);font-weight:600;text-decoration:none;
  min-width:0;overflow-wrap:anywhere}
.loccontact-d a:hover{color:var(--maroon-700)}
.loccontact-f{background:var(--surface-page);border:1px solid var(--border-default);
  border-radius:var(--radius-md,4px);padding:clamp(26px,2.8vw,40px)}
.loccontact-f form{display:flex;flex-direction:column;gap:20px}

/* Feature block: image one side, copy the other. */
.locfeatureg{display:grid;grid-template-columns:.86fr 1.14fr;gap:clamp(40px,4.4vw,72px);align-items:center}
.locfeature-b .body{margin-top:20px}
.locfeature-b .body:first-of-type{margin-top:26px}
/* margin-top:auto would need a flex parent; this is a grid cell, so the button
   is spaced from the last paragraph directly. */
.locfeature-b > a{margin-top:28px}

@media (max-width:900px){
  .loccontactg,.locfeatureg{grid-template-columns:1fr;gap:40px}
  /* The image leads on mobile: a 4/5 portrait above the copy reads as an
     opener, whereas below it reads as an afterthought. */
  .locfeature-m{order:-1}
}

/* ---------- Locations index --------------------------------------
   Cards carry a REAL map crop, drawn from the same projected Queensland
   geography as the home page panel (LOCS / QLD_PATHS in home-sections-b.jsx),
   so the pin sits where the town actually is. The .locmap-* and .locpin-*
   classes are shared with that panel and are NOT redefined here: restyling
   them would silently change the home page map too. */
.locintro{padding-bottom:0}
.loccards{padding-top:clamp(34px,3.6vw,46px)}
.locgrid{list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(3,1fr);gap:32px}

.loccard{display:flex;flex-direction:column;height:100%;
  background:var(--surface-card);border:1px solid var(--border-hairline);
  border-radius:var(--radius-md,4px);overflow:hidden;
  transition:transform var(--dur-base) var(--ease-out),box-shadow var(--dur-base) var(--ease-out)}
/* 2px, the same lift the post cards use. The brand rule is explicit that card
   hover lifts exactly 2px and nothing bounces. */
.loccard:hover{transform:var(--lift-hover);box-shadow:var(--shadow-md)}
/* aspect-ratio rather than a fixed height, so the map crop keeps its shape at
   every column width. The SVG fills it with preserveAspectRatio slice. */
.loccard-media{aspect-ratio:16/9;overflow:hidden;background:var(--navy-900);
  border-bottom:1px solid var(--border-hairline)}
/* position:relative so the pin can sit at the centre. The map image is
   generated centred on the location's real coordinates, so dead centre IS the
   town: the pin is not approximated. */
.loccard-map{position:relative;display:block;width:100%;height:100%}
.loccard-map img{display:block;width:100%;height:100%;object-fit:cover}
/* Pin, drawn over the image rather than baked into it, so the mark stays crisp
   at any card width and can be restyled without regenerating six images.
   Colours are taken from the same maroon the home page map's pins use. */
.loccard-pin{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  display:block;width:34px;height:34px;pointer-events:none}
.loccard-pin-halo{position:absolute;inset:0;border-radius:50%;
  background:var(--maroon-500);opacity:.30}
.loccard-pin-dot{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);
  width:13px;height:13px;border-radius:50%;background:var(--maroon-500);
  border:2.5px solid var(--bone-50)}

/* The .locattr credit under the card grid was moved to the footer legal bar on
   18 August. See .fbarmaps. It was moved, not dropped: it is an ODbL condition. */
/* flex:1 so every card's link sits on the same baseline whatever the length of
   the description above it. Consistent-but-unequal is on the recurring
   client-feedback list. */
.loccard-body{display:flex;flex-direction:column;flex:1 1 auto;padding:22px 26px 26px}
.loccard-body h3{display:flex;align-items:center;flex-wrap:wrap;gap:10px;
  font-size:19px;font-weight:600;line-height:1.35;color:var(--text-heading);margin:0 0 10px}
.loccard-body p{font-size:15px;line-height:1.65;color:var(--text-muted);margin:0 0 20px;max-width:none}
.loccard-body .cardlink{margin-top:auto}

/* The .loccard-ph placeholder tiles were removed on 18 August along with the
   third row of the grid, and their rules went with them rather than being left
   as dead weight. The git history has them if a 3x3 is ever agreed. */

/* Text panel: the same rail shape as the FAQ and MVV panels, on the brand's
   navy gradient rather than sunken bone. --gradient-brand is the design
   system's defined third surface; do not substitute a flat navy or a new blue.
   The heading and intro are recoloured by SectionHead's `dark` prop; the body
   paragraphs are not, so they are set here. */
.loctext{background:var(--gradient-brand)}
.loctextg{display:grid;grid-template-columns:.82fr 1.18fr;gap:72px;align-items:start}
.loctextside .shead{margin-bottom:0}
.loctext .loctextmain p{color:var(--text-on-dark-muted)}
.loctextmain p:last-child{margin-bottom:0}

@media (max-width:1080px){
  .locgrid{grid-template-columns:repeat(2,1fr);gap:24px}
  .loctextg{grid-template-columns:1fr;gap:36px}
}
@media (max-width:640px){
  /* One column. A 2-up at 390px gives each card about 170px, which is narrower
     than the map crop reads at and wraps "Atherton Tablelands" to three lines. */
  .locgrid{grid-template-columns:1fr}
}

/* ---------- Single post -------------------------------------------
   Hero metadata, body measure, author panel. The hero itself is .hero-sm,
   shared with every other internal page; only --hero-photo differs. */

/* THE POST HERO WAS CENTRED AND IS NOT ANY MORE. Centring was asked for on
   18 August when this banner was a single copy column over a full-bleed
   photograph; it is a two-column split banner now, and centring a column that
   occupies the left half of a banner reads as a mistake rather than as a
   choice. The three rules that did it (text-align on .herocopy, margin-inline
   on the h1, justify-content on the metadata row) came out together.
   If it is ever centred again, all three are needed, plus max-width:none on
   the category chip: see the note on .pcat below, which is still live. */
/* Category chip above the h1. A <p><span>, never a heading: an eyebrow set as
   a heading is what breaks heading order, and this page has h2s below it. */
/* max-width:none is load-bearing. The token sheet caps EVERY <p> at
   --measure-prose, so the chip's paragraph was 66ch wide and pinned left, and
   text-align:center then centred the chip inside that capped block rather than
   in the hero. It looked centred up to 1024 and drifted further left at every
   width above it: 449.6 against a 720 midpoint at 1440, 889.6 against 1280 at
   2560. Centring a capped block needs the cap removed or the block centred. */
/* RESCOPED FROM .hero-post TO .hero-split on 20 August, and the rename is
   exactly why this needs saying. The post hero's section class changed when
   every banner became the split layout, and these two rules kept their old
   scope: the chip silently lost its border, background, padding and uppercase
   tracking and rendered as plain muted text. Nothing errored and no assertion
   could see it, because a chip that is still a <p><span> with the right words
   in it satisfies every structural check written. It was visible in a
   screenshot and nowhere else.
   RENAMING A CLASS MEANS GREPPING FOR IT, not just changing where it is set.
   max-width:none is still load-bearing: the token sheet caps every <p> at
   --measure-prose, which would otherwise pin this one to 66ch. */
.hero-split .pcat{margin:0 0 18px;max-width:none}
.hero-split .pcat span{display:inline-block;padding:6px 12px;font-size:11px;font-weight:600;
  letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;color:var(--text-on-dark);
  background:rgba(251,250,249,.12);border:1px solid var(--border-on-dark);
  border-radius:var(--radius-xs)}
/* Author, date, reading time. flex-wrap so it stacks rather than overflowing at
   390px, where three items and their icons do not fit on one line. */
.pmetahero{list-style:none;display:flex;flex-wrap:wrap;align-items:center;
  gap:10px 26px;margin:26px 0 0;padding:0}
.pmetahero li{display:flex;align-items:center;gap:9px;font-size:14px;
  color:var(--text-on-dark-muted);min-width:0}
.pmetahero svg{color:var(--maroon-300);flex:none}

/* The article column runs the FULL container width, on request (18 August).
   It was capped at --measure-prose (66ch) and centred.

   Worth knowing rather than re-litigating: at 1440 the container is 1320px, so
   a paragraph is roughly 150 characters per line against the 66 the rest of the
   site uses. That is a readability cost, not an accessibility failure (the 80
   character guideline is WCAG AAA, not AA). Raised once; this is what was
   asked for. If it ever reads as too wide on review, the middle option is a
   wider cap rather than a return to 66ch. */
/* NOTE THERE IS NO max-width HERE, AND max-width:none WOULD BE A BUG.
   .pbodyg is a .wrap, and .wrap carries the container's own
   max-width: min(calc((100% + 1200px)/2), 1560px). Writing max-width:none here
   overrides that at equal specificity and later in the sheet, so the article
   ran edge to edge of the VIEWPORT: 1440px at 1440 and 2560px at 2560, with no
   gutter at all. That was the first attempt and the measurement caught it.
   "Full content width" means the container, not the window. Let .wrap govern. */
.pbody p{font-size:17px;line-height:1.75;color:var(--text-body);margin:0 0 26px;
  max-width:none;text-wrap:pretty}
.pbody p:last-child{margin-bottom:0}

/* ============================================================
   ARTICLE BODY TYPOGRAPHY. Added 8 September 2026, when three real articles
   replaced the lorem placeholder.
   Until then .pbody styled ONLY <p>, because a flat array of paragraphs was
   all the template could render. The real articles carry ten h2s, h3s, lists
   and inline links, and every one of those was falling through to the design
   system's SECTION heading styles: an h2 rendered at 37px against 17px body
   text, with margin-top AND margin-bottom of zero, so headings sat jammed
   against the paragraphs either side with no separation at all.
   ============================================================ */

/* Sized against the 17px body, not against a section heading. The space ABOVE
   a heading is what makes it read as a new section, so it is much larger than
   the space below it: the heading has to belong to what follows. */
.pbody h2{font-size:27px;line-height:1.28;font-weight:700;color:var(--text-heading);
  letter-spacing:-.012em;margin:54px 0 18px;text-wrap:balance}
.pbody h2:first-child{margin-top:0}
.pbody h3{font-size:20px;line-height:1.35;font-weight:700;color:var(--text-heading);
  margin:36px 0 12px;text-wrap:balance}

/* Lists matched to the paragraph, not left at the browser default. They were
   16px against 17px body, with a 40px indent and no space between items. */
.pbody ul{margin:0 0 26px;padding-left:22px}
.pbody li{font-size:17px;line-height:1.75;color:var(--text-body);margin:0 0 12px}
.pbody li:last-child{margin-bottom:0}
.pbody li::marker{color:var(--maroon-700)}

/* UNDERLINED, deliberately. In running prose a link that is only a colour
   change fails WCAG 1.4.1: colour cannot be the sole means of conveying
   information. Everywhere else on this site links sit in navigation or on
   buttons where position and shape carry the meaning; inside a paragraph
   nothing else marks them. */
.pbody a{color:var(--maroon-700);text-decoration:underline;
  text-underline-offset:3px;text-decoration-thickness:1px}
.pbody a:hover{text-decoration-thickness:2px}
.pbody a:focus-visible{outline:none;box-shadow:var(--ring-focus);border-radius:2px}

.pbody strong{font-weight:700;color:var(--text-heading)}
.pbody em{font-style:italic}

/* Ordered lists. The court process article's seven step sequence is the spine
   of that piece, so the numbers are content and have to look deliberate rather
   than like a browser default. */
.pbody ol.pbody-ol{margin:0 0 26px;padding-left:26px}
.pbody ol.pbody-ol li{font-size:17px;line-height:1.75;color:var(--text-body);margin:0 0 14px;padding-left:4px}
.pbody ol.pbody-ol li:last-child{margin-bottom:0}
.pbody ol.pbody-ol li::marker{color:var(--maroon-700);font-weight:700}

/* TABLES. The wrapper scrolls, not the page: the penalties table is four
   columns and cannot fit a 390px phone at a readable size, and the house rule
   is that the body never scrolls sideways. */
.pbody-tw{overflow-x:auto;margin:0 0 30px;-webkit-overflow-scrolling:touch}
.pbody-tw:focus-visible{outline:none;box-shadow:var(--ring-focus)}
.pbody table{width:100%;border-collapse:collapse;font-size:15.5px;line-height:1.55;
  min-width:560px}
.pbody thead th{text-align:left;font-weight:700;color:var(--text-on-dark);
  background:var(--navy-900);padding:14px 16px;vertical-align:bottom;
  font-size:14px;letter-spacing:.01em}
.pbody tbody td{padding:14px 16px;color:var(--text-body);
  border-bottom:1px solid var(--border-hairline);vertical-align:top}
.pbody tbody tr:last-child td{border-bottom:0}
/* Zebra striping on the body rows only, so a four column row stays trackable
   across the full width. */
.pbody tbody tr:nth-child(even) td{background:var(--bone-100,rgba(0,0,0,.025))}


/* MEASURE: FULL CONTENT WIDTH, confirmed by Wayde on 8 September 2026.
   An 80ch cap was tried here and is REVERSED. The article text runs the full
   container width, the same as the author panel and the related rail below it.
   This is the second time the question has been settled the same way, so treat
   it as decided rather than as an oversight, and do not reintroduce a cap. */


/* Author panel: mark left, text right. */
/* Full container width too, matching the body above it. */
.pauthorg{display:grid;grid-template-columns:auto 1fr;gap:clamp(24px,3vw,44px);
  align-items:start}
/* flex:none and a fixed box, or the mark's column collapses when the bio wraps */
.pauthor-mark{flex:none;display:grid;place-items:center;width:104px;height:104px;
  background:var(--surface-card);border:1px solid var(--border-hairline);
  border-radius:var(--radius-md,4px);color:var(--navy-800)}
.pauthor-b{min-width:0}
.pauthor-k{font-size:11px;font-weight:600;letter-spacing:var(--tracking-eyebrow);
  text-transform:uppercase;color:var(--text-muted);margin:0 0 8px}
.pauthor .pauthor-b h2{font-size:23px;font-weight:600;color:var(--text-heading);margin:0}
.pauthor-r{font-size:14px;color:var(--maroon-700);font-weight:600;margin:6px 0 0}
/* max-width:none is load-bearing: the token sheet gives every <p> a
   --measure-prose cap, so without it the bio would stay 66ch wide inside a
   column that is now far wider, and the panel would look half-empty. */
.pauthor-x{font-size:16px;line-height:1.7;color:var(--text-muted);margin:16px 0 0;max-width:none}

@media (max-width:640px){
  /* The mark drops above the text. auto/1fr would squeeze the bio to a few
     characters per line on a phone. */
  .pauthorg{grid-template-columns:1fr;gap:22px}
  .pauthor-mark{width:84px;height:84px}
}

/* ---------- Blog index: filter bar and feed states ----------------
   A slim white control band between the navy hero and the bone feed. White
   rather than bone so it reads as a control surface rather than as the first
   row of the feed, and it gives the page a light step down from the hero.

   The DS Input and Select each wrap themselves in a flex column div with
   inline styles, so the widths are set on OUR wrappers around them and the
   component fills its parent. Fighting a DS component's inline styles with a
   stylesheet needs !important and loses silently when it is forgotten. */
.blogbar{padding:clamp(20px,2.2vw,28px) 0;border-bottom:1px solid var(--border-hairline)}
/* flex-end so the Clear button's baseline lines up with the fields, whose
   labels sit above them and make the controls taller than the button. */
.blogbar-f{display:flex;align-items:flex-end;gap:16px;flex-wrap:wrap}
.blogbar-s{flex:1 1 340px;min-width:0}
.blogbar-c{flex:0 0 260px;min-width:0}

/* Category dropdown. A listbox, not a native <select>: a native select on
   macOS draws its menu OVER the trigger, aligned to the selected option, and
   nothing in the page can move it because the OS draws it.

   Every value here is copied from the DS Select's inline styles so the two
   controls are indistinguishable side by side (the search field beside it IS
   a DS Input). If the design system's Select changes, change these to match:
     padding 13px 44px 13px 16px, font --text-base, --radius-input,
     1px --border-default, --navy-800 on focus, caret 16px from the right.

   NO ANIMATION, and that is not an oversight. Animating the Services flyout
   promoted it to a composited layer Chrome kept after the animation ended, and
   the stale layer painted behind the hero while still hit-testing on top. See
   the flyout note in this file and CONTEXT.md section 6. */
.csel{position:relative;display:flex;flex-direction:column;gap:var(--space-2)}
.csel-l{font:var(--text-eyebrow);letter-spacing:var(--tracking-eyebrow);
  text-transform:uppercase;color:var(--text-muted)}
/* text-align:left and the explicit font: a <button> centres its text and does
   NOT inherit font, so without these it would not match the DS control. */
.csel-t{width:100%;text-align:left;padding:13px 44px 13px 16px;font:var(--text-base);
  color:var(--text-heading);background:var(--white) no-repeat right 16px center;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235E6E78' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
  border:1px solid var(--border-default);border-radius:var(--radius-input);
  cursor:pointer;transition:var(--transition-control)}
.csel-t:hover{border-color:var(--border-strong)}
.csel-t[aria-expanded="true"]{border-color:var(--navy-800)}

/* Opens BELOW the trigger, which is the whole point of the component.
   top:100% plus the 6px gap, never a negative offset. */
.csel-m{position:absolute;top:calc(100% + 6px);left:0;right:0;z-index:var(--z-flyout);
  margin:0;padding:6px;list-style:none;max-height:280px;overflow-y:auto;
  background:var(--surface-card);border:1px solid var(--border-default);
  border-radius:var(--radius-input);box-shadow:var(--shadow-lg)}
.csel-m[hidden]{display:none}
.csel-o{padding:11px 14px;font-size:15px;line-height:1.4;color:var(--text-body);
  border-radius:var(--radius-xs);cursor:pointer}
/* Hover and keyboard highlight are the SAME state, driven by .is-active, so a
   mouse and a keyboard cannot disagree about which row is current. */
.csel-o.is-active{background:var(--surface-sunken);color:var(--text-heading)}
.csel-o.is-sel{font-weight:600;color:var(--text-heading)}

/* The bar has to outrank the feed below it or the open menu is painted under
   the first row of cards. */
.blogbar{position:relative;z-index:var(--z-raised)}

/* The live region above the grid. It is the only thing that reports what the
   filter did, so it is not decoration: without it a screen reader user gets
   silence when cards appear and disappear. */
.blogcount{font-size:13px;color:var(--text-muted);margin:0 0 22px}
.blogempty{font-size:16px;line-height:1.65;color:var(--text-muted);margin:0;
  padding:44px 0;border-top:1px solid var(--border-hairline);max-width:60ch}

@media (max-width:640px){
  /* Full width and stacked. flex-basis alone would leave the select at 260px
     on a 390px screen with a 16px gap and no room for the label. */
  .blogbar-s,.blogbar-c{flex:1 1 100%}
}

/* ---------- Contact page: the enquiry panel ----------------------
   Distinct from .contact above, which is the closing CTA band: that one is a
   navy gradient over a photograph with a raised white card floating on it.
   This is page content on the white card surface, so the form is a bordered
   well rather than a shadowed card (a shadow needs something to lift off, and
   here there is nothing behind it), and the details are labelled tiles rather
   than a divided list.

   Every rule is scoped to .cmain. The closing panel's .contactg, .cdetails and
   .formcard are untouched and still serve the home and About pages: a
   section-scoped element rule quietly outranking a single-class rule has bitten
   this build four times, so nothing here is allowed to reach outside .cmain. */
.cmaing{display:grid;grid-template-columns:1fr 1fr;gap:clamp(48px,5vw,80px);align-items:start}

/* Four contact methods as tiles. Label, value, note, so a scanning visitor can
   find the number without reading a sentence. */
/* One column, not a 2x2. Two reasons, both found by looking at it rather than
   by measuring: at half-width the email tile chopped the domain mid-word even
   with a <wbr>, and a short left column left a ~200px hole beside a much taller
   form. Stacked, the four tiles fill the column and every value sits on one
   line. */
.cmain .ctiles{list-style:none;margin:clamp(28px,3vw,40px) 0 0;padding:0;
  display:grid;grid-template-columns:1fr;gap:14px}
.cmain .ctiles li{display:flex;align-items:flex-start;gap:14px;min-width:0;
  padding:18px;border:1px solid var(--border-hairline);border-radius:var(--radius-md,4px);
  background:var(--surface-page)}
/* The icon sits in its own square so the three text lines align to one edge
   regardless of icon width. flex:none, or it collapses when the value wraps. */
.cmain .ctile-i{flex:none;display:grid;place-items:center;width:38px;height:38px;
  border-radius:var(--radius-sm,3px);background:var(--surface-card);
  border:1px solid var(--border-hairline);color:var(--maroon-700)}
/* min-width:0 is load-bearing: a flex item defaults to min-width:auto, and the
   email is long enough to push the tile wider than its column without it. */
.cmain .ctile-b{display:flex;flex-direction:column;gap:2px;min-width:0}
.cmain .ctile-l{font-size:11px;font-weight:600;letter-spacing:var(--tracking-eyebrow);
  text-transform:uppercase;color:var(--text-muted);line-height:var(--leading-snug)}
.cmain .ctile-v{font-size:15px;font-weight:600;color:var(--text-heading);
  line-height:1.4;text-decoration:none;min-width:0;overflow-wrap:anywhere}
.cmain a.ctile-v:hover{color:var(--maroon-700)}
.cmain .ctile-n{font-size:13px;color:var(--text-muted);line-height:1.45;text-wrap:pretty}

/* A well, not a card. Bordered and flat, because it sits ON the card surface
   rather than over a photograph. radius-md to match the tiles beside it, not
   the closing panel's radius-lg. */
.cmain .cform{background:var(--surface-page);border:1px solid var(--border-default);
  border-radius:var(--radius-md,4px);padding:clamp(26px,2.8vw,40px)}
.cmain .cform form{display:flex;flex-direction:column;gap:20px}

@media (max-width:900px){
  .cmaing{grid-template-columns:1fr;gap:44px}
}


.formcard{background:var(--surface-card);border-radius:var(--radius-lg);padding:44px;box-shadow:var(--shadow-lg)}
.formcard form{display:flex;flex-direction:column;gap:20px}
.formh{font-size:23px;font-weight:600;color:var(--text-heading);margin:0 0 4px}
.formrow{display:grid;grid-template-columns:1fr 1fr;gap:20px}
.formhint{display:flex;align-items:center;gap:8px;justify-content:center;font-size:13px;color:var(--text-muted);margin:0}
.formhint svg{color:var(--maroon-700);flex:none}
.formok{display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;padding:56px 24px}
.formok h3{font-size:22px;font-weight:600;color:var(--text-heading);margin:12px 0 0}
.formok p{font-size:15px;color:var(--text-muted);margin:0}

/* ---------- Footer ---------------------------------------------- */
.foot{background:var(--navy-900);padding:80px 0 32px}
/* the contact column carries the longest unbreakable string (the email), so it
   gets the widest track; overflow-wrap stays as the safety net below that */
.footg{display:grid;grid-template-columns:1.2fr .85fr 1.1fr .9fr 1.45fr;gap:40px;padding-bottom:56px}
/* The column headings are h2, not h4 (see the note in home-sections-b.jsx).
   line-height is restated because the base sheet gives h4 --leading-snug and
   h2 --leading-heading, so the tag change alone moved these from 16.2px to
   18px. Measured, not guessed: everything else in the computed style was
   already identical because this rule declares it. */
.foot h2{font-size:12px;font-weight:600;letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;color:var(--text-on-dark);margin:6px 0 22px;line-height:var(--leading-snug)}
/* A footer column heading that names a real page is a LINK. It must look
   exactly like the headings that are not, at rest, or the footer grows two
   kinds of column heading and the difference reads as a styling bug rather
   than as "this one is clickable".

   `color:inherit` is doing real work here, not tidying. The heading colour is
   set on .foot h2; an <a> inside it is NOT matched by `.foot ul a` (it is not
   in a list), so without this it falls through to the design system's own link
   colour and the three linked headings come out a different colour from the
   two that are not. That is the "the Kit forcibly recolours links" trap from
   the master CLAUDE.md, arriving through a selector that simply does not match
   rather than through one that loses.

   The hover affordance is an UNDERLINE, not a colour change, and that is
   deliberate. The other footer links go muted -> white on hover; a heading is
   already at full white, so it has nowhere brighter to go, and the only accent
   in the system is maroon, which at 12px on navy would fail contrast. An
   underline costs no contrast and is the conventional signal.
   text-underline-offset keeps it clear of the uppercase baseline at
   0.18em tracking. */
.foot h2 a.fhead-a{color:inherit;text-decoration:none;
  transition:text-decoration-color var(--dur-fast) var(--ease-out)}
.foot h2 a.fhead-a:hover{text-decoration:underline;text-underline-offset:4px;
  text-decoration-thickness:1px}
.foot h2 a.fhead-a:focus-visible{outline:none;box-shadow:var(--ring-focus-dark);
  border-radius:var(--radius-sm)}
.foot ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:12px}
.foot ul a{color:var(--text-on-dark-muted);font-size:14px;text-decoration:none;transition:color var(--dur-fast) var(--ease-out)}
.foot ul a:hover{color:var(--text-on-dark)}
/* The Company column's three links, 21 August (BugHerd 5). They carry the
   .foot h2 typography because they name top-level pages exactly as the Services,
   Locations and Contact column headings do, and rendering the same kind of
   destination two different ways in one footer is what the ticket called
   inconsistent.

   `.foot ul a.fnav-head` is (0,2,2) and beats `.foot ul a` at (0,1,2), which is
   what makes the colour and size stick. Note this is a SEPARATE class from
   .fhead-a on purpose: behaviour.js counts fhead-a to assert that exactly three
   footer column headings are links, and sharing the class would push that to
   six and fail a check that is still doing its job.

   Hover is an underline, not a colour change, for the same reason the column
   headings use one: these sit at full white already and have nowhere brighter
   to go, and maroon at 12px on navy fails contrast.

   THE GAP STAYS AT THE INHERITED 12px, and that is load-bearing now that the
   items carry the heading's own typography. .foot h2 sits 22px above its list;
   at an 18px item gap (tried first) the four rows came out on a near-uniform
   44px pitch and "COMPANY" stopped reading as the column label at all, because
   nothing separated it from three links that now look exactly like it. 22
   against 12 is the difference that puts the three back into a group under it.
   Same reason the other three columns read correctly: heading margin beats item
   gap. If these ever need more air, raise the heading margin, not the gap. */
.foot ul a.fnav-head{font-size:12px;font-weight:600;letter-spacing:var(--tracking-eyebrow);
  text-transform:uppercase;color:var(--text-on-dark);line-height:var(--leading-snug);
  transition:text-decoration-color var(--dur-fast) var(--ease-out)}
.foot ul a.fnav-head:hover{color:var(--text-on-dark);text-decoration:underline;
  text-underline-offset:4px;text-decoration-thickness:1px}
.foot ul a.fnav-head:focus-visible{outline:none;box-shadow:var(--ring-focus-dark);
  border-radius:var(--radius-sm)}
/* The footer wordmark is a link now, so it needs the same focus ring the header
   logo has. inline-block so the ring wraps the image rather than a zero-height
   inline box. */
.footlogo{display:inline-block;border-radius:var(--radius-sm)}
.footlogo:focus-visible{outline:none;box-shadow:var(--ring-focus-dark)}
.fabout{font-size:15px;line-height:1.65;color:var(--text-on-dark-muted);margin:22px 0 18px;max-width:34ch}
.flegal{font-size:12px;line-height:1.6;color:var(--navy-300);margin:0;max-width:38ch}
.fcontact li{display:flex;align-items:flex-start;gap:12px;font-size:14px;line-height:1.55;color:var(--text-on-dark-muted);min-width:0}
.fcontact svg{color:var(--maroon-300);flex:none;margin-top:2px}
.fcontact a{overflow-wrap:anywhere;min-width:0}
/* Social row under the opening hours in the footer contact column.
   Scoped `.foot .fsocial`, not `.fsocial`: the generic `.foot ul` sets
   flex-direction:column at (0,1,1) and `.foot ul a:hover` sits at (0,2,2), so
   single-class rules lose and the icons stack in a column with no hover. */
.foot .fsocial{display:flex;flex-direction:row;gap:10px;list-style:none;margin:24px 0 0;padding:0}
.foot .fsocial a{display:inline-flex;align-items:center;justify-content:center;width:38px;height:38px;
  color:var(--text-on-dark-muted);border:1px solid var(--border-on-dark);border-radius:var(--radius-xs);
  text-decoration:none;transition:var(--transition-control)}
.foot .fsocial a:hover{color:var(--navy-900);background:var(--bone-50);border-color:var(--bone-50)}
.foot .fsocial a:focus-visible{outline:none;box-shadow:var(--ring-focus-dark)}
/* flex-wrap added when the maps credit became a third child: at 1024 the three
   items no longer fit on one line and space-between would otherwise squeeze
   the links rather than wrap. The mobile rule below already stacks them. */
.fbar{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:14px 24px;border-top:1px solid var(--border-on-dark);padding-top:24px;font-size:13px;color:var(--text-on-dark-muted)}
/* .fbarmaps removed 19 August with the OSM credit. See the note in
   home-sections-b.jsx and CONTEXT.md item 54: the maps it covered are still on
   the site. flex-wrap below stays; it is harmless with two children and the
   bar wraps correctly at small widths regardless. */
.fbarlinks{display:flex;gap:24px}
.fbarlinks a{color:var(--text-on-dark-muted);text-decoration:none}
.fbarlinks a:hover{color:var(--text-on-dark)}

/* ---------- Motion ---------------------------------------------
   One orchestrated moment, on the hero only. Deliberately NOT a
   fade-rise on every section: that uniform reflex is the tell.
   These are keyframe animations, so the resting state is the
   visible one; if animation never runs the content still shows. */
@keyframes riseIn{from{opacity:0;transform:translateY(16px)}to{opacity:1;transform:none}}
@keyframes wipeIn{from{opacity:0;clip-path:inset(0 0 0 12%)}to{opacity:1;clip-path:inset(0 0 0 0)}}
.herocopy>*{animation:riseIn var(--dur-reveal) var(--ease-entrance) backwards}
.herocopy>*:nth-child(1){animation-delay:60ms}
.herocopy>*:nth-child(2){animation-delay:140ms}
.herocopy>*:nth-child(3){animation-delay:210ms}
.herocopy>*:nth-child(4){animation-delay:280ms}
.herocopy>*:nth-child(5){animation-delay:350ms}
.heroimg{animation:wipeIn var(--dur-slow) var(--ease-out) 120ms backwards}

@media (prefers-reduced-motion:reduce){
  .herocopy>*,.heroimg{animation:none}
  .stickycta{transition:none}
  .scard::before,.nav a::after{transition:none}
  .post:hover .postmedia .ph,.scard:hover .scardimg{transform:none}
  .locpin-halo,.locpin-dot,.locpin-label,.locrow{transition:none}
  .scardimg{transition:none}
}

/* ---------- Breakpoints ----------------------------------------- */
@media (max-width:1080px){
  /* below 1080 the hero stacks, so only the copy is guaranteed above the fold;
     the portrait follows it */
  .herog{grid-template-columns:1fr;gap:32px;min-height:0;padding-top:44px;padding-bottom:56px}
  .heroimg{height:320px;max-height:320px}
  .herocopy{max-width:none}
  h1{font-size:clamp(32px,5.4vw,44px)}
  .lead{font-size:17px}
  .usps li{font-size:14px}
  .grid3,.grid4,.procgrid{grid-template-columns:repeat(2,1fr)}
  /* below 1080 the four USPs no longer fit on one line. Free wrapping split
     them 3+1, which leaves an orphan; a two-column grid sized to its content
     and centred as a block gives a clean 2x2 instead. */
  .uspstrip-in{display:grid;grid-template-columns:repeat(2,auto);justify-content:center;
    gap:16px clamp(28px,5vw,56px)}
  .uspstrip-in li{white-space:normal}
  .stickycta-t{display:none}
  .stickycta-in{justify-content:center}
  .svctrack .scard{flex:0 0 calc((100% - 2px) / 2)}
  .scard{min-height:390px}
  .statgrid{grid-template-columns:1fr;gap:0}
  .stat{padding:24px 0}
  .stat+.stat::before{left:0;right:auto;top:0;bottom:auto;width:100%;height:1px;transform:none;background:var(--border-on-dark)}
  .aboutg,.contactg,.locg,.faqg{grid-template-columns:1fr;gap:52px}
  /* stacked: let the map take its natural aspect. A forced min-height makes
     the box portrait, and `slice` then crops sideways and clips the
     right-hand pin labels. */
  .locmap{min-height:0}
  .locmap{order:-1}
  .aboutmark{left:-18px}
  /* brand block spans, the four nav columns fall into a clean 2x2 */
  .footg{grid-template-columns:1fr 1fr;gap:44px}
  .footg>div:first-child{grid-column:1/-1}
  .nav{display:none}
}
@media (max-width:640px){
  /* BUGHERD 15, 2 SEPTEMBER: .footg IS NOT IN THIS LIST ANY MORE. It used to
     collapse to one column here, which made the footer a very long single
     ribbon on a phone. It now keeps the 1fr 1fr it takes at 1080 and the brand
     block keeps spanning both, which is the rule immediately below this block
     doing nothing new. Removing it from this list IS the fix. */
  .grid3,.grid4,.formrow,.procgrid{grid-template-columns:1fr}
  .uspstrip-in{grid-template-columns:1fr;justify-content:stretch;gap:12px}
  /* the number plus the CTA label do not fit in a 310px gutter box, so the
     bar takes tighter gutters and the phone collapses to its icon */
  .stickycta .wrap{padding-left:16px;padding-right:16px}
  .stickycta-a{width:100%;justify-content:space-between;gap:10px}
  .stickycta-ph span{display:none}
  .stickycta-ph{padding:11px 14px;border:1px solid var(--border-on-dark);border-radius:var(--radius-button)}
  /* .footg>div:first-child{grid-column:auto} removed with BugHerd 15: the
     brand block spans both columns at every width below 1080 now. */
  .usps{grid-template-columns:1fr}
  /* one card with the next peeking, so the swipe affordance is obvious */
  .svctrack .scard{flex:0 0 86%}
  .scard{min-height:360px}
  .stat{padding:22px 0}
  /* BUGHERD 10, 2 SEPTEMBER: THE UTILITY BAR IS GONE BELOW 640, not merely
     thinned. It used to keep the phone number here while hiding the address and
     the hours, so the number appeared twice in the top 100px of a phone screen.
     The header's own call link is the mobile CTA and the burger menu repeats
     the number in full, so the bar was the third copy.
     The address and the hours are NOT lost: both are in the footer contact
     column on every page, and the address is in the schema. */
  .utilbar{display:none}
  /* the header CTA cannot fit beside the logo at this width; the phone link is
     the mobile CTA, and since BugHerd 11 it is an icon-only target sized for a
     thumb rather than a 16px glyph */
  .hdract .navcall span{display:none}
  /* the DS Button writes display:inline-flex as an inline style, so this
     override has to be !important or it silently loses.
     :not(.burger) ADDED 2 SEPTEMBER WITH BUGHERD 12. The burger is a <button>
     and it lives in .hdract beside the phone link, so the bare `.hdract>button`
     swallowed it: the menu mounted, matched, and was display:none on exactly
     the screens it exists for. It cost a screenshot to find, because nothing
     asserts the presence of a control that is supposed to be invisible at the
     width the audits run. There is now a check at 390px that does. */
  .hdract>a[href^="#contact"],.hdract>button:not(.burger){display:none!important}
  .hdrin{gap:16px}
  .formcard{padding:28px}
  .secrow{flex-direction:column;align-items:flex-start;gap:20px}
  .secrow .shead{margin-bottom:28px}
  /* BUGHERD 16, 2 SEPTEMBER: centred, not left aligned. It was
     align-items:flex-start. text-align matters as well as align-items: the
     copyright line can wrap to two lines at 390px and align-items only centres
     the box, not the text inside it. */
  .fbar{flex-direction:column;align-items:center;text-align:center;gap:14px}
  .fbarlinks{justify-content:center}
  .cell{padding:30px 26px}
  .aboutmark{left:-10px;bottom:20px}
}

/* =========================================================================
   ABOUT PAGE (about.html)
   Everything below is scoped to classes that only about.html renders, so it
   cannot reach the home page. Added 14 August 2026.
   ========================================================================= */

/* ---------- Short hero -------------------------------------------
   The home hero owns a calc(100svh - 128px) box because its whole job is to
   hold the headline, CTA, credentials and USP strip above the fold. An
   internal page hero has no such contract, so both the height and the column
   flex are dropped. display:block matters: the moment .hero is a flex column,
   .herog (which is a .wrap, and so carries margin:0 auto) stops stretching and
   shrink-wraps instead, which silently centres and indents the whole column.
   That exact bug shipped on the home page and survived several deploys.
   No fold assertion applies to this hero. It is deliberately not full height. */
/* Photographic ground, added on 18 August feedback. Same treatment as the
   closing contact panel, deliberately: a navy scrim carrying the brand
   gradient's own stops and angle, over a source that is ALREADY pre-darkened at
   build (brightness 0.62, saturation 0.58), so the image reads as texture
   rather than as a picture competing with the headline. The scrim is a touch
   heavier than the contact panel's because bone text sits directly on it here
   with no card behind it. Contrast measured from rendered pixels, not by eye.
   The layer order matters: gradient FIRST, image second, or the photo paints
   over the scrim. Overlays are drawn as a layered background-image because
   container overlays do not render. */
.hero-sm{min-height:0;display:block;
  background-image:linear-gradient(155deg,rgba(12,39,58,.86) 0%,rgba(6,19,29,.93) 100%),
    var(--hero-photo,var(--gradient-scrim-left));
  background-size:cover;background-position:center;background-repeat:no-repeat}
/* The scrim is declared ONCE, above. Per-page grounds set only the photo and,
   where the crop needs it, the focal point. Do not copy the gradient into a
   page modifier: the two would drift, and the scrim is the thing carrying the
   measured contrast. The About page sets nothing and takes the fallback, so it
   is untouched by this. */
/* THE PER-PAGE PHOTO GROUNDS ARE GONE. .hero-contact, .hero-blog, .hero-post,
   .hero-locations, .hero-services and .hero-service each set --hero-photo to a
   pre-darkened photograph that painted under the whole banner. Every internal
   banner is the split layout now, so the photograph is an <img> beside the copy
   and the ground is the scrim on .hero-split above. The six darkened encodes
   they pointed at were deleted with them; the undarkened banner crops replace
   them. A page that needs its own GROUND again sets --hero-photo, but a page
   that needs its own PHOTO just passes a different src. */
/* Taller than it was, on the same feedback: the CTA came out and the lead grew
   by two lines, and the banner was asked to grow with it rather than just
   absorb the change. */
.herog-sm{padding-top:clamp(64px,7.8vw,108px);padding-bottom:clamp(64px,7.8vw,108px);
  grid-template-columns:1fr;gap:clamp(40px,4.4vw,72px);align-items:center}
/* The home hero drops its left padding above 1081px because its portrait bleeds
   to the viewport edge and a gutter on one side only looked wrong. Nothing
   bleeds here, so the normal .wrap padding is restored and the hero copy lines
   up with every section below it instead of hanging 40px further left. */
@media (min-width:1081px){
  .herog-sm{padding-left:40px;padding-right:40px}
}
/* The h1 is short. Let it use the column rather than wrapping early. */
.hero-sm .herocopy h1{max-width:26ch}
/* The lead is the whole copy column now the CTA has gone, so it takes a fuller
   measure than the home hero's 56ch and reads as a paragraph, not a caption. */
.hero-sm .herocopy .lead{max-width:62ch;margin-bottom:0}

/* The About hero's stacked credentials block (QLS mark over the Google rating,
   in a bordered box on the right) was removed on 18 August. Its rules are gone
   with it rather than left behind as dead weight; the git history has them if
   it ever comes back. Note .herocreds, further up, is a DIFFERENT rule: it is
   the home hero's inline credentials row and is still in use. */

@media (max-width:1080px){
  /* grid-template-columns:1fr is LOAD-BEARING and was missed first time round.
     .herog already declares it at this breakpoint, but .herog-sm sets its own
     columns at the same specificity later in the sheet, so the base mobile rule
     loses and the hero stayed two-column on a phone: the copy squeezed to about
     one word per line and the credentials panel sat on top of it.
     Overriding a rule only protects the properties you actually restate. */
  .herog-sm{grid-template-columns:1fr;padding-top:56px;padding-bottom:60px;gap:32px}
}

/* ---------- Mission, vision and values ---------------------------
   Rail layout, the same shape the FAQ panel uses: the section heading sits in
   a left rail and the tall tabbed block takes the right. The first attempt put
   a centred tab row above a left-aligned column in a full-width container, and
   left the right half of the panel empty. Centred furniture over left-aligned
   content reads as a mistake, not as space. */
.mvvg{display:grid;grid-template-columns:.82fr 1.18fr;gap:72px;align-items:start}
.mvvside .shead{margin-bottom:0}
/* Left-aligned, unlike .abouttabs which centres itself above a symmetric
   two-column grid. Here the content under it is left-aligned, so the tab row
   has to be too. */
.mvvtabs{width:fit-content;margin:0 0 32px}
.mvvbody{animation:aboutIn var(--dur-base) var(--ease-out)}
@media (prefers-reduced-motion:reduce){.mvvbody{animation:none}}
.mvvh{font-size:clamp(20px,1.9vw,25px);line-height:1.24;letter-spacing:var(--tracking-heading);
  font-weight:600;color:var(--text-heading);margin:0 0 18px}
/* .body caps at 56ch for the narrow column beside the About panel portrait.
   This column is wider, so the measure relaxes to suit it. */
.mvvbody .body{max-width:64ch}
.mvvbody .body:last-child{margin-bottom:0}

@media (max-width:1080px){
  .mvvg{grid-template-columns:1fr;gap:36px}
}

/* ---------- Mirrored feature panel -------------------------------
   FeaturePanel takes a `reverse` prop and any page may use it. Copy left,
   image right. The eight practice-area pages use it for their "help" panel.

   THIS BLOCK IS THE ONLY SURVIVOR OF THE PPC LANDING PAGE'S CSS, which was
   removed with the page on 2 September 2026. It was written for that page and
   was always marked shared rather than landing-only, so it stayed. If you are
   looking for .hero-lp, .lpform, .lpintro, .lpcall or .lpcta, they are gone and
   the git history has them.

   The two-class selectors below outrank the bare .locfeature-m{order:-1}
   stacking rule and the bare .locfeatureg column rule, so BOTH are restated
   for mobile. Without that restatement the mirrored panel would stay
   two-column on a phone and put its image under its text while the unmirrored
   panel above it kept the image on top. Same trap as .herog-sm, documented in
   CONTEXT.md section 6: overriding a rule only protects the properties you
   actually restate.

   THE RESTATEMENT MUST USE THE SAME BREAKPOINT AS THE RULE IT OVERRIDES, AND
   FOR A WHILE IT DID NOT. It was written inside the landing page's own
   max-width:1080px block, but .locfeatureg stacks at max-width:900px. Between
   901px and 1080px the mirrored panel was therefore already stacked while the
   unmirrored panels beside it were still two-column: one panel in a page of
   three collapsing to the mobile layout 180px early. Every assertion passed
   throughout, because a stacked panel produces no overflow, no heading skip
   and no negative left. It was only visible by measuring the two columns'
   rects against each other at nine widths.
   A mirrored panel is a MODIFIER of the base panel, so its breakpoint is not a
   free choice: it is whatever the base uses. Change one and you must change
   the other. */
.locfeature-rev .locfeatureg{grid-template-columns:1.14fr .86fr}
.locfeature-rev .locfeature-m{order:2}
.locfeature-rev .locfeature-b{order:1}
/* 900px, matching .locfeatureg above. Placed after the base .locfeature-rev
   rules because they sit at equal specificity and a media query does not
   raise it. */
@media (max-width:900px){
  .locfeature-rev .locfeatureg{grid-template-columns:1fr;gap:40px}
  .locfeature-rev .locfeature-m{order:-1}
  .locfeature-rev .locfeature-b{order:0}
}


/* ---------- Terms and conditions --------------------------------
   Scoped to classes that appear on terms-and-conditions.html only. Nothing
   here restyles a shared class: the page's header, hero, closing contact panel
   and footer are the site's own, unmodified.

   The page is a legal document, so it is set as one measure-capped reading
   column rather than as panels. No cards, no grid, no accordion. A disclosure
   that hides half of itself behind a control is a weaker disclosure, and a
   reader who has been told to read something before contacting a criminal
   lawyer should be able to read it in one scroll.

   THE MEASURE IS 68ch, NOT THE 56ch OF .body. Body copy on this site sits
   beside an image or inside a card and 56ch is right there. Here the column is
   alone on a white ground with nothing to balance against, and at 56ch it
   reads as a narrow ribbon down the middle of a 1200px container. 68ch is
   still inside the comfortable range for continuous text. */
.termsg{max-width:68ch;margin:0 auto}

/* The date line. Small, muted and set above the first clause rather than in
   the hero: it belongs to the document, not to the page furniture, and a
   reader checking which version bound them looks at the top of the text. */
.termsmeta-d{font-size:14px;line-height:1.5;color:var(--text-muted);
  margin:0 0 clamp(28px,3.4vw,40px);padding-bottom:clamp(20px,2.4vw,26px);
  border-bottom:1px solid var(--border-hairline)}

/* CLAUSE NUMBERS ARE A CSS COUNTER, NOT TEXT IN THE HEADINGS.
   A number typed into a heading string goes stale the moment a clause is
   inserted above it, and the ids in terms-sections.jsx deliberately do not
   encode the number for the same reason. Renumbering is now automatic and the
   ids stay stable, so an engagement letter can cite #liability and keep
   working after a clause is added. */
.termslist{counter-reset:tclause;list-style:none;margin:0;padding:0;
  display:flex;flex-direction:column;gap:clamp(30px,3.4vw,40px)}
.termsitem{counter-increment:tclause;margin:0}
/* scroll-margin-top so a direct link to a clause does not land it under the
   sticky header. 108px clears the header at every width it is sticky. */
.termsitem{scroll-margin-top:108px}
.termsitem>h2{font-size:clamp(19px,1.5vw,22px);line-height:1.3;font-weight:600;
  letter-spacing:var(--tracking-heading);color:var(--text-heading);
  margin:0 0 12px;text-wrap:balance}
.termsitem>h2::before{content:counter(tclause) ". ";color:var(--maroon-700);
  font-variant-numeric:tabular-nums}
.termsitem>p{font-size:16.5px;line-height:1.72;color:var(--text-body);
  margin:0 0 14px;text-wrap:pretty}
.termsitem>p:last-child{margin-bottom:0}

/* The contact clause's definition-style rows. A two-column grid rather than a
   <dl>, because the label and the value are one line each and the labels are
   short enough that a fixed first column keeps the values aligned.

   min-width:0 on the grid and overflow-wrap on the cell are BOTH needed: the
   email is 34 characters with no space in it, grid items default to
   min-width:auto, and without these it pushes the column wider than the
   measure at 390px. This is the long-email-in-a-flex-row trap from the master
   CLAUDE.md, in its grid form. */
.termscontact{list-style:none;margin:18px 0 0;padding:0;
  display:flex;flex-direction:column;gap:10px}
.termscontact li{display:grid;grid-template-columns:72px minmax(0,1fr);gap:14px;
  align-items:baseline;font-size:16px;line-height:1.6;color:var(--text-body);
  overflow-wrap:anywhere}
.termscontact li>span{font-size:12px;font-weight:600;letter-spacing:var(--tracking-eyebrow);
  text-transform:uppercase;color:var(--text-muted)}
.termscontact a{color:var(--maroon-700);text-decoration:none}
.termscontact a:hover{text-decoration:underline}
.termscontact a:focus-visible{outline:none;box-shadow:var(--ring-focus);border-radius:3px}

@media (max-width:640px){
  /* The label column becomes a row of its own. At 390px a 72px label plus a
     14px gap leaves 224px for an email that needs more, and anywhere-wrapping
     an address mid-word to save 86px is a worse read than stacking. */
  .termscontact li{grid-template-columns:1fr;gap:2px}
}

/* =========================================================================
   MOBILE PASS, BugHerd tickets 8 to 20, 2 September 2026
   Everything below is inside a max-width query and changes nothing on desktop.
   Two breakpoints are in play and the choice is not arbitrary:
     1080px is where .nav is display:none and .herog stops being two columns,
            so anything about the header or the hero belongs here.
      640px is where .grid3 goes to one column and the header CTA is dropped,
            so anything about card layout or the phone-sized header belongs here.
   Each rule names its ticket.
   ========================================================================= */

@media (max-width:1080px){
  /* ---- BugHerd 8 and 17: the banner photograph goes ABOVE the copy --------
     Two tickets, one rule. 8 asked for it on the home hero and 17 asked for it
     on every hero on the site, so it is written once against .herog, which all
     fourteen banners share, rather than per page.
     order:-1 rather than reordering the JSX, because the DOM order is the
     reading order a screen reader and a keyboard get, and there the heading
     should still come before a decorative photograph. This is a visual
     reordering only, which is what a ticket about the mobile view is asking
     for. */
  /* FULL BLEED TO BOTH VIEWPORT EDGES, added 2 September on top of the reorder.
     .herog is a .wrap, so it carries `padding: 0 40px`, and the banner sat in a
     40px gutter on each side while every band above and below it (the navy
     hero ground, the USP strip) ran edge to edge. Cancelling the wrap's own
     padding is what pulls it out.

     -40px rather than the `width:100vw; margin-left:calc(50% - 50vw)` trick,
     deliberately. 100vw INCLUDES the scrollbar, so that version overflows by
     the scrollbar width at any size where one is present, and 901 to 1080 is
     desktop. .hero is overflow:hidden so it would be clipped rather than
     scroll, which makes it the kind of bug you never see and never fix.
     The value is the wrap's padding and nothing else. If .wrap ever takes a
     different gutter, this moves with it. */
  /* margin-top:0 CANCELS A SEPARATOR THAT HAS LOST ITS JOB. The max-width:900px
     block gives .hero-split .heroimg an 8px top margin, written when the banner
     sat BELOW the copy and needed clearing from it. With the photo reordered to
     the top that 8px became a strip of bare navy between the header and the
     banner: the home hero read flush and the thirteen internal ones were 8px
     short of it, which looks like a rendering fault rather than a decision. */
  .herog>.heroimg{order:-1;margin-inline:-40px;margin-top:0}

  /* ---- Flush to the header, added 2 September ---------------------------
     With the photograph reordered to the top, the grid's own top padding sat
     BETWEEN the header and the banner as a band of bare navy: 44px on the home
     hero, 56px on the internal ones. Zeroing it puts the photo hard against the
     header, which is what was asked for and is what the full-bleed edges were
     already implying.

     THE BOTTOM PADDING STAYS. It is not symmetry: it is the gap between the
     end of the hero copy and whatever follows, and the copy is now the LAST
     thing in this grid rather than the first. Removing it would run the lead
     straight into the USP strip. */
  .herog{padding-top:0}
  /* AND THE ORDER IS UNDONE AGAIN BETWEEN 901 AND 1080, WHICH IS NOT A
     CONTRADICTION. The two heroes on this site stack at two different widths.
     The home page's .herog is one column from 1080 down. The thirteen internal
     .hero-split .herog-sm banners are still TWO columns until 900, because
     `@media (min-width:901px)` five hundred lines above gives them
     1.04fr .96fr and that selector is (0,2,0) against the max-width block's
     (0,1,0).
     So a bare order:-1 at 1080 does not put the photo above the copy on an
     internal page: it SWAPS THE TWO COLUMNS, putting the copy on the right and
     the photo on the left. That is what shipped in the first draft of this
     rule and assert.js caught it at exactly 1080px, on the "hero copy aligns
     with header logo" check: copy at 580.7 against a logo at 40.
     A reorder's breakpoint is not a free choice. It is whatever the stack it
     reorders uses, which is the same lesson .locfeature-rev is carrying at the
     bottom of this file. Two classes beat one, so this wins wherever it
     applies. */

  /* ---- BugHerd 9: the hero credentials stack, centre, and lose the rule ----
     This closes CONTEXT.md item 61, which has been open since the landing page
     work. The 1px vertical hairline between the QLS lockup and the Google
     rating stayed on the first line when the row wrapped to two, so it hung off
     the right of the block with nothing after it. It is not a wrapping problem
     to be tuned: at this width the block is two stacked items and a divider
     between two stacked items is meaningless, so it goes.
     align-items:center centres the two items against each other; the block
     itself is centred by margin-inline:auto, and text-align carries the words
     inside the QLS lockup, which align-items alone would leave ragged. */
  .herocreds{flex-direction:column;align-items:center;text-align:center;
    margin-inline:auto;gap:18px;
    /* BUGHERD 26, 2 September: the HORIZONTAL rule above this block goes too.
       BugHerd 9 removed .herocreds-rule, the 1px VERTICAL hairline between the
       QLS lockup and the Google rating. This is a different line: .herocreds
       carries a border-top that separates the whole block from the CTA above
       it. On desktop it closes a wide row. Against a centred column it is a
       full-width rule over centred content, which reads as a section break
       rather than as a join, and it was the last horizontal line left in the
       hero. The padding goes with it, or the gap it opened stays behind.
       At 1080, not 640, so the credentials block has ONE mobile treatment at
       one breakpoint rather than two rules that disagree between 641 and 1080. */
    border-top:0;padding-top:0}
  .herocreds-rule{display:none}
  /* The badge words grew for BugHerd 102; at this width the enlarged sizes
     wrap "Michael Finch, AccS(Crim)" onto two lines, so they step back down. */
  .herocreds .qlswords{font-size:11px}
  .herocreds .qlswords b{font-size:13px}

  /* ---- BugHerd 19: the FAQ button drops below the questions ---------------
     Sitewide, on every FAQ panel, which is what the ticket asked for.
     display:contents on .faqside is the whole trick and it is worth
     understanding rather than copying: the button lives INSIDE .faqside in the
     markup, beside the heading, which is right on desktop where that column is
     the rail. Dissolving .faqside's box promotes its two children to grid items
     of .faqg, so heading, button and accordion become three siblings that can
     be ordered independently. No JSX change, so the desktop rail is untouched.
     The gap is zeroed and spacing moved to margins, because a single row gap
     cannot be 28px in one place and 32px in another. */
  .faqside{display:contents}
  .faqg{gap:0}
  /* THE CHILD COMBINATOR STILL POINTS AT .faqside, NOT AT .faqg, AND THAT IS
     THE WHOLE TRAP IN THIS RULE. `display:contents` removes the BOX, so the
     heading and the button become grid items of .faqg for layout purposes. It
     does NOT change the DOM, so a selector written `.faqg>.shead` matches
     nothing: the heading's parent element is still .faqside.
     The first draft was written that way. .faqg>.faqlist matched (it really is
     a child of .faqg) and picked up order:2, while the heading and the button
     kept the default order:0 and therefore stayed ABOVE the accordion, which is
     exactly the arrangement the ticket asked to change. Everything measured as
     "applied" because one of the three rules had. Found by reading the computed
     order off all three elements rather than off one. */
  .faqside>.shead{order:1;margin-bottom:28px}
  .faqg>.faqlist{order:2}
  .faqside>a{order:3;margin-top:32px;justify-self:start}
}

/* BOUNDED AT BOTH ENDS, AND THE UPPER BOUND IS LOAD-BEARING.
   This block only has to undo what the max-width:1080px block above does, so it
   must not outlive it. Written as a bare `min-width:901px` it also applied at
   1081 and up, where `.hero-split .herog-sm>.heroimg` (0,3,0) outranks the
   min-width:1081px block's `.hero-split .heroimg` (0,2,0) and `margin-inline:0`
   silently cancelled the approved one-sided bleed to the right viewport edge on
   all thirteen internal banners. Measured at 1440 the photo stopped 60px short
   of the edge, and at 1920 it stopped 180px short. Nothing failed: it is not
   overflow, not a heading skip and not a contrast problem, just the design
   quietly not happening. Caught by diffing the banner's rect against the
   previous commit rather than by looking at it.
   The lesson is the shorthand, not the breakpoint: `margin-inline` sets BOTH
   sides, so a reset written with it reaches further than a reset written as
   `margin-left`. Bound any override that uses a shorthand to exactly the range
   it is correcting. */
@media (min-width:641px) and (max-width:1080px){
  /* ---- BugHerd 18: the mission/vision/values tabs centre -----------------
     .mvvtabs is width:fit-content, so it is already only as wide as its three
     tabs; auto side margins are all that is needed to centre the block. The
     tablist's own display:flex comes from an inline style written by the design
     system's Tabs, so justify-content cannot be set from here without
     !important, and centring the block achieves the same thing without it.

     641 TO 1080, NOT max-width:640. The rail stacks at 1080 and below, so the
     centring is wanted from there down, but below 641 the tabs do not exist:
     that is where MobileAccordion takes over. A rule aimed at an element that
     is not rendered is dead weight that reads as coverage. */
  .mvvtabs{margin-inline:auto}
}

@media (min-width:901px) and (max-width:1080px){
  /* The other half of the note above. See BugHerd 8 and 17.
     margin-inline goes back with the order for the same reason: between 901 and
     1080 the internal banners are still a two-column layout, and a column that
     bleeds off the left of the grid is not a full-width banner, it is a broken
     column. Full bleed belongs to the stacked state only. */
  .hero-split .herog-sm>.heroimg{order:0;margin-inline:0}
  /* And the top padding comes back with them. In this band the banner is a
     column beside the copy, not a strip under the header, so there is nothing
     to sit flush against and a hero starting hard against the header with its
     copy at the very top reads as a rendering fault.
     56px is .herog-sm's own value from its max-width:1080px rule, restated here
     rather than referenced because that rule sits at equal specificity to the
     `.herog{padding-top:0}` above and loses to it on source order. Two classes
     beat one, so this wins wherever it applies. If that 56px ever changes,
     change it here too. */
  .hero-split .herog-sm{padding-top:56px}
}

@media (max-width:900px){
  /* ---- BugHerd 20: the contact form goes above the detail tiles -----------
     Scoped to 900px, NOT 640 or 1080, because that is where .cmaing stops
     being two columns. A reorder written at a different breakpoint from the
     stack it reorders is the trap the mirrored feature panel already walked
     into: between the two widths the page would be two columns with one of
     them ordered, which is not a layout anyone designed. */
  .cmaing>.cform{order:-1}
}

@media (max-width:640px){
  /* ---- BugHerd 11: the click-to-call target grows ------------------------
     The label is hidden at this width, so the whole control is one 16px glyph,
     which is under any reasonable thumb target. The SVG carries width and
     height ATTRIBUTES from the design system's Icon, and CSS beats a
     presentational attribute, so this needs no change in the JSX.
     The padding is what actually makes the target: 22px of glyph plus 10px of
     padding all round is a 42px box, which clears the 44px guidance once the
     header's own line box is counted. */
  .hdract .navcall{padding:10px;margin:-10px}
  .hdract .navcall svg{width:22px;height:22px}

  /* ---- BugHerd 13 and 14: testimonials and articles become a carousel -----
     One card per view, snapped, with the controls underneath. The element
     keeps .grid3 so nothing above this breakpoint changes; .msnap only takes
     effect here, where display:flex overrides the grid.
     flex:0 0 100% rather than the services track's 86% peek. Those cards are
     image tiles in a set of eight where the peek is the affordance; these are
     three text cards, and a clipped quote reads as a rendering fault rather
     than as an invitation to swipe. The controls carry that job instead, which
     is what the tickets asked for.
     scrollbar-width:none hides the bar on a track the buttons already drive.
     The overflow is still keyboard and gesture scrollable. */
  .msnap{display:flex;gap:16px;overflow-x:auto;overflow-y:hidden;
    scroll-snap-type:x mandatory;scroll-behavior:smooth;
    -webkit-overflow-scrolling:touch;scrollbar-width:none;
    margin-inline:0;padding-bottom:2px}
  .msnap::-webkit-scrollbar{display:none}
  .msnap>*{flex:0 0 100%;scroll-snap-align:center;min-width:0}
  /* Centred under the track, which is where the tickets put them. The buttons
     are the services carousel's .svcbtn, reused rather than restyled: two sets
     of carousel controls on one site that are nearly the same size is the
     "consistent but unequal" complaint on the recurring feedback list. */
  .msnav{display:flex;justify-content:center;gap:12px;margin-top:26px}

  /* BugHerd 18's `.mvvtabs{margin-inline:auto}` USED TO LIVE HERE and has moved
     to its own query below. Below 640px there is no tab strip to centre any
     more: BugHerd 25 was generalised to every tabbed block on 2 September and
     the MVV rail is an accordion at this width. The rule is still needed
     between 641 and 1080, where the rail is stacked but the tabs are real. */

  /* ---- BugHerd 15, continued: keep the two footer columns readable -------
     The Services column carries "Homicide & serious crime" and the Contact
     column carries a 34-character email with no spaces in it. At 390px each
     column is about 155px, so both need to be allowed to break inside a word
     rather than push the grid wider than the viewport. min-width:0 is the
     other half: a grid item defaults to min-width:auto and will refuse to
     shrink below its longest unbreakable run without it. */
  .footg{gap:32px 20px}
  .footg>*{min-width:0}
  .footg a,.footg .fcontact li{overflow-wrap:anywhere}
}

/* ---------- Mobile navigation, BugHerd 12 -------------------------------
   The burger and its panel. Both are rendered by MobileNav in
   home-sections-a.jsx, which MOUNTS NOTHING above 1080px, so none of this can
   apply on desktop even by accident.

   NO ANIMATION ON THE PANEL OR ANYTHING INSIDE IT. This is the same rule that
   governs .navdd-m twelve hundred lines above, for the same reason: .hdr is
   sticky and carries a backdrop-filter on a pseudo-element, and anything inside
   it that gets promoted to its own composited layer is painted wrong by Chrome
   while still hit-testing correctly. It has bitten twice on the desktop flyout.
   A transition on transform is fine. An `animation` is not. Do not add one.
   ----------------------------------------------------------------------- */
.burger{display:inline-flex;flex-direction:column;justify-content:center;
  gap:5px;width:44px;height:44px;padding:0 10px;margin-right:-10px;
  appearance:none;background:transparent;border:0;cursor:pointer;flex:none}
.burger-b{display:block;height:2px;width:100%;background:var(--text-heading);
  border-radius:1px;transition:transform var(--dur-fast) var(--ease-out),
  opacity var(--dur-fast) var(--ease-out)}
.burger[aria-expanded="true"] .burger-b:nth-child(1){transform:translateY(7px) rotate(45deg)}
.burger[aria-expanded="true"] .burger-b:nth-child(2){opacity:0}
.burger[aria-expanded="true"] .burger-b:nth-child(3){transform:translateY(-7px) rotate(-45deg)}
.burger:focus-visible{outline:none;box-shadow:var(--ring-focus);border-radius:var(--radius-xs)}

/* Full bleed under the header. .hdr is position:sticky, which is a positioned
   ancestor, and .hdrin is not positioned, so left:0/right:0 resolve against the
   header rather than against the 1200px container. That is deliberate: a panel
   inset by the wrap's 40px gutters reads as a floating card rather than as the
   navigation taking over the screen. */
.mnav{position:absolute;top:100%;left:0;right:0;z-index:var(--z-flyout);
  background:var(--surface-card);border-top:1px solid var(--border-hairline);
  box-shadow:var(--shadow-lg);padding:10px 0 18px;
  max-height:calc(100dvh - 100%);overflow-y:auto;overscroll-behavior:contain}
.mnav[hidden]{display:none}
/* Only the nav takes the gutter as padding. The call button below takes it as
   MARGIN instead, because it is a filled button: padding here would be added to
   its own padding and the two rules sit at equal specificity, so the later one
   would silently win and flush it to the panel edges. */
.mnav>nav{padding-inline:clamp(20px,5vw,40px)}
.mnav-l{list-style:none;margin:0;padding:0}
.mnav-l>li{border-bottom:1px solid var(--border-hairline)}
.mnav-l>li:last-child{border-bottom:0}
.mnav-l a{display:block;padding:15px 0;font-size:15px;font-weight:600;
  letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;
  color:var(--text-heading);text-decoration:none}
.mnav-l a:focus-visible{outline:none;box-shadow:var(--ring-focus);border-radius:var(--radius-xs)}
/* The row is a link and a disclosure button side by side, not one control
   doing both jobs. A row that sometimes navigates and sometimes expands is the
   thing people complain about on phone menus, and a combined control also
   leaves no way to reach the Services index itself. */
.mnav-row{display:flex;align-items:center;justify-content:space-between;gap:12px}
.mnav-row a{flex:1 1 auto;min-width:0}
.mnav-x{flex:none;display:inline-flex;align-items:center;justify-content:center;
  width:44px;height:44px;margin-right:-11px;appearance:none;background:transparent;
  border:0;color:var(--text-muted);cursor:pointer}
.mnav-x svg{transition:transform var(--dur-fast) var(--ease-out)}
.mnav-x[aria-expanded="true"] svg{transform:rotate(180deg)}
.mnav-x:focus-visible{outline:none;box-shadow:var(--ring-focus);border-radius:var(--radius-xs)}
/* The eight practice areas. Sentence case, lighter weight and indented, so the
   submenu reads as contents of Services rather than as eight more top-level
   destinations. */
.mnav-sub{list-style:none;margin:0 0 14px;padding:0 0 0 14px;
  border-left:2px solid var(--border-hairline)}
.mnav-sub[hidden]{display:none}
.mnav-sub a{padding:11px 0;font-size:14px;font-weight:500;letter-spacing:0;
  text-transform:none;color:var(--text-body)}
/* The number, repeated as a full-width action. The header's call link is an
   icon at this width and a person who opened this menu at 11pm should not have
   to find it again. */
.mnav-call{display:flex;align-items:center;justify-content:center;gap:10px;
  margin:18px clamp(20px,5vw,40px) 0;padding:15px 20px;border-radius:var(--radius-button);
  background:var(--maroon-700);color:var(--bone-50);font-size:15px;font-weight:700;
  text-decoration:none}
.mnav-call:hover{background:var(--navy-800)}
.mnav-call:focus-visible{outline:none;box-shadow:var(--ring-focus)}

/* ---------- Banner framing note, BugHerd 7 ------------------------------
   THERE WAS AN `object-position` OVERRIDE HERE AND IT IS GONE WITH THE CROPS.
   On 2 September the flag was cropped out of firm-office-team.webp and
   hero-founder.webp, which turned both from landscape into portrait sources. A
   portrait source in a wide banner box is cover-cropped VERTICALLY, and the
   inherited `object-position: 70% center` then ran that crop straight through
   both subjects' heads, so the rule existed to pull the frame back up.

   Both images were REVERTED to the originals the same day, on Wayde's
   instruction: he is editing the flags out by hand and reuploading. The
   originals are landscape again, the default object-position frames them the
   way it always did, and the override would now reframe them for no reason.

   IF THE REPLACEMENTS COME BACK AT A DIFFERENT ASPECT RATIO, LOOK AT THE
   BANNERS BEFORE ASSUMING THEY ARE FINE. Anything appreciably taller than the
   1.67 the current firm-office-team.webp has will need this rule back, with the
   percentage re-measured rather than copied. It was `center 10%`.
   CONTEXT.md item 81. */

/* ---------- BugHerd 24 and 30: the row's right-hand control moves under the
   feed it belongs to -------------------------------------------------------
   Three sections share one markup shape: a `.secrow` holding a heading on the
   left and a control on the right, then the feed itself underneath.

     #services  heading + .svcnav        then .svctrack     (BugHerd 24)
     #insights  heading + View all       then .msnap/.msnav (BugHerd 30)
     #related   heading + View all       then .grid3        (post.html, same
                                                             shape, included so
                                                             one page does not
                                                             read differently)

   On desktop that row is right: heading left, control right, one line above the
   feed. On a phone `.secrow` stacks, so the control sat BETWEEN the heading and
   the thing it acts on and read as part of the heading block.

   display:contents on .secrow, then order. Same trick as the FAQ button, and
   the same caveat: the child combinator still points at the REAL parent, so
   these are written `.secrow .shead`, not `#services > .shead`.

   IT NEEDS THE SECTION'S `.wrap` TO BECOME A FLEX COLUMN FIRST. `order` only
   means anything to a flex or grid item, and those wraps are plain blocks, so
   dissolving .secrow alone would have promoted the heading and the control to
   block children where order is inert. That is a different failure from the FAQ
   one and would have looked identical: nothing moves.

   SCOPED BY SECTION ID, NOT TO `.secrow` BROADLY. The class is shared, and a
   blanket rule would reach any future row whose right-hand element genuinely
   belongs beside its heading. Three ids, listed, so adding a fourth is a
   decision someone makes on purpose. */
@media (max-width:640px){
  #services .wrap,#insights .wrap,#related .wrap{display:flex;flex-direction:column}
  #services .secrow,#insights .secrow,#related .secrow{display:contents}
  #services .secrow .shead,#insights .secrow .shead,#related .secrow .shead{order:1}

  /* The feed, whatever shape it takes in each section. */
  #services .svctrack{order:2}
  #insights .msnap{order:2}
  #related .grid3{order:2}

  /* Then the control. On #insights the carousel's own prev/next sits directly
     under the track (BugHerd 14), so "View all articles" goes below THAT, not
     between the feed and its controls. */
  #insights .msnav{order:3}
  /* Centred and 26px clear, matching .msnav exactly. Five sections now put a
     control under a feed and two sets that are nearly but not quite the same is
     the "consistent but unequal" complaint on the recurring feedback list in
     the master CLAUDE.md. `align-self:center` on the two links comes from the
     BugHerd 27/28 rule further down; only the order and the gap are set here. */
  #services .svcnav{order:3;justify-content:center;margin-top:26px}
  #insights .secrow>a{order:4;margin-top:26px}
  #related .secrow>a{order:3;margin-top:26px}
}

/* ---------- Mobile accordion, BugHerd 25 ---------------------------------
   Rendered by MobileAccordion, which is MOUNTED ONLY below 640px by each of its
   callers, so none of this can reach a desktop panel even by accident. A tab
   strip and its accordion are never both in the DOM.

   `.macc`, not `.aboutacc`. It was named for the About panel when that was the
   only tabbed block it served; it now serves every tabbed block on the site
   (the home About panel and the About page's Mission / Vision / Values rail)
   and a name that claims otherwise is the start of a second copy.

   The triggers are buttons, not headings: every panel already carries a real
   heading inside its content, and heading order is asserted on every page. */
.macc{display:flex;flex-direction:column}
.macc-i{border-bottom:1px solid var(--border-hairline)}
.macc-i:first-child{border-top:1px solid var(--border-hairline)}
.macc-b{width:100%;display:flex;align-items:center;justify-content:space-between;
  gap:16px;appearance:none;background:transparent;border:0;cursor:pointer;
  padding:20px 0;text-align:left;font-size:14px;font-weight:600;
  letter-spacing:var(--tracking-eyebrow);text-transform:uppercase;
  color:var(--text-heading)}
.macc-b svg{flex:none;color:var(--text-muted);
  transition:transform var(--dur-fast) var(--ease-out)}
.macc-b[aria-expanded="true"] svg{transform:rotate(180deg)}
.macc-b[aria-expanded="true"]{color:var(--maroon-700)}
.macc-b:focus-visible{outline:none;box-shadow:var(--ring-focus);border-radius:var(--radius-xs)}
.macc-p[hidden]{display:none}
.macc-p{padding-bottom:36px}
/* Tighter than the 52px .aboutg takes at this width. Inside an accordion the
   panel is already separated from its neighbours by the trigger rows and the
   rules, so the section-sized gap reads as a hole. */
.macc-p .aboutg{gap:30px}
/* The MVV body carries its own bottom margin on its last paragraph, so it needs
   less than the About panel does. */
.macc-p .mvvbody{margin-top:2px}


/* ---------- BugHerd 27 and 28: standalone buttons centre on mobile --------
   27 pinned the Locations "View all locations" button; 28 generalised it to
   "all buttons that appear on their own row on mobile view". These are the four
   containers where that happens, found by walking every DS button on ten page
   templates at 390 and asking whether anything shares its row.

   THE DS BUTTON'S COMPUTED display IS NOT ALWAYS inline-flex, and that matters
   for anyone extending this. The component writes `display:inline-flex` as an
   inline style, but a flex ITEM blockifies, so inside .ctarow and .secrow it
   computes to `flex` instead. A first pass at finding these buttons filtered on
   `display === "inline-flex"` and silently missed the two the tickets actually
   pinned. Match on both, or on the padding, or on the class.

   Two techniques below, because the containers are not the same:
     - a flex or grid parent takes align-self / justify-self, which is clean;
     - a BLOCK parent needs the button made block-level to accept auto margins,
       and `display` there has to be !important because it is fighting the
       design system's inline style. width:fit-content keeps it hugging its
       label rather than stretching to the column.

   NOT INCLUDED, deliberately: .ctarow>a in the hero, which is already
   width:100% at this breakpoint, so its label is centred inside a full-width
   button and there is nothing to move. */
@media (max-width:640px){
  /* "View all locations" (#locations) and "View all articles" (#insights).
     .secrow is a column here with align-items:flex-start, which is right for
     the heading and wrong for the button under it. */
  .secrow>a{align-self:center}

  /* "Speak to a lawyer" on every FAQ panel sitewide. .faqside is display:contents
     from 1080 down (BugHerd 19), so this button is a GRID item of .faqg and
     takes justify-self, not align-self. It was justify-self:start from that
     ticket; this overrides it at 640 and leaves 641 to 1080 alone, where the
     panel is stacked but the phone rules do not apply. */
  .faqside>a{justify-self:center}

  /* The About panel CTA: "More about the firm" on the home page, "Enquire now"
     twice on the About page. Its parent is a plain block div holding the
     heading, body, ticked points and then the button. */
  .aboutg>div>a{display:flex!important;width:fit-content;margin-inline:auto}

  /* The feature panel CTA: "Speak to a lawyer" on the services index
     (#approach), the six location pages (#local) and all eight practice-area
     pages (#help). Same block-parent case. */
  .locfeature-b>a{display:flex!important;width:fit-content;margin-inline:auto}
}
