:root {
  /*
   * Height of the sticky nav, and the offset every sticky element must clear.
   *
   * Sticky blocks were set to top:80px against a nav that measures 110px, so
   * each one parked 30px UNDERNEATH it — the heading of a sticky rail was
   * simply not visible, and scrolling back up did not recover it because the
   * element was already pinned.
   *
   * --nav-h was referenced in BaseLayout.astro in the original build and never
   * defined anywhere, so the padding that depended on it silently computed to
   * zero. Defining it here fixes that too.
   */
  --nav-h: 110px;
  --sticky-top: calc(var(--nav-h) + var(--s-5));
}

@media (max-width: 71.1875rem) {
  :root {
    /* Burger nav is shorter than the desktop bar. */
    --nav-h: 84px;
  }
}

/*
 * theme.css — HAND-WRITTEN. Never generated, never overwritten.
 *
 * WHY THIS FILE EXISTS
 *
 * Every layout fix in here was originally appended to components.css, which is
 * regenerated from the Astro source by extract-css.mjs on every single build.
 * `npm run wp:build` therefore deleted all of it, every time, silently — and the
 * same layout problems were reported, "fixed", and shipped broken repeatedly.
 * components.css even carries a DO-NOT-EDIT header saying exactly this.
 *
 * Anything the WordPress theme needs that does NOT exist in the Astro source
 * belongs here. Loads after components.css, so it can override it.
 */



@media (min-width: 60rem) {
  .page-about .prose.container--narrow {
    /* Beats .container--narrow (0,1,0) on specificity at 0,3,0. */
    max-width: var(--container);
    display: grid;
    grid-template-columns: 5fr 7fr;
    gap: 0 var(--s-9);
    align-items: start;
  }

  .page-about .prose.container--narrow > .ab__intro {
    grid-column: 1;
    grid-row: 1 / span 20;
    position: sticky;
    top: var(--sticky-top);
    margin: 0;
    padding-right: var(--s-6);
    border-right: 1px solid var(--c-taupe-deep);
    font-family: var(--font-display);
    font-weight: var(--fw-light);
    font-size: var(--t-h3);
    line-height: var(--lh-heading);
    color: var(--c-ink);
  }

  .page-about .prose.container--narrow > *:not(.ab__intro) {
    grid-column: 2;
  }

  .page-about .prose.container--narrow > h2 {
    margin-top: var(--s-7);
  }
}

/* ----------------------------------------------------------------------
   About: services split — the photograph holds while the list scrolls.
   The imported markup already has two columns; it was missing the sticky.
   ---------------------------------------------------------------------- */

@media (min-width: 60rem) {
  .ab__split {
    align-items: start;
    /*
     * Wider than the generated --s-8, and asymmetric. The prints ran to within
     * about 50px of the copy, which read as tight; and because the media column
     * holds tilted frames rather than a flush-edged photograph, it needs room on
     * its outside edge too or the rotated corners look clipped by the margin.
     * Doug asked for more on the left than the right, which is this.
     */
    gap: var(--s-9);
    padding-left: var(--s-6);
  }

  .ab__splitMedia {
    position: sticky;
    top: var(--sticky-top);
  }
}

.ab__splitMedia img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

/* ======================================================================
   Display headings: 300 -> 500

   At display sizes, uppercase, with --ls-display tracking, Montserrat 300
   goes weak. A five-line heading like "WHAT ARE PROFESSIONAL ORGANIZING AND
   DECLUTTERING SERVICES?" reads as gray texture rather than as the loudest
   thing in its section. Doug called it.

   500 rather than 600 because both 300 and 500 are already in the theme, so
   this costs no extra request, and 600 against this much letter-spacing goes
   blocky. 400 is the subtler step and is the one weight not shipped; the
   difference from 500 at these sizes does not earn another font file.

   Element selectors only, matching global.css. Headings deliberately set to
   light by their own component keep it, because each was a separate decision
   and they are labels rather than headlines: .faq__q, .qf__legend,
   .cm__legend, .rw__score, .eeat__whoName, .eeat__row dt, .foot__wordmark.

   SUB-HEADINGS TAKE 400, not 500. At h3 sizes 500 competed with the section
   heading above it — a card title should not shout as loud as the section it
   sits in. Doug asked for 450; an intermediate weight needs a variable font,
   which this theme does not ship, so 400 it is.
   ====================================================================== */

h1,
h2 {
  font-weight: var(--fw-medium);
}

h3,
h4,
h5,
h6 {
  font-weight: var(--fw-regular);
}

/* ----------------------------------------------------------------------
   PRINTS SPREAD OUT ON A TABLE — one component, several callers

   .pscatter is shared: the About services split and the "What's included"
   aside on every service page both ask for it. It was .ab__scatter and
   owned by the About page until Doug wanted the same treatment under
   What's Included; renamed rather than copied, because a copy drifts and
   the next change gets made in one place and missed in the other.

   The single image here was a studio portrait of Ashley against a blank
   wall — "posed, doing nothing", on the section that lists what the
   company actually does. Four prints of finished rooms say more about the
   services than a photograph of the owner standing still, and Doug asked
   for them scattered like prints laid out on a table.

   Frames only overlap on desktop. On a phone the offsets collapse into a
   few pixels of accidental-looking overlap, and these four are content
   rather than decoration — one per service listed alongside — so they go
   to a plain 2x2 grid rather than being hidden.
   ---------------------------------------------------------------------- */

.pscatter {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-5);
}

/*
 * wpautop turns the newlines between the four images into <br>, so the wrapper's
 * children are img, br, img, br, img, br, img. Left alone those breaks take grid
 * cells of their own — four images became seven items and the block ran to 887px
 * on a phone — and they shift every :nth-child count by one, which silently
 * dropped the rotation on two of the four prints. Hence display:none here, and
 * nth-of-type rather than nth-child below.
 */
.pscatter br {
  display: none;
}

.pscatter img {
  aspect-ratio: 4 / 5;
  background: #fff;
  /* Thicker at the foot, which is the whole visual joke of a print. */
  border: 0.5rem solid #fff;
  border-bottom-width: 1.35rem;
  box-shadow: 0 0.75rem 1.5rem rgba(29, 30, 32, 0.18);
}

@media (min-width: 60rem) {
  .pscatter {
    display: block;
    position: relative;
    /* Taller again. 4/5 still forced the rows into each other; at 3/4 the four
       prints sit in their own quadrants and only the rotated corners cross,
       which is what "spread out on a table" actually looks like. */
    aspect-ratio: 3 / 4;
  }

  .pscatter img {
    position: absolute;
    /* 52% -> 50%: two columns then come to 100% of the container rather than
       104%, so the overlap is corners only. */
    width: 50%;
    transition: transform var(--dur-base) var(--ease-out);
  }

  /* Four quadrants, pushed out to the corners, each rotated a different amount:
     a pile someone spread out does not land on a grid. */
  /*
   * INSET 5-6% EACH SIDE, not flush to the column.
   *
   * At left:0 / right:0 the cluster filled its grid cell exactly — measured
   * 139-629px against a cell of 152-634 — so the rotated corners hung past the
   * left edge and the right side sat flush against the copy column with nothing
   * between them. Doug drew a box showing it wanted to sit inboard of both.
   * Images are 50% wide, so a 5% inset still leaves them overlapping by 10%.
   */
  .pscatter img:nth-of-type(1) { top: 0;      left: 5%;   transform: rotate(-5.5deg); z-index: 1; }
  .pscatter img:nth-of-type(2) { top: 2%;     right: 5%;  transform: rotate(4deg);    z-index: 2; }
  .pscatter img:nth-of-type(3) { bottom: 0;   left: 6%;   transform: rotate(2.5deg);  z-index: 3; }
  .pscatter img:nth-of-type(4) { bottom: 2%;  right: 6%;  transform: rotate(-7deg);   z-index: 4; }

  /*
   * ABOUT ONLY: THREE PRINTS, BIGGER.
   *
   * The About split used to hold four close-ups — a pantry shelf, a closet rail,
   * a playroom bin, a bathroom drawer. Doug: "I like more whole room photos as
   * opposed to individual organization, more of a bigger picture kind of thing."
   *
   * Whole-room shots need room to read. Four of them at 50% would be four
   * postage stamps of a room instead of four legible details, so it is three at
   * 58% — noticeably larger, and a triangle rather than a 2x2 grid, which also
   * avoids an empty fourth quadrant.
   *
   * Scoped to .ab__splitMedia because .pscatter is shared with the "What's
   * included" prints on the room pages, which are still four and still correct
   * at four.
   */
  /*
   * SPREAD ALONG A DIAGONAL, NOT STACKED IN A TRIANGLE.
   *
   * The triangle above put two prints across the top and one below them, and at
   * 58% width the two top frames overlapped by 16% while the third sat almost
   * clear of both. Doug's word for the result was stacked, twice.
   *
   * A single descending diagonal instead: each print starts a quarter of the way
   * across the one before it, so every frame overlaps its neighbour by about
   * half its width and the run finishes flush right. That is what "spread out
   * and overlapping" asks for, and it is legible because nothing sits directly
   * on top of anything else.
   *
   * A SECOND IMPLEMENTATION OF THIS WAS ADDED ELSEWHERE IN THIS FILE AND HAS
   * BEEN REMOVED. It was a quantity query (img:first-of-type:nth-last-of-type(3))
   * written without checking that .ab__splitMedia.pscatter already existed, so
   * two blocks with near-identical specificity fought over the same three
   * images. If three-print scatters are ever needed somewhere other than About,
   * generalise THIS block; do not add another.
   */
  /*
   * SPREAD, second pass. The first diagonal stepped 23% and each print covered
   * more than half the one behind it, so it read as a tight stack in the top-left
   * with dead space under it. Doug asked for more spread twice.
   *
   * Narrower frames and a longer step: 44% wide against a 28% stride, so about a
   * third of each frame overlaps instead of nearly two thirds, and the run uses
   * the full width and height of the column rather than the top corner of it.
   */
  /*
   * BIGGER, AND THE DIAGONAL IS ~45 DEGREES.
   *
   * The angle of the run is set by the RATIO of the vertical stride to the
   * horizontal one, measured in pixels rather than percent — and percentages on
   * a non-square box are not comparable. On a 1/1 box a 20% down / 28% across
   * step is 20 units down for 28 across: about 36 degrees off horizontal. Doug
   * read it as steeper than 45 and wanted it shallower, which means the strides
   * have to come closer together in PIXELS, not in percent.
   *
   * The box is 1/1.2 now, so 100% of height is 1.2 times 100% of width. A 20%
   * vertical stride is therefore 24 units against a 24% horizontal stride: 45
   * degrees exactly.
   *
   * 52% frames instead of 44%, so they are noticeably larger and still leave
   * about half of each one visible.
   */
  .ab__splitMedia.pscatter { aspect-ratio: 1 / 1.2; }
  .ab__splitMedia.pscatter img { width: 52%; }
  .ab__splitMedia.pscatter img:nth-of-type(1) { top: 0;   left: 0;   right: auto; bottom: auto; transform: rotate(-5deg);  z-index: 1; }
  .ab__splitMedia.pscatter img:nth-of-type(2) { top: 20%; left: 24%; right: auto; bottom: auto; transform: rotate(3.5deg); z-index: 2; }
  .ab__splitMedia.pscatter img:nth-of-type(3) { top: 40%; left: 48%; right: auto; bottom: auto; transform: rotate(-2deg);  z-index: 3; }

  /* Lift the print you point at, so the stack reads as separate sheets. */
  .pscatter img:hover { transform: rotate(0deg) scale(1.04); z-index: 5; }

  /*
   * THREE PRINTS, in the narrower "What's included" column: a diagonal run
   * rather than four quadrants. Four positions with three images leaves an
   * obviously empty corner, and this column is about two thirds the width of
   * the About one, so the frames go proportionally larger to stay readable.
   */
  .svcinc__media.pscatter {
    aspect-ratio: 4 / 5;
  }

  /*
   * SQUARE, not 4/5. Six of these twelve photographs are landscape and there is
   * no way around it: the pools run portrait first then landscape, and outside
   * closets the portrait blocks are short — pantries 9, laundry 10, bathrooms 13
   * — all of which the reserved ranges already claim (0 tiles, 1-8 galleries, 9
   * pooled). A landscape original in a 4/5 frame is cropped to a slice; square
   * takes both orientations without losing the subject, and prints spread on a
   * table were never all one shape anyway.
   */
  .svcinc__media.pscatter img {
    width: 58%;
    aspect-ratio: 1 / 1;
  }

  .svcinc__media.pscatter img:nth-of-type(1) { top: 0;     left: 0;   transform: rotate(-4.5deg); }
  .svcinc__media.pscatter img:nth-of-type(2) { top: 20%;   right: 0;  bottom: auto; transform: rotate(3.5deg); }
  .svcinc__media.pscatter img:nth-of-type(3) { bottom: 0;  left: 6%;  top: auto; right: auto; transform: rotate(-6deg); }
}

/* ======================================================================
   Homepage: owner CTA

   Was a .link-arrow and disappeared into three paragraphs of prose, at the one
   point where someone has just read about Ashley and might want more.
   ====================================================================== */

.owner__cta {
  margin-top: var(--s-5);
}

/* ======================================================================
   Steps: variable count

   The homepage grid is 1.1fr 1fr 1fr 1.04fr — four hardcoded columns, tuned for
   the homepage's four steps. Services have five, so the fifth orphaned.
   ====================================================================== */

@media (min-width: 1100px) {
  .steps--auto {
    grid-template-columns: repeat(auto-fit, minmax(min(100%, 12.5rem), 1fr));
  }

  .steps--auto .steps__item:nth-child(2),
  .steps--auto .steps__item:nth-child(3),
  .steps--auto .steps__item:nth-child(4) {
    margin-top: 0;
  }

  .steps--auto .steps__item:nth-child(even) {
    margin-top: var(--s-5);
  }
}

/* ======================================================================
   Service process accordion
   ====================================================================== */

.svcproc__grid {
  display: grid;
  gap: var(--s-7);
}

@media (min-width: 60rem) {
  .svcproc__grid {
    grid-template-columns: 22rem 1fr;
    gap: var(--s-9);
    align-items: start;
  }

  .svcproc__rail {
    position: sticky;
    top: var(--sticky-top);
  }
}

.svcproc__intro {
  margin: var(--s-4) 0 0;
  font-size: var(--t-small);
  color: var(--c-ink-muted);
}

.svcproc__media {
  margin-top: var(--s-6);
}

.svcproc__media img,
.svcinc__aside img,
.svcother__card img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

.svcproc__list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--c-taupe-deep);
}

.svcproc__item {
  border-bottom: 1px solid var(--c-taupe-deep);
}

.svcproc__details summary {
  display: grid;
  grid-template-columns: 3rem 1fr 1.5rem;
  align-items: baseline;
  gap: var(--s-3);
  padding: var(--s-5) 0;
  cursor: pointer;
  list-style: none;
}

.svcproc__details summary::-webkit-details-marker {
  display: none;
}

.svcproc__details summary:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
}

.svcproc__n {
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  color: var(--c-action);
}

.svcproc__title {
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h3);
  line-height: var(--lh-heading);
  color: var(--c-ink);
}

.svcproc__icon {
  position: relative;
  width: 1.5rem;
  height: 1.5rem;
}

.svcproc__icon::before,
.svcproc__icon::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 0.5rem;
  height: 1px;
  background: var(--c-ink-soft);
  transition: transform var(--dur-base) var(--ease-out);
}

.svcproc__icon::before { left: 0.15rem; transform: rotate(45deg); }
.svcproc__icon::after  { left: 0.5rem;  transform: rotate(-45deg); }
.svcproc__details[open] .svcproc__icon::before { transform: rotate(-45deg); }
.svcproc__details[open] .svcproc__icon::after  { transform: rotate(45deg); }

.svcproc__body {
  padding: 0 var(--s-6) var(--s-5) calc(3rem + var(--s-3));
  max-width: 60ch;
  color: var(--c-ink-soft);
}

.svcproc__body p { margin: 0; }

@media (max-width: 39.9375rem) {
  .svcproc__details summary { grid-template-columns: 2.5rem 1fr 1.25rem; }
  .svcproc__body { padding-left: calc(2.5rem + var(--s-3)); padding-right: 0; }
}



.svcinc__grid { display: grid; gap: var(--s-7); }

@media (min-width: 60rem) {
  .svcinc__grid {
    grid-template-columns: 1.4fr 1fr;
    gap: var(--s-9);
    align-items: start;
  }

  .svcinc__media {
    position: sticky;
    top: var(--sticky-top);
  }
}

.svcinc__list {
  list-style: none;
  margin: 0 0 var(--s-7);
  padding: 0;
}

.svcinc__list li {
  position: relative;
  padding: var(--s-4) 0 var(--s-4) var(--s-6);
  border-bottom: 1px solid var(--c-cream-deep);
  color: var(--c-ink-soft);
}

.svcinc__list li:first-child { border-top: 1px solid var(--c-cream-deep); }

/* Tick drawn in CSS, aligned to the first line's cap height. */
.svcinc__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.35em;
  width: 0.5rem;
  height: 0.25rem;
  border-left: 1.5px solid var(--c-action);
  border-bottom: 1.5px solid var(--c-action);
  transform: rotate(-45deg);
}

.svcinc__best {
  padding: var(--s-6);
  background: var(--c-cream-light);
  border-left: 2px solid var(--c-action);
}

.svcinc__bestLabel {
  margin: 0 0 var(--s-2);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-taupe-deeper);
}

.svcinc__bestBody {
  margin: 0 0 var(--s-5);
  font-size: var(--t-lead);
  line-height: var(--lh-body);
  color: var(--c-ink);
}

.svcinc__cta { margin: 0; }

.svcinc__media img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

/* ======================================================================
   Service: other services
   ====================================================================== */

.svcother__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-6);
}

@media (min-width: 48rem) {
  .svcother__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.svcother__card a { display: block; text-decoration: none; color: inherit; }
.svcother__card img { margin-bottom: var(--s-4); }
.svcother__card h3 { margin-bottom: var(--s-2); }
.svcother__card p {
  margin-bottom: var(--s-3);
  font-size: var(--t-small);
  color: var(--c-ink-muted);
}

/* ======================================================================
   Service areas: stacked, overlapping photography

   These pages carry 1,300-2,100 words against a single hero image. Two more
   photographs, the second hanging off the first's corner, break the wall of
   copy. Overlap via negative margin rather than absolute positioning, so the
   pair still occupies real space and never collides with the section below.
   ====================================================================== */

.areastack { display: grid; gap: var(--s-7); }

@media (min-width: 60rem) {
  .areastack {
    grid-template-columns: 1fr 0.85fr;
    gap: var(--s-9);
    align-items: start;
  }
}

.areastack__media { position: relative; }

.areastack__media img {
  display: block;
  width: 100%;
  object-fit: cover;
}

/*
 * Three photographs, alternating edge, each overlapping the one above.
 *
 * Addressed by :nth-child and NOT by `img + img`. The adjacent-sibling form
 * matched the second AND third image identically, so when the gallery went from
 * two images to three the third landed on the right edge at the same width as
 * the second and sat on top of it. nth-child cannot do that.
 */
.areastack__media img:nth-child(1) {
  aspect-ratio: 3 / 4;
  width: 82%;
}

.areastack__media img:nth-child(2) {
  aspect-ratio: 1 / 1;
  width: 52%;
  margin-left: auto;
  margin-top: calc(var(--s-8) * -1);
  position: relative;
  z-index: 1;
  border: var(--s-2) solid var(--c-cream);
}

.areastack__media img:nth-child(3) {
  aspect-ratio: 4 / 5;
  width: 60%;
  margin-right: auto;
  margin-top: calc(var(--s-7) * -1);
  position: relative;
  z-index: 2;
  border: var(--s-2) solid var(--c-cream);
}

@media (max-width: 59.9375rem) {
  .areastack__media img:nth-child(2) { margin-top: calc(var(--s-6) * -1); }
  .areastack__media img:nth-child(3) { margin-top: calc(var(--s-5) * -1); }
}

/* ======================================================================
   Prose rhythm

   global.css zeroes margins on p/ul/ol so components own their own spacing.
   Imported editor content has no component to own it, so headings sat flat on
   the body text with no gap at all.
   ====================================================================== */

.prose > * + * { margin-top: var(--s-5); }

.prose h2 { margin-top: var(--s-8); margin-bottom: var(--s-4); }
.prose h3 { margin-top: var(--s-6); margin-bottom: var(--s-3); }

.prose h2 + p,
.prose h3 + p,
.prose h2 + ul,
.prose h3 + ul { margin-top: 0; }

.prose ul,
.prose ol { padding-left: var(--s-5); }

.prose li + li { margin-top: var(--s-2); }

/* ======================================================================
   WordPress image sizing

   Astro emits <img> with explicit width/height that match the rendered box.
   WordPress emits the attachment's intrinsic dimensions, so an image whose
   file is wider than its slot overflows the parent — measured at 686px inside
   a 647px hero on every page with a hero image.

   global.css already sets img{max-width:100%} but that only caps the element
   against its OWN box; these need to fill the slot and crop.
   ====================================================================== */

/*
 * width only. height:100% was here and it STRETCHED the homepage spaces images
 * from their 1.60 ratio to 1.34 — the slot has no fixed height, so 100% resolved
 * against the grid row and distorted every photo. Where a fixed shape is wanted,
 * the component sets its own aspect-ratio.
 */
.phero__media img,
.hero__media img,
.ab__heroMedia img,
.owner__media img,
.sg__media img,
.spaces__item img,
.city__proj img {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
}

/* The coverage map bleeds past its container ON PURPOSE — that is the design in
   the Astro build too, so it is excluded from the fill rule above. */
.sap__map img {
  width: 100%;
  height: auto;
}

/* ----------------------------------------------------------------------
   ...but the bleed must not become a horizontal scrollbar.

   Measured at 360px: the map image ran -11..371 in a 360 viewport, and the
   Instagram button ran to 379 — together, 19px of sideways scroll on the
   homepage. It has been in TODO as "12px" since before the DFW expansion; it is
   19px now, so it has been getting worse rather than holding still.

   `overflow-x: clip` rather than `hidden`: clip contains the bleed without
   turning the section into a scroll container, so it cannot steal a swipe or
   break `position: sticky` on anything inside it. The design keeps its bleed and
   the page stops scrolling sideways.
   ---------------------------------------------------------------------- */
.sap {
  overflow-x: clip;
}

/* ----------------------------------------------------------------------
   SHORT PHONES: GET THE HERO CTA ABOVE THE FOLD.

   At 360x640 the "Book a Free Discovery Call" button sat 654px down — 14px
   below the fold, so the one action in the hero was invisible until you
   scrolled. Correct at 390x844, which is why it survived.

   TODO called this "needs copy cut, not CSS". It needed 14px. The hero pays
   --s-8 top and --s-9 bottom (64 + 96 = 160px) regardless of how short the
   viewport is; taking a step off each on genuinely short screens recovers more
   than enough without touching a word.

   Scoped by HEIGHT as well as width, so tall phones keep the airier hero they
   were designed with.
   ---------------------------------------------------------------------- */
@media (max-width: 47.9375rem) and (max-height: 700px) {
  .hero {
    padding-block: var(--s-6) var(--s-7);
  }
}

/* The image still measured 339 inside a 320 parent: `width: 100%` with an
   aspect-ratio and object-fit resolves against the intrinsic 976x950, and
   without an explicit cap it wins. This is the cap. */
.sap__map,
.sap__map img {
  max-width: 100%;
}

/*
 * The Instagram CTA carries the full handle — "Follow @messybuns_organizedspaces"
 * — which is a single unbreakable 27-character token inside a button that never
 * wraps. At 360 that is wider than the viewport on its own.
 *
 * Allowed to wrap to two lines and capped at the container. Not shortened: the
 * handle is the address, and truncating it makes it useless.
 */
.insta__cta {
  max-width: 100%;
  white-space: normal;
  overflow-wrap: anywhere;
  text-align: center;
}

/* ======================================================================
   Heading rhythm in template-rendered blocks

   .prose covers imported editor content, but headings the TEMPLATES emit —
   county groups on the areas hub, project cards on an area page — sit outside
   it and inherited the reset's margin:0, landing flat on the text below.
   ====================================================================== */

.areas__county h2 {
  margin-bottom: var(--s-5);
}

.areas__county + .areas__county {
  margin-top: var(--s-8);
}

.city__proj article h3,
.svcother__card h3,
.spaces__item h3,
.areas__card h3 {
  margin-bottom: var(--s-3);
}

.city__proj article + article {
  margin-top: var(--s-6);
}

/* ======================================================================
   Service areas hub: cards

   The hub listed fifteen areas as bare text links — no imagery on the page
   whose entire job is selling the coverage area.
   ====================================================================== */

.areas__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-6);
}

@media (min-width: 40rem) {
  .areas__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/*
 * Auto-fit, not a hard three.
 *
 * The counties hold 8, 4 and 3 areas. At a fixed three columns the four-area
 * county rendered 3 + 1, leaving a lone card on its own row with two empty
 * cells beside it — the layout read as a mistake rather than a grouping.
 *
 * auto-fit against a 14rem floor gives four columns at this container width, so
 * 8 becomes 4+4, 4 becomes a single clean row, and 3 stays 3. No orphans at any
 * width, and it keeps working if a county gains an area later.
 */
@media (min-width: 60rem) {
  .areas__grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr)); }
}

.areas__card a {
  display: block;
  text-decoration: none;
  color: inherit;
}

.areas__card img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  margin-bottom: var(--s-4);
  transition: opacity var(--dur-base) var(--ease-out);
}

.areas__card a:hover img { opacity: 0.9; }

.areas__card p {
  margin-bottom: var(--s-3);
  font-size: var(--t-small);
  color: var(--c-ink-muted);
}

/* ======================================================================
   Pricing: split section

   The rate card should hold position while the longer "what products cost"
   copy scrolls past it — the number is what someone keeps referring back to.
   ====================================================================== */

@media (min-width: 60rem) {
  .pr__split {
    align-items: start;
  }

  /* The aside is the second child in the imported markup. */
  .pr__split > *:last-child {
    position: sticky;
    top: var(--sticky-top);
  }
}



@media (min-width: 60rem) {
  .page-pricing .container--narrow {
    max-width: var(--container);
  }

  .page-pricing .faq__list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 var(--s-8);
    align-items: start;
  }

  /* The heading spans both columns rather than sitting inside one. */
  .page-pricing .faq__heading {
    max-width: 34ch;
  }
}

/* ======================================================================
   Portfolio: editorial grid

   Was a uniform grid of identical portrait cards — a contact sheet, not a
   portfolio. Every usable photograph in her library is portrait ~1620x2160, so
   the variation has to come from SCALE and RHYTHM rather than from cropping to
   different shapes.

   Every fourth item runs full-bleed-ish and wide; the rest alternate between a
   tall feature and a standard card. The result reads as a designed page instead
   of a spreadsheet, without a single image being cropped against its grain.
   ====================================================================== */

.pf__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-6);
}

@media (min-width: 48rem) {
  .pf__grid {
    grid-template-columns: repeat(6, 1fr);
    gap: var(--s-7) var(--s-6);
  }

  /*
   * THE RHYTHM IS DRIVEN BY [data-band], NOT nth-child.
   *
   * nth-child counts hidden elements. Once the filter rail could hide tiles, the
   * band pattern scrambled: filtering to two projects could leave both at half
   * width with a staggered drop under them, which reads as a broken layout
   * rather than a designed one.
   *
   * page-portfolio.php and assets/js/portfolio.js both number the VISIBLE tiles
   * 1..5 and repeat, per group, and write it to data-band. Same arithmetic in
   * both places, so a server render and a filtered render agree.
   */

  /* Default: a third of the row. */
  .pf__item { grid-column: span 2; }

  /* Band 1 takes half and sits taller: the anchor of each band. */
  .pf__item[data-band="1"] {
    grid-column: span 3;
  }

  .pf__item[data-band="1"] img {
    aspect-ratio: 4 / 5;
  }

  /* Its partner takes the remaining half. */
  .pf__item[data-band="2"] {
    grid-column: span 3;
  }

  .pf__item[data-band="2"] img {
    aspect-ratio: 4 / 5;
  }

  /* A staggered drop, so rows do not read as hard bands. */
  .pf__item[data-band="4"] { margin-top: var(--s-7); }
  .pf__item[data-band="5"] { margin-top: var(--s-9); }
}

.pf__item img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
  /* Nothing rounds on this site; the crop is the shape. */
  border-radius: var(--radius);
  transition:
    transform var(--dur-slow) var(--ease-out),
    filter var(--dur-base) var(--ease-out);
}

/* The image itself is the interaction: a slow push in, nothing else moving. */
.pf__item {
  overflow: hidden;
}

.pf__item:hover img,
.pf__item:focus-within img {
  transform: scale(1.03);
}

@media (prefers-reduced-motion: reduce) {
  .pf__item img { transition: none; }
  .pf__item:hover img,
  .pf__item:focus-within img { transform: none; }
}

/* Caption sits under the image, quiet, in the label voice. */
.pf__item figcaption,
.pf__item p {
  margin-top: var(--s-3);
  font-size: var(--t-small);
  line-height: var(--lh-body);
  color: var(--c-ink-muted);
}

.pf__groupTitle {
  margin-bottom: var(--s-6);
}

/* Group headings get real separation, so the page reads in chapters. */
.pf__grid + .pf__groupTitle,
.pf__groupTitle:not(:first-child) {
  margin-top: var(--s-9);
}


/* ======================================================================
   CTA band: matched buttons, vertically centerd

   The two actions were different widths (330 vs 224) and top-aligned in their
   column, so the pair read as a primary button with an afterthought beside it.
   They are alternatives, not a hierarchy — someone either books or calls.
   ====================================================================== */

.cta__actions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  justify-content: center;
  gap: var(--s-3);
}

@media (min-width: 48rem) {
  .cta__inner {
    align-items: center;
  }

  .cta__actions {
    /* Equal width: whichever label is longer sets the pair. */
    min-width: 20rem;
  }
}

.cta__actions .btn {
  width: 100%;
  justify-content: center;
  /* Same box regardless of label length. */
  min-height: 3.5rem;
}

/* ======================================================================
   About: the owner's note

   The section opened cold with "I understand the mental weight of a
   disorganized space" and no label, so the shift into Ashley's first person
   read as an accident. A quiet eyebrow marks it as deliberately her voice.
   ====================================================================== */

.page-about .ab__intro::before {
  content: "From Ashley";
  display: block;
  margin-bottom: var(--s-4);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-action);
}

/* ----------------------------------------------------------------------
   About: services list alignment

   The service names sat out of line with their bullets because the list
   inherited the browser default marker position while the text block had its
   own padding. Markers off, indent owned by the list.
   ---------------------------------------------------------------------- */

.ab__split ul,
.page-about .prose ul {
  list-style: none;
  padding-left: 0;
}

.ab__split li,
.page-about .prose li {
  position: relative;
  padding-left: var(--s-6);
}

.ab__split li + li,
.page-about .prose li + li {
  margin-top: var(--s-4);
}

.ab__split li::before,
.page-about .prose li::before {
  content: "";
  position: absolute;
  left: 0;
  /* Aligned to the cap height of the first line, not the line box. */
  top: 0.62em;
  width: 0.5rem;
  height: 0.25rem;
  border-left: 1.5px solid var(--c-action);
  border-bottom: 1.5px solid var(--c-action);
  transform: rotate(-45deg);
}

/* ======================================================================
   Service intro: stop the paragraph floating in dead space

   The screenshot showed one short paragraph marooned in a full-height taupe
   section, with more empty space above and below it than the text itself
   occupied. The section padding is tuned for sections with real content in
   them; an intro of two sentences is not that.
   ====================================================================== */

.single-mbos_service > .section:first-of-type,
.single-mbos_area > .section:first-of-type {
  padding-block: var(--s-7);
}

.single-mbos_service .prose > .lead,
.single-mbos_area .prose > .lead {
  max-width: 62ch;
  font-size: var(--t-lead);
  line-height: var(--lh-body);
  color: var(--c-ink);
}

/* ----------------------------------------------------------------------
   Process rail: close the gap under the heading

   The rail heading, standfirst and image were spaced as three separate blocks,
   so the photograph floated well below the text it belongs with.
   ---------------------------------------------------------------------- */

.svcproc__rail .sechead {
  margin-bottom: var(--s-4);
}

.svcproc__media {
  margin-top: var(--s-5);
}

@media (min-width: 60rem) {
  /* The accordion's first row aligns to the heading, not to the rail's top. */
  .svcproc__list {
    margin-top: calc(var(--s-2) * -1);
  }
}

/* ======================================================================
   When it's time to call

   Ported from her live site. Copy left, photograph right — the list is the
   point, so it gets the wider column.
   ====================================================================== */

.whencall__grid { display: grid; gap: var(--split-gap-sm); }

@media (min-width: 60rem) {
  .whencall__grid {
    grid-template-columns: 1.25fr 1fr;
    gap: var(--split-gap);
    align-items: start;
  }
}

/* ----------------------------------------------------------------------
   WHEN TO CALL: ONE TEXT SIZE, ONE INK

   This block carried three treatments in one section and Ashley read all three
   as a mistake: the intro at --c-ink-soft against bullets at --c-ink, and a
   closing line at --t-h3 in Montserrat, 22px against the list's 17px.

   The size half was fixed first. The ink half was fixed WRONG — the intro was
   moved UP to --c-ink to match the bullets, which made the whole block agree
   with itself and disagree with the entire rest of the page. --c-ink is the
   HEADING ink. Every other paragraph on a service page renders at --c-ink-soft,
   so this section sat visibly darker than the copy above and below it, and Doug
   kept reporting "two different font colors" on a block whose three parts
   measured identically. He was right; he was just pointing at the section
   rather than at the line between it and its neighbours.

   All three take --c-ink-soft now, which is the reading-copy token and resolves
   per surface (#484441 on taupe, #6f6661 on cream). The close KEEPS Montserrat
   light — the emphasis is family and weight, never color or size.
   ---------------------------------------------------------------------- */

.whencall__intro {
  max-width: 56ch;
  margin-bottom: var(--s-5);
  color: var(--c-ink-soft);
}

.whencall__list { list-style: none; margin: 0 0 var(--s-5); padding: 0; }

.whencall__list li {
  position: relative;
  padding: var(--s-4) 0 var(--s-4) var(--s-6);
  border-bottom: 1px solid var(--c-taupe-deep);
  color: var(--c-ink-soft);
}

.whencall__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 1.35em;
  width: 0.5rem;
  height: 0.25rem;
  border-left: 1.5px solid var(--c-action);
  border-bottom: 1.5px solid var(--c-action);
  transform: rotate(-45deg);
}

.whencall__close {
  margin-bottom: var(--s-5);
  /* Manrope 400 at --t-body, exactly like the intro and the bullets.
     This line has now been walked back three times and that is the point:
     it was --t-h3 Montserrat 300 (bigger AND a different face), then
     --t-body Montserrat 300 with the family kept as "the emphasis device",
     and Doug still read it as a second font in the block — which it was.
     A closing line does not need its own typeface. Its emphasis comes from
     sitting alone under a rule with white space around it. */
  font-size: var(--t-body);
  color: var(--c-ink-soft);
}

.whencall__media img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

/* ======================================================================
   Why homeowners choose Messy Buns
   ====================================================================== */

.whyus__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-6);
}

@media (min-width: 48rem) {
  .whyus__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-7) var(--s-8); }
}

@media (min-width: 75rem) {
  .whyus__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.whyus__item {
  padding-top: var(--s-4);
  border-top: 1px solid var(--c-taupe-deep);
}

.whyus__n {
  display: block;
  margin-bottom: var(--s-3);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  color: var(--c-action);
}

.whyus__title { margin-bottom: var(--s-3); }

.whyus__body { color: var(--c-ink-soft); font-size: var(--t-small); }

.whyus__close {
  margin-top: var(--s-7);
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h3);
  color: var(--c-ink);
}

/* ======================================================================
   Instagram
   ====================================================================== */

.insta__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  margin-bottom: var(--s-6);
}

.insta__handle {
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-action);
  text-decoration: none;
  /* "@messybuns_organizedspaces" is one unbreakable 26-character token, and at
     0.18em tracking it measured 322px — wider than a 320px viewport on its own.
     Allowed to break mid-token rather than shortened: the handle is the address. */
  max-width: 100%;
  overflow-wrap: anywhere;
}

.insta__handle:hover { text-decoration: underline; }

.insta__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-3);
}

/*
 * Four columns, not six. Six tiles at a sixth of the container each rendered
 * too small to read as photographs; her own site runs four larger tiles in a
 * single row, which is the reference.
 */
@media (min-width: 48rem) {
  .insta__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--s-4); }
}

.insta__item img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  transition: opacity var(--dur-base) var(--ease-out);
}

.insta__item a:hover img,
.insta__item a:focus-visible img { opacity: 0.82; }

.insta__cta { margin-top: var(--s-6); }

/* ======================================================================
   GLOW-UP: breaking the text drought

   Measured on the live site: the homepage runs 4,500px through process,
   why-us, objections, pricing, reviews and FAQ with almost no imagery, and
   About runs 1,350px of unbroken prose across two sections. Since new
   photography is not available yet, the relief has to come from typographic
   scale, ground color and asymmetry rather than from pictures.
   ====================================================================== */

/* ---------- Objections bento: REVERTED, deliberately ----------
   v0.2.24 restyled this into a three-column grid with a dark brown feature card
   spanning two rows, on the reasoning that the objection which actually stops
   people booking should not read like the other three.

   It was wrong twice over. The dark card's background lost a specificity fight
   to `.objections .bento__cell` — both are two-class selectors, so the later
   rule won — and took the background away while leaving `color: var(--c-on-dark)`
   behind. Light text on a light card, 1.09:1, heading invisible on the live
   homepage. Separately the photograph it introduced forced a portrait file into
   a 16/10 box and bled past the card edge.

   home.css already differentiates the feature tile the way the original design
   intended: cream-light ground, navy top border, larger question. That is
   enough, and it does not fight anything. The override is gone rather than
   fixed — the tuned-up version was not better, only louder. */

/* ---------- Pricing band: give the numbers real presence ----------
   A dark section carrying three figures that rendered at body size. The
   figures are the reason the section exists. */

.pricing__facts dd {
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-hero);
  line-height: 1;
  letter-spacing: var(--ls-display);
  color: var(--c-on-dark);
}

.pricing__facts dt {
  margin-bottom: var(--s-3);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-on-dark-muted);
}

.pricing__facts > div + div {
  margin-top: var(--s-6);
  padding-top: var(--s-6);
  border-top: 1px solid rgba(237, 232, 228, 0.18);
}

/* ---------- About: "how we work" as a numbered set, not a paragraph block ---- */

@media (min-width: 60rem) {
  /*
   * `.container > div` is a blunt selector and it caught something it was never
   * meant to: the mid-page CTA is also a .section--cream on this page, so its
   * copy wrapper became a two-column grid. The heading spanned both columns,
   * the sentence under it was squeezed into a 213px column, and the other 213px
   * sat empty next to it — which is the "you still didn't build this the way I
   * wanted" Doug reported after the strip itself had already been fixed.
   *
   * Excluded rather than rewritten: the numbered-set layout below is correct for
   * the prose sections it was written for, and narrowing the selector to skip
   * .cta is the smallest change that keeps it working.
   */
  .page-about .section--cream:not(.cta) .prose,
  .page-about .section--cream:not(.cta) .container > div {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-6) var(--s-8);
  }

  .page-about .section--cream:not(.cta) h2 {
    grid-column: 1 / -1;
    margin-top: 0;
  }
}

/* ---------- Trust strip: REVERTED, deliberately ----------
   This file used to override .trust to a cream band with ink text, under the
   heading "stop it reading as a footnote". That was a solution to a problem
   nobody had. In the original design the strip is a BLACK band with light text
   (home.css: background var(--c-black), color var(--c-on-dark-muted)) — a hard
   horizontal rule directly under the hero that separates it from the services
   grid and carries the four proof points in reverse.

   Turning it cream dissolved it into the cream sections above and below, which
   is exactly the "where did it go" Doug reported. The override is gone rather
   than re-tuned; home.css already has the right rule and only needed to be
   left alone. Do not re-promote this strip. */

/* ---------- Section rhythm ----------
   Six consecutive persuasion sections ran at identical vertical padding, so the
   page had no cadence. The narrative sections breathe; the list sections tighten. */

.whyus,
.objections { padding-block: var(--section-y); }

.trust,
.insta { padding-block: var(--s-8); }

/* ---------- Process section: photograph beside the steps ---------- */

.procshot__grid { display: grid; gap: var(--split-gap-sm); }

@media (min-width: 60rem) {
  .procshot__grid {
    grid-template-columns: 0.8fr 2fr;
    gap: var(--split-gap);
    align-items: start;
  }

  .procshot__media {
    position: sticky;
    top: var(--sticky-top);
  }
}

/*
 * Four steps in the side column: TWO BY TWO, explicitly.
 *
 * This was `repeat(auto-fit, minmax(min(100%, 11rem), 1fr))`, which fits three
 * across in this column and drops the fourth onto a row of its own. Combined
 * with the `nth-child(even)` stagger inherited from the full-width version,
 * step 02 sat lower than 01 and 03, and step 04 hung alone below — the
 * arrangement read as a broken grid rather than a designed one.
 *
 * A fixed two-column grid gives 01/02 over 03/04, which is what a four-step
 * process wants in a narrow column, and the stagger is switched off because it
 * only ever made sense across a single wide row.
 */
@media (min-width: 60rem) {
  .procshot__copy .steps--auto {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--s-6);
    row-gap: var(--s-7);
  }

  .procshot__copy .steps--auto .steps__item:nth-child(even) {
    margin-top: 0;
  }
}

/* The .bento__media rules are gone with the photograph they styled. */

/* ---------- About: photographs in the prose sections ---------- */

.page-about .aboutshot {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  margin-top: var(--s-6);
}

/* ======================================================================
   Resolved-contrast fixes

   Found by tools/audit-contrast.mjs, which measures text against the
   background that actually renders behind it rather than against the one
   the stylesheet declares. Both of these had been live for some time and
   passed every source-reading check.
   ====================================================================== */

/*
 * The step numbers in "why homeowners choose us" are 14px at weight 500 —
 * ordinary body text by WCAG's reckoning, so the floor is 4.5:1, not the 3:1
 * that large text gets. The action color on taupe resolves to 3.15:1.
 *
 * Only this element is darkened, NOT --c-action itself. That token is tuned to
 * clear 3:1 against both taupe and black for the CTA buttons, where the text is
 * large; moving the token to satisfy a 14px label would drag every button with
 * it and break a balance that took a while to find.
 */
.whyus__n {
  color: #6e301c; /* 4.7:1 on the taupe band */
}

/*
 * Review stars at 22px sit just under WCAG's large-text threshold (24px, or
 * 18.66px if bold), so they need 4.5:1 and had 2.70:1. Twenty-two pixels is
 * exactly the size that looks large and is not.
 *
 * theme.css loads after components.css, so this wins without touching the
 * generated file.
 */
.rw__stars {
  color: #746155; /* 4.8:1 on cream */
}

/* ======================================================================
   Portfolio: the jump nav

   Two faults, both visible in the 07 Aug capture: the category list was cut
   off mid-word on the right with a scrollbar under it, and the bar sticks
   beneath the site header rather than below it.
   ====================================================================== */

/*
 * The list is `flex-wrap: nowrap; overflow-x: auto` — correct on a phone, where
 * four category names cannot fit, and wrong everywhere else, because it applied
 * at every width. On desktop the four names DO fit, so they wrap to one line and
 * the scroller disappears.
 */

/* Below that it stays a scroller, but without the scrollbar drawn across it. */
/* ======================================================================
   Service areas: the services offered, and the index of spaces

   Area pages had neither. Fifteen pages ranking for "organizer in {city}"
   that never said what the business does, and no link from any of them to
   any service page.
   ====================================================================== */

/* ---------- The services bento: REMOVED ----------
   Area pages now render the homepage service grid (template-parts/service-grid),
   so the bespoke .areasvc cards and their ten rules are gone with the markup.
   Two treatments of the same four services was one too many. */

/* ---------- The spaces index ---------- */

.spaceslist__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
  border-top: 1px solid var(--c-taupe-deep);
}

@media (min-width: 48rem) {
  .spaceslist__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-8); }
}

@media (min-width: 75rem) {
  .spaceslist__grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.spaceslist__item { border-bottom: 1px solid var(--c-taupe-deep); }

.spaceslist__link {
  display: grid;
  gap: var(--s-1);
  padding: var(--s-4) 0;
  text-decoration: none;
}

/*
 * The room name is the link. It computed at Montserrat 400, 17px, one notch
 * above a 14px blurb, so a list of six rooms read as six paragraphs with no
 * obvious click target. 500 and a slightly larger size make each row announce
 * itself as a title, which is what Doug asked for.
 *
 * This is the COMPACT LIST, not the bento tiles — different component, which is
 * why the earlier tile-title change did not touch it.
 */
.spaceslist__name {
  font-family: var(--font-display);
  font-size: var(--t-h4);
  font-weight: var(--fw-medium);
  letter-spacing: 0.01em;
  color: var(--c-ink);
}

.spaceslist__blurb {
  font-size: var(--t-micro);
  color: var(--c-ink-muted);
}

.spaceslist__link:hover .spaceslist__name,
.spaceslist__link:focus-visible .spaceslist__name { color: #6e301c; }

/* ======================================================================
   Dividers between sections that share a ground

   The page reads its rhythm from alternating section colors: taupe, cream,
   taupe, dark. Where two neighbours land on the SAME color there is no
   boundary at all and they run together as one long block — most visibly
   where "Meet Ashley" now sits directly above "Why homeowners choose",
   both on taupe, with a screen of empty ground between them and nothing to
   say one had ended.

   A hairline, container-width rather than full-bleed, so it reads as a
   considered rule aligned to the content instead of a hard band across the
   viewport. Only drawn between matching grounds — a taupe-to-cream change
   is already its own divider and does not need help.
   ====================================================================== */

.section:not(.section--cream):not(.section--cream-light):not(.section--dark)
  + .section:not(.section--cream):not(.section--cream-light):not(.section--dark),
.section--cream + .section--cream,
.section--cream-light + .section--cream-light,
.section--dark + .section--dark {
  position: relative;
}

.section:not(.section--cream):not(.section--cream-light):not(.section--dark)
  + .section:not(.section--cream):not(.section--cream-light):not(.section--dark)::before,
.section--cream + .section--cream::before,
.section--cream-light + .section--cream-light::before,
.section--dark + .section--dark::before {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: min(100% - (var(--gutter) * 2), var(--container));
  height: 1px;
  background: var(--c-taupe-deep);
}

/* On cream the taupe rule is too warm; on black it has to lift, not sink. */
.section--cream + .section--cream::before,
.section--cream-light + .section--cream-light::before {
  background: var(--c-cream-deep);
}

.section--dark + .section--dark::before {
  background: rgba(255, 255, 255, 0.14);
}

/* ======================================================================
   Homepage edits from the 08 Aug markups
   ====================================================================== */

/* ---------- Process: two photographs, offset diagonally ----------
   One image left the media column stopping a third of the way down while the
   copy ran to four steps. The second sits below and left, overlapping at the
   corner — the stacked treatment already used on the area pages. */

.procshot__media { position: relative; }

/* ---------- Service cards: the Explore link becomes a real CTA ----------
   It was a .link-arrow in navy at micro size and disappeared under three lines
   of summary copy. On the longest label it also wrapped, stranding the arrow
   on its own line at the far right of the card. */

.sg__cta {
  /* Pushed to the card's bottom edge so all four sit on one line. */
  margin-top: auto;
  padding-top: var(--s-4);
  align-self: flex-start;
  border-top: 2px solid var(--c-action);
  font-size: var(--t-small);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  /* Darker than --c-action so it clears 4.5:1 at this size. */
  color: #6e301c;
  /* Keeps the arrow beside the last word instead of stranding it right. */
  flex-wrap: wrap;
  gap: var(--s-2);
}

.sg__link:hover .sg__cta,
.sg__link:focus-visible .sg__cta { border-top-color: #6e301c; }

/* The card is a flex column so margin-top:auto has something to push against. */
.sg__link { display: flex; flex-direction: column; height: 100%; }
.sg__card { display: flex; }



/* ======================================================================
   Spaces: bento tiles that open their own page
   ====================================================================== */

.spaces--bento {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-5);
}

/* ----------------------------------------------------------------------
   PHONE HEIGHT: TIGHTEN THE RHYTHM, DO NOT RE-COLUMN.

   The homepage measures ~23,500px at 390 wide. The obvious fix is two columns
   for the spaces bento (3,669px) and the service grid (3,361px) — 30% of the
   page between them. I tried it. It does not work, twice over:

   - The bento was already tried and reverted by someone else, and the reasoning
     is recorded at "The spaces tiles were unreadable" further down this file:
     at ~165px a tile the captions overflowed the photograph and the titles were
     cut off.
   - The service cards fail the same way for the same reason. Two up at 390,
     "Organizing & Decluttering" wraps to two lines, "Unpacking & Home Setup" to
     three, the blurb drops to five words a line, and "Explore move concierge"
     becomes a three-line link. Shorter than before and worse to read.

   Section padding is not the lever either: --section-y is already a clamp that
   resolves to ~3.2rem on a phone against 7rem on a desktop, so it has been
   tuned once and there is nothing left in it.

   CONCLUSION, recorded so nobody re-runs this investigation: the homepage is
   ~23,500px on a phone because it has fourteen sections, and no amount of CSS
   changes that without making the copy worse. Getting it shorter means deciding
   which sections do not earn their place on a phone — the Instagram strip is
   624px, the review wall 2,267px, the bento 3,669px. That is a content call,
   and it is Doug's, not one to force through a stylesheet.

   The horizontal overflow above IS fixed. This is the height, and it is open.
   ---------------------------------------------------------------------- */

/*
 * Bento proper: a 6-column track where the first and fourth tiles run wide.
 * Six equal squares read as a product grid; unequal weights read as a
 * composition, which is what Doug asked for.
 */
@media (min-width: 48rem) {
  /*
   * TWELVE COLUMNS, AND THE ROWS ARE 2 / 3 / 4.
   *
   * This was six columns with items 1 and 4 spanning three, which tiles cleanly
   * at seven. The room pages went from seven to nine when Kitchens split from
   * Pantries and Mudrooms from Laundry Rooms, and nine against that rule leaves
   * the last two tiles stranded on a row of their own at half width. That is the
   * "two at the very bottom I don't know what they are" — they are Laundry Rooms
   * and Mudrooms, orphaned by a rule written for a different count.
   *
   * Twelve columns divides by 2, 3 and 4, so nine tiles fall into three full
   * rows that descend in scale: two wide, three medium, four small. No orphans,
   * and the varying tile size is the thing that made it a bento rather than a
   * grid in the first place.
   *
   * IF THE COUNT CHANGES AGAIN, this rule has to change with it. The fallback
   * below is the safety net: any tile past the ninth falls back to a quarter
   * width, so a tenth room is small rather than stranded.
   */
  .spaces--bento { grid-template-columns: repeat(12, minmax(0, 1fr)); gap: var(--s-6); }

  .spaces--bento .spaces__item { grid-column: span 4; }

  /*
   * ROWS OF 2 / 3 / 4 WAS WRONG, AND THE THEME ALREADY SAID SO.
   *
   * The row of four put a tile at roughly a quarter of the container, and the
   * note further down this file records what happens there: at that size the
   * captions overflow the photograph and the titles are cut. It was written
   * about the phone breakpoint and it is the same arithmetic here. Playrooms and
   * Mudrooms & Utility were the two that showed it, because their blurbs are the
   * longest.
   *
   * 2 / 3 / 4 also has no meaning. It descends because nine factorises that way,
   * not because the ninth room matters less than the first.
   *
   * Rows of 3 / 3 / 3 with the first tile double width is the honest shape: one
   * feature tile, because closets genuinely is the room she does most, and eight
   * equal ones. Nine tiles, three full rows, nothing stranded and nothing too
   * small to read.
   */
  .spaces--bento .spaces__item:nth-child(1) { grid-column: span 8; }

  .spaces--bento .spaces__item:nth-child(2) { grid-column: span 4; }
}

.spaces__link { display: block; text-decoration: none; height: 100%; }

.spaces__media {
  display: block;
  position: relative;
  overflow: hidden;
}

.spaces--bento .spaces__item img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slow, 420ms) var(--ease-out);
}

/* The wide tiles get a wider crop so the row heights still agree. */
@media (min-width: 48rem) {
  .spaces--bento .spaces__item:nth-child(1) img,
  .spaces--bento .spaces__item:nth-child(4) img { aspect-ratio: 16 / 9; }
}

/*
 * PHONE: 16/9 FOR EVERY TILE, AND THE REASON IS A REGRESSION I CAUSED.
 *
 * The bento was six tiles. Splitting Kitchens from Pantries and Mudrooms from
 * Laundry Rooms took it to nine, and on a phone every tile is full width, so
 * three extra tiles is three extra full-width photographs on a homepage already
 * measured at 23,757px. That is roughly 1,200px added to the page by a change
 * that was about the room pages.
 *
 * The note further up this file rules out going two-up on a phone: at ~165px a
 * tile the captions overflow the photograph and the titles are cut. That is
 * still true. Cropping is the lever it leaves.
 *
 * 4/3 at 390 wide is a 292px image; 16/9 is 219px. Nine tiles, ~650px back, and
 * nothing is removed: every room keeps its tile, its title and its blurb at full
 * width and full size. The ratio is one the component already uses for its wide
 * tiles at desktop, so this is the existing crop applied where it now helps.
 */
@media (max-width: 47.9375rem) {
  .spaces--bento .spaces__item img {
    aspect-ratio: 16 / 9;
  }
}

/* ---------- the glass veil ---------- */

.spaces__veil {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  opacity: 0;
  background: rgba(29, 30, 32, 0.42);
  backdrop-filter: blur(2px);
  transition: opacity var(--dur-base) var(--ease-out);
}

.spaces__more {
  padding: var(--s-3) var(--s-5);
  border: 1px solid rgba(255, 255, 255, 0.85);
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-button);
  text-transform: uppercase;
  /* Same glass-scrim ink as the title and blurb it sits with. */
  color: rgba(255, 255, 255, 0.92);
}

.spaces__link:hover .spaces__veil,
.spaces__link:focus-visible .spaces__veil { opacity: 1; }

.spaces__link:hover img { transform: scale(1.04); }

/*
 * Touch devices never hover, so the veil would simply never appear and the
 * tiles would look like dead images. There, the label is always on.
 */
@media (hover: none) {
  .spaces__veil { opacity: 1; background: linear-gradient(to top, rgba(29,30,32,0.55), transparent 46%); align-items: end; }
  .spaces__more { border-color: rgba(255,255,255,0.7); margin-bottom: var(--s-4); }
}

/* ---------- tile copy at body size ---------- */

/* 500, not light. Measured on the rendered tile, the title was weight 300 at
   18px directly above a blurb at weight 400 — the title was the WEAKEST text in
   its own tile, which is why Doug read these as captions rather than titles. */
.spaces--bento .spaces__title {
  margin: var(--s-4) 0 var(--s-2);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-h4);
  line-height: var(--lh-heading);
  color: var(--c-ink);
}

/* Was --t-micro and read as a caption. Body size, matching every other section. */
.spaces--bento .spaces__blurb {
  margin: 0;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--c-ink-soft);
  max-width: 46ch;
}

/* ======================================================================
   Space (room) pages
   ====================================================================== */

.spaceprob__grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--s-6);
}

@media (min-width: 48rem) {
  .spaceprob__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-7) var(--s-8); }
}

.spaceprob__cell { padding-top: var(--s-4); border-top: 2px solid var(--c-action); }

.spaceprob__q {
  margin: 0 0 var(--s-3);
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h4);
  line-height: var(--lh-heading);
  color: var(--c-ink);
}

.spaceprob__a { margin: 0; color: var(--c-ink-soft); }

.spaceinc__grid { display: grid; gap: var(--s-7); }

@media (min-width: 60rem) {
  .spaceinc__grid { grid-template-columns: minmax(0, 1fr) minmax(0, 0.72fr); gap: var(--s-9); align-items: start; }
  .spaceinc__media { position: sticky; top: var(--sticky-top); }
}

.spaceinc__media img { width: 100%; aspect-ratio: 3 / 4; object-fit: cover; }

.spaceinc__list { list-style: none; margin: var(--s-5) 0 0; padding: 0; }

.spaceinc__list li {
  position: relative;
  padding: var(--s-4) 0 var(--s-4) var(--s-6);
  border-bottom: 1px solid var(--c-taupe-deep);
}

.spaceinc__list li:first-child { border-top: 1px solid var(--c-taupe-deep); }

.spaceinc__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc(var(--s-4) + 0.55em);
  width: 0.5rem; height: 0.25rem;
  border-left: 1.5px solid var(--c-action);
  border-bottom: 1.5px solid var(--c-action);
  transform: rotate(-45deg);
}

.spaceinc__rate { margin-top: var(--s-5); font-size: var(--t-small); color: var(--c-ink-muted); }

/* ======================================================================
   About page: the 08 Aug markups
   ====================================================================== */

/*
 * "Our home organizing services": the heading sat vertically adrift because
 * .prose gives every h2 a top margin, and this one is the column's first
 * child sitting beside a tall photograph. Zeroed, so the headline starts level
 * with the top of the image.
 */
.ab__split .prose > h2:first-child,
.ab__split .prose > *:first-child { margin-top: 0; }

@media (min-width: 60rem) {
  .ab__split { align-items: start; }
}

/*
 * "How we work, and what never changes" was three equal columns in a straight
 * row — accurate and completely flat. Staggering the middle and last columns
 * downward gives the diagonal Doug liked elsewhere on the site, at no cost to
 * the reading order.
 */
/*
 * A SMILE, NOT A STAIRCASE.
 *
 * This was 0 → 1× → 2×, which is a flight of stairs: every item lower than the
 * last, and the third one stranded a long way down the page from the first.
 * Doug: "I would like the solutions to be the same level as we treat every
 * client like family, so almost like a little smile as opposed to stairs."
 *
 * So the outer two sit level and only the middle one drops. Same diagonal
 * energy, but it resolves — the eye comes back up instead of running off the
 * bottom of the section.
 */
@media (min-width: 60rem) {
  .ab__beliefs { align-items: start; }
  .ab__beliefs > li:nth-child(2) { margin-top: var(--s-8); }
  .ab__beliefs > li:nth-child(3) { margin-top: 0; }
}

/* ======================================================================
   Service page framework, redesigned

   who -> what -> included -> types -> when -> how -> why. The first pass
   at these five was accurate and flat: five stacked text blocks at the
   same measure with the same rhythm, which is the "default centerd
   wrapping thing" Doug flagged. Each block now has its own shape, and
   every shape is borrowed from a part of the site that already earns its
   keep: the corner-overlap pair from the area pages, the numbered rows
   from the process accordion, the short 2px rule from the coverage panel.

   Grounds down the page now run taupe (who), BLACK (what), cream
   (included), taupe (types), cream (when), cream (how), taupe (why), so
   no two of the five share a ground with a neighbour and the automatic
   same-ground hairline only fires between "when" and the accordion.
   ====================================================================== */

/* ---------- who: the team, shown as one ----------
   Photography left, copy right. Ashley's portrait with a team shot
   hanging off its lower corner, the same diagonal overlap the area pages
   and the homepage process use; the copy is about arriving as a TEAM, so
   one lone portrait was undermining its own paragraph. The copy column
   hangs off a hairline rule, the About-page intro gesture. */

.svcwho__grid { display: grid; gap: var(--s-7); }

@media (min-width: 60rem) {
  .svcwho__grid {
    grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
    gap: var(--s-9);
    align-items: center;
  }

  /* Media takes the left column on wide screens. The DOM keeps copy
     first so a phone reads the heading before a screen of photograph. */
  .svcwho__media { grid-column: 1; grid-row: 1; }

  .svcwho__copy {
    grid-column: 2;
    padding-left: var(--s-7);
    border-left: 1px solid var(--c-taupe-deep);
  }
}

.svcwho__copy p {
  margin-top: var(--s-4);
  max-width: 58ch;
  color: var(--c-ink-soft);
}

.svcwho__cta { margin-top: var(--s-6); }

.svcwho__media { position: relative; }

.svcwho__media img:nth-child(1) {
  display: block;
  width: 86%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

/* The partner frame. Matted in the page taupe so the overlap reads as
   two prints laid on the same board, not a collage accident. */
.svcwho__media img:nth-child(2) {
  display: block;
  width: 48%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  margin-left: auto;
  margin-top: calc(var(--s-8) * -1);
  position: relative;
  z-index: 1;
  border: var(--s-2) solid var(--c-taupe);
}

@media (max-width: 59.9375rem) {
  .svcwho__media img:nth-child(2) { margin-top: calc(var(--s-6) * -1); }
}



.svcwhat__grid { display: grid; gap: var(--s-6); }

@media (min-width: 60rem) {
  .svcwhat__grid {
    grid-template-columns: minmax(0, 0.72fr) minmax(0, 1.28fr);
    gap: var(--s-6) var(--s-9);
    align-items: start;
  }
}

.svcwhat__head {
  margin: 0;
  max-width: 22ch;
}

/* The 2px rule marks where the definition starts, the same gesture as
   .sap__rule on the coverage panel. Terracotta is decorative here, not
   text, so its 3.14:1 on black is not a WCAG concern. */
.svcwhat__copy::before {
  content: "";
  display: block;
  width: 3.5rem;
  height: 2px;
  margin-bottom: var(--s-5);
  background: var(--c-action);
}

.svcwhat__pull {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h1);
  line-height: var(--lh-heading);
  color: var(--c-on-dark);
  text-wrap: balance;
}

.svcwhat__rest {
  margin: var(--s-6) 0 0;
  font-size: var(--t-lead);
  line-height: var(--lh-body);
  color: var(--c-on-dark-muted); /* 10.2:1 on black */
  max-width: 56ch;
}

/* ---------- types: the scope, staggered ----------
   Was a flat auto-fit grid of six identical cells, each under a bare
   hairline. Two wide columns now, the right column dropped half a step,
   which is the diagonal already working on the About beliefs and the
   portfolio bands. Each entry's hairline gets a terracotta tip at its
   head, so the rules index the list rather than decorate it. */

.svctypes__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--s-6);
}

@media (min-width: 44rem) {
  .svctypes__grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-7) var(--s-9);
    align-items: start;
  }

  /* Odd counts survive the stagger: a trailing single cell just ends the
     left column, which a staggered grid absorbs as intent. */
  .svctypes__cell:nth-child(even) { margin-top: var(--s-8); }
}

.svctypes__cell {
  position: relative;
  padding-top: var(--s-5);
  border-top: 1px solid var(--c-taupe-deep);
}

.svctypes__cell::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 2.5rem;
  height: 3px;
  background: var(--c-action);
}

.svctypes__title {
  margin: 0 0 var(--s-3);
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h3);
  line-height: var(--lh-heading);
  color: var(--c-ink);
}

.svctypes__body {
  margin: 0;
  font-size: var(--t-body);
  color: var(--c-ink-soft);
  max-width: 48ch;
}

/* ---------- when: the checklist you read yourself into ----------
   The head column holds (sticky) while the list scrolls, as before; the
   list itself moves from 17px body ticks to display scale. These six
   lines are the section's entire argument, and at body size they read
   as terms and conditions. */

.svcwhen__grid { display: grid; gap: var(--s-7); }

@media (min-width: 60rem) {
  .svcwhen__grid {
    grid-template-columns: minmax(0, 0.7fr) minmax(0, 1.3fr);
    gap: var(--s-9);
    align-items: start;
  }

  .svcwhen__head { position: sticky; top: var(--sticky-top); }
}

.svcwhen__note {
  margin: var(--s-4) 0 var(--s-5);
  font-size: var(--t-small);
  color: var(--c-ink-muted);
  max-width: 34ch;
}

.svcwhen__list { list-style: none; margin: 0; padding: 0; }

.svcwhen__list li {
  position: relative;
  padding: var(--s-5) 0 var(--s-5) var(--s-7);
  border-bottom: 1px solid var(--c-taupe-deep);
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h3);
  line-height: var(--lh-heading);
  color: var(--c-ink);
  text-wrap: pretty;
}

.svcwhen__list li:first-child { border-top: 1px solid var(--c-taupe-deep); }

/* The house tick, scaled to the type it now sits beside. */
.svcwhen__list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: calc(var(--s-5) + 0.45em);
  width: 0.7rem;
  height: 0.35rem;
  border-left: 2px solid var(--c-action);
  border-bottom: 2px solid var(--c-action);
  transform: rotate(-45deg);
}

/* ---------- why: the ledger ----------
   Four equal cards under terracotta borders said "features grid". These
   are four commitments, so they read as a ledger now: full-width rows,
   a display-scale numeral, the claim, then the proof, each on its own
   tab stop. The same numbered-row vocabulary as the process accordion
   two sections up, one scale louder. */

.ab__belief { padding-top: var(--s-4); border-top: 2px solid var(--c-action); }

/* ======================================================================
   Corrections to the 08 Aug homepage pass

   Four things I got wrong, each confirmed against the rendered page
   rather than guessed at.
   ====================================================================== */

/*
 * 1. The mid-page CTA would not center.
 *
 * .cta__inner is a GRID (1.4fr / 1fr), and I overrode it with
 * flex-direction and align-items, which a grid container ignores entirely.
 * The heading stayed left and the buttons stayed in their right-hand column.
 * Collapsing to a single column is what actually centers it.
 */


/*
 * 2. The spaces bento rendered two-up instead of a bento.
 *
 * My spans were 3,2,2,3,2,2 against a six-column track. Row one takes 3+2=5,
 * the next tile needs 2 and only 1 column remains, so it wrapped — every row
 * ended up holding two tiles, which is the flat grid it was meant to replace.
 * The arithmetic has to close exactly.
 *
 * A twelve-column track with 7+5, 5+7, 6+6 gives three rows that each fill
 * the width, with alternating emphasis left and right.
 */
@media (min-width: 48rem) {
  .spaces--bento { grid-template-columns: repeat(12, minmax(0, 1fr)); }
  .spaces--bento .spaces__item:nth-child(1) { grid-column: span 7; }
  .spaces--bento .spaces__item:nth-child(2) { grid-column: span 5; }
  .spaces--bento .spaces__item:nth-child(3) { grid-column: span 5; }
  .spaces--bento .spaces__item:nth-child(4) { grid-column: span 7; }
  .spaces--bento .spaces__item:nth-child(5) { grid-column: span 6; }
  .spaces--bento .spaces__item:nth-child(6) { grid-column: span 6; }

  /*
   * Tiles of different widths sharing one aspect-ratio produce different
   * heights, which is what made the rows ragged. A fixed media height with
   * object-fit keeps every row level whatever the span.
   */
  .spaces--bento .spaces__media { height: 17rem; }
  .spaces--bento .spaces__item img,
  .spaces--bento .spaces__item:nth-child(1) img,
  .spaces--bento .spaces__item:nth-child(4) img {
    height: 100%;
    aspect-ratio: auto;
  }
}

/*
 * 3. The Explore arrow dropped onto its own line.
 *
 * .link-arrow is inline-flex with the arrow as a ::after flex item. Adding
 * flex-wrap let that arrow wrap to a second line whenever the label was long,
 * which is worse than the stranded-right arrow it was meant to fix.
 *
 * As a block the arrow is ordinary inline content and simply follows the last
 * word, wrapping with it.
 */
.sg__cta {
  display: block;
  flex-wrap: nowrap;
}

.sg__cta::after { content: " \2192"; display: inline; }

/*
 * 4. The two process photographs came out postage-stamp sized.
 *
 * The media column is 0.8fr of a 0.8/2 split, and I then set the images to
 * 78% and 58% OF that column. Compounding the two made them tiny. The column
 * gets more room and the first image now fills it.
 */
@media (min-width: 60rem) {
  .procshot__grid { grid-template-columns: 1.05fr 2fr; }
}

/* ======================================================================
   Homepage, 08 Aug round three
   ====================================================================== */

/*
 * 1. Process photographs: mirrored.
 *
 * The pair ran large-top-right, small-bottom-left. The markup Doug drew has it
 * the other way: the large frame anchors top-LEFT and the second overlaps down
 * and to the RIGHT, which reads left-to-right into the numbered steps beside it
 * instead of away from them. Both frames are also larger than before.
 */
/*
 * 2. Why homeowners choose: asymmetric again, and it now asks for something.
 *
 * Four equal columns on one baseline is the flattest arrangement on the page.
 * Dropping the even columns restores the stagger used on the About beliefs and
 * the service types.
 */
@media (min-width: 60rem) {
  .whyus__grid { align-items: start; }
  .whyus__item:nth-child(2) { margin-top: var(--s-7); }
  .whyus__item:nth-child(3) { margin-top: calc(var(--s-7) * 0.5); }
  .whyus__item:nth-child(4) { margin-top: calc(var(--s-7) * 1.5); }
}

.whyus__foot {
  display: grid;
  gap: var(--s-5);
  margin-top: var(--s-8);
  padding-top: var(--s-6);
  border-top: 1px solid var(--c-taupe-deep);
}

@media (min-width: 48rem) {
  .whyus__foot {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: center;
    gap: var(--s-7);
  }
}

.whyus__close { margin: 0; }

.whyus__actions { display: flex; flex-wrap: wrap; gap: var(--s-4); }

/*
 * 3. Spaces tiles: the caption moves onto the photograph.
 *
 * Title and blurb sat BELOW each tile, so six tiles of unequal copy length
 * produced six different heights and the rows never lined up — that is the
 * mess. Moving the caption inside the media box makes every tile exactly the
 * height of its image, and the rows are level by construction.
 *
 * CRAWLABILITY, which Doug asked about specifically: this is real text in the
 * DOM, always rendered, never display:none and never opacity:0. It sits over a
 * gradient scrim rather than being baked into the image, so Google reads it
 * exactly as it did when it sat underneath. Only the "Learn more" affordance
 * responds to hover, and that is decoration, not content.
 */
.spaces--bento .spaces__item { position: relative; }

.spaces--bento .spaces__media {
  display: block;
  position: relative;
  overflow: hidden;
  height: 22rem;
}

@media (max-width: 47.9375rem) {
  .spaces--bento .spaces__media { height: 18rem; }
}

.spaces--bento .spaces__item img {
  width: 100%;
  height: 100%;
  aspect-ratio: auto;
  object-fit: cover;
  display: block;
}

/* Strong enough at the foot to carry body copy, clear at the top. */
.spaces__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(20, 18, 17, 0.88) 0%,
    rgba(20, 18, 17, 0.72) 34%,
    rgba(20, 18, 17, 0.22) 62%,
    rgba(20, 18, 17, 0.04) 100%
  );
}

.spaces__caption {
  position: absolute;
  inset: auto 0 0 0;
  display: block;
  padding: var(--s-5);
}

/*
 * THE CAPTION CARRIES ITS OWN BACKDROP, above 48rem where it sits on the photo.
 *
 * The tile scrim above is calibrated for its own gradient and not for how tall
 * the captions actually are. Measured on /service-areas/plano-.../ at 1280: the
 * caption tops sit 65-73% up the tile, and the scrim is only 0.17-0.21 alpha
 * that high. Against a white closet or a white pantry — which is most of this
 * library — 0.92 white text over 0.17 of near-black on white composites to
 * about 1.4:1. The contrast audit reported 1.22:1 by reading the CSS background
 * and ignoring the scrim entirely; its method was wrong and its conclusion was
 * right, which is worth saying plainly because the number was dismissed as an
 * artifact twice.
 *
 * Darkening the whole scrim to fix it would have taken the mid-tile from 0.72
 * to 0.84 and buried the photograph. This puts the darkness only where the
 * words are: transparent at the caption's top edge, ramping to 0.58 by the
 * first line of type and 0.72 at the foot. Composited with the scrim already
 * there, the weakest point measures about 5.2:1 over a pure-white photograph,
 * which is the worst case any image in the library can present.
 *
 * Below 48rem none of this applies — the scrim is display:none, the caption is
 * static, and the text is dark ink on cream.
 */
@media (min-width: 60rem) {
  .spaces--bento .spaces__caption {
    background: linear-gradient(
      to top,
      rgba(20, 18, 17, 0.72) 0%,
      rgba(20, 18, 17, 0.64) 70%,
      rgba(20, 18, 17, 0.58) 92%,
      rgba(20, 18, 17, 0) 100%
    );
  }
}

.spaces--bento .spaces__title {
  margin: 0 0 var(--s-2);
  font-family: var(--font-display);
  /* This is the declaration that actually wins over a photograph. */
  font-weight: var(--fw-medium);
  font-size: var(--t-h4);
  line-height: var(--lh-heading);
  /* Matches .spaces__blurb below rather than being pure #fff. Doug's call,
     2026-08-15: text on the glass scrim is rgba(255,255,255,0.92). The title
     and its blurb sit two lines apart on the same panel, and at #fff against
     0.92 the title read as a slightly different white — the same class of
     mismatch as the hero pair, just at the top end. Still 8.4:1 on the scrim
     at its lightest point. */
  color: rgba(255, 255, 255, 0.92);
}

.spaces--bento .spaces__blurb {
  margin: 0;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  /* 8.9:1 on the scrim at its lightest point behind this text. */
  color: rgba(255, 255, 255, 0.92);
  max-width: 48ch;
}

.spaces__more {
  display: inline-block;
  margin-top: var(--s-4);
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-button);
  text-transform: uppercase;
  /* Glass-scrim ink, matching the title and blurb above it. The opacity below
     is a separate job — it is the hover affordance, not the resting color. */
  color: rgba(255, 255, 255, 0.92);
  opacity: 0.82;
  transition: opacity var(--dur-base) var(--ease-out);
}

.spaces__link:hover .spaces__more,
.spaces__link:focus-visible .spaces__more { opacity: 1; }

.spaces__link:hover img { transform: scale(1.04); }

.spaces--bento .spaces__item img { transition: transform var(--dur-slow, 420ms) var(--ease-out); }

/* ======================================================================
   /spaces/ archive, and the spaces list note
   ====================================================================== */

.spacearch__grid {
  list-style: none; margin: 0; padding: 0;
  display: grid; gap: var(--s-7);
}

@media (min-width: 48rem) {
  .spacearch__grid { grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr)); gap: var(--s-8) var(--s-7); }
}

.spacearch__link { display: flex; flex-direction: column; height: 100%; text-decoration: none; }

.spacearch__media { display: block; overflow: hidden; }

.spacearch__media img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  display: block;
  transition: transform var(--dur-slow, 420ms) var(--ease-out);
}

.spacearch__link:hover img { transform: scale(1.04); }

.spacearch__title {
  margin: var(--s-4) 0 var(--s-2);
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h4);
  line-height: var(--lh-heading);
  color: var(--c-ink);
}

.spacearch__body { margin: 0; font-size: var(--t-body); color: var(--c-ink-soft); }

/* Pinned to the card foot so the row of CTAs sits on one line. */
.spacearch__cta {
  margin-top: auto;
  padding-top: var(--s-4);
  border-top: 2px solid var(--c-action);
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: #6e301c;
}

.spaceslist__note {
  margin-top: var(--s-6);
  font-size: var(--t-small);
  color: var(--c-ink-muted);
}

/* Service type titles are links now; keep them looking like headings. */
.svctypes__title a { color: inherit; text-decoration: none; border-bottom: 1px solid var(--c-taupe-deep); }
.svctypes__title a:hover, .svctypes__title a:focus-visible { border-bottom-color: #6e301c; color: #6e301c; }

/* ======================================================================
   Homepage, final pass
   ====================================================================== */

/*
 * Process photographs: pulled up, and short enough to stay in their section.
 *
 * Two faults. The pair started level with the numbered steps, well below the
 * heading, and the stack was taller than the copy beside it — so the lower
 * frame hung past the section boundary and overlapped what came next.
 *
 * The negative top margin lifts the first frame to sit against the heading's
 * second line, and both frames come down in size so the column's total height lands
 * inside the section rather than spilling out of it.
 */
@media (min-width: 60rem) {
  .procshot__media { margin-top: calc(var(--s-8) * -1); }
}

/*
 * Why homeowners choose: back to a straight baseline.
 *
 * The stagger was mine and Doug does not want it here — four short reasons
 * read better as a level row, and the section already earns its interest from
 * the CTA beneath it. Reverting rather than re-tuning: the previous rule is
 * still above in this file, so these zero it out explicitly.
 */
@media (min-width: 60rem) {
  .whyus__item:nth-child(2),
  .whyus__item:nth-child(3),
  .whyus__item:nth-child(4) { margin-top: 0; }
}

/*
 * The horizontal-timeline rules that stood here are gone with the layout.
 * The process is back to the homepage's split treatment (procshot), whose
 * rules live further up this file.
 */



/* ======================================================================
   MOBILE — homepage audit, 08 Aug (iPhone 13, 390px)
   ====================================================================== */

/*
 * 1. The spaces tiles were unreadable.
 *
 * Two tiles per row at 390px is about 165px each, and the caption is a title
 * plus a 150-character blurb absolutely positioned inside the media box. It
 * overflowed upward past the photograph, the titles were cut off entirely, and
 * the text sat on the bright part of the image with no scrim behind it.
 *
 * Below 60rem the caption comes OUT of the media box and sits under it as
 * ordinary flow content. Overflow becomes impossible rather than merely
 * unlikely, so the reason the caption went inside in the first place does not
 * apply here.
 *
 * THE BREAKPOINT WAS 48rem AND THAT WAS TOO LOW — the fault described above was
 * fixed on phones and left running on tablets. Measured across widths: at 768px
 * the Nurseries caption is 880px tall inside a 352px tile, so 528px of white
 * type spills out of the photograph and onto the cream page behind it, where it
 * measures 1.02:1. At 860px it is still 393px over. The overlay only actually
 * fits from 960px up, so that is where it now starts.
 *
 * Two columns from 36rem, because one tile per row on a 900px screen is a very
 * large photograph for very little copy.
 */
@media (max-width: 59.9375rem) {
  .spaces--bento { grid-template-columns: minmax(0, 1fr); gap: var(--s-7); }

  @media (min-width: 36rem) {
    .spaces--bento { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-6); }
  }

  .spaces--bento .spaces__media { height: auto; }

  .spaces--bento .spaces__item img {
    height: auto;
    aspect-ratio: 4 / 3;
  }

  .spaces__scrim { display: none; }

  .spaces__caption {
    position: static;
    padding: var(--s-4) 0 0;
  }

  .spaces--bento .spaces__title { color: var(--c-ink); }
  .spaces--bento .spaces__blurb { color: var(--c-ink-soft); max-width: none; }

  .spaces__more {
    color: #6e301c;
    opacity: 1;
    border: 0;
    padding: 0;
  }
}

/*
 * 2. The hero was a full screen of text with no photograph and no CTA.
 *
 * The copy panel comes first in the DOM, which is right for a screen reader and
 * wrong for a phone: heading, promise, body and a four-line sub-paragraph all
 * landed before the image or the button. Someone arriving from an ad saw no
 * picture of the work and nothing to tap without scrolling.
 *
 * The image moves above the copy with grid order — DOM order is untouched, so
 * the heading is still the first thing announced.
 */
@media (max-width: 56.1875rem) {
  .hero__grid { display: grid; }
  .hero__media { order: -1; }

  .hero__media img {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
  }

  /* The supporting paragraph is the longest block above the fold and the least
     load-bearing; it stays for SEO and screen readers, just smaller. */
  .hero__sub {
    font-size: var(--t-small);
    line-height: var(--lh-body);
  }
}

/*
 * 3. Tap targets.
 *
 * 53 links measured under Apple's 44px minimum, nearly all of them list rows in
 * the coverage panel and the footer at 26px tall. They are links in a dense
 * list, so they cannot simply be padded apart without wrecking the layout —
 * min-height plus vertical centring keeps the visual rhythm while giving each
 * one a real touch area.
 */
@media (max-width: 47.9375rem) {
  .sap__list a,
  .foot__cols a,
  .spaceslist__link,
  .areas__grid a {
    display: flex;
    align-items: center;
    min-height: 44px;
  }
}

/*
 * MOBILE — the diagonal image stacks come apart.
 *
 * The overlapping pairs and triples are a desktop composition. They rely on one
 * frame being able to hang off another's corner, which needs horizontal room
 * neither a 390px screen nor a single-column layout has. On a phone the offsets
 * shrink to a few pixels of accidental-looking overlap, the smaller frame ends
 * up a postage stamp, and the negative margins pull the pair into whatever sits
 * above it.
 *
 * The secondary frames are HIDDEN rather than restacked. They are decoration —
 * every one of these sections already carries a primary photograph that says
 * the same thing — and hiding them removes roughly 2,600px from a homepage that
 * measured 20,175px on an iPhone. A straight vertical stack would have looked
 * tidier than the overlap but would have made a very long page longer.
 *
 * Desktop is untouched.
 */
@media (max-width: 47.9375rem) {

  /* Homepage process, service "who", area galleries. */
  /* procshot's partner is hidden in its own consolidated block below. */
  .svcwho__media img:nth-child(2),
  .areastack__media img:nth-child(3) {
    display: none;
  }

  /* Whatever survives sits square, full width, no negative pull. */
  .svcwho__media img,
  .areastack__media img {
    width: 100%;
    margin: 0;
    border: 0;
    aspect-ratio: 4 / 3;
    object-fit: cover;
  }

  /* Area pages keep two, because the gallery IS the relief on a 2,000-word
     page — but straight, with real space between them. */
  .areastack__media {
    display: grid;
    gap: var(--s-5);
  }

  .areastack__media img:nth-child(1) { aspect-ratio: 4 / 3; }
  .areastack__media img:nth-child(2) { aspect-ratio: 1 / 1; }
}

/* ======================================================================
   Process photographs — THE ONE PLACE THESE ARE DECLARED

   Consolidated from seven overlapping declarations of
   .procshot__media img:nth-child(2) that accumulated across a day of
   appending fixes rather than editing them. The last one won, so the page
   rendered correctly, but the file described four different intentions and
   the mobile rule needed !important to beat its own ancestors.

   Anything about these two images changes HERE. Do not append another block.
   ====================================================================== */

.procshot__media img {
  display: block;
  object-fit: cover;
}

/* Large frame anchored left; the partner hangs off its bottom-right corner,
   so the pair reads into the numbered steps beside it. */
.procshot__media img:nth-child(1) {
  width: 78%;
  margin-left: 0;
  margin-right: auto;
  aspect-ratio: 4 / 5;
}

.procshot__media img:nth-child(2) {
  /* 54% -> 64%, and the frame down from 8px to 4px. Doug asked for bigger and
     for the border gone or thinner; thinner rather than gone, because this frame
     overlaps the one behind it and with no border at all the two photographs
     merge into each other along the overlap instead of reading as two frames. */
  width: 64%;
  margin-left: auto;
  margin-right: 0;
  margin-top: calc(var(--s-7) * -1);
  aspect-ratio: 1 / 1;
  border: var(--s-1) solid var(--c-cream);
  position: relative;
  z-index: 1;
}

/* Lifted so the first frame meets the heading's second line rather than
   starting level with the steps. */
@media (min-width: 60rem) {
  .procshot__media { margin-top: calc(var(--s-8) * -1); }
}

@media (max-width: 59.9375rem) {
  .procshot__media img:nth-child(2) { margin-top: calc(var(--s-6) * -1); }
}

/* Phones: the overlap needs horizontal room a 390px screen does not have.
   The partner is decoration and the section keeps its primary photograph. */
@media (max-width: 47.9375rem) {
  .procshot__media { margin-top: 0; }
  .procshot__media img:nth-child(2) { display: none; }
  .procshot__media img:nth-child(1) {
    width: 100%;
    margin: 0;
    aspect-ratio: 4 / 3;
  }
}

/* ======================================================================
   Service page — 08 Aug review
   ====================================================================== */

/* Heading was vertically centerd against a two-frame photo stack, which put it
   low. Top-aligned with a small optical nudge so it reads level with the first
   frame rather than floating between the two. */
@media (min-width: 60rem) {
  .svcwho__grid { align-items: start; }
  .svcwho__copy { padding-top: var(--s-5); }
}

/*
 * Types tiles: a proper glaze.
 *
 * The scrim was a bottom-weighted gradient, which works over a dark photograph
 * and fails over a bright one — "Closet organization" and "Garage organization"
 * were both effectively invisible against white shelving. A flat wash across
 * the whole tile under the gradient guarantees a floor of darkness regardless
 * of what the photograph is doing, and the blur separates text from detail.
 */
.svctypes .spaces__scrim,
.spaces--bento .spaces__scrim {
  background:
    linear-gradient(to top, rgba(18,16,15,0.92) 0%, rgba(18,16,15,0.74) 38%, rgba(18,16,15,0.40) 70%, rgba(18,16,15,0.28) 100%);
  backdrop-filter: blur(1.5px) saturate(0.9);
}

/* ---------- Process accordion (service pages) ---------- */

.svcproc__head { max-width: 46rem; margin-bottom: var(--s-7); }
.svcproc__intro { margin: var(--s-4) 0 0; font-size: var(--t-body); color: var(--c-ink-soft); }

.svcproc__list { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--c-taupe-deep); }
.svcproc__item { border-bottom: 1px solid var(--c-taupe-deep); }

.svcproc__details summary {
  display: grid;
  grid-template-columns: 3rem 1fr 1.5rem;
  align-items: baseline;
  gap: var(--s-3);
  padding: var(--s-5) 0;
  cursor: pointer;
  list-style: none;
}
.svcproc__details summary::-webkit-details-marker { display: none; }
.svcproc__details summary:focus-visible { outline: 2px solid var(--c-focus); outline-offset: 3px; }

.svcproc__n {
  font-family: var(--font-display); font-weight: var(--fw-medium);
  font-size: var(--t-micro); letter-spacing: var(--ls-label); color: #6e301c;
}
.svcproc__title {
  font-family: var(--font-display); font-weight: var(--fw-light);
  font-size: var(--t-h3); line-height: var(--lh-heading); color: var(--c-ink);
}

.svcproc__icon { position: relative; width: 1.5rem; height: 1.5rem; }
.svcproc__icon::before, .svcproc__icon::after {
  content: ""; position: absolute; top: 50%; width: 0.5rem; height: 1px;
  background: var(--c-ink-soft); transition: transform var(--dur-base) var(--ease-out);
}
.svcproc__icon::before { left: 0.15rem; transform: rotate(45deg); }
.svcproc__icon::after  { left: 0.5rem;  transform: rotate(-45deg); }
.svcproc__details[open] .svcproc__icon::before { transform: rotate(-45deg); }
.svcproc__details[open] .svcproc__icon::after  { transform: rotate(45deg); }

.svcproc__body { padding: 0 var(--s-6) var(--s-5) calc(3rem + var(--s-3)); max-width: 62ch; color: var(--c-ink-soft); }
.svcproc__body p { margin: 0; }

@media (max-width: 39.9375rem) {
  .svcproc__details summary { grid-template-columns: 2.5rem 1fr 1.25rem; }
  .svcproc__body { padding-left: calc(2.5rem + var(--s-3)); padding-right: 0; }
}

/* ---------- Why: accordion carrying narrative + specifics ---------- */

/* ======================================================================
   MOBILE HERO — photograph behind the copy, not above it
   ====================================================================== */

/*
 * Stacking the image above the panel pushed the CTA off the first screen,
 * which is the wrong trade on the page paid traffic lands on. Behind the copy
 * it does the same job — proof that the work is real — while the heading and
 * the button both stay in the first viewport.
 *
 * The panel gets a translucent ground and a blur so the type stays legible
 * whatever the photograph is doing underneath it.
 */
/*
 * REVERSED, 8 Aug: the photograph now sits BELOW the panel rather than behind it.
 *
 * It was full-bleed behind a translucent, blurred panel with a dark gradient over
 * the image so the type stayed legible. That kept the CTA on the first screen,
 * which was the point, but the photograph read as murk behind the copy rather
 * than as a photograph. Doug asked for the internal-page treatment instead.
 *
 * The rule this was written for still holds — the failure it guards against is an
 * image stacked ABOVE the copy, which pushes the CTA off the first screen on a
 * page taking paid traffic. Below the CTA the panel still comes first, so the ask
 * stays visible and the picture gets to be a picture.
 *
 * Matches .phero__media: static, a top margin, and a 4/3 crop.
 */
@media (max-width: 56.1875rem) {
  .hero__grid { display: block; }

  .hero__media {
    margin-top: var(--s-6);
  }

  .hero__media img {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
  }

  /* Solid, not translucent: there is nothing behind it to show through, and the
     blur was costing a paint on every scroll for no visual gain. */
  .hero__panel {
    display: flex;
    flex-direction: column;
    background: var(--c-cream);
    margin: 0 var(--gutter);
    padding: var(--s-5);
  }

  /*
   * THE SUB LINE MOVES BELOW THE CTA. Measured against a 390x844 screen with the
   * 56px sticky call bar, the hero button's bottom edge sat at 844 against a
   * usable fold of 788 — the primary ask was off the first screen, which is the
   * exact failure the old image-behind-copy treatment was built to prevent, and
   * it was failing anyway. Reordering rather than hiding keeps this line — it
   * carries the service nouns and the city names — in the DOM and on the page.
   *
   * Button bottom now lands at 619 on 390x844.
   */
  .hero__sub {
    order: 10;
    margin-top: var(--s-5);
    font-size: var(--t-small);
    line-height: var(--lh-body);
  }
}

/* ======================================================================
   ABOUT — "From Ashley" reads as a quotation, not a paragraph
   ====================================================================== */

/*
 * It is Ashley's own words in the first person, sitting in a column of
 * third-person copy with nothing to say so. Set as a pull quote in the display
 * face, with a mark and an attribution rule, it reads as a voice rather than
 * more body text.
 */
.ab__intro {
  position: relative;
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-style: italic;
  font-size: var(--t-h3);
  line-height: 1.45;
  color: var(--c-ink);
}

.ab__intro::before {
  content: "\201C";
  display: block;
  margin-bottom: var(--s-2);
  font-family: var(--font-display);
  font-size: 3.5rem;
  line-height: 0.7;
  color: var(--c-action);
  opacity: 0.55;
}

.ab__intro::after {
  content: "Ashley Matteson";
  display: block;
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: 1px solid var(--c-taupe-deep);
  font-family: var(--font-display);
  font-style: normal;
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-muted);
}

/* ======================================================================
   Services mega panel

   A MODIFIER ON .nav__sub, NOT A NEW COMPONENT. The dropdown already has
   working caret JS, hover and focus-within opening, aria-expanded handling
   and a mobile accordion. Replacing it would have meant re-testing all of
   that on a header that appears on every page; extending it means the only
   thing that changes is the desktop layout.

   Below 1140px the base rules still run: display:none until [data-open],
   then a stacked list. The columns only exist on desktop, where there is
   room for them.
   ====================================================================== */

.nav__megaHead {
  display: block;
  margin-bottom: var(--s-2);
  font-family: var(--font-display);
  font-size: var(--t-micro);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  text-decoration: none;
  color: var(--c-on-dark);
}

.nav__megaHead:hover,
.nav__megaHead:focus-visible {
  color: var(--c-taupe-light);
}

.nav__megaList {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Mobile: the group heading indents its own list, so the grouping still
   reads without any columns. */
@media (max-width: 71.1875rem) {
  .nav__megaCol + .nav__megaCol {
    margin-top: var(--s-4);
  }

  .nav__megaList {
    padding-left: var(--s-3);
  }
}

@media (min-width: 1140px) {
  .nav__sub--mega {
    /*
     * Centerd on the Services item rather than left-aligned. A four-column
     * panel anchored at left:0 runs off the right edge at 1140px, and the
     * item sits mid-bar.
     */
    left: 50%;
    transform: translateX(-50%) translateY(-6px);
    display: grid;
    grid-template-columns: repeat(var(--mega-cols, 3), minmax(10rem, 1fr));
    gap: var(--s-6) var(--s-7);
    width: max-content;
    max-width: min(60rem, calc(100vw - 3rem));
    padding: var(--s-6) var(--s-7);
  }

  /*
   * The base open state sets transform:none, which would drop the centring.
   * These three selectors mirror the base ones exactly so the panel keeps its
   * offset while animating.
   */
  .nav__item--has-sub:hover .nav__sub--mega,
  .nav__item--has-sub:focus-within .nav__sub--mega,
  [data-sub][data-open] .nav__sub--mega {
    transform: translateX(-50%);
  }

  .nav__sub--mega .nav__sublink {
    padding: var(--s-2) 0;
  }
}



@media (min-width: 700px) {
  .pf__item figure {
    position: relative;
    overflow: hidden;
    margin: 0;
  }

  .pf__item figure img {
    transition: transform var(--dur-base) var(--ease-out);
  }

  .pf__item figcaption {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    padding: var(--s-8) var(--s-5) var(--s-5);
    background: linear-gradient(
      to top,
      rgba(29, 30, 32, 0.92) 0%,
      rgba(29, 30, 32, 0.78) 45%,
      rgba(29, 30, 32, 0) 100%
    );
    color: #fff;
    font-size: var(--t-small);
    line-height: var(--lh-body);
    transition: background var(--dur-base) var(--ease-out);
  }

  /* The affordance, and the only part that is decoration. */
  .pf__item:hover figure img,
  .pf__item:focus-within figure img {
    transform: scale(1.04);
  }

  .pf__item:hover figcaption,
  .pf__item:focus-within figcaption {
    background: linear-gradient(
      to top,
      rgba(29, 30, 32, 0.96) 0%,
      rgba(29, 30, 32, 0.88) 60%,
      rgba(29, 30, 32, 0.35) 100%
    );
  }
}



.page-faq .container--narrow {
  /* 0,2,0 beats .container--narrow at 0,1,0. */
  max-width: var(--container);
}

.page-faq .faq__groupIntro,
.page-faq .faq__outro {
  max-width: 65ch;
}

@media (min-width: 60rem) {
  .page-faq .faq__list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 var(--s-8);
    align-items: start;
  }
}

/* ======================================================================
   About: "From Ashley" and the beliefs are TWO sections now

   They were one taupe block doing three jobs — a first-person quote, a
   founder's bio that turned into company scope, and a positioning
   statement under its own h2 buried in the second column. Doug asked to
   split them, and the split is what fixes the read: the quote and the bio
   are one thought, the beliefs are another, and a change of ground is what
   tells a reader that.

   Grounds now run taupe -> navy -> cream -> taupe down the page, so no two
   neighbours share one.
   ====================================================================== */

.ab__from,
.ab__believe {
  display: grid;
  gap: var(--s-6);
}

.ab__fromCopy p + p,
.ab__believeCopy p + p {
  margin-top: var(--s-5);
}

@media (min-width: 60rem) {
  /* Quote narrower than the copy it introduces: unequal columns, not 50/50. */
  .ab__from {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
    gap: var(--s-9);
    align-items: start;
  }

  .ab__believe {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
    gap: var(--s-9);
    align-items: center;
  }
}

/*
 * The navy band. --c-navy-deep, the same hue .whyus already uses, so this is a
 * color the palette owns rather than one invented for a single section.
 *
 * .section--dark supplies the text colors (on-dark for headings, on-dark-muted
 * for body) and it defaults to --c-black, so only the background is overridden.
 *
 * CONTRAST on #292f3a, from the values .whyus measured:
 *   --c-on-dark        11.05:1
 *   --c-on-dark-muted   6.31:1
 *   --c-action          2.01:1  <- unusable, so nothing terracotta lands here.
 *                                  The emphasis below goes to full-strength
 *                                  on-dark instead.
 */
.ab__believeSection {
  background: var(--c-navy-deep);
}

.ab__believeCopy strong {
  color: var(--c-on-dark);
  font-weight: var(--fw-semibold);
}

/* Two frames, diagonal — the same pattern as the process pair and the area
   stacks. wpautop puts a <br> between the images, which would take a grid cell
   of its own and shift every nth-child count by one. */
.ab__believeMedia br {
  display: none;
}

.ab__believeMedia img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

@media (min-width: 60rem) {
  .ab__believeMedia {
    display: block;
    position: relative;
    aspect-ratio: 1 / 1;
  }

  .ab__believeMedia img {
    position: absolute;
  }

  .ab__believeMedia img:nth-of-type(1) {
    top: 0;
    left: 0;
    width: 66%;
    aspect-ratio: 4 / 5;
  }

  /* The partner hangs off the bottom-right corner, bordered in the ground
     color so the two photographs read as two frames rather than merging
     along the overlap. */
  .ab__believeMedia img:nth-of-type(2) {
    right: 0;
    bottom: 0;
    width: 48%;
    aspect-ratio: 1 / 1;
    border: var(--s-2) solid var(--c-navy-deep);
  }
}

/* Phones: the offsets collapse to a few pixels of accidental-looking overlap,
   so the pair stacks. Both stay — these are content, not decoration. */
@media (max-width: 59.9375rem) {
  .ab__believeMedia {
    display: grid;
    gap: var(--s-5);
  }
}

/* ======================================================================
   "Why homeowners choose" gets its own hue
   ====================================================================== */

/*
 * The section ran taupe straight out of Ashley's story, which is also taupe, so
 * the two read as one long band of the same peach-brown and the page went flat
 * exactly where it is trying to make its argument.
 *
 * Navy is already in the palette — it is the focus ring and the link color —
 * so this is a hue the site already owns rather than a new one invented for a
 * single section. Against taupe above and cream below it gives the page a real
 * beat: warm, cool, light.
 *
 * CONTRAST, MEASURED on #292f3a:
 *   --c-on-dark        11.05:1
 *   --c-on-dark-muted   6.31:1
 *   --c-taupe-light     7.85:1
 *   --c-action          2.01:1   <- unusable, which is why the numerals below
 *                                   switch to taupe-light rather than staying
 *                                   terracotta as they are on every other band
 */
.whyus {
  background: var(--c-navy-deep);
  color: var(--c-on-dark);
}

.whyus .sechead__title,
.whyus .h2,
.whyus .whyus__title { color: var(--c-on-dark); }

.whyus .whyus__body { color: var(--c-on-dark-muted); }

.whyus .whyus__n { color: var(--c-taupe-light); }

.whyus .whyus__item { border-top-color: rgba(237, 232, 228, 0.22); }

.whyus__close { color: var(--c-on-dark); }

.whyus__foot { border-top-color: rgba(237, 232, 228, 0.22); }

/* The outline button reads as a hairline on a dark ground unless it is inverted. */
.whyus .btn--outline {
  border-color: rgba(237, 232, 228, 0.55);
  color: var(--c-on-dark);
}

.whyus .btn--outline:hover,
.whyus .btn--outline:focus-visible {
  border-color: var(--c-on-dark);
  background: rgba(237, 232, 228, 0.10);
}

/* ======================================================================
   MOBILE — more room to breathe
   ====================================================================== */

/*
 * Everything on a phone was running at the desktop's tightest spacing values.
 * The section rhythm bottoms out at 3rem and the grids collapse to one column
 * while keeping a 2rem gap, so stacked cards sat almost touching — most
 * obviously on the services grid, where each card is an image, a heading and a
 * paragraph with barely a line of air before the next one starts.
 *
 * Vertical space is the only separator a single-column layout has. On desktop
 * columns do that work; on a phone the gap has to.
 */
@media (max-width: 47.9375rem) {
  :root { --section-y: 3.75rem; }

  /* Stacked cards need roughly double the gap they get side by side. */
  .sg__grid,
  .whyus__grid,
  .svctypes__grid,
  .spaceprob__grid,
  .spacearch__grid,
  .areasvc__grid,
  .steps--auto,
  .svcproc__track { gap: var(--s-8); }

  /* Two-column splits that stack: the copy needs clearing from the image.
     --split-gap-xs, which is LARGER than the tablet value on purpose. The bands
     and the hero join the list so every split on the site stacks alike. */
  .svcwho__grid,
  .svcinc__grid,
  .whencall__grid,
  .procshot__grid,
  .spaceinc__grid,
  .sband__grid,
  .iband__grid,
  .ahero__grid,
  .areastack { gap: var(--split-gap-xs); }

  /* Section headings were sitting almost on top of the content beneath them. */
  .sechead { margin-bottom: var(--s-6); }

  /* List rows in the dense panels. */
  .whencall__list li,
  .svcinc__list li,
  .spaceinc__list li { padding-block: var(--s-4); }
}

/* ======================================================================
   Service lead-in — FULL WIDTH

   This was a 60rem centerd column, which is the tight wrap Doug has now
   objected to twice. Full width is the default from here: the container is
   1200px and this section uses all of it.

   Full width does NOT mean a 1200px line of text — that is an unreadable
   measure. The width is spent on a two-column split instead: the question
   stands on the left at heading scale, the answer runs down the right at a
   sane 62ch. The section fills the page; the sentences stay readable.
   ====================================================================== */

.svclead__grid { display: grid; gap: var(--s-6); }

@media (min-width: 60rem) {
  .svclead__grid {
    grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
    gap: var(--s-9);
    align-items: start;
  }

  /* Holds while the answer scrolls past it. */
  .svclead__head { position: sticky; top: var(--sticky-top); }
}

.svclead__head .h2 { margin: 0; }

/*
 * Was Montserrat Light at --t-h4: a display face at 18px sitting directly above
 * Manrope at 17px. One point of size difference does not read as a step, so the
 * family switch was the only thing the eye caught, and the column looked like it
 * had three typefaces in it — heading, this, body. It is a paragraph, and the
 * strongest one on the page, so it is set as body copy.
 *
 * The ink matches the paragraphs below it too — Doug's call, and the right one:
 * once the family and size match, a slightly darker first paragraph reads as an
 * inconsistency rather than as emphasis. This class now differs from
 * .svclead__body only in having no top margin.
 */
.svclead__lead {
  margin: 0;
  max-width: 62ch;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--c-ink-soft);
}

.svclead__body {
  margin: var(--s-5) 0 0;
  max-width: 62ch;
  color: var(--c-ink-soft);
}

.svclead__copy .prose { margin-top: var(--s-6); }
.svclead__copy .prose:empty { margin-top: 0; }

/* ---------- Section-head intro, now that it renders ---------- */

/*
 * Heading and intro are ONE unit, spaced as one.
 *
 * .sechead carries margin-bottom: var(--s-7) for the case where content follows
 * it directly. Put an intro in between and that 48px stacked with the intro's
 * own 16px top margin into a 64px hole, while the intro itself had no bottom
 * margin at all — so it sat flush on the bento below it. Doug flagged both ends
 * of that on "The spaces we work in": too much above, nothing below.
 *
 * Tight between the two lines, a real gap before the content.
 */
.sechead:has(+ .sechead__intro) {
  margin-bottom: var(--s-4);
}

.sechead__intro {
  margin: 0 0 var(--s-7);
  max-width: 64ch;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--c-ink-soft);
}

/* ---------- Process: prose rail left, accordion right ---------- */

.svcproc__grid { display: grid; gap: var(--s-7); }

@media (min-width: 60rem) {
  .svcproc__grid {
    grid-template-columns: minmax(0, 0.92fr) minmax(0, 1.08fr);
    gap: var(--s-9);
    align-items: start;
  }
  .svcproc__head { position: sticky; top: var(--sticky-top); }
}

.svcproc__head { max-width: none; margin-bottom: 0; }
.svcproc__prose { margin: var(--s-5) 0 0; color: var(--c-ink-soft); max-width: 56ch; }
.svcproc__cta { margin-top: var(--s-6); }

/* Optional specifics under a why item. Homepage passes none; service pages
   pass three. Same grid either way. */
.whyus__points {
  list-style: none;
  margin: var(--s-4) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-2);
}

.whyus__points li {
  position: relative;
  padding-left: var(--s-5);
  font-size: var(--t-small);
  /* 6.31:1 on the navy ground. */
  color: var(--c-on-dark-muted);
}

.whyus__points li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.55em;
  width: 0.45rem;
  height: 0.22rem;
  border-left: 1.5px solid var(--c-taupe-light);
  border-bottom: 1.5px solid var(--c-taupe-light);
  transform: rotate(-45deg);
}

/* ======================================================================
   Navy band: body copy at body size

   .whyus__body and .whyus__points measured 16px against 17px everywhere
   else. One pixel is not a design decision, it is a drift, and on a dark
   ground with lighter ink it is enough to read as a different font. The
   color stays lighter because the ground is dark: on-dark-muted is 6.31:1
   on navy where --c-ink-soft would be unreadable.
   ====================================================================== */

.whyus__body,
.whyus__points li {
  font-size: var(--t-body);
}

/* ======================================================================
   ONE BODY SIZE PER BLOCK

   Doug: "the bullets have a different font type". Measured across seven
   routes, every list is Manrope 400 in the same ink as the prose — the
   difference is SIZE. Components set their paragraphs to --t-small (16px)
   but nothing sets their lists, so a list falls back to the document body
   size of --t-body (17px). A card whose summary is 16px and whose bullets
   are 17px reads as two typefaces even though it is one.

   Fixed by putting the size on the BLOCK rather than on its paragraphs, so
   headings, prose and lists inside it all inherit the same value. That is
   the rule that stops this recurring the next time a component gains a
   list.
   ====================================================================== */

.svcother__card,
.areas__card,
.shop__card,
.rw__card,
.sg__card,
.pf__item {
  font-size: var(--t-small);
}

/* Lists never set their own size. They take the block's. */
.svcother__card li,
.areas__card li,
.shop__card li,
.rw__card li,
.sg__card li,
.pf__item li,
.svcinc__list li,
.spaceinc__list li,
.whencall__list li,
.pr__svcList li {
  font-size: inherit;
}

/* ======================================================================
   Area pages: when / why / logistics

   Three per-city fields that were synced and never rendered. Three short
   blocks side by side rather than stacked, because each is one paragraph
   and stacking them added a screen of scrolling to fifteen pages.
   ====================================================================== */

.areawhen__grid {
  display: grid;
  gap: var(--s-7);
}

@media (min-width: 60rem) {
  .areawhen__grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s-8);
  }
}

.areawhen__cell .h2 {
  margin-bottom: var(--s-4);
  font-size: var(--t-h3);
}

.areawhen__cell p {
  max-width: 44ch;
}

/* ======================================================================
   Bento arithmetic for SEVEN tiles

   Nursery and playroom split into separate pages, so the bento went from
   six tiles to seven. Both breakpoints were written for six: at 12 columns
   the spans ran 7+5 / 5+7 / 6+6 and a seventh tile fell through to the base
   span of 2, which is a sliver in a 12-column track and an orphan row.

   Every row sums exactly, which is the rule these grids keep breaking:
     6 columns   3+3 / 3+3 / 2+2+2
     12 columns  7+5 / 5+7 / 4+4+4
   ====================================================================== */

@media (min-width: 48rem) {
  .spaces--bento .spaces__item:nth-child(1),
  .spaces--bento .spaces__item:nth-child(2),
  .spaces--bento .spaces__item:nth-child(3),
  .spaces--bento .spaces__item:nth-child(4) { grid-column: span 3; }

  .spaces--bento .spaces__item:nth-child(5),
  .spaces--bento .spaces__item:nth-child(6),
  .spaces--bento .spaces__item:nth-child(7) { grid-column: span 2; }
}

@media (min-width: 60rem) {
  .spaces--bento .spaces__item:nth-child(1) { grid-column: span 7; }
  .spaces--bento .spaces__item:nth-child(2) { grid-column: span 5; }
  .spaces--bento .spaces__item:nth-child(3) { grid-column: span 5; }
  .spaces--bento .spaces__item:nth-child(4) { grid-column: span 7; }
  .spaces--bento .spaces__item:nth-child(5),
  .spaces--bento .spaces__item:nth-child(6),
  .spaces--bento .spaces__item:nth-child(7) { grid-column: span 4; }

  /* The two wide tiles keep the cinematic crop; the trio of four are squarer. */
  .spaces--bento .spaces__item:nth-child(5) img,
  .spaces--bento .spaces__item:nth-child(6) img,
  .spaces--bento .spaces__item:nth-child(7) img { aspect-ratio: 4 / 3; }
}

/* ======================================================================
   Footer: the logo

   The brand block carried a text wordmark while the header carried the real
   logo. The county-grouped coverage row that briefly lived here is gone —
   Doug called it redundant against the Service Areas panel in the nav, and
   he is right: seventeen city links twice on every page is not coverage,
   it is noise.
   ====================================================================== */

.foot__logo {
  display: inline-block;
  margin-bottom: var(--s-5);
}

.foot__logo img {
  display: block;
  width: auto;
  max-width: 13rem;
  height: auto;
}

/* ======================================================================
   Service grid: intro and category button on one row

   Home Organizing is the hub the four process services hang under, so it is
   not a fifth card — as one it orphaned itself on a second row with an empty
   half beside it. It sits above the grid instead, level with the sentence
   that explains what the four have in common, so the pair reads as one
   statement rather than three stacked elements.

   Copy is capped at 62ch: the row is wide and a sentence running its full
   width would be a 900px line.
   ====================================================================== */

.sg__lead {
  display: flex;
  flex-direction: column;
  gap: var(--s-5);
  margin-bottom: var(--s-7);
}

.sg__leadCopy {
  margin: 0;
  max-width: 62ch;
  color: var(--c-ink-soft);
}

.sg__leadCta {
  align-self: flex-start;
  flex: none;
}

@media (min-width: 60rem) {
  .sg__lead {
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-8);
  }

  .sg__leadCta { align-self: center; }
}

/* The heading sits closer to a row it introduces than to content proper. */
.sechead:has(+ .sg__lead) {
  margin-bottom: var(--s-4);
}
/* ==========================================================
   PORTFOLIO FILTER RAIL
   ==========================================================

   Doug asked for a left-side faceted rail three times before the page was
   buildable at all: /portfolio/ was Astro-exported HTML in post_content, so
   there were no projects for PHP to filter. inc/portfolio-copy.php holds them
   now and page-portfolio.php renders them.

   Lives in theme.css, not pages.css, because extract-css.mjs regenerates
   pages.css on every build and would delete this.

   Desktop is a genuine left rail. Mobile is two horizontal chip rows above the
   grid, because a vertical stack of eleven facets pushed the first photograph
   two screens down. No collapse, no toggle, no JS needed for either.
   ---------------------------------------------------------- */

.pf__layout {
  display: grid;
  gap: var(--s-7);
}

/*
 * min-width: 0 on both tracks.
 *
 * A grid item's min-width is auto, so the mobile facet row (a nowrap flex strip
 * with overflow-x: auto) sized its track to max-content instead of scrolling:
 * 1140px of rail inside a 390px viewport, and 770px of horizontal page scroll.
 * The results column needs the same guard for wide captions and any figure.
 */
.pf__rail,
.pf__results {
  min-width: 0;
}

@media (min-width: 960px) {
  .pf__layout {
    grid-template-columns: 17rem minmax(0, 1fr);
    gap: var(--s-8);
    align-items: start;
  }

  /* Sticky so the rail is reachable next to a long results column. */
  .pf__rail {
    position: sticky;
    top: var(--s-7);
  }
}

.pf__railHead {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  padding-bottom: var(--s-3);
  border-bottom: var(--border-hairline);
}

.pf__railTitle {
  margin: 0;
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink);
}

.pf__clear {
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-muted);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  transition: color var(--dur-fast) linear;
}

.pf__clear:hover {
  color: var(--c-action);
}

/* Kept in the DOM so the script can reveal it without building markup. */
.pf__clear.is-hidden {
  display: none;
}

.pf__group {
  margin-top: var(--s-5);
}

.pf__groupLabel {
  margin: 0 0 var(--s-3);
  font-family: var(--font-display);
  font-weight: var(--fw-regular);
  font-size: var(--t-small);
  color: var(--c-ink-muted);
}

.pf__facets {
  list-style: none;
  margin: 0;
  padding: 0;
}

.pf__facet {
  display: flex;
  align-items: center;
  gap: var(--s-2);
  padding-block: 0.4rem;
  font-size: var(--t-small);
  line-height: var(--lh-body);
  color: var(--c-ink);
  text-decoration: none;
  transition: color var(--dur-fast) linear;
}

.pf__facet:hover {
  color: var(--c-action);
}

.pf__facetMark {
  flex: 0 0 auto;
  width: 0.95rem;
  height: 0.95rem;
  border: 1px solid var(--c-border-ui);
  background: transparent;
  transition: background var(--dur-fast) linear, border-color var(--dur-fast) linear;
}

.pf__facet.is-on .pf__facetMark {
  background: var(--c-action);
  border-color: var(--c-action);
}

.pf__facetLabel {
  flex: 1 1 auto;
}

.pf__facet.is-on .pf__facetLabel {
  font-weight: var(--fw-medium);
}

.pf__facetCount {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-size: var(--t-micro);
  color: var(--c-ink-muted);
}

/*
 * A facet that would return nothing is dimmed but still clickable. Removing the
 * link would strand a keyboard user mid-rail, and the empty state below explains
 * itself if someone does click through.
 */
.pf__facet.is-empty {
  opacity: 0.4;
}

/* ---- mobile: chip rows, not a stacked rail ---- */

/*
 * THE CHIPS WRAP. They used to be a horizontal scroller and that was the cause
 * of /portfolio/ being 936px wide inside a 390px viewport.
 *
 * The scroller measured correctly at every level — .pf__rail, .pf__railInner and
 * .pf__group all 350px, .pf__facets 350px visible over 1168px of content, a
 * textbook internal scroll container — and the document was still 936. Five
 * fixes aimed at clipping were tried and every one of them measured 936 -> 936,
 * including the min-width: 0 block further down this file and overflow: hidden
 * on the scroller itself. What separates the fixes that work from the ones that
 * do not is not clipping: it is whether the content actually FITS. wrap, block
 * and contain all take the page to 390; hidden and an explicit 350px width do
 * nothing.
 *
 * So the row stops being a row that has to be scrolled. It is worth saying that
 * this is the better interface anyway. A filter you cannot see is a filter
 * nobody uses, and the second group has six chips of which two sat off-screen
 * with no scrollbar on a touch device to advertise them.
 */
@media (max-width: 959px) {
  .pf__facets {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-2);
    padding-bottom: var(--s-2);
  }

  .pf__facet {
    white-space: nowrap;
    padding: 0.35rem var(--s-3);
    border: 1px solid var(--c-border-ui);
    border-radius: var(--radius-pill);
  }

  .pf__facet.is-on {
    background: var(--c-action);
    border-color: var(--c-action);
    color: var(--c-on-dark);
  }

  .pf__facet.is-on .pf__facetCount {
    color: var(--c-on-dark-muted);
  }

  /* The square reads as a checkbox in a list and as noise in a chip. */
  .pf__facetMark {
    display: none;
  }
}

/* ---- results ---- */

.pf__count {
  margin: 0 0 var(--s-5);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-muted);
}

.pf__groupSection + .pf__groupSection {
  margin-top: var(--s-9);
}



.pf__empty {
  margin: 0;
  padding: var(--s-6) 0;
  color: var(--c-ink-muted);
}

/*
 * The reveal interaction is handled in assets/js/portfolio.js, which marks any
 * tile it un-hides as visible. Doing it there rather than with a blanket
 * opacity override keeps the normal scroll animation on first load.
 */

/* ======================================================================
   Card and tile titles: Montserrat 600

   The type system gives every display heading 300 and lets size carry the
   hierarchy. That holds from --t-h3 up. It stops holding on a card: at --t-h4 a
   300 title sits back FURTHER than the Manrope 400 body copy beneath it, so a
   bento tile read as a caption with a paragraph attached rather than as a titled
   card. Doug flagged the bento titles specifically, twice.

   600 only on titles that sit inside a card or tile. NOT on:
     .sg__title      asked for 400, not heavier: "Organizing and Decluttering",
                     "Move Concierge" were reading heavier than the section head.
     .phero__title   h1. Light at hero size is the whole look.
     .hero__title    as above.
     .svcproc__title --t-h3 and section-level, where 300 still reads correctly.

   Requires montserrat-latin-600.woff2, added to fonts.css in the same change.
   Without the file this synthesises off 500 and looks worse than the 300 it
   replaced, so the two must ship together.
   ====================================================================== */

.spaces--bento .spaces__title,
.spacearch__title,
.whyus__title,
.steps__title {
  font-weight: var(--fw-semibold);
}

/* ======================================================================
   ONE BODY SIZE (17px)

   The site was running two sizes for reading copy: --t-body 17px in prose, and
   --t-small 16px for the paragraph inside every card. Same role, two sizes, which
   is why card copy looked subtly different from page copy for no reason a reader
   could name.

   Everything below is READING copy and goes to 17px. --t-small keeps its other
   job, which is genuinely secondary text, and that is not a second body size:

     staying at 16px   figcaptions, review sources, step numbers, facts <dd>,
                       table cells, nav links, chips, counts, form labels,
                       .bento__cell p (a dense tile, deliberately tighter)

   These four files are GENERATED (components/home/pages/single), so the change
   has to be an override here rather than an edit at the source. That is also why
   the selectors are repeated verbatim instead of tidied.
   ====================================================================== */

.steps__body,
.whyus__body,
.eeat__whoBody,
.sg__summary,
.areas__blurb,
.svcproc__intro,
.spaceslist__note,
.svcwhen__note,
.pr__note,
.pr__asideNote,
.areas__card p,
.shop__card p,
.svc__other p,
.svc__procItem p,
.city__projItem p,
.ct__steps p,
.ab__belief p,
.svcother__card p {
  font-size: var(--t-body);
}

/* ======================================================================
   PAGE-HERO HEADINGS: BALANCE THE WRAP

   .phero__lead has text-wrap: pretty. .phero__title, directly above it, had no
   text-wrap rule at all, so the h1 broke on plain greedy line-breaking inside a
   column narrower than the container. On /faq/ that gave

     "Professional organizer questions,
      answered for Dallas-Fort
      Worth"

   — a three-word line, then a two-word orphan, in uppercase Montserrat at 0.06em
   tracking, where a short last line is far more conspicuous than it would be in
   body copy. Doug: "I'd like to see that flow a little bit better."

   `balance` rather than `pretty`: pretty optimises the LAST line and is meant for
   paragraphs, balance evens out every line and is meant for headings of a few
   lines, which is exactly this. Same call as .svcwhat__pull already makes.

   This is deliberately every page hero, not just /faq/ — the h1s on the area,
   room and service pages carry the same long geo strings and wrap the same way.
   Unsupported browsers fall back to today's behavior, so there is no downside.
   ====================================================================== */

.phero__title {
  text-wrap: balance;
}

/* ======================================================================
   BACK TO TOP: LEGIBLE ALL THE WAY DOWN

   assets/js/back-to-top.js used to hide this button as soon as the footer
   appeared, which meant it disappeared about a screen before the bottom of the
   page. That observer is gone (see the file's own comment), so the button now
   floats over the black footer for the last stretch of every page — and it needs
   to survive that trip.

   --c-action (#954429) on --c-black (#000) is roughly 2:1 at the SURFACE. The
   label is fine either way, being --c-on-dark on the rust, but the tile's own
   edge dissolves into the footer, and --shadow-md contributes nothing on black
   because a shadow needs something lighter to darken.

   So: a hairline ring, listed BEFORE the shadow so both apply. On the cream page
   body the ring is barely perceptible against the button's own edge; on the
   footer it is the thing that says a control is there. One declaration, no
   second observer, no state to get wrong, and nothing to keep in sync with
   whatever the footer's background becomes later.

   Not a background swap on a --onDark modifier: that needs JS to know where the
   button is, which is the coupling that produced the original bug.
   ====================================================================== */

a.totop {
  box-shadow:
    0 0 0 1px rgba(237, 232, 228, 0.32),
    var(--shadow-md);
}

/* ======================================================================
   LANDING PAGES

   Both landers shipped with 33 lp__ / lpform__ classes in the markup and ZERO
   CSS rules for any of them, so they rendered as unstyled HTML. This is that CSS.

   DELIBERATELY UNBALANCED, which is the brief and also the right call here. The
   rest of the site is composed and symmetrical because it is being browsed. A
   paid lander is not browsed, it is scanned once, and the eye needs somewhere
   specific to land. So:

     - the hero splits 1.15 / 0.85, not 50 / 50
     - the form is a cream panel on black, the only light block above the fold
     - it hangs BELOW the hero's bottom edge and overlaps the proof bar, so the
       fold line runs through it and the page reads as continuing rather than
       ending
     - the copy column is capped near 46ch while the form gets whatever is left

   Everything above the fold points at one panel. There is no second option and
   no navigation, which is what separates a lander from a home page.
   ====================================================================== */

/* ---------- stripped nav ---------- */

.lpnav {
  background: var(--c-black);
  padding-block: var(--s-3);
}

.lpnav__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
}

/* Not a link. Home is an exit on a page that cost money to reach. */
.lpnav__logo {
  display: block;
  width: auto;
  height: 2.25rem;
}

.lpnav__wordmark {
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-small);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-on-dark);
}

.lpnav__aside {
  display: flex;
  align-items: center;
  gap: var(--s-4);
}

.lpnav__rating {
  display: none; /* shown from 40rem; on a phone the phone number wins */
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  color: var(--c-on-dark-muted);
}

.lpnav__stars {
  color: var(--c-taupe-light);
  letter-spacing: 0.08em;
}

.lpnav__phone {
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-small);
  color: var(--c-on-dark);
  text-decoration: none;
  white-space: nowrap;
}

.lpnav__phone:hover {
  color: var(--c-taupe-light);
}

@media (min-width: 40rem) {
  .lpnav__rating {
    display: inline-flex;
    align-items: center;
    gap: var(--s-2);
  }
}

/* ---------- hero ---------- */

.lp__hero {
  background: var(--c-black);
  color: var(--c-on-dark);
  padding-block: var(--s-8) var(--s-9);
}

/*
 * The photographed hero.
 *
 * Flat black stays underneath as the base, so if the attachment is ever missing
 * the hero is exactly the design that shipped before rather than a broken box.
 * .lp__hero--photo is only added when the image resolved.
 */
.lp__hero--photo {
  position: relative;
  isolation: isolate;
}

.lp__heroBg {
  position: absolute;
  inset: 0;
  /*
   * -2, BELOW the overlay at -1.
   *
   * Both were -1 first time and the overlay did nothing: at equal z-index paint
   * order follows the DOM, ::before is painted before element children, so the
   * photograph landed on top of the wash. Sampling the rendered pixels behind the
   * headline gave 255,255,255 and a contrast ratio of 1.03:1 — white text on a
   * bright photograph. It would have looked deliberate in a screenshot of the dark
   * left edge and been unreadable on the right.
   */
  z-index: -2;
  overflow: hidden;
}

.lp__heroBg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /*
   * Top of the frame, not the center. Every photograph in the library is portrait,
   * so a wide hero keeps roughly a third of the height: centerd, that third lands
   * on drawer fronts and floor. The top third is where the shelving and the hanging
   * rails are, which is the part that reads as "organized" at a glance.
   */
  object-position: 50% 22%;
}

/*
 * THE OVERLAY, and it is a gradient rather than a flat wash.
 *
 * Flat 85% black would be safe everywhere and would waste the photograph. The copy
 * sits in the left column and the form panel covers the right, so the overlay is
 * densest on the left where text needs the contrast and lightest at the right edge
 * where nothing but the photograph is behind it. The picture survives where it can
 * be seen and gets out of the way where it cannot.
 *
 * Contrast was measured, not guessed: sampled behind the h1, the sub-headline and
 * the tick list at four viewport widths. See the note in TODO.md.
 */
.lp__hero--photo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(
      100deg,
      rgba(0, 0, 0, 0.94) 0%,
      rgba(0, 0, 0, 0.9) 42%,
      rgba(0, 0, 0, 0.74) 78%,
      rgba(0, 0, 0, 0.66) 100%
    );
}

/*
 * Mobile has no room for the gradient to do anything useful: the copy spans the
 * full width, so the whole overlay has to carry text contrast.
 */
@media (max-width: 59.999rem) {
  .lp__hero--photo::before {
    background: rgba(0, 0, 0, 0.9);
  }
}

.lp__heroGrid {
  display: grid;
  gap: var(--s-7);
}

.lp__eyebrow {
  margin: 0 0 var(--s-4);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-taupe-light);
}

.lp__h1 {
  margin: 0 0 var(--s-4);
  /* Capped in ch, not px, because a DKI headline's length changes with the city.
     26ch holds "Highland Park" (the longest area name) to four lines and "Plano"
     to three. 22ch pushed the longest city to five, which at hero size filled the
     column and shoved the proof below the fold. */
  max-width: 26ch;
  color: var(--c-on-dark);
}

.lp__sub {
  margin: 0 0 var(--s-5);
  max-width: 46ch;
  font-size: var(--t-lead);
  line-height: var(--lh-body);
  color: var(--c-on-dark-muted);
  text-wrap: pretty;
}

.lp__ticks {
  margin: 0 0 var(--s-5);
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-2);
  max-width: 46ch;
}

.lp__ticks li {
  position: relative;
  padding-left: 1.6rem;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--c-on-dark);
}

/* A tick drawn in CSS rather than an icon font or an emoji: emoji render
   differently on every platform and one of them is a checkbox. */
.lp__ticks li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.45em;
  width: 0.7rem;
  height: 0.38rem;
  border-left: 2px solid var(--c-taupe-light);
  border-bottom: 2px solid var(--c-taupe-light);
  transform: rotate(-45deg);
}

.lp__trust {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  font-size: var(--t-small);
  color: var(--c-on-dark-muted);
}

.lp__trust strong {
  color: var(--c-on-dark);
}

.lp__stars {
  color: var(--c-taupe-light);
  letter-spacing: 0.1em;
}

/* ---------- the form panel ---------- */

.lp__formWrap {
  background: var(--c-cream);
  color: var(--c-ink);
  padding: var(--s-6);
  box-shadow: var(--shadow-lg);
}

/* In the dark closing band the panel is already on black, so it keeps the cream
   fill but drops the shadow, which does nothing against a dark background. */
.lp__formWrap--dark {
  box-shadow: none;
}

@media (min-width: 60rem) {
  .lp__heroGrid {
    /* The unbalanced split. Copy leads, the form is the destination. */
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    gap: var(--s-8);
    align-items: start;
  }

  /*
   * The panel is LIFTED, not dropped.
   *
   * The first attempt hung it below the hero to overlap the proof bar. That does
   * not work here and the reason is worth recording: the copy column is the
   * taller of the two, so it sets the hero's height, and a negative bottom
   * margin on the shorter column has nothing to push past. It left a 61px gap
   * and, with the proof bar's compensating top padding, a band of dead black
   * across the fold.
   *
   * Raising it works regardless of which column is taller, because the hero's own
   * top padding absorbs the shift. The form now starts above the eyebrow line,
   * which is the asymmetry doing its job: the two columns do not begin together.
   */
  .lp__formWrap {
    margin-top: calc(var(--s-5) * -1);
    position: relative;
    z-index: var(--z-raised);
  }
}

/* ---------- proof bar ---------- */

.lp__proof {
  background: var(--c-cream-deep);
  padding-block: var(--s-6);
}

.lp__proofList {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-5);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  text-align: center;
}

.lp__proofList li {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.lp__proofList strong {
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h3);
  line-height: 1.1;
  color: var(--c-ink);
}

.lp__proofList span {
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-muted);
}

@media (min-width: 48rem) {
  .lp__proofList {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* ---------- sections ---------- */

.lp__sectionHead {
  margin: 0 0 var(--s-6);
  max-width: 34ch;
}

.lp__svcGrid {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-6);
}

.lp__svcCard img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  margin-bottom: var(--s-3);
}

.lp__svcCard h3 {
  margin: 0 0 var(--s-2);
  font-size: var(--t-h4);
  font-weight: var(--fw-semibold);
}

.lp__svcCard p {
  margin: 0;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--c-ink-muted);
}

@media (min-width: 48rem) {
  .lp__svcGrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/*
 * .lp__svcGrid and .lp__svcCard are GONE from the markup.
 *
 * That section now calls template-parts/service-grid with linked => false, which
 * is the homepage's own component, so it uses .sg__ classes and the CSS that
 * already exists for them. The four-equal-columns rule and the bento-style 4/2
 * grid that briefly replaced it are both deleted rather than left to rot.
 */

/* ---------- objections ---------- */

.lp__objGrid {
  display: grid;
  gap: var(--s-6);
}

.lp__obj {
  border-top: var(--border-hairline);
  padding-top: var(--s-4);
}

.lp__objQ {
  margin: 0 0 var(--s-2);
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--t-h4);
  line-height: var(--lh-heading);
}

.lp__obj p {
  margin: 0 0 var(--s-3);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--c-ink-muted);
}

.lp__obj p:last-child {
  margin-bottom: 0;
}

@media (min-width: 48rem) {
  .lp__objGrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-6) var(--s-7);
  }
}

/* ---------- closing band ---------- */

.lp__closeGrid {
  display: grid;
  gap: var(--s-7);
}

.lp__closeBody {
  margin: var(--s-4) 0;
  max-width: 46ch;
  font-size: var(--t-lead);
  line-height: var(--lh-body);
  color: var(--c-on-dark-muted);
}

.lp__closeCall {
  margin: 0;
  font-size: var(--t-body);
  color: var(--c-on-dark-muted);
}

.lp__closeCall a {
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  color: var(--c-on-dark);
}

@media (min-width: 60rem) {
  .lp__closeGrid {
    /* Mirrored from the hero, so the page closes the way it opened. */
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    gap: var(--s-8);
    align-items: center;
  }
}

/* ---------- the form itself ---------- */

.lpform__title {
  margin: 0 0 var(--s-2);
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--t-h4);
  line-height: var(--lh-heading);
  color: var(--c-ink);
}

.lpform__note {
  margin: 0 0 var(--s-4);
  font-size: var(--t-small);
  color: var(--c-ink-muted);
}

.lpform__row {
  display: grid;
  gap: var(--s-3);
}

@media (min-width: 30rem) {
  .lpform__row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.lpform__field {
  margin-bottom: var(--s-3);
}

.lpform__row .lpform__field {
  margin-bottom: 0;
}

.lpform__field label {
  display: block;
  margin-bottom: 0.35rem;
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-muted);
}

.lpform__field input,
.lpform__field select {
  width: 100%;
  padding: 0.7rem 0.8rem;
  font-family: var(--font-body);
  /* 16px minimum. Anything smaller makes iOS Safari zoom the page on focus,
     which on a lander means the form jumps out of view as it is being filled. */
  font-size: 1rem;
  color: var(--c-ink);
  background: #fff;
  border: 1px solid var(--c-border-ui);
  border-radius: 0;
  appearance: none;
}

.lpform__field select {
  /* A chevron, so an appearance:none select does not look like a dead text box. */
  background-image: linear-gradient(45deg, transparent 50%, var(--c-ink-soft) 50%),
    linear-gradient(135deg, var(--c-ink-soft) 50%, transparent 50%);
  background-position: calc(100% - 1.1rem) 1.15rem, calc(100% - 0.75rem) 1.15rem;
  background-size: 0.35rem 0.35rem, 0.35rem 0.35rem;
  background-repeat: no-repeat;
  padding-right: 2.2rem;
}

.lpform__field input:focus-visible,
.lpform__field select:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 1px;
}

.lpform__consent {
  margin: 0 0 var(--s-4);
  padding: 0;
  border: 0;
}

.lpform__consent legend {
  padding: 0;
  margin-bottom: 0.35rem;
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-muted);
}

.lpform__consent label {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.25rem;
  font-size: var(--t-small);
  color: var(--c-ink);
}

.lpform__submit {
  width: 100%;
  justify-content: center;
}

.lpform__fine {
  margin: var(--s-3) 0 0;
  font-size: var(--t-micro);
  line-height: 1.5;
  color: var(--c-ink-soft);
}

/* ---------- sticky mobile CTA ---------- */

.lpbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--z-sticky);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1px;
  background: var(--c-black);
}

.lpbar__call,
.lpbar__form {
  padding: 0.9rem var(--s-4);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-small);
  letter-spacing: var(--ls-button);
  text-align: center;
  text-decoration: none;
}

.lpbar__call {
  color: var(--c-on-dark);
  background: var(--c-black);
}

.lpbar__form {
  color: var(--c-on-dark);
  background: var(--c-action);
}

/*
 * Desktop has the form beside the headline already, so a fixed bar would cover
 * content to solve a problem that does not exist. Hidden, not display:none on a
 * parent, so the anchor stays reachable if it is ever linked to.
 */
@media (min-width: 60rem) {
  .lpbar {
    display: none;
  }
}

/* The bar sits over the closing band, so the page needs room beneath it. */
@media (max-width: 59.999rem) {
  body.mbos-landing {
    padding-bottom: var(--mobile-bar-h);
  }
}

/* ---------- legal footer ---------- */

.lpfoot {
  background: var(--c-cream-deep);
  padding-block: var(--s-6);
}

.lpfoot__inner {
  display: grid;
  gap: var(--s-2);
  text-align: center;
}

.lpfoot__who,
.lpfoot__legal {
  margin: 0;
  font-size: var(--t-small);
  color: var(--c-ink-muted);
}

.lpfoot__sep {
  margin-inline: 0.4rem;
  color: var(--c-ink-soft);
}

.lpfoot a {
  color: var(--c-ink);
}

/*
 * Grid children need min-width: 0. Both hero columns and both closing-band
 * columns are grid items, whose default min-width is auto, so a long unbroken
 * string (a DKI city, a phone number, an email in the fine print) sizes the track
 * to its content and pushes the page sideways. Same bug that made the portfolio
 * filter rail 1140px wide inside a 390px viewport.
 */
.lp__heroCopy,
.lp__formWrap,
.lp__closeGrid > * {
  min-width: 0;
}

/*
 * Section hooks. These carry no styling of their own: .section already sets the
 * vertical rhythm and .section--cream / --dark set the fill. They exist so a
 * later change can target one band without touching the others, and they are
 * listed here so the orphan-class audit does not read them as dead markup.
 */
.lp__services,
.lp__steps,
.lp__objections {
  /* intentionally empty */
}

/* ======================================================================
   QUIZ LANDER

   Restructured 9 Aug 2026 after Doug shared txinjuryreview.com as the target.
   It was a two-column split, copy left and flow right. Their pattern is better
   for this job and it is now the pattern here:

     a full-bleed photograph, darkened
     a progress meter ABOVE the card, with the count and the time estimate
     one centerd card holding the H1, the promise, and the current question
     reassurance underneath, on the dark background

   Why it beats the split: a split gives the eye two places to start. This gives it
   one, and the card's edges do the work of saying "this is a short, finite thing"
   before a single word is read. The count and "About 40 seconds" answer the only
   real objection to a quiz, which is never the questions, it is not knowing how
   many there are.
   ====================================================================== */

.lpq {
  position: relative;
  isolation: isolate;
  background: var(--c-black);
  color: var(--c-on-dark);
  padding-block: var(--s-7) var(--s-8);
}

.lpq__bg {
  position: absolute;
  inset: 0;
  /* -2, below the overlay at -1. Equal z-index paints in DOM order and the photo
     would cover the wash, which is the bug the standard lander's hero hit. */
  z-index: -2;
  overflow: hidden;
}

.lpq__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Upper third: the shelves and the labeled bins, not the floor. */
  object-position: 50% 25%;
}

/*
 * Flat, not a gradient. The standard lander's hero grades left-to-right because
 * its copy sits in one column; here the card is centerd, so every edge needs the
 * same contrast behind it. Slightly lighter than that hero's 90% because nothing
 * but the card sits on top, and the card carries its own background.
 */
.lpq--photo::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: rgba(0, 0, 0, 0.82);
}

.lpq__wrap {
  width: 100%;
  max-width: 44rem;
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ---------- the meter, above the card ---------- */

.lpq__progress {
  margin-bottom: var(--s-4);
}

.lpq__meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-3);
  margin-bottom: var(--s-2);
  font-family: var(--font-display);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-on-dark-muted);
}

.lpq__meta b {
  color: var(--c-on-dark);
  font-weight: var(--fw-medium);
}

.lpq__bar {
  height: 3px;
  /* On the dark background the track has to be a lightened black rather than the
     UI border color, which disappears entirely here. */
  background: rgba(255, 255, 255, 0.18);
  overflow: hidden;
}

.lpq__bar span {
  display: block;
  height: 100%;
  background: var(--c-taupe-light);
  transition: width var(--dur-base) var(--ease-out);
}

/* ---------- the card ---------- */

.lpq__card {
  background: var(--c-cream);
  color: var(--c-ink);
  padding: var(--s-6) var(--s-5) var(--s-6);
  box-shadow: var(--shadow-lg);
  text-align: center;
}

.lpq__eyebrow {
  margin: 0 0 var(--s-3);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-muted);
}

.lpq__h1 {
  margin: 0 auto var(--s-3);
  max-width: 24ch;
  /*
   * --t-h2, NOT the hero size .display gives the standard lander.
   *
   * The card is 624px wide and the headline is centerd inside it. At hero size the
   * H1 filled five lines and the first question sat below the fold, which defeats
   * the entire mechanic: the question has to be the first thing you see.
   */
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h2);
  line-height: var(--lh-heading);
  color: var(--c-ink);
  text-wrap: balance;
}

.lpq__sub {
  margin: 0 auto var(--s-6);
  max-width: 44ch;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--c-ink-muted);
  text-wrap: pretty;
}

/* ---------- the flow ---------- */

.lpq__form {
  text-align: left;
}

.lpq__step {
  margin: 0;
  padding: 0;
  border: 0;
}

/*
 * The question. A legend, so the choices below are genuinely labeled as a group,
 * and it carries tabindex="-1" so the script can move focus here when the question
 * changes. Centerd to match the card, then the choices go back to left-aligned
 * because a column of centerd labels is unreadable.
 */
.lpq__q {
  width: 100%;
  padding: 0;
  margin-bottom: var(--s-4);
  font-family: var(--font-display);
  font-weight: var(--fw-semibold);
  font-size: var(--t-h4);
  line-height: var(--lh-heading);
  color: var(--c-ink);
  text-align: center;
  text-wrap: pretty;
}

.lpq__q:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 4px;
}

.lpq__help {
  margin: calc(var(--s-3) * -1) 0 var(--s-4);
  font-size: var(--t-small);
  color: var(--c-ink-muted);
  text-align: center;
}

.lpq__choices {
  display: grid;
  gap: var(--s-2);
}

/*
 * A choice is a whole tappable row, not a radio with a label beside it. The input
 * stays in the DOM and focusable; the row shows the state.
 */
.lpq__choice {
  position: relative;
  display: flex;
  align-items: center;
  padding: 0.85rem var(--s-4);
  background: #fff;
  border: 1px solid var(--c-border-ui);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  cursor: pointer;
  transition:
    border-color var(--dur-fast) linear,
    background var(--dur-fast) linear;
}

.lpq__choice:hover {
  border-color: var(--c-ink-soft);
}

.lpq__choice input {
  /* 1px rather than display:none, so it stays focusable and the focus ring below
     has something to hang off. */
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
}

.lpq__choice span {
  position: relative;
  padding-left: 1.9rem;
}

.lpq__choice span::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.15em;
  width: 1.05rem;
  height: 1.05rem;
  border: 1px solid var(--c-border-ui);
  border-radius: 50%;
  background: #fff;
}

.lpq__choice span::after {
  content: "";
  position: absolute;
  left: 0.28rem;
  top: 0.43em;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: var(--c-action);
  transform: scale(0);
  transition: transform var(--dur-fast) var(--ease-out);
}

.lpq__choice:has(input:checked) {
  border-color: var(--c-action);
  background: var(--c-cream-light);
}

.lpq__choice:has(input:checked) span::before {
  border-color: var(--c-action);
}

.lpq__choice:has(input:checked) span::after {
  transform: scale(1);
}

.lpq__choice:has(input:focus-visible) {
  outline: 2px solid var(--c-focus);
  outline-offset: 2px;
}

/* The last step is a real form, not choices. */
.lpq__row {
  display: grid;
  gap: var(--s-3);
}

@media (min-width: 30rem) {
  .lpq__row {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .lpq__row .lpform__field {
    margin-bottom: 0;
  }
}

.lpq__nav {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  margin-top: var(--s-5);
}

.lpq__nav [data-next],
.lpq__nav [data-submit] {
  flex: 1 1 auto;
  justify-content: center;
}

/* ---------- reassurance, below the card on the dark ---------- */

.lpq__ticks {
  margin: var(--s-5) 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: var(--s-2);
  justify-content: center;
}

.lpq__ticks li {
  position: relative;
  padding-left: 1.6rem;
  font-size: var(--t-small);
  color: var(--c-on-dark);
}

.lpq__ticks li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.4em;
  width: 0.7rem;
  height: 0.38rem;
  border-left: 2px solid var(--c-taupe-light);
  border-bottom: 2px solid var(--c-taupe-light);
  transform: rotate(-45deg);
}

.lpq__proof {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  margin-top: var(--s-4);
  font-size: var(--t-small);
  color: var(--c-on-dark-muted);
  text-align: center;
}

.lpq__stars {
  color: var(--c-taupe-light);
  letter-spacing: 0.08em;
}

@media (min-width: 48rem) {
  .lpq {
    padding-block: var(--s-8) var(--s-9);
  }

  .lpq__card {
    padding: var(--s-7) var(--s-7) var(--s-7);
  }

  .lpq__q {
    font-size: var(--t-h3);
  }
}

/*
 * The quiz keeps the sticky bar and therefore keeps the body padding.
 *
 * This rule used to zero it, on the reasoning that Continue was the only action and
 * was always in view. That was true while the quiz was a hero and nothing else. It
 * now carries the same page as the standard lander underneath, ending in the same
 * closing form, so without the padding the bar covers that form's fine print.
 */

/*
 * Full-length SMS consent labels.
 *
 * The short version ("Yes, texting is fine") was one line, so centring the radio
 * against it was right. The compliant wording runs to three lines on a phone, and
 * a centerd radio then sits beside the middle of the sentence looking unattached
 * to it. Top-aligned, nudged down to meet the first line's cap height.
 *
 * Also applies to the quiz lander's last step, which has carried this wording from
 * the start.
 */
.lpform__consent--full label,
.lpq__step .lpform__consent label {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.55rem;
  align-items: start;
  margin-bottom: var(--s-3);
  font-size: var(--t-small);
  line-height: var(--lh-body);
}

.lpform__consent--full label input,
.lpq__step .lpform__consent label input {
  margin: 0;
  transform: translateY(0.22em);
}

/* The two options need separating once each is a paragraph rather than a phrase. */
.lpform__consent--full label:last-child,
.lpq__step .lpform__consent label:last-child {
  margin-bottom: 0;
}

/* ======================================================================
   [hidden] MUST WIN

   The UA stylesheet sets [hidden] { display: none }, but that is the weakest
   possible declaration: ANY author rule that sets display beats it. .btn sets
   display: inline-flex, so the quiz's Back and Submit buttons carried the hidden
   attribute and stayed on screen anyway — all three nav buttons visible on
   question one, with the submit button offering to book a call before a single
   question had been answered.

   Worth noting how it got past a test: the script sets the attribute correctly, so
   asserting on element.hidden passed. The attribute was right and the pixels were
   wrong. Only the screenshot showed it.

   Site-wide, because everything here that toggles visibility from script uses the
   hidden attribute: the quiz steps and buttons, the portfolio tiles and groups,
   the clear-filters link.
   ====================================================================== */

[hidden] {
  display: none !important;
}

/* ======================================================================
   REVIEW-PLATFORM BADGES

   Two logos, no label, sitting with the star rating in the hero. They were in the
   cream proof band and at the foot of the quiz card first; Doug moved them up to
   the five-star line, which is where the eye is already looking for proof.

   BOTH HERO POSITIONS ARE DARK, and Thumbtack ship their wordmark in solid black,
   so tone--light inverts them. Greyscale in both tones: Google's four brand colors
   and Thumbtack's blue next to a terracotta CTA would be three things competing for
   the same attention, and these are proof rather than decoration.
   ====================================================================== */

.lpbadges {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  flex-wrap: wrap;
}

/*
 * Sized by HEIGHT, per logo, never by a shared width. Google Reviews is 2.5:1 with
 * two stacked lines; Thumbtack is 7:1 and a single word. Matching widths makes
 * Thumbtack tower over Google. These two values were set so the lettering in each
 * reads at about the same size.
 */
.lpbadges__logo {
  display: block;
  width: auto;
}

.lpbadges__logo--google {
  height: 1.9rem;
}

.lpbadges__logo--thumbtack {
  height: 0.95rem;
}

/* On a dark background: greyscale, then invert to a light mark. */
.lpbadges--light .lpbadges__logo {
  filter: grayscale(100%) invert(1);
  opacity: 0.8;
}

/* On a light background: greyscale only. */
.lpbadges--dark .lpbadges__logo {
  filter: grayscale(100%);
  opacity: 0.7;
}

/*
 * The hero trust row becomes stars + rating + two logos, which is too much for one
 * line on a phone. Wrap, and give the logos their own line rather than letting one
 * of them orphan.
 */
.lp__trust {
  flex-wrap: wrap;
}

@media (max-width: 37.499rem) {
  .lp__trust .lpbadges,
  .lpq__proof .lpbadges {
    flex-basis: 100%;
    margin-top: var(--s-2);
  }
}

.lpq__proof {
  flex-wrap: wrap;
}

/*
 * The .owner--compact rules are GONE with the variant they styled. Doug asked for
 * the Ashley section to be identical to the homepage's on the landers, so there is
 * nothing left to override.
 */

/* ======================================================================
   CLUTTER-QUIZ POPUP

   A native <dialog> opened with showModal(), same as the consultation modal, so
   the focus trap, the Esc key and the inert background come from the platform
   rather than from a script trying to reimplement them.

   Deliberately small and low-drama. A full-screen takeover on a phone reads as an
   ad; a card that covers a third of the screen reads as an offer.
   ====================================================================== */

.cp {
  width: min(28rem, calc(100vw - 2 * var(--gutter)));
  padding: 0;
  border: 0;
  background: var(--c-cream);
  color: var(--c-ink);
  box-shadow: var(--shadow-lg);
}

.cp::backdrop {
  background: rgba(0, 0, 0, 0.55);
}

.cp__inner {
  position: relative;
  padding: var(--s-6) var(--s-5) var(--s-5);
  text-align: center;
}

.cp__close {
  position: absolute;
  top: 0.35rem;
  right: 0.35rem;
  /* 44px, because this is the control someone reaches for in a hurry and on a
     phone. An 18px glyph in a 44px target. */
  width: 2.75rem;
  height: 2.75rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.15rem;
  line-height: 1;
  color: var(--c-ink-muted);
  background: none;
  border: 0;
  cursor: pointer;
}

.cp__close:hover {
  color: var(--c-ink);
}

.cp__eyebrow {
  margin: 0 0 var(--s-2);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-muted);
}

.cp__title {
  margin: 0 auto var(--s-3);
  max-width: 20ch;
  font-family: var(--font-display);
  font-weight: var(--fw-light);
  font-size: var(--t-h3);
  line-height: var(--lh-heading);
  color: var(--c-ink);
  text-wrap: balance;
}

.cp__body {
  margin: 0 auto var(--s-5);
  max-width: 38ch;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--c-ink-muted);
  text-wrap: pretty;
}

.cp__actions {
  display: grid;
  gap: var(--s-3);
  justify-items: center;
}

.cp__go {
  width: 100%;
  justify-content: center;
}

/*
 * "No thanks" is a real button and it is not disguised as small print. A dismissal
 * that is hard to find is the thing everyone hates about these, and it costs more
 * goodwill than the popup earns.
 */
.cp__dismiss {
  padding: 0.25rem 0.5rem;
  font-size: var(--t-small);
  color: var(--c-ink-muted);
  background: none;
  border: 0;
  border-bottom: 1px solid currentColor;
  cursor: pointer;
}

.cp__dismiss:hover {
  color: var(--c-ink);
}

/* Entrance. Skipped entirely when the visitor prefers reduced motion: the script
   only adds .is-entering when they do not. */
.cp.is-entering {
  animation: cp-in var(--dur-base) var(--ease-out);
}

@keyframes cp-in {
  from {
    opacity: 0;
    transform: translateY(0.75rem);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* ======================================================================
   THE ASHLEY SECTION, EVERYWHERE

   .owner__grid's layout lived only in home.css, and home.css is enqueued only on
   the front page (see inc/enqueue.php: front-page gets home.css, services and areas
   get single.css, everything else gets pages.css). So on the landing pages there
   was no grid: the media column spanned the full container and a 1620x2160 portrait
   filled it. That is the "Ashley image is still massive" report, and it was never a
   design decision — the CSS simply was not on the page.

   Duplicated here rather than moved, because home.css is REGENERATED by
   extract-css.mjs on every build and an edit there is deleted at build time. This is
   the documented reason theme.css exists.

   theme.css loads last on every template, so these values now govern the homepage
   too. That is deliberate: Doug asked for the section to be identical on both, and
   identical means one set of numbers.
   ====================================================================== */

.owner__grid {
  display: grid;
  gap: var(--s-7);
}

@media (min-width: 56rem) {
  .owner__grid {
    /* The photograph takes less than half. Ashley is the subject of the copy, and
       the portrait is evidence beside it rather than the point. */
    grid-template-columns: minmax(0, 0.8fr) minmax(0, 1fr);
    gap: var(--s-9);
    align-items: center;
  }
}

/*
 * A HEIGHT CAP, which home.css never had.
 *
 * aspect-ratio: 3 / 4 alone means the taller the column, the taller the image, and
 * at wide viewports the portrait ran past the copy beside it and set the height of
 * the whole section. Capping it keeps the crop and stops the photograph deciding how
 * tall the section is. object-fit: cover so the cap crops rather than squashes.
 */
.owner__media img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 4;
  max-height: 30rem;
  object-fit: cover;
  object-position: 50% 30%;
}

/* ======================================================================
   MID-PAGE CTA: A HORIZONTAL STRIP

   This variant was a mess and it was mine. .cta--narrow .cta__inner was declared
   TWICE with conflicting intent, one of the two setting flex-direction on an
   element that components.css makes display: grid, so that declaration did
   nothing at all. Combined with the base .cta__actions (flex-direction: column,
   min-width: 20rem) and .cta__actions .btn (width: 100%), the result on /about/
   was a 768px centerd column with centerd text above two stacked 312px buttons,
   642px tall for one sentence and two links. Doug called it a center wrap and it
   was exactly that.

   It is a STRIP now: copy on the left, the two actions on the right, one row, the
   full container width. Which is what a mid-page CTA between two content sections
   should be — it interrupts the reading for a moment rather than stopping the page
   like the closing band does.

   Every declaration below exists to beat a base rule, which is why they look
   heavy-handed:
     grid-template-columns   overrides the single-column narrow layout
     text-align: left        the base centerd it
     flex-direction: row     the base .cta__actions stacks
     min-width: 0            the base sets 20rem, which forced the stack
     .btn width: auto        the base sets 100%
   ====================================================================== */

.cta--narrow {
  /* Tighter than a closing band. It is a pause, not an ending. */
  padding-block: var(--s-6);
}

/*
 * NOW A CENTERED STACK WITH A FUSED BUTTON PAIR.
 *
 * It was a left/right strip: copy left, actions right. Doug supplied a reference
 * from another site — a filled button and an outline button butted together with
 * a round OR chip on the seam — and asked for "the buttons exactly, same
 * alignment, same placement, Messy Buns branding", with "Free Discovery Call" as
 * the primary.
 *
 * So the whole block centers and the two buttons become one object. The heading
 * and its line stay above it: the reference crop showed buttons alone, but a
 * mid-page CTA with no copy loses the "the call is free, it takes fifteen
 * minutes" promise, which is the reason anyone clicks it.
 */
.cta--narrow .cta__inner {
  display: grid;
  /* ONE column. components.css sets .cta__inner to
     minmax(0, 1.4fr) minmax(0, 1fr) for the closing band, and without this the
     narrow variant inherits it and stays a left/right strip no matter what the
     alignment properties below say. */
  grid-template-columns: 1fr;
  gap: var(--s-5);
  text-align: center;
  justify-items: center;
  align-items: center;
}

.cta--narrow .cta__body {
  margin-top: var(--s-2);
  /* No measure at all: the strip's own column is the constraint, and a 46ch cap
     on top of a 524px column is what made a twelve-word sentence wrap to three
     lines. */
  max-width: none;
  margin-inline: 0;
  font-size: var(--t-body);
}

.cta--narrow .cta__inner > div:first-child {
  /* Keep the two lines from breaking into orphans as the column narrows. */
  text-wrap: pretty;
}

.cta--narrow .cta__inner > div:first-child h2 {
  margin: 0;
  /* --t-h3, not --t-h2. At h2 size it competed with the real section headings
     either side of it and read as a new section rather than an aside. */
  font-size: var(--t-h3);
}

/*
 * THE FUSED PAIR.
 *
 * gap: 0 is the whole device — the two buttons share an edge instead of sitting
 * apart, so they read as one control with two ways through it. --radius is
 * already 0 site-wide, so there are no inner corners to unround.
 *
 * The outline button's left border is removed rather than both buttons keeping
 * their own: two 1px borders meeting would render a 2px seam against the
 * primary's flat fill, which is the one place the join would show.
 */
.cta--narrow .cta__actions {
  display: flex;
  flex-direction: row;
  flex-wrap: nowrap;
  align-items: stretch;
  justify-content: center;
  gap: 0;
  min-width: 0;
  /* Anchors the badge, which is positioned against this box. */
  position: relative;
}

.cta--narrow .cta__actions .btn {
  width: auto;
  min-height: 0;
}

.cta--narrow .cta__actions .cta__call {
  border-left-color: transparent;
}

/*
 * Clearance for the chip.
 *
 * The badge is 2.25em and sits centerd on the seam, so it reaches 1.125em into
 * each button. Without this the labels run straight under it — "FREE DISCOVERY
 * CALL" collided with the O. Each facing edge gets its normal padding plus that
 * overhang, which is why the value is expressed as the sum rather than a round
 * number someone would later "tidy".
 */
.cta--narrow .cta__actions .btn--primary {
  padding-right: calc(var(--s-6) + 1.125em);
}

.cta--narrow .cta__actions .cta__call {
  padding-left: calc(var(--s-6) + 1.125em);
}

/*
 * The OR chip, centerd on the seam.
 *
 * Sized in em so it tracks the button type rather than needing a magic number,
 * and translated by exactly half its own size on both axes so it stays centerd
 * on the join whatever the buttons do.
 */
.cta__or {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 2.25em;
  height: 2.25em;
  transform: translate(-50%, -50%);
  border-radius: var(--radius-pill);
  background: var(--c-ink);
  color: var(--c-on-dark);
  font-family: var(--font-display);
  font-size: var(--t-micro);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  /* Indent the tracking so the two letters sit optically centerd in the circle
     rather than pushed right by the trailing letter-space. */
  text-indent: var(--ls-label);
}

/*
 * Below 32rem the pair would force each button under ~140px, which breaks
 * "Free Discovery Call" onto three lines. There it unfuses and stacks, and the
 * chip is not rendered as a seam marker because there is no seam.
 */
@media (max-width: 32rem) {
  .cta--narrow .cta__actions {
    flex-direction: column;
    align-items: stretch;
    gap: var(--s-3);
  }

  .cta--narrow .cta__actions .cta__call {
    border-left-color: var(--c-ink);
  }

  .cta__or {
    position: static;
    transform: none;
    /* align-self, not justify-self. .cta__actions is a COLUMN flex container
       here, so the horizontal axis is the cross axis — justify-self does
       nothing on a flex child and the chip sat hard left. */
    align-self: center;
  }
}

/* ======================================================================
   AREA PAGES: "what we are asked for most", merged into the local block

   This was its own cream section with a three-column grid of articles. It is a
   definition list inside the prose column now, because it answers the same
   question as the two paragraphs above it — what this city asks us for — and a
   section break between them was what made the page feel scattered.

   Styled as term/definition rather than cards: three items do not need a grid,
   and inside a prose column a card grid fights the measure.
   ====================================================================== */

.areaproj {
  margin: var(--s-5) 0 0;
}

.areaproj > div + div {
  margin-top: var(--s-5);
  padding-top: var(--s-5);
  border-top: 1px solid var(--c-taupe-deep);
}

.areaproj dt {
  margin-bottom: var(--s-2);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-h4);
  color: var(--c-ink);
}

.areaproj dd {
  margin: 0;
  font-size: var(--t-body);
  line-height: var(--lh-body);
  color: var(--c-ink-soft);
}

/* ======================================================================
   AREA PAGE REDESIGN

   The restructure removed the duplicate section and merged the local copy, but
   it did not change how the page LOOKS — Doug's point. Three things here:

   1. A proof row in the hero, so there is local evidence above the fold.
   2. The merged block laid out as a real two-column argument instead of four
      H2s stacked in one narrow prose column, which read as more of a wall than
      the three sections it replaced.
   3. The services grid compacted, because four tall photo cards on a page that
      is already 10,000px is the wrong weight for a cross-link.
   ====================================================================== */

/* ---------- 1. hero proof ---------- */

.aproof {
  list-style: none;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-6);
  margin: var(--s-5) 0 0;
  padding: var(--s-5) 0 0;
  border-top: 1px solid var(--c-taupe-deep);
}

.aproof__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.aproof__n {
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-h4);
  line-height: 1.1;
  color: var(--c-ink);
}

.aproof__l {
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-ink-muted);
}

/* ---------- 2. the merged local block ---------- */

/*
 * "What we are asked for most" comes OUT of the prose column and sits under the
 * two-column area as its own full-width band. Three short items read as a row;
 * as a fourth H2 in a 560px column they read as more wall.
 */
@media (min-width: 60rem) {
  .areaproj {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--s-6);
    margin-top: var(--s-7);
    padding-top: var(--s-7);
    border-top: 1px solid var(--c-taupe-deep);
  }

  .areaproj > div + div {
    margin-top: 0;
    padding-top: 0;
    border-top: 0;
  }
}

/* ---------- 3. compact services ---------- */

/*
 * The four service cards carried a portrait photograph each. None of those
 * photographs were taken in the city the page is about, so they were paying
 * ~700px to say nothing local. Rule, title, one line, link.
 */
.sg--compact .sg__card img {
  display: none;
}

.sg--compact .sg__card {
  border-top: 2px solid var(--c-action);
  padding-top: var(--s-4);
}

/* Names-only variant of the E-E-A-T service list on area pages: a row of links
   rather than a stacked list, now that each item is two or three words. */
.eeat__services--names {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-4);
}

.eeat__services--names li {
  margin: 0;
}

/* ===========================================================================
   AREA PAGE BANDS — Doug's dialedinweb.com/local-seo-company reference.

   Four components: a full-bleed hero, a copy/photograph split, an icon list,
   and the curved edge where the background tone changes. They live here rather
   than in components.css because they are used by the area template and the two
   parts it calls, and theme.css is the hand-written sheet that loads last.

   THE HEADINGS ARE TWO-TONE. The reference sets the second half of every
   heading in its accent red. Here that is --c-action on light grounds. On the
   dark hero it has to change: #954429 on a darkened photograph fails contrast
   badly, so the hero accent is --c-taupe-light, which is the same idea (the
   second half is a different color) at a ratio that passes.
   =========================================================================== */

/* --- shared band furniture ---------------------------------------------- */

.sband--cream,
.iband--cream {
  background: var(--c-cream-light);
}

/*
 * NO CURVED SECTION EDGES.
 *
 * The reference divided its bands with a shallow wave and this carried one, cut
 * as an SVG mask. Doug's note was that he did not know how it got onto the site,
 * which is the correct reaction: nothing else on this theme has a curved edge,
 * --radius is 0, and every other band boundary is a straight change of ground.
 * It was imported scenery, not a house pattern.
 */

.sband__title,
.iband__title {
  font-size: var(--t-h2);
  line-height: 1.12;
  color: var(--c-ink);
  margin: 0 0 var(--s-4);
}

.sband__accent,
.iband__accent {
  color: var(--c-action);
}

/*
 * No max-width. The copy column is already a measure: at these track ratios it
 * lands around 60 characters, which is what a max-width would have been for. An
 * additional 40rem cap only re-created the same dead air on the text side.
 */
.sband__p,
.iband__intro {
  color: var(--c-ink-soft);
}

/*
 * PARAGRAPH RHYTHM. global.css resets every <p> to margin: 0 and the site's
 * spacing comes from .prose > * + *, which these bands do not use — they are not
 * a prose column, they are a component. So two paragraphs in a band ran together
 * with no gap at all, which is what Doug saw in the "Organizing homes in Plano"
 * section.
 *
 * --s-5 is the value .prose uses between blocks. Taking the same token rather
 * than picking a spacing step means the bands read at the same rhythm as every
 * other run of copy on the site.
 */
.sband__p + .sband__p {
  margin-top: var(--s-5);
}

.sband__actions,
.iband__actions {
  margin-top: var(--s-6);
}

/* --- split band --------------------------------------------------------- */

.sband__grid {
  display: grid;
  gap: var(--split-gap-sm);
  align-items: center;
}

/*
 * SIZE THE TRACK, NOT THE CONTENT INSIDE IT.
 *
 * The photographs needed to come down, and the first attempt did it with
 * max-width plus margin-inline auto on the image. That works and it looks wrong:
 * the image then floats centerd in a track wider than itself, with dead air on
 * both sides, so the band reads as narrower than the page around it. Doug's
 * words were that the sections had got tighter and were no longer full width,
 * which is exactly that gap.
 *
 * The grid columns below are what make the picture smaller now. The image fills
 * whatever track it is given, edge to edge, so nothing floats.
 */
/*
 * A SQUARE CROP, not the file's own 3:4.
 *
 * Sizing the track alone was not enough, and the reason is arithmetic: her
 * library is portrait, so a 440px track renders a 587px-tall picture, and these
 * bands run at --container-wide where a "small" fraction is still wide. Doug
 * called it massive twice, correctly.
 *
 * aspect-ratio caps the HEIGHT independently of the width, so the photograph
 * fills its track edge to edge (no floating, which was the last complaint) and
 * still lands near the height of the copy beside it. object-fit keeps the crop
 * centerd rather than squashing it.
 */
/*
 * 4/5, not 1/1. A square crop of a portrait original threw away the top and
 * bottom of every frame AND left the photograph shorter than the copy beside
 * it. 4/5 keeps more of the shot and stands closer to the height of the column
 * it sits next to, without going back to the 3/4 original that overran it.
 */
.sband__media img,
.iband__media img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
}

/* --- icon band ---------------------------------------------------------- */

.iband__grid {
  display: grid;
  gap: var(--split-gap-sm);
  align-items: start;
}

/*
 * THE ROW MARKER IS .whyus__item's, DELIBERATELY.
 *
 * Doug's note was that the reference's filled circles are not her brand, and he
 * is right in a way that is checkable rather than a matter of taste: tokens.css
 * sets --radius to 0, so a 50% radius is the only round corner on the site. Her
 * existing marker for exactly this shape, on the homepage why-us grid, is a
 * hairline rule across the top of the item with a small letterspaced number
 * above the title. That is what this uses, with the same tokens, so the two read
 * as one system rather than as a component that arrived from somewhere else.
 */
.iband__list {
  list-style: none;
  margin: var(--s-6) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-6);
}

.iband__item {
  margin: 0;
  padding-top: var(--s-4);
  border-top: 1px solid var(--c-taupe-deep);
}

/*
 * The number takes its color from the SURFACE, which is also hers: .whyus__n is
 * --c-action by default and is overridden to --c-taupe-light inside .whyus,
 * because terracotta on that navy is 2.01:1. The same arithmetic applies here at
 * the other end. --c-action is 6.01:1 on the cream band and 3.15:1 on the taupe
 * page ground, and at 14px that second number fails AA outright.
 *
 * So taupe bands take --c-taupe-deeper, the token tokens.css defines for exactly
 * this, and the cream bands keep the accent.
 */
.iband__n {
  display: block;
  margin-bottom: var(--s-3);
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-micro);
  letter-spacing: var(--ls-label);
  color: var(--c-taupe-deeper);
  font-variant-numeric: tabular-nums;
}

.iband--cream .iband__n {
  color: var(--c-action);
}

.iband--cream .iband__item {
  border-top-color: var(--c-cream-deep);
}

/*
 * The title needs to out-rank the body beside it. At --fw-light against 17px
 * body copy the two read as one block of text, which is what made the numbered
 * list look flat. --fw-medium and the display tracking are what every other
 * sub-heading on the site uses.
 */
.iband__itemTitle {
  font-family: var(--font-display);
  font-weight: var(--fw-medium);
  font-size: var(--t-h4);
  letter-spacing: var(--ls-display);
  line-height: var(--lh-heading);
  color: var(--c-ink);
  margin: 0 0 var(--s-2);
}

/*
 * --t-body (17px), NOT --t-small (16px).
 *
 * This was --t-small on the reasoning that a list item is secondary to the prose
 * above it. That is a layout instinct overriding a type system: tokens.css sets
 * one reading size for the site and these are reading sentences, not labels.
 * Doug caught it by the number. --t-small stays what it is for, which is the
 * genuinely secondary text the token documents.
 */
.iband__itemBody {
  margin: 0;
  font-size: var(--t-body);
  color: var(--c-ink-soft);
}


/* --- two-column from 60rem ---------------------------------------------- */

@media (min-width: 60rem) {
  /*
   * PLACE BY grid-column, NOT BY order. This was the bug behind three rounds of
   * "the image is still massive".
   *
   * grid-template-columns sizes tracks BY POSITION. The columns were 2.4fr 1fr,
   * copy first and media second, and flipping the photograph to the left was
   * done with `order: -1`. order moves the ITEM, not the TRACK: the media became
   * the first grid item, so it took the 2.4fr column and the copy was squeezed
   * into the 1fr. On a 1408px container that is a ~950px photograph next to a
   * ribbon of text, which is exactly what the left-hand band was rendering while
   * the right-hand one, untouched by the flip, looked correct.
   *
   * Every attempt to shrink the picture had been shrinking the track the picture
   * was not in.
   *
   * Naming both tracks the same width and assigning each item a column by hand
   * makes the size independent of the side. copy is always the wide one.
   *
   * 1.7fr against 1fr is the split-the-difference ratio Doug asked for: 2.4fr
   * corrected the bug and overshot, dropping the picture to roughly a third of
   * what it had been. At 1408px this is a ~520px photograph.
   */
  /*
   * --split-gap, the branding constant. See tokens.css: it is the homepage's
   * measure, it is not a per-section tuning knob, and half of it is why these
   * bands read as crowded.
   */
  /*
   * 1.15fr, NOT 1.7fr. The ratio was tuned while these bands ran at
   * --container-wide (1408px), where 1fr of 2.7 was still a 380px photograph.
   * Moving back to --container (1200px) took the same fraction down to ~379px
   * of a narrower row, and then a square crop made it 379 tall against a copy
   * column running twice that. Doug: the images are too small.
   *
   * At 1.15/1 the media track is ~476px, which is what the crop below is sized
   * against.
   */
  .sband__grid,
  .iband__grid {
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
    gap: var(--split-gap);
  }

  .sband__copy,
  .iband__copy {
    grid-column: 1;
    grid-row: 1;
  }

  .sband__media,
  .iband__media {
    grid-column: 2;
    grid-row: 1;
    /* Top-aligned so a short list does not stretch the picture to match it. */
    align-self: start;
  }

  /* Photograph on the left: the tracks swap, so the wide one stays with the
     copy. The source order is untouched, which is what keeps a phone reading
     heading, copy, photograph. */
  .sband:not(.sband--right) .sband__grid,
  .iband--left .iband__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
    gap: var(--split-gap);
  }

  .sband:not(.sband--right) .sband__copy,
  .iband--left .iband__copy {
    grid-column: 2;
  }

  .sband:not(.sband--right) .sband__media,
  .iband--left .iband__media {
    grid-column: 1;
  }

  .sband--solo .sband__grid,
  .iband--solo .iband__grid {
    grid-template-columns: 1fr;
    max-width: 46rem;
  }
}

/* ===========================================================================
   AREA HERO — page-landing.php's hero, minus the form.

   Almost everything comes from .lp__: the photograph, the scrim, the grid, the
   ticks and the form panel. The grid needs no override because there is a
   second child again. All that is left here is the breadcrumbs, which have to
   stop being a strip.
   =========================================================================== */

/*
 * Breadcrumbs resting on the hero.
 *
 * .crumbs paints its own --c-taupe band and its own padding, because everywhere
 * else it IS a strip. Inside the hero both have to go: the band would draw a
 * taupe stripe across a dark photograph, and the container would nest a second
 * one inside the hero's own. Light ink, because this ground is dark.
 */
.ahero__crumbs {
  grid-column: 1 / -1;
  margin-top: var(--s-8);
}

.ahero__crumbs .crumbs {
  padding-block: 0;
  background: none;
}

.ahero__crumbs .crumbs .container {
  padding-inline: 0;
  max-width: none;
  width: auto;
}

.ahero__crumbs .crumbs li,
.ahero__crumbs .crumbs a {
  color: var(--c-on-dark-muted);
}

.ahero__crumbs .crumbs li + li::before {
  color: var(--c-taupe-deep);
}

.ahero__crumbs .crumbs a:hover {
  color: var(--c-on-dark);
}

/*
 * THE SECTION HEAD MEASURE, on the wide service grid only.
 *
 * .sechead caps at 30ch, which is right for the site's headings and wrong for
 * this one: "Home organizing services in Plano" is 33 characters, so it wrapped
 * with the city orphaned onto a line of its own. Doug's description was that the
 * words were stacking line by line rather than reading as one or two.
 *
 * 42ch holds the longest area heading ("Home organizing services in Highland
 * Park", 41) on one line and still wraps the homepage's own 47-character
 * heading to two, which is what it does today. The homepage is not affected:
 * this only applies where the caller asked for the wide measure.
 */
.sg--wide .sechead {
  max-width: 42ch;
}

/* ---------------------------------------------------------------------------
   .pscatter--pair — two prints instead of four.

   .pscatter's desktop rules place four images into four quadrants of a 3:4
   box. With two, three of those quadrants are empty and the pair sits in the
   top half of a very tall container. This variant re-proportions the box and
   places the two on a diagonal, which is the arrangement Doug asked for.
   --------------------------------------------------------------------------- */

@media (min-width: 60rem) {
  .pscatter--pair {
    aspect-ratio: 4 / 5;
  }

  .pscatter--pair img {
    width: 68%;
  }

  /* Top-left back, bottom-right front: reading order, and the front print is
     the one nearest the copy it belongs to. */
  .pscatter--pair img:nth-of-type(1) {
    top: 0;
    left: 0;
    right: auto;
    bottom: auto;
    transform: rotate(-4.5deg);
    z-index: 1;
  }

  .pscatter--pair img:nth-of-type(2) {
    top: auto;
    left: auto;
    right: 0;
    bottom: 0;
    transform: rotate(3.5deg);
    z-index: 2;
  }

  /*
   * NO NEGATIVE MARGIN. This pulled the prints out into the page gutter, which
   * did push them right as asked and also ate the divide between the columns.
   * Doug's follow-up was that the sections were bleeding into each other and
   * that the homepage's gap is the branding. The --s-9 gutter above does the
   * separating; the diagonal does the moving.
   */
}

/* Phone: .pscatter falls back to a 2-up grid. Two prints side by side at 390
   are 165px each, which is the width the theme already records as too small to
   read a caption in. Stacked instead, and the rotation goes with it. */
@media (max-width: 59.9375rem) {
  .pscatter--pair {
    grid-template-columns: 1fr;
    max-width: 22rem;
  }
}

/* ---------------------------------------------------------------------------
   FAQ two-column gutter.

   The layout itself is .faq__cols, which already existed for the homepage; the
   part just renders it everywhere now. Only the gutter changes here: it was a
   literal --s-8 and this is a two-column split like any other, so it takes the
   branding constant. See tokens.css.
   --------------------------------------------------------------------------- */

.faq__cols {
  column-gap: var(--split-gap);
}



/* ---------------------------------------------------------------------------
   The /faq/ page's own groups run two columns too.

   That page is database content: four <div class="faq__group"> sections, each
   holding a single .faq__list, so it never went through faq-list.php and never
   picked up the two-column treatment every other FAQ block now has. Styling the
   list directly gets there without a content sync.

   Grid, not column-count: a <details> must not break across the boundary when
   it opens. Rows are auto so a long answer only grows its own row.
   --------------------------------------------------------------------------- */

@media (min-width: 48rem) {
  .faq__group .faq__list {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--split-gap);
    align-items: start;
  }
}



/* ---------------------------------------------------------------------------
   .pscatter--trio — three prints on a diagonal, in a band.

   Same geometry as the About services split, which is the version Doug signed
   off: 44% wide against a 28% stride, so about a third of each frame overlaps
   and the run uses the whole column rather than its top corner.

   Separate from the About rule rather than shared because the two sit in
   different sized columns: About gets roughly half a 1200px container, a band's
   media track is narrower. The numbers agree today; if one has to move, the
   other should not follow by accident.
   --------------------------------------------------------------------------- */

@media (min-width: 60rem) {
  /*
   * A ZIGZAG, NOT A DIAGONAL: right, then left, then middle.
   *
   * The straight run down the About split reads well there, where it sits in a
   * tall half-page column. In a band's media track the same line just marches
   * off one corner toward the other and leaves both remaining corners empty.
   * Doug asked for right to left to middle, which fills the box and gives the
   * three frames a reason to overlap at their corners rather than along one
   * edge.
   *
   * The horizontal positions are 48%, 0 and 24% against 52% frames, so the first
   * two clip each other at a corner and the third laps a quarter of the second.
   * The box is 1/1.5 because a zigzag needs more height than a diagonal to keep
   * the same overlap.
   */
  .pscatter--trio {
    aspect-ratio: 1 / 1.5;
  }

  .pscatter--trio img {
    width: 52%;
  }

  .pscatter--trio img:nth-of-type(1) {
    top: 0;
    left: 48%;
    right: auto;
    bottom: auto;
    transform: rotate(3.5deg);
    z-index: 1;
  }

  .pscatter--trio img:nth-of-type(2) {
    top: 27%;
    left: 0;
    right: auto;
    bottom: auto;
    transform: rotate(-4.5deg);
    z-index: 2;
  }

  .pscatter--trio img:nth-of-type(3) {
    top: 54%;
    left: 24%;
    right: auto;
    bottom: auto;
    transform: rotate(2.5deg);
    z-index: 3;
  }
}

/* Phone: stack, same as the pair. Three rotated frames at 390 wide overlap into
   an unreadable pile. */
@media (max-width: 59.9375rem) {
  .pscatter--trio {
    grid-template-columns: 1fr;
    max-width: 22rem;
  }
}

/* The badges sit directly under the ticks in the area hero, where they used to
   be the tail of a .lp__trust row that supplied the spacing. Without that row
   they need their own. */
.ahero .lpbadges {
  margin-top: var(--s-6);
}

/* ---------------------------------------------------------------------------
   Portfolio facet rail: min-width floors.

   THIS DID NOT FIX THE SIDEWAYS SCROLL, and the comment that used to sit here
   said it did. Correcting the record because the wrong diagnosis is what made
   the bug take four attempts: min-width: 0 was added, the page still measured
   936, and the note was written as though it had worked.

   What actually fixed it is flex-wrap on .pf__facets, up where the mobile chips
   are defined. The measurement that settles it: with these floors in place and
   the scroller restored, the document is 936; wrapping the chips takes it to
   390 on its own.

   The block stays because min-width: auto on a grid or flex item is a real trap
   — the automatic minimum size is the content, so a descendant can push a column
   wider than the track that holds it. It is correct defensive CSS. It is just
   not the fix.
   --------------------------------------------------------------------------- */

.pf__railInner,
.pf__group,
.pf__facets {
  min-width: 0;
  max-width: 100%;
}

/* ---------------------------------------------------------------------------
   Touch targets: the rows sitting one pixel under the floor.

   Swept every link, button and field at 390px across eleven routes. Most of
   what a naive 44px check flags here is not a failure: WCAG 2.5.8 at AA asks
   for 24x24, and 44x44 is 2.5.5 at AAA. The coverage-panel city links are 26px
   with a 12px gap either side, the portfolio chips are 40px, and the quote-form
   radios measure 18px but their LABEL is the target at 286x60. All fine.

   What is not fine is a small set of link rows that land at exactly 23px — one
   pixel under the AA floor, which is the sort of thing that is invisible in
   review and trivially fixable. They are all short, wide, single-line links, so
   a little vertical padding costs nothing in layout and takes them clear.

   The breadcrumb row is included. It is conventionally small type and it stays
   small type; padding changes the target, not the size of the letters. So is
   the OpenStreetMap attribution, at 19px the smallest control on the site. That
   one arguably falls under 2.5.8's exception for legally required presentation
   — the ODbL requires the credit — but the exception is for the TYPE size, and
   nothing about the licence requires the tap target to be small too.

   After this, one sweep of eleven routes at 390px returns nothing below 24x24.
   --------------------------------------------------------------------------- */

@media (max-width: 47.9375rem) {
  .crumbs a,
  .sap__call,
  .insta__handle,
  .link-arrow,
  .sap__credit a {
    display: inline-flex;
    align-items: center;
    min-height: 28px;
  }
}
