/* ==========================================================================
   HireJuno — Design System layer  (css/ds.css)
   Loaded AFTER brand.css / tokens.css. Additive only.
   --------------------------------------------------------------------------
   SCOPE RULE (hard constraint)
   Every selector here is prefixed `.ds-`. Nothing in this file may reach an
   existing component class (.btn, .card, .chip, .container, .navbar, the
   mega-menu). brand.css is the frozen token layer — this file CONSUMES its
   tokens and never redefines one.

   Pattern:  Marketplace / Directory  (search-led hero, categories, featured
             listings, trust, CTA).
   Style:    Trust & Authority — real photography, visible credentials, before
             /after proof. Restraint over decoration.

   Vanilla CSS only. No build step, no framework, no CDN — nothing here needs
   compiling. Every effect the 21st.dev references achieve with Tailwind +
   Framer Motion is done natively: CSS marquee, mask-image fade, scroll-snap,
   and IntersectionObserver-driven reveals.

   Light theme only. This brand does not ship a dark mode: every page is
   <html data-theme="light">, and the dark blocks that used to live here (and
   in tokens/home/account/motion/service.css) were removed rather than left
   dead. Do not reintroduce a prefers-color-scheme block — it would fire on a
   dark-mode OS regardless of the data-theme attribute.
   ========================================================================== */

/* Screen-reader-only. brand.css does not define this and reviews.css owns a
   copy that only that page loads, so a visually-hidden <label> rendered as
   visible text anywhere else. Defined here so the DS owns it. */
.sr-only {
  position: absolute !important;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0, 0, 0, 0);
  white-space: nowrap; border: 0;
}

:root {
  /* Motion — one rhythm for the whole system, so the site never has two
     competing speeds. */
  --ds-dur-fast:   150ms;
  --ds-dur:        240ms;
  --ds-dur-slow:   400ms;
  --ds-ease:       cubic-bezier(.16, 1, .3, 1);   /* the reference's easing */
  --ds-ease-out:   cubic-bezier(.22, .61, .36, 1);

  /* Marquee speeds for the three testimonial columns. Deliberately unequal —
     equal speeds read as one moving block instead of three. */
  --ds-col-a: 46s;
  --ds-col-b: 58s;
  --ds-col-c: 52s;

  /* Layered z-index scale, so nothing invents its own number. Stays below the
     mega-menu and the toast layer. */
  --ds-z-base:    1;
  --ds-z-raised: 10;
  --ds-z-sticky: 20;
  --ds-z-overlay:40;

  /* Section rhythm. Fluid so small screens don't get 96px of dead space. */
  --ds-sec-y:    clamp(var(--s-7), 7vw, var(--s-9));
  --ds-sec-y-sm: clamp(var(--s-6), 5vw, var(--s-8));

  /* Type scale — one modular ramp rather than clamp() values invented per
     component, which is what the skill's font-scale rule asks for. Every
     type size in this file is a step on this ramp. */
  --ds-t-2xs: 0.75rem;    /* 12 — counts, meta */
  --ds-t-xs:  0.8125rem;  /* 13 — tags, helper text */
  --ds-t-sm:  0.875rem;   /* 14 — labels, captions */
  --ds-t-md:  1rem;       /* 16 — body; never smaller on mobile */
  --ds-t-lg:  1.125rem;   /* 18 — lead paragraphs */
  --ds-t-xl:  1.25rem;    /* 20 — card titles */
  --ds-t-2xl: 1.5rem;     /* 24 — sub-headings */
  --ds-t-3xl: 2rem;       /* 32 — section titles, small screens */
  --ds-t-4xl: 2.5rem;     /* 40 — section titles, desktop */
  --ds-t-5xl: 3.25rem;    /* 52 — page titles */

  /* One elevation ramp. A component picks a step; none invent a shadow. */
  --ds-e-1: 0 1px 2px rgba(27,42,65,.05);
  --ds-e-2: 0 2px 6px rgba(27,42,65,.06), 0 1px 2px rgba(27,42,65,.04);
  --ds-e-3: 0 10px 24px rgba(27,42,65,.10), 0 2px 6px rgba(27,42,65,.05);
}

/* ==========================================================================
   1. SECTION PRIMITIVES
   ========================================================================== */

.ds-sec              { padding: var(--ds-sec-y) 0; position: relative; }
.ds-sec--tight       { padding: var(--ds-sec-y-sm) 0; }
.ds-sec--sand        { background: var(--sand); }
.ds-sec--tint        { background: var(--juno-green-tint); }

.ds-head             { max-width: 62ch; margin: 0 0 var(--s-6); }
.ds-head--center     { margin-inline: auto; text-align: center; }

.ds-eyebrow {
  display: inline-flex; align-items: center; gap: var(--s-2);
  font: 600 .78rem/1 var(--font-body);
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--juno-green-dark);
  margin-bottom: var(--s-3);
}
.ds-eyebrow svg { width: 15px; height: 15px; }

.ds-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(var(--ds-t-3xl), 3.4vw, var(--ds-t-4xl));
  line-height: 1.12;
  letter-spacing: -.02em;
  color: var(--ink);
  margin: 0 0 var(--s-3);
}
.ds-title em { font-style: normal; color: var(--juno-green-dark); }

.ds-sub {
  font-size: var(--ds-t-lg); line-height: 1.6;
  color: var(--ink-soft);
  margin: 0;
  max-width: 60ch;             /* line-length-control: 60–75 chars desktop */
}
.ds-head--center .ds-sub { margin-inline: auto; }

/* ==========================================================================
   2. HERO  (.ds-hero)  — adapted from the dynamic-hero reference
   --------------------------------------------------------------------------
   The reference full-bleeds an autoplaying looped video behind the copy. That
   is the annoying part: motion never stops, it competes with the text people
   came to read, and it costs mobile data before anything useful renders. This
   keeps the composition — full-bleed image, copy left, one primary action —
   and replaces the loop with a single slow settle that finishes and stays
   finished. LCP is a plain <img>, so it can be preloaded and sized.
   ========================================================================== */

.ds-hero {
  position: relative;
  isolation: isolate;
  min-height: clamp(420px, 56vh, 620px);
  display: grid;
  align-items: center;
  overflow: hidden;
  background: var(--navy-deep);
}
.ds-hero--compact { min-height: clamp(300px, 38vh, 420px); }

.ds-hero__media { position: absolute; inset: 0; z-index: -2; }
.ds-hero__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* The single settle: starts a touch over-scaled, comes to rest. One shot,
     no loop, no infinite ken-burns. */
  animation: ds-settle 1200ms var(--ds-ease) both;
}
@keyframes ds-settle {
  from { transform: scale(1.06); }
  to   { transform: scale(1); }
}

/* Scrim. Two stops rather than a flat overlay so the left copy column clears
   4.5:1 while the right side of the photo stays visible. */
.ds-hero::after {
  content: ''; position: absolute; inset: 0; z-index: -1;
  background:
    linear-gradient(90deg, rgba(15,27,45,.90) 0%, rgba(15,27,45,.72) 46%, rgba(15,27,45,.34) 100%),
    linear-gradient(180deg, rgba(15,27,45,.30) 0%, transparent 40%);
}

.ds-hero__inner { position: relative; padding-block: var(--s-8); }
.ds-hero__copy  { max-width: 40rem; }

.ds-hero__kicker {
  display: inline-flex; align-items: center; gap: var(--s-2);
  padding: 6px 12px 6px 8px;
  border-radius: var(--r-pill);
  background: rgba(255,255,255,.12);
  border: 1px solid rgba(255,255,255,.20);
  color: var(--ink-on-dark);
  font: 600 .82rem/1 var(--font-body);
  margin-bottom: var(--s-4);
  backdrop-filter: blur(6px);
}
.ds-hero__kicker svg { width: 16px; height: 16px; color: var(--juno-green-bright); }

.ds-hero__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2rem, 5.2vw, 3.6rem);
  line-height: 1.06;
  letter-spacing: -.025em;
  color: #fff;
  margin: 0 0 var(--s-4);
  text-wrap: balance;
}
.ds-hero__title span { display: block; color: var(--juno-green-bright); }

.ds-hero__sub {
  font-size: clamp(1rem, 1.5vw, 1.15rem);
  line-height: 1.6;
  color: var(--ink-on-dark-soft);
  margin: 0 0 var(--s-6);
  max-width: 46ch;
}

.ds-hero__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }

/* Trust strip — Trust & Authority style wants credentials visible above the
   fold, not buried in a footer. */
.ds-hero__trust {
  display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-5);
  margin-top: var(--s-6);
  padding-top: var(--s-5);
  border-top: 1px solid rgba(255,255,255,.16);
}
.ds-hero__trust span {
  display: inline-flex; align-items: center; gap: 7px;
  color: var(--ink-on-dark-soft);
  font-size: .88rem; font-weight: 500;
}
.ds-hero__trust svg { width: 16px; height: 16px; color: var(--juno-green-bright); flex-shrink: 0; }

/* ==========================================================================
   3. BROWSE SERVICES  (.ds-svc) — adapted from the integrations-section
      reference. That layout is a calm tiled grid of equal cards; here each
      tile is a service, carrying its own 3D icon and a real "from" price out
      of data/services.json. Prices are never hardcoded in CSS or markup —
      HJ.getCatalog() fills them.
   ========================================================================== */

/* 14 services. A minmax(180px) auto-fill lands 5 across at desktop and leaves
   an orphan row of 4, which is what made this read as a default grid. Explicit
   breakpoints give 2 / 4 / 7 — and 7 divides 14 into two clean rows. */
.ds-svc__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-3);
}
@media (min-width: 640px)  { .ds-svc__grid { grid-template-columns: repeat(4, 1fr); gap: var(--s-4); } }
@media (min-width: 1080px) { .ds-svc__grid { grid-template-columns: repeat(7, 1fr); } }

/* Centred tile, like the integrations grid the reference builds: the icon is
   the thing you scan, the name confirms it, the price closes it. Previously
   the icon was 38px adrift in a left-aligned box with a large dead gap under
   it — all the height, none of the presence. */
.ds-svc__card {
  display: flex; flex-direction: column; align-items: center;
  gap: var(--s-2);
  padding: var(--s-5) var(--s-3) var(--s-4);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--ds-e-1);
  text-align: center;
  text-decoration: none;
  color: inherit;
  transition: transform var(--ds-dur) var(--ds-ease),
              box-shadow var(--ds-dur) var(--ds-ease),
              border-color var(--ds-dur) var(--ds-ease);
}
.ds-svc__card:hover,
.ds-svc__card:focus-visible {
  transform: translateY(-2px);
  box-shadow: var(--ds-e-3);
  border-color: var(--juno-green);
}
.ds-svc__card:focus-visible { outline: 2px solid var(--juno-green-dark); outline-offset: 3px; }

/* The icon carries the tile, so it gets real size — 64px plate, 44px art,
   against 52/38 before. The tint plate keeps the Fluent icons from floating
   on bare white. */
.ds-svc__ico {
  width: 64px; height: 64px;
  display: grid; place-items: center;
  border-radius: var(--r-lg);
  background: var(--juno-green-tint);
  flex-shrink: 0;
  transition: transform var(--ds-dur) var(--ds-ease);
}
.ds-svc__card:hover .ds-svc__ico { transform: scale(1.06); }
.ds-svc__ico img { width: 44px; height: 44px; object-fit: contain; }
.ds-svc__ico svg { width: 30px; height: 30px; color: var(--juno-green-dark); }

.ds-svc__name {
  font: 600 var(--ds-t-sm)/1.35 var(--font-display);
  color: var(--ink);
  text-wrap: balance;
}
.ds-svc__price {
  font-size: var(--ds-t-xs);
  color: var(--ink-soft);
  margin-top: auto;
  padding-top: 2px;
}
.ds-svc__price b { color: var(--juno-green-dark); font-weight: 700; font-variant-numeric: tabular-nums; }

.ds-svc__foot {
  display: flex; flex-wrap: wrap; gap: var(--s-3);
  align-items: center; justify-content: center;
  margin-top: var(--s-6);
}

/* ==========================================================================
   4. TESTIMONIAL COLUMNS  (.ds-tcol)
   --------------------------------------------------------------------------
   Rebuild of testimonials-columns-1. The reference uses Framer Motion; this
   is a pure CSS marquee. Each column translates -50% while its content is
   duplicated once, so the loop is seamless with no JS.

   Speeds are much slower than the reference's 15/19/17s: those durations are
   tuned for three short cards, and at our card count they scroll too fast to
   read. Unequal values keep the columns from locking into step.
   ========================================================================== */

.ds-tcol {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-4);
  max-height: 720px;
  overflow: hidden;
  /* The reference fades 25% at each end. At our card height that ghosts nearly
     two full quotes top and bottom — they read as empty space rather than as a
     soft edge. 8% is enough to sell the clip without wasting the band. */
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 8%, #000 92%, transparent);
          mask-image: linear-gradient(to bottom, transparent, #000 8%, #000 92%, transparent);
}
@media (min-width: 768px) { .ds-tcol { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px){ .ds-tcol { grid-template-columns: repeat(3, 1fr); } }

/* Columns 2 and 3 only exist on wider screens — matches the reference's
   hidden md:block / hidden lg:block. */
.ds-tcol__col--2 { display: none; }
.ds-tcol__col--3 { display: none; }
@media (min-width: 768px)  { .ds-tcol__col--2 { display: block; } }
@media (min-width: 1024px) { .ds-tcol__col--3 { display: block; } }

.ds-tcol__track {
  display: flex; flex-direction: column; gap: var(--s-4);
  animation: ds-marquee var(--ds-col-a) linear infinite;
  will-change: transform;
}
.ds-tcol__col--2 .ds-tcol__track { animation-duration: var(--ds-col-b); }
.ds-tcol__col--3 .ds-tcol__track { animation-duration: var(--ds-col-c); }

/* Pause on hover so a quote can actually be read — the reference doesn't, and
   it's the main reason these sections frustrate people. */
.ds-tcol:hover .ds-tcol__track,
.ds-tcol:focus-within .ds-tcol__track { animation-play-state: paused; }

@keyframes ds-marquee {
  from { transform: translateY(0); }
  to   { transform: translateY(-50%); }
}

.ds-quote {
  /* <figure> carries margin: 1em 40px from the UA stylesheet. Left unreset it
     shrank each card to 280px inside a 360px column and stacked 16+16 on top
     of the 16px track gap, so cards sat 48px apart instead of 16. That was the
     white space. */
  margin: 0;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: var(--s-5);
  box-shadow: var(--ds-e-1);
}
.ds-quote__body {
  font-size: .95rem; line-height: 1.62;
  color: var(--ink-soft);
  margin: 0 0 var(--s-4);
}
.ds-quote__who { display: flex; align-items: center; gap: var(--s-3); }
.ds-quote__av {
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--juno-green-tint);
  color: var(--juno-green-deep);
  font: 600 .9rem/1 var(--font-display);
  flex-shrink: 0;
}
/* Both are spans inside a span, so they were laid out inline and ran together
   as "Ahmad Safwan2 weeks ago" — margin-top on an inline box does nothing.
   Blocking them puts the date on its own line, which is what the reference's
   name-over-role stack does. */
.ds-quote__name { display: block; font: 600 var(--ds-t-sm)/1.3 var(--font-body); color: var(--ink); }
/* --ink-faint measures 3.06:1 on white — fine for a 24px label, a failure at
   this size. These stay subordinate through weight and size rather than
   colour, which is the more robust hierarchy anyway. */
.ds-quote__meta { display: block; font-size: var(--ds-t-xs); color: var(--ink-soft); margin-top: 2px; }
.ds-quote__stars { display: inline-flex; gap: 1px; margin-bottom: var(--s-3); }
.ds-quote__stars svg { width: 15px; height: 15px; color: var(--warning); }

/* ==========================================================================
   5. WORK GALLERY  (.ds-gal) — the /work repository page
      Search-led hero from the hero-09 reference, plus pill filters and a
      scroll-snap slider per card. Photos are real before/after job shots out
      of the public review-images bucket.
   ========================================================================== */

.ds-galhero {
  background: linear-gradient(180deg, var(--juno-green-tint) 0%, var(--cream) 100%);
  padding: var(--ds-sec-y) 0 var(--ds-sec-y-sm);
  text-align: center;
}
.ds-galhero__title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(var(--ds-t-3xl), 4.6vw, var(--ds-t-5xl));
  line-height: 1.08;
  letter-spacing: -.025em;
  color: var(--ink);
  margin: 0 0 var(--s-4);
  text-wrap: balance;
}
.ds-galhero__title span { display: block; color: var(--juno-green-dark); }

/* Search bar is the CTA — the marketplace pattern's core rule. */
.ds-search {
  display: flex; gap: var(--s-2);
  max-width: 620px; margin: var(--s-6) auto 0;
  background: var(--paper);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  padding: 6px 6px 6px var(--s-5);
  box-shadow: var(--shadow-md);
  transition: border-color var(--ds-dur) var(--ds-ease), box-shadow var(--ds-dur) var(--ds-ease);
}
.ds-search:focus-within {
  border-color: var(--juno-green);
  box-shadow: var(--shadow-lg), 0 0 0 4px var(--juno-green-tint);
}
.ds-search__ico { width: 20px; height: 20px; color: var(--ink-faint); align-self: center; flex-shrink: 0; }
.ds-search input {
  flex: 1; border: 0; background: none; outline: none;
  font: 400 1rem/1 var(--font-body);
  color: var(--ink);
  min-width: 0;
  min-height: 44px;                     /* touch-target-size */
}
.ds-search input::placeholder { color: var(--ink-faint); }

/* Pill filters */
.ds-pills {
  display: flex; flex-wrap: wrap; gap: var(--s-2);
  justify-content: center;
  margin-top: var(--s-5);
}
.ds-pill {
  display: inline-flex; align-items: center; gap: 6px;
  min-height: 40px; padding: 0 var(--s-4);
  border-radius: var(--r-pill);
  border: 1px solid var(--line-strong);
  background: var(--paper);
  color: var(--ink-soft);
  font: 500 .88rem/1 var(--font-body);
  cursor: pointer;
  transition: background var(--ds-dur-fast) var(--ds-ease),
              color var(--ds-dur-fast) var(--ds-ease),
              border-color var(--ds-dur-fast) var(--ds-ease);
}
.ds-pill:hover { border-color: var(--juno-green); color: var(--juno-green-dark); }
.ds-pill:focus-visible { outline: 2px solid var(--juno-green-dark); outline-offset: 2px; }
.ds-pill[aria-pressed="true"] {
  background: var(--juno-green-dark);
  border-color: var(--juno-green-dark);
  color: #fff;
}
.ds-pill__n { opacity: .7; font-variant-numeric: tabular-nums; }

/* Result grid */
.ds-gal__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--s-5);
}
.ds-gal__card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  display: flex; flex-direction: column;
}

/* Per-card slider: scroll-snap + arrow buttons. No JS library. */
.ds-slider { position: relative; }
.ds-slider__track {
  display: flex;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  aspect-ratio: 4 / 3;
}
.ds-slider__track::-webkit-scrollbar { display: none; }
.ds-slider__track img {
  flex: 0 0 100%;
  width: 100%; height: 100%;
  object-fit: cover;
  scroll-snap-align: center;
  background: var(--sand);
}
.ds-slider__btn {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 36px; height: 36px;
  display: grid; place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: rgba(255,255,255,.92);
  color: var(--ink);
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--ds-dur) var(--ds-ease), background var(--ds-dur-fast) linear;
  z-index: var(--ds-z-raised);
}
.ds-slider__btn svg { width: 18px; height: 18px; }
.ds-slider__btn--prev { left: var(--s-3); }
.ds-slider__btn--next { right: var(--s-3); }
.ds-slider:hover .ds-slider__btn,
.ds-slider:focus-within .ds-slider__btn { opacity: 1; }
.ds-slider__btn:focus-visible { opacity: 1; outline: 2px solid var(--juno-green-dark); outline-offset: 2px; }
.ds-slider__btn[disabled] { opacity: 0 !important; pointer-events: none; }
/* Arrows are hover-revealed on pointer devices but always visible on touch,
   where there is no hover to reveal them (hover-vs-tap). */
@media (hover: none) { .ds-slider__btn { opacity: 1; } }

.ds-slider__count {
  position: absolute; right: var(--s-3); bottom: var(--s-3);
  padding: 3px 9px; border-radius: var(--r-pill);
  background: rgba(15,27,45,.72); color: #fff;
  font: 500 .74rem/1 var(--font-body);
  font-variant-numeric: tabular-nums;
  z-index: var(--ds-z-raised);
}

.ds-gal__body { padding: var(--s-4); display: flex; flex-direction: column; gap: var(--s-3); flex: 1; }
.ds-gal__tags { display: flex; flex-wrap: wrap; gap: 6px; }
.ds-gal__tag {
  font: 500 .74rem/1 var(--font-body);
  padding: 4px 9px; border-radius: var(--r-pill);
  background: var(--juno-green-tint); color: var(--juno-green-deep);
}
.ds-gal__quote { font-size: .9rem; line-height: 1.55; color: var(--ink-soft); margin: 0; }
.ds-gal__who   { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); margin-top: auto; }
.ds-gal__name  { font: 600 .86rem/1 var(--font-body); color: var(--ink); }
.ds-gal__date  { font-size: .78rem; color: var(--ink-soft); }   /* see .ds-quote__meta */

.ds-gal__empty { text-align: center; padding: var(--s-9) var(--s-4); color: var(--ink-soft); }
.ds-gal__empty svg { width: 40px; height: 40px; color: var(--ink-faint); margin-bottom: var(--s-4); }

/* Skeletons — reserve space so loading never shifts layout (CLS). */
.ds-skel { background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-lg); overflow: hidden; }
.ds-skel__img { aspect-ratio: 4/3; background: var(--sand); }
.ds-skel__line { height: 11px; border-radius: var(--r-sm); background: var(--sand); margin: var(--s-3) var(--s-4); }
.ds-skel__line:last-child { width: 55%; margin-bottom: var(--s-5); }
.ds-skel__img, .ds-skel__line { animation: ds-shimmer 1.4s ease-in-out infinite; }
@keyframes ds-shimmer { 0%,100% { opacity: 1; } 50% { opacity: .55; } }

/* ==========================================================================
   6. FORM CTA  (.ds-cta) — sits on every page alongside the browse section
   ========================================================================== */

.ds-cta {
  background: var(--navy-deep);
  border-radius: var(--r-3xl);
  padding: clamp(var(--s-6), 5vw, var(--s-8));
  display: grid; gap: var(--s-6);
  align-items: center;
}
@media (min-width: 900px) { .ds-cta { grid-template-columns: 1.05fr .95fr; gap: var(--s-8); } }

.ds-cta__title {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(var(--ds-t-2xl), 3vw, var(--ds-t-4xl));
  line-height: 1.14; letter-spacing: -.02em;
  color: #fff; margin: 0 0 var(--s-3);
  text-wrap: balance;
}
.ds-cta__sub { color: var(--ink-on-dark-soft); line-height: 1.6; margin: 0 0 var(--s-5); max-width: 44ch; }
.ds-cta__trust { display: flex; flex-wrap: wrap; gap: var(--s-2) var(--s-4); }
.ds-cta__trust span { display: inline-flex; align-items: center; gap: 6px; color: var(--ink-on-dark-soft); font-size: .85rem; }
.ds-cta__trust svg { width: 15px; height: 15px; color: var(--juno-green-bright); }

.ds-form { display: grid; gap: var(--s-3); }
.ds-form__row { display: grid; gap: var(--s-3); }
@media (min-width: 560px) { .ds-form__row { grid-template-columns: 1fr 1fr; } }

.ds-field { display: grid; gap: 6px; }
.ds-field label {                          /* visible label, never placeholder-only */
  font: 500 .82rem/1 var(--font-body);
  color: var(--ink-on-dark-soft);
}
.ds-field label .req { color: var(--juno-green-bright); }
.ds-field input, .ds-field select {
  min-height: 46px;                        /* touch-friendly-input */
  padding: 0 var(--s-4);
  border-radius: var(--r-md);
  border: 1px solid rgba(255,255,255,.22);
  background: rgba(255,255,255,.07);
  color: #fff;
  font: 400 .95rem/1 var(--font-body);
  transition: border-color var(--ds-dur-fast) linear, background var(--ds-dur-fast) linear;
}
.ds-field input::placeholder { color: rgba(255,255,255,.42); }
.ds-field input:focus, .ds-field select:focus {
  outline: none;
  border-color: var(--juno-green-bright);
  background: rgba(255,255,255,.12);
}
.ds-field input:focus-visible, .ds-field select:focus-visible {
  outline: 2px solid var(--juno-green-bright); outline-offset: 1px;
}
.ds-field select option { color: var(--ink); }
.ds-field__err { font-size: .78rem; color: #FFB4B4; min-height: 0; }
.ds-field--bad input, .ds-field--bad select { border-color: #FF8A8A; }

.ds-form__note { font-size: .78rem; color: var(--ink-on-dark-soft); opacity: .8; margin: 0; }
.ds-form__ok {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-5);
  border-radius: var(--r-md);
  background: rgba(52,199,89,.14);
  border: 1px solid rgba(52,199,89,.4);
  color: var(--ink-on-dark);
}
.ds-form__ok svg { width: 22px; height: 22px; color: var(--juno-green-bright); flex-shrink: 0; }
/* `display: flex` above outranks the UA rule for the `hidden` attribute, so the
   success message rendered before anyone had submitted anything: every suburb
   page showed "Got it. Taking you to your price now." next to an empty form.
   It only stayed hidden on the homepage because wheel.css carries a global
   [hidden] rule marked !important. Every other component here pairs its class
   with this (address-input.css:80, account.css:304, depth.css:151); this one
   was missed. */
.ds-form__ok[hidden] { display: none; }

/* ==========================================================================
   7. REVEAL — entrance motion, matched to the reference's easing
   ========================================================================== */

.ds-reveal {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity var(--ds-dur-slow) var(--ds-ease),
              transform var(--ds-dur-slow) var(--ds-ease);
  transition-delay: calc(var(--i, 0) * 70ms);   /* stagger-sequence */
}
.ds-reveal.is-in { opacity: 1; transform: none; }

/* ==========================================================================
   8. REDUCED MOTION
   Everything decorative stops. The marquee is the important one: an infinite
   scroll is exactly what vestibular-sensitive users need turned off, so the
   columns become a plain static list rather than merely slowing down.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .ds-hero__media img { animation: none; }
  .ds-tcol__track     { animation: none; }
  .ds-tcol            { max-height: none; -webkit-mask-image: none; mask-image: none; }
  .ds-skel__img, .ds-skel__line { animation: none; }
  .ds-reveal          { opacity: 1; transform: none; transition: none; }
  .ds-slider__track   { scroll-behavior: auto; }
  .ds-svc__card:hover { transform: none; }
}

/* ==========================================================================
   9. DARK MODE
   Follows tokens.css: explicit [data-theme="dark"] plus [data-theme="auto"]
   inside the media query, so the site's own toggle wins either way.
   ========================================================================== */




/* ==========================================================================
   VIEW TOGGLE + PHOTO CARDS  (/work — the single reviews page)
   ========================================================================== */

.ds-toggle {
  display: inline-flex; gap: 4px;
  margin: var(--s-5) auto 0;
  padding: 4px;
  background: var(--paper);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-pill);
  box-shadow: var(--ds-e-1);
}
.ds-toggle__btn {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 40px; padding: 0 var(--s-4);
  border: 0; border-radius: var(--r-pill);
  background: none; color: var(--ink-soft);
  font: 600 var(--ds-t-sm)/1 var(--font-body);
  cursor: pointer;
  transition: background var(--ds-dur-fast) linear, color var(--ds-dur-fast) linear;
}
.ds-toggle__btn svg { width: 16px; height: 16px; }
.ds-toggle__btn:hover { color: var(--juno-green-dark); }
.ds-toggle__btn:focus-visible { outline: 2px solid var(--juno-green-dark); outline-offset: 2px; }
.ds-toggle__btn[aria-pressed="true"] { background: var(--juno-green-dark); color: #fff; }
.ds-toggle__n { opacity: .72; font-variant-numeric: tabular-nums; font-weight: 500; }

/* A review card that carries a photo. */
.ds-quote__shot {
  position: relative; display: block; width: 100%;
  margin: calc(var(--s-5) * -1) calc(var(--s-5) * -1) var(--s-4);
  width: calc(100% + var(--s-5) * 2);
  border: 0; padding: 0; cursor: pointer; background: var(--sand);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  overflow: hidden;
  aspect-ratio: 4 / 3;
}
.ds-quote__shot img { width: 100%; height: 100%; object-fit: cover; display: block; transition: scale 300ms var(--ds-ease); }
.ds-quote__shot:hover img { scale: 1.04; }
.ds-quote__shot:focus-visible { outline: 2px solid var(--juno-green-dark); outline-offset: -2px; }
.ds-quote__more {
  position: absolute; right: 10px; bottom: 10px;
  padding: 3px 9px; border-radius: var(--r-pill);
  background: rgb(15 27 45 / .72); color: #fff;
  font: 600 var(--ds-t-2xs)/1 var(--font-body);
}

/* The wall runs taller on a page that is only reviews. */
.ds-tcol--tall { max-height: 1180px; }

@media (prefers-reduced-motion: reduce) {
  .ds-quote__shot:hover img { scale: 1; }
  .ds-tcol--tall { max-height: none; }
}


/* ==========================================================================
   STORE BADGES  (.ds-store)
   The official Apple and Google artwork, used as supplied. Two earlier
   attempts rebuilt the badges from markup and both were wrong: the wordmarks
   are specific typefaces at fixed proportions, and neither vendor permits
   redrawing them. Shipping the real asset is both correct and less code.
   ========================================================================== */

.ds-stores { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }

.ds-store {
  display: inline-block; line-height: 0;
  border-radius: 10px;
  transition: transform var(--ds-dur) var(--ds-ease),
              box-shadow var(--ds-dur) var(--ds-ease);
}
.ds-store img {
  display: block;
  height: 52px; width: auto;   /* height-locked so the pair aligns exactly */
}
a.ds-store:hover { transform: translateY(-2px); box-shadow: var(--ds-e-3); }
a.ds-store:focus-visible { outline: 3px solid var(--juno-green-bright); outline-offset: 4px; }

/* Neither app has shipped. /app says the iPhone build is not on the App Store
   yet and Android has not started, and both vendors forbid a live badge for an
   app nobody can download — so these are spans, dimmed, with the reason next
   to them. To go live: span -> a, add href, drop --soon and the note. */
/* Full strength. Dimming them made the artwork look broken or disabled, and
   the line underneath already states plainly that neither app has shipped, so
   the fade was doing nothing except damaging the badges. */
.ds-store--soon { cursor: default; }
.ds-store--soon:hover { transform: none; box-shadow: none; }

.ds-stores__note {
  flex-basis: 100%;
  margin: var(--s-2) 0 0;
  font-size: var(--ds-t-xs);
  color: var(--ink-soft);
}
.ds-cta .ds-stores__note { color: var(--ink-on-dark-soft); }

@media (prefers-reduced-motion: reduce) { a.ds-store:hover { transform: none; } }


/* ==========================================================================
   PHOTO LIGHTBOX  (.svc-shot)
   Lives here rather than service.css because three surfaces use it now:
   the service pages, /work, and the homepage review columns. The class
   name keeps its svc- prefix so the service-page markup did not have to
   change; ds.css is loaded everywhere, service.css is not.
   ========================================================================== */

/* ---- lightbox ---------------------------------------------------------- */
.svc-shot {
  padding: 0; border: 0; background: transparent;
  max-width: min(1040px, 94vw); max-height: 92vh;
  overflow: visible;
}
.svc-shot::backdrop { background: rgb(9 14 22 / .74); backdrop-filter: blur(3px); }
.svc-shot__panel {
  position: relative;
  display: grid; grid-template-columns: 1fr;
  background: var(--hj-surface, #fff);
  border-radius: var(--hj-radius-lg, 16px);
  overflow: hidden;
  max-height: 92vh;
}
@media (min-width: 820px) { .svc-shot__panel { grid-template-columns: minmax(0, 1.45fr) minmax(280px, .55fr); } }

/* Fixed pane, not a pane that resizes to each photo. object-fit: contain on a
   height:100% image let the panel take the image's own aspect ratio, so the
   dialog jumped between portrait and landscape as you paged through and the
   controls moved with it. The pane now holds 4:3 and the photo is letterboxed
   inside it: whole image, always, and the container never moves. */
.svc-shot__media {
  position: relative;
  background: #0E141C;
  aspect-ratio: 4 / 3;
  max-height: 72vh;
  display: grid; place-items: center;
  overflow: hidden;
}
.svc-shot__media img {
  width: 100%; height: 100%;
  object-fit: contain;
  display: block;
}
.svc-shot__nav {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 40px; height: 40px; display: grid; place-items: center;
  border: 0; border-radius: 50%; cursor: pointer;
  background: rgb(255 255 255 / .9); color: #1B2A41;
}
.svc-shot__nav svg { width: 20px; height: 20px; }
.svc-shot__nav--prev { left: 12px; }
.svc-shot__nav--next { right: 12px; }
.svc-shot__nav:focus-visible { outline: 2px solid #34C759; outline-offset: 2px; }
.svc-shot__pos {
  position: absolute; right: 12px; bottom: 12px;
  padding: 3px 9px; border-radius: var(--hj-radius-pill, 999px);
  background: rgb(0 0 0 / .62); color: #fff;
  font-size: var(--hj-text-xs, .8125rem); font-variant-numeric: tabular-nums;
}
.svc-shot__x {
  position: absolute; top: 10px; right: 10px; z-index: 2;
  width: 34px; height: 34px; display: grid; place-items: center;
  border: 0; border-radius: 50%; cursor: pointer;
  background: rgb(255 255 255 / .9); color: #1B2A41;
}
.svc-shot__x svg { width: 18px; height: 18px; }
.svc-shot__x:focus-visible { outline: 2px solid #34C759; outline-offset: 2px; }

.svc-shot__side { padding: var(--hj-space-lg, 24px); overflow-y: auto; }
.svc-shot__name { margin: 0; font-weight: 700; color: var(--hj-fg, #1B2A41); }
.svc-shot__meta { margin: 2px 0 12px; font-size: var(--hj-text-sm, .875rem); color: #B8860B; font-weight: 600; }
.svc-shot__body { margin: 0; line-height: 1.6; color: var(--hj-fg-muted, #4A5568); }
.svc-shot__note {
  margin: 16px 0 0; padding-top: 12px;
  border-top: 1px solid var(--hj-border);
  font-size: var(--hj-text-xs, .8125rem); color: var(--hj-fg-subtle, #8A94A6);
}



/* ==========================================================================
   SUBURB PAGE additions — trust strip and photo band
   ========================================================================== */

.ds-trustbar { background: var(--paper); border-block: 1px solid var(--line); }
.ds-trustbar__inner {
  display: flex; flex-wrap: wrap; gap: var(--s-3) var(--s-6);
  align-items: center; justify-content: center;
  padding: var(--s-4) 0;
}
.ds-trustbar span {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: var(--ds-t-sm); font-weight: 500; color: var(--ink-soft);
}
.ds-trustbar span::before {
  content: ''; width: 7px; height: 7px; border-radius: 50%;
  background: var(--juno-green); flex: none;
}

.ds-photoband { margin: 0; }
.ds-photoband img {
  width: 100%; height: auto; display: block;
  aspect-ratio: 16 / 9; object-fit: cover;
  border-radius: var(--r-xl);
  background: var(--sand);
}
.ds-photoband figcaption {
  margin-top: var(--s-3);
  font-size: var(--ds-t-sm); color: var(--ink-soft); text-align: center;
}


/* ==========================================================================
   SUBURB HERO — two columns, like the homepage
   ========================================================================== */
.sub-hero__inner { display: grid; gap: var(--s-6); align-items: center; }
@media (min-width: 900px) {
  .suburb-hero--split .sub-hero__inner {
    grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr);
    gap: clamp(var(--s-6), 4vw, var(--s-8));
  }
}
.sub-hero__copy { min-width: 0; }
.sub-hero__copy .lead { max-width: 56ch; }
.sub-hero__fig { margin: 0; }
.sub-hero__fig img {
  width: 100%; height: auto; display: block;
  aspect-ratio: 4 / 3; object-fit: cover;
  border-radius: var(--r-xl);
  background: var(--sand);
  box-shadow: var(--ds-e-3);
}
@media (max-width: 899px) { .sub-hero__fig { order: -1; } }


/* ---- three-step explainer, same shape as the homepage ------------------- */
.ds-steps {
  display: grid; gap: var(--s-4);
  grid-template-columns: 1fr;
}
@media (min-width: 760px) { .ds-steps { grid-template-columns: repeat(3, 1fr); } }
.ds-step {
  position: relative;
  padding: var(--s-6) var(--s-5) var(--s-5);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--ds-e-1);
}
.ds-step__n {
  position: absolute; top: var(--s-4); right: var(--s-5);
  font-family: var(--font-display); font-weight: 800;
  font-size: var(--ds-t-3xl); line-height: 1;
  color: var(--juno-green-tint);
}
.ds-step h3 {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--ds-t-xl); color: var(--ink);
  margin: 0 0 var(--s-2);
}
.ds-step p { margin: 0; color: var(--ink-soft); line-height: 1.6; font-size: var(--ds-t-md); }


/* ==========================================================================
   ALL-SERVICES PAGE  (/services/)
   ========================================================================== */
.ds-allsvc__grid {
  display: grid; gap: var(--s-5);
  grid-template-columns: 1fr;
}
@media (min-width: 620px)  { .ds-allsvc__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .ds-allsvc__grid { grid-template-columns: repeat(3, 1fr); } }

.ds-allsvc__card {
  display: flex; flex-direction: column;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  text-decoration: none; color: inherit;
  box-shadow: var(--ds-e-1);
  transition: transform var(--ds-dur) var(--ds-ease),
              box-shadow var(--ds-dur) var(--ds-ease),
              border-color var(--ds-dur) var(--ds-ease);
}
.ds-allsvc__card:hover {
  transform: translateY(-3px);
  box-shadow: var(--ds-e-3);
  border-color: var(--juno-green);
}
.ds-allsvc__card:focus-visible { outline: 2px solid var(--juno-green-dark); outline-offset: 3px; }

.ds-allsvc__media { display: block; aspect-ratio: 16 / 10; background: var(--sand); overflow: hidden; }
.ds-allsvc__media img { width: 100%; height: 100%; object-fit: cover; display: block; transition: scale 320ms var(--ds-ease); }
.ds-allsvc__card:hover .ds-allsvc__media img { scale: 1.04; }
.ds-allsvc__ico { width: 100%; height: 100%; display: grid; place-items: center; background: var(--juno-green-tint); }

.ds-allsvc__body { display: flex; flex-direction: column; gap: var(--s-2); padding: var(--s-5); flex: 1; }
.ds-allsvc__name { font: 700 var(--ds-t-xl)/1.25 var(--font-display); color: var(--ink); }
.ds-allsvc__desc { font-size: var(--ds-t-sm); line-height: 1.55; color: var(--ink-soft); }
.ds-allsvc__foot {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3);
  margin-top: auto; padding-top: var(--s-3);
}
.ds-allsvc__price { font-size: var(--ds-t-sm); color: var(--ink-soft); }
.ds-allsvc__price b { font-size: var(--ds-t-xl); color: var(--ink); font-family: var(--font-display); font-variant-numeric: tabular-nums; }
.ds-allsvc__go { font: 600 var(--ds-t-sm)/1 var(--font-body); color: var(--juno-green-dark); }

@media (prefers-reduced-motion: reduce) {
  .ds-allsvc__card:hover { transform: none; }
  .ds-allsvc__card:hover .ds-allsvc__media img { scale: 1; }
}


/* ==========================================================================
   REGION LIST  (/suburbs/) — dropdown swaps the suburb list in place
   ========================================================================== */
.ds-regionlist__bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3);
  margin-bottom: var(--s-5);
}
.ds-regionlist__bar label { font: 600 var(--ds-t-sm)/1 var(--font-body); color: var(--ink); }
.ds-regionlist__bar select {
  min-height: 46px; padding: 0 var(--s-4);
  border-radius: var(--r-md);
  border: 1px solid var(--line-strong);
  background: var(--paper); color: var(--ink);
  font: 500 var(--ds-t-md)/1 var(--font-body);
  min-width: min(320px, 100%);
}
.ds-regionlist__bar select:focus-visible { outline: 2px solid var(--juno-green-dark); outline-offset: 2px; }
.ds-regionlist__count { margin: 0 0 var(--s-4); font-size: var(--ds-t-sm); color: var(--ink-soft); }

.ds-regionlist__grid {
  display: grid; gap: 2px;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
}
.ds-regionlist__grid a {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-2);
  padding: 9px var(--s-3);
  border-radius: var(--r-sm);
  text-decoration: none;
  font-size: var(--ds-t-sm);
  color: var(--ink);
  transition: background var(--ds-dur-fast) linear, color var(--ds-dur-fast) linear;
}
.ds-regionlist__grid a:hover { background: var(--juno-green-tint); color: var(--juno-green-deep); }
.ds-regionlist__grid a:focus-visible { outline: 2px solid var(--juno-green-dark); outline-offset: -2px; }
.ds-regionlist__grid a span { font-size: var(--ds-t-2xs); color: var(--ink-faint); font-variant-numeric: tabular-nums; }


/* ==========================================================================
   DEPTH SECTIONS  (.dx-panel) — suburb + service long-form content
   --------------------------------------------------------------------------
   Nine of these run down a suburb page. As nine identical 820px columns of
   prose they read as one undifferentiated wall, which is the complaint. Each
   now sits on its own card with a real heading, so the eye can tell where one
   ends and the next starts, and list-type content becomes a two-column card
   grid rather than another paragraph.
   ========================================================================== */

.dx-panel {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  padding: clamp(var(--s-5), 3vw, var(--s-7));
  box-shadow: var(--ds-e-1);
  max-width: 1080px;
  margin-inline: auto;
}
/* On tinted/sand bands the card would disappear, so it inverts to plain. */
.section--sand .dx-panel,
.section--tint .dx-panel { background: var(--paper); }

.dx-panel__title {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(var(--ds-t-2xl), 2.4vw, var(--ds-t-3xl));
  line-height: 1.18; letter-spacing: -.015em;
  color: var(--ink);
  margin: 0 0 var(--s-4);
  padding-bottom: var(--s-4);
  border-bottom: 1px solid var(--line);
}
/* The measure belongs on the prose, not on the container: the container also
   holds card grids and tables that want the full width. */
/* 1ch is the advance of "0" — 9.95px in Work Sans — but the real average
   glyph in this copy is 7.71px, so 68ch renders about 88 characters, not 68.
   That is why lines ran 82-86 characters and broke badly, stranding prices
   like "to $150." alone on a final line. 58ch measures 64-74 real characters.
   text-wrap: pretty then stops the last line orphaning. */
.dx-panel__body > p,
.dx-panel__body > ol > li,
.dx-panel__body > ul > li { max-width: 58ch; text-wrap: pretty; }
.section-head p { max-width: 52ch; margin-inline: auto; text-wrap: pretty; }
[data-cx] > .container > p { max-width: 58ch; margin-inline: auto; text-wrap: pretty; }
.dx-panel__body > p:last-child { margin-bottom: 0; }
.dx-panel__body > *:first-child { margin-top: 0; }

/* Card grid used by problems, steps and anything else list-shaped. */
.dx-cards { display: grid; gap: var(--s-3); margin: var(--s-5) 0 0; }
@media (min-width: 720px) { .dx-cards--2 { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 980px) { .dx-cards--3 { grid-template-columns: repeat(3, 1fr); } }

.dx-card {
  padding: var(--s-4) var(--s-5);
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}
.dx-card h3 {
  /* Was --ds-t-md, identical to page body copy, so the subhead relied on
     weight alone. One step up gives it a real place in the hierarchy. */
  font: 700 var(--ds-t-lg)/1.3 var(--font-display);
  color: var(--ink);
  margin: 0 0 var(--s-2);
}
.dx-card p { margin: 0; font-size: var(--ds-t-sm); line-height: 1.6; color: var(--ink-soft); }

/* Long list content inside a panel goes two-up instead of one long ladder. */
@media (min-width: 760px) {
  .dx-panel__body .tick-list,
  .dx-panel__body .svc-incl { columns: 2; column-gap: var(--s-6); }
  .dx-panel__body .tick-list li,
  .dx-panel__body .svc-incl li { break-inside: avoid; }
}

/* The "on the day" checklist carried style="columns:1" inline, which defeated
   layout from inside the generated HTML and left six items stretched to 68ch
   with most of each line empty. CSS owns the column count now. */
.dx-panel__body .inc { columns: 2; column-gap: var(--s-6); }
.dx-panel__body .inc li { break-inside: avoid; }
@media (max-width: 760px) { .dx-panel__body .inc { columns: 1; } }


/* ==========================================================================
   SURFACE AND TEXT CORRECTIONS  (from the contrast/rhythm audit)
   ========================================================================== */

:root {
  /* --ink-faint (#8A94A6) measures 2.88:1 on cream, 2.66:1 on sand and
     3.06:1 on paper. It is used for prices, section leads and table headers,
     none of which are large text, so all three fail AA. tokens.css already
     documents the remedy but suburb pages never load tokens.css, so the value
     lives here where every surface can see it. 5.08 / 4.70 / 5.39:1. */
  --ds-fg-subtle: #5F6B7F;

  /* Cards ship one constant edge onto bands that differ by 2.4x in
     separation: paper on sand is 1.148:1 but paper on cream is only 1.061:1,
     below any perceptual threshold. The band declares how much help a card
     standing on it needs; the card consumes it. Both branches are existing
     steps on the --ds-e-* ramp. */
  --ds-card-edge: var(--line-strong);
  --ds-card-lift: var(--ds-e-2);
}

/* Bands whose own fill already separates the card. */
.ds-sec--sand, .ds-sec--tint,
.section--sand, .section--tint,
.hh-sec--sand {
  --ds-card-edge: var(--line);
  --ds-card-lift: var(--ds-e-1);
}

.ds-quote, .ds-step, .ds-svc__card, .dx-panel, .hh-card {
  border-color: var(--ds-card-edge);
  box-shadow: var(--ds-card-lift);
}

/* .dx-card was cream on a paper panel — 1.061:1 with no shadow at all. Sand
   is 1.148:1, more than double the separation, for a one-token change. */
.dx-card { background: var(--sand); }

/* Prices, section leads and table headers off --ink-faint. */
.muted, .crumbs span, .hh-soon p, .hh-soon__it, .hh-card__price,
.hh-search__field label, .section-head p, .table th,
.mm-col__head, .mm-price, .from, .field__hint { color: var(--ds-fg-subtle); }

/* sand -> tint is 1.024:1: a hue-only step that vanishes for anyone with a
   red-green deficiency, and it occurs three times per suburb page. Rule only
   the invisible boundaries, not every section. */
.section--sand + .section--tint, .section--tint + .section--sand,
.ds-sec--sand  + .ds-sec--tint,  .ds-sec--tint  + .ds-sec--sand,
.section--sand + .section--sand, .ds-sec--sand + .ds-sec--sand {
  border-top: 1px solid var(--line-strong);
}

/* Suburb pages never load tokens.css, so --hj-measure never reached them and
   one prose block measured 127 characters per line. */
.dx .container > p, .section .container > p, .card > p { max-width: 68ch; }


/* ==========================================================================
   AUDIT CORRECTIONS — /work, /services/, /suburbs/
   ========================================================================== */

/* Marquee columns were 58 / 73 / 56 px/s: the durations were tuned without
   accounting for the three tracks being different heights, so the middle
   column ran 29% faster than its neighbours and read as a bug rather than as
   the intended offset. These land all three within 1% of each other while
   staying coprime enough not to lock in step. */
:root { --ds-col-a: 60s; --ds-col-b: 94s; --ds-col-c: 65s; }

/* The mask ghosted 8% of a 1180px column — 94px of unreadable card at each
   end, on top of a 128px gap above the wall. A fixed 40px reads as an edge. */
.ds-tcol--tall {
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 40px, #000 calc(100% - 40px), transparent 100%);
}

/* brand.css sets svg { display: block }, so text-align could not centre the
   empty-state icon and it sat hard left above centred text. */
.ds-gal__empty svg { margin-inline: auto; }

/* Service photos are portrait (560x1024) and were being cover-cropped into a
   16:10 box, discarding 66% of each and cutting heads off. 4:3 with the focal
   point high keeps the subject. */
.ds-allsvc__media { aspect-ratio: 4 / 3; }
.ds-allsvc__media img { object-position: 50% 28%; }

/* One price treatment. The all-services cards used --ink while the shared
   service grid used green, on the same page. */
.ds-allsvc__price b { color: var(--juno-green-dark); }

/* 14 cards in a 3-column grid left an orphan row of two. */
@media (min-width: 1000px) { .ds-allsvc__grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1240px) { .ds-allsvc__grid { grid-template-columns: repeat(2, 1fr); } }

/* The suburb list was a row-major grid, so A-Z ran ACROSS five columns and
   finding one name meant a serpentine scan of 118 rows. Newspaper columns
   read down, which is how anyone scans an alphabetical list. */
.ds-regionlist__grid {
  display: block;
  columns: 5 190px;
  column-gap: var(--s-5);
}
.ds-regionlist__grid a { break-inside: avoid; min-height: 40px; }
@media (max-width: 900px) { .ds-regionlist__grid { columns: 2 160px; } }

/* Postcodes were --ink-faint at 12px: 3.06:1. */
.ds-regionlist__grid a span { color: var(--ds-fg-subtle); }

/* The region control sat hard left under a centred heading. */
.ds-regionlist__bar { justify-content: center; }
.ds-regionlist__count { text-align: center; }

/* Lightbox: 34px and 40px controls against a 44px minimum, and no entrance,
   so the dialog snapped in. */
.svc-shot__x   { width: 44px; height: 44px; }
.svc-shot__nav { width: 44px; height: 44px; }
.svc-shot__x svg   { width: 20px; height: 20px; }
.svc-shot__nav svg { width: 22px; height: 22px; }
.svc-shot__nav { background: rgb(15 27 45 / .58); color: #fff; }
.svc-shot__nav:hover { background: rgb(15 27 45 / .78); }
.svc-shot__x:focus:not(:focus-visible) { outline: none; }
.svc-shot[open] { animation: ds-shot-in 200ms var(--ds-ease) both; }
@keyframes ds-shot-in { from { opacity: 0; transform: translateY(8px) scale(.985); } }

/* App copy ran 102 characters per line beside a 236px phone. */
.hh-app__copy { max-width: 62ch; }

@media (prefers-reduced-motion: reduce) {
  .svc-shot[open] { animation: none; }
}


/* ==========================================================================
   PRICE TABLES — the numbers the page exists to quote
   ========================================================================== */

/* Prices were set in proportional figures, so the $ glyphs in a column sat at
   four different x positions and 1 was visibly narrower than 0. That is the
   "disproportionate" reading of the numbers. */
[data-price-src],
[data-cx="worked-pricing"] .table,
[data-cx="postcode-facts"] .summary__row,
.svc-lines__row, .svc-total__value {
  font-variant-numeric: tabular-nums lining-nums;
}

/* The total was marked up as <th>, which brand.css styles as an admin column
   header: 12.48px uppercase grey on sand, 2.67:1 — smaller and fainter than
   the line items it totals, and failing AA. It is the most important number
   in the block. */
[data-cx="worked-pricing"] .table td { font-size: var(--ds-t-md); color: var(--ink); padding: var(--s-3) 0; }
[data-cx="worked-pricing"] .table td:last-child { color: var(--ink-soft); white-space: nowrap; }
[data-cx="worked-pricing"] .table th {
  font-size: var(--ds-t-md); font-weight: 700;
  text-transform: none; letter-spacing: 0;
  color: var(--ink); background: transparent;
  border-top: 2px solid var(--ink); border-bottom: 0;
  padding: var(--s-4) 0 0; vertical-align: baseline;
}
[data-cx="worked-pricing"] .table th:last-child {
  font-family: var(--font-display); font-size: var(--ds-t-2xl);
  line-height: 1.1; color: var(--juno-green-dark); white-space: nowrap;
}
/* Hover feedback on a static receipt implies it is clickable. */
[data-cx="worked-pricing"] .table tr:hover td { background: transparent; }

/* ============================================================================
   §  ACCESSIBILITY CORRECTIONS
   Every ratio below was measured with a WCAG 2.x relative-luminance function
   against the composited ancestor background, not estimated. brand.css is the
   frozen token layer, so where a token value is itself the defect it is
   re-pointed here rather than edited there.
   ========================================================================== */

/* --ink-faint #8A94A6 never clears 4.5:1 on any surface this site uses:
   3.06 on white, 2.88 on cream, 2.66 on sand. #5F6B7F gives 5.39 / 5.08 / 4.70
   and keeps the same cool-grey cast. Roughly 100 nodes site-wide. */
:root { --ink-faint: #5F6B7F; }

/* Control borders were --line-strong #D8D4C9: 1.48 on white, 1.29 on sand,
   against the 3:1 that SC 1.4.11 asks of a UI boundary. #8C8574 gives
   3.67 / 3.20. Scoped to controls, so decorative card hairlines keep their
   softness where no interaction is implied. */
:root { --line-control: #8C8574; }
.input, .select, .textarea, .mm-search, .hh-chip, .chip, .ds-pill, .ds-search,
.hh-search, select, input[type="text"], input[type="email"],
input[type="search"], input[type="tel"] { border-color: var(--line-control); }
/* Same defect on the dark CTA band: #444D5B on #0F1B2D was 2.02. */
.ds-field input, .ds-field select, .ds-field textarea { border-color: #6E7A8C; }

/* .footer sets --ink-on-dark-soft, but brand.css `p { color: var(--ink-soft) }`
   outranks inheritance, so footer body copy rendered #4A5568 on #0F1B2D:
   2.30:1. Restoring the intended colour takes it to 9.80:1. */
.footer p, .footer li, .footer address { color: var(--ink-on-dark-soft); }

/* --juno-green #2E8B44 as *text* tops out at 4.04 (cream) / 3.74 (sand), and
   as a *background* under white text at 4.29. --juno-green-dark #1F6B33 is
   already in the palette and gives 6.17 / 5.70 / 6.54. */
.hh-kicker, .hh-card__go, .btn--outline, .btn--outline:link,
.btn--outline:visited, .nav-links a.btn--outline, .ds-allsvc__go,
a.mm-trigger[aria-expanded="true"] { color: var(--juno-green-dark); }
.btn--outline { border-color: var(--juno-green-dark); }
.hh-skip, .hh-search__go { background: var(--juno-green-dark); }

/* The green CTA gradients ended at #3AA357, where white body copy is 3.20:1.
   Ending at #256F39 holds the same hue family at 6.16:1. */
:root { --hh-pro-grad: linear-gradient(135deg, #14532D, #1F6B33 62%, #256F39); }
.svc-cta { background: linear-gradient(135deg, #14532D, #1F6B33); }
/* The .9/.88 alphas on these cost about 0.4 of the ratio for no visual gain. */
.hh-pro p, .hh-pro__list li, .svc-cta p { color: #fff; }
/* Outline button on the band: rgba(255,255,255,.5) resolved to 1.85:1. */
.hh-btn-outline { border-color: rgb(255 255 255 / .82); }

/* The suburb CTA panel was copied from the navy panel and kept its text
   colour, so #B9C4D4 landed on green: 2.43:1. */
.cta-panel { background: linear-gradient(135deg, #14532D, #1F6B33); }
.cta-panel p, .cta-panel .ds-cta__sub { color: #fff; }

/* `.ds-search input { outline: none }` is a base declaration at (0,1,1), which
   beats both the global `:focus-visible` ring (0,1,0) and the `:where()` rule
   (0,1,0). The review search had no keyboard focus indicator: its only
   replacement was a 1px border tint plus a halo measuring 1.12:1. */
.ds-search input:focus-visible {
  outline: 3px solid var(--juno-green-dark);
  outline-offset: 2px;
  border-radius: 6px;
}
.ds-search:focus-within { border-color: var(--juno-green-dark); }

/* Touch targets, measured at 375px. 44px is the Apple HIG floor.
   Breadcrumb and footer links are left alone on purpose: WCAG 2.5.8 exempts
   inline text links, and growing them would break those layouts for no gain. */
.nav-toggle { padding: 9px; }                        /* was 42x42 */
.hh-chip, .chip, .ds-pill, .ds-toggle__btn { min-height: 44px; }
.dx-rail__btn { width: 44px; height: 44px; }
/* The 60px-tall wrapper here is a div, so its padding was not part of the hit
   area and the control itself measured 22px. */
.hh-search__field > input, .hh-search__field > select { min-height: 44px; }
/* Keep the 8px gap SC 2.5.8 asks for once the pills grow. */
.hh-pop, .chip-scroll, .ds-pillrow { gap: 8px; }

/* Footer column headings moved h4 -> h3 to close the h2 -> h4 skip on every
   page. brand.css only styles .footer h4, so carry the look across. */
.footer h3 {
  color: var(--ink-on-dark); margin-bottom: var(--s-4); font-size: .95rem;
  text-transform: uppercase; letter-spacing: .06em;
}

/* ============================================================================
   §  SUBURB PAGE SECTION RHYTHM
   Thirteen concept sections each carried their own inline measure: 680, 720,
   760, 820 and 860px. Scrolling one page therefore stepped through five
   different column widths, which reads as broken rather than composed. They
   now all consume one token.
   ========================================================================== */
:root { --ds-measure: 820px; }

/* The receipt ran the label column to 551px at 1280px wide and stranded the
   amount 480px away from the thing it priced. A receipt has to be scannable
   down its right edge, so it gets its own tighter measure inside the card. */
[data-cx="worked-pricing"] .table { max-width: 460px; }
[data-cx="worked-pricing"] .table td:first-child,
[data-cx="worked-pricing"] .table th:first-child { padding-right: var(--s-5); }

/* Two-column comparison tables were 141px and 121px wide at 375px, turning
   "The usual quote-first way" into three lines and every cell into five to
   seven. Below the point where two columns stop being readable, each row
   becomes a labelled pair stacked down the page. */
@media (max-width: 700px) {
  [data-cx="honest-comparison"] .table,
  [data-cx="honest-comparison"] .table tbody,
  [data-cx="honest-comparison"] .table tr,
  [data-cx="honest-comparison"] .table td { display: block; width: auto; }
  [data-cx="honest-comparison"] .table thead { display: none; }
  [data-cx="honest-comparison"] .table tr {
    padding: var(--s-4) 0;
    border-bottom: 1px solid var(--line);
  }
  [data-cx="honest-comparison"] .table tr:last-child { border-bottom: 0; }
  [data-cx="honest-comparison"] .table td { border: 0; padding: 0; }
  /* Without the header row each cell has to say which side it belongs to. */
  [data-cx="honest-comparison"] .table td::before {
    content: attr(data-side);
    display: block;
    font-size: var(--ds-t-xs);
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: .06em;
    color: var(--ds-fg-subtle);
    margin-bottom: var(--s-1);
  }
  [data-cx="honest-comparison"] .table td + td { margin-top: var(--s-3); }
}

/* At 375px the container's 24px and the card's 24px cost 96px of a 375px
   viewport, so a table had 262px to lay out in. */
@media (max-width: 560px) {
  .card { padding: var(--s-4); }
  .container { padding: 0 var(--s-4); }
  /* .chip-scroll bleeds by a negative margin so its first and last chip line
     up with the container edge. That margin is hardcoded to --s-5, so once the
     container drops to --s-4 the rail runs 8px past the viewport on each side
     and the document scrolls sideways. The bleed has to track the padding. */
  .chip-scroll {
    margin-inline: calc(-1 * var(--s-4));
    padding-inline: var(--s-4);
  }
}

/* Four text-led concept sections used a plain .container, so they ran to the
   full 1160px while the prose sections beside them sat at 820. Scrolling the
   page stepped between the two widths with no logic to it. Text now shares one
   measure; only suburb-map stays wide, because it carries a real map and the
   extra width is doing something. */
[data-cx="streets-we-cover"] > .container,
[data-cx="seasonal-angle"] > .container,
[data-cx="postcode-facts"] > .container,
[data-cx="adjacent-suburbs"] > .container { max-width: var(--ds-measure); }

/* ============================================================================
   §  SECTION BANDS AND VERTICAL RHYTHM
   ========================================================================== */

/* The depth band picks its tints from a per-suburb rotation, and one section
   (whats-included) carries a hardcoded sand. When the two agree you get two
   identical bands with an invisible seam between them, which is what made the
   page read as arbitrarily coloured. Enforcing alternation here holds for every
   rotation rather than for the one case I happened to look at. */
.section--sand + .section--sand,
.section--tint + .section--tint,
.ds-sec--sand + .ds-sec--sand,
.ds-sec--tint + .ds-sec--tint { background: transparent; }

/* Two stacked sections each contributed a full --ds-sec-y, so 89.6 top and
   89.6 bottom left 179px of nothing between them. A tinted band still needs
   its full padding to read as a band, so only untinted neighbours collapse. */
.ds-sec + .ds-sec:not([class*="--sand"]):not([class*="--tint"]),
.section + .ds-sec:not([class*="--sand"]):not([class*="--tint"]) {
  padding-top: calc(var(--ds-sec-y) * .55);
}
.section + .section:not([class*="--sand"]):not([class*="--tint"]),
.ds-sec + .section:not([class*="--sand"]):not([class*="--tint"]) {
  padding-top: var(--s-6);
}


/* The depth panel was 1080px wide while its paragraphs capped at 58ch, about
   575px. Every card therefore wrapped its text early and left roughly 425px of
   empty card to the right of it: sentences breaking onto the next line with
   visible room to spare. The card now hugs the measure instead of the measure
   hiding inside the card. 780px of panel minus its padding leaves the body
   around 600px. Measured, that renders 75 characters a line, the top of the
   60-75 range; the 780px panel it replaced was running 88. */
.dx-panel { max-width: 680px; }
.dx-panel__body > p,
.dx-panel__body > ol > li,
.dx-panel__body > ul > li { max-width: none; text-wrap: pretty; }

/* The closing block absorbed the old separate CTA section, so it carries a
   straight-to-booking link for anyone who would rather not fill in the form. */
.ds-cta__alt {
  margin: var(--s-4) 0 0;
  font-size: var(--ds-t-sm);
  color: var(--ds-fg-subtle);
}
.ds-cta__alt a {
  color: var(--juno-green-dark);
  font-weight: 600;
  text-underline-offset: 3px;
}
