/* ==========================================================================
   Page compositions — hero, portfolio grid, work, about, contact
   ========================================================================== */

/* ---------- Hero ----------
   A solid surface of its own, warmed from the top right, closed with a hairline.
   The heroes are the one place the page should feel built rather than open. */
.hero {
  position: relative;
  padding-top: clamp(7.5rem, 6rem + 6vw, 11rem);
  padding-bottom: var(--s-10);
  background:
    radial-gradient(118% 96% at 80% 0%, var(--hero-glow), transparent 60%),
    var(--hero-bg);
  border-bottom: 1px solid var(--line);
}

.hero__grid {
  display: grid;
  /* minmax(0, 1fr), not the implicit auto track. An auto track sizes to its
     widest item's max-content and will happily exceed its container: at 375px
     this resolved to a 425px column inside a 335px grid and pushed the whole
     document sideways. */
  grid-template-columns: minmax(0, 1fr);
  gap: var(--s-9);
  align-items: center;
}

@media (min-width: 1000px) {
  .hero__grid {
    grid-template-columns: minmax(0, 1.04fr) minmax(0, 0.96fr);
    gap: var(--s-8);
    min-height: min(74dvh, 44rem);
  }
}

.hero__lede { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-5); align-content: center; }

/* The eyebrow is nowrap so its dot, rule and label stay on one line. Once it
   read "RE/MAX Bozz Realty · Surrey and the Fraser Valley" that made its
   min-content 425px, which forced the whole hero column past a 375px viewport.
   It wraps on a phone; the rule and dot stay with the first line. */
@media (max-width: 700px) {
  .hero .eyebrow { white-space: normal; }
}

.hero__title {
  font-size: var(--t-5);
  line-height: 0.98;
  letter-spacing: -0.032em;
  max-width: 13ch;
}

.hero__title em {
  font-style: italic;
  color: var(--brass);
}

.hero__sub {
  max-width: 46ch;
  font-size: var(--t-1);
  color: var(--ink-2);
  line-height: 1.6;
}

.hero__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-top: var(--s-2); }

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3) var(--s-6);
  margin-top: var(--s-5);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
  font-size: var(--t--2);
  color: var(--ink-3);
}

.hero__meta b {
  display: block;
  font-family: var(--font-display);
  font-size: var(--t-1);
  font-weight: 400;
  color: var(--ink);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

/* Stacked figures, offset like prints laid on a desk */
.hero__stack { position: relative; }

.hero__stack-main { position: relative; z-index: 1; }

/* Sharry sits in front of the property, overlapping its lower right corner.
   Portrait aspect rather than the square the interior shot used, because a
   square crop of a standing figure either cuts him off at the chest or leaves
   him small in the middle of it. */
.hero__portrait {
  position: relative;
  z-index: 2;
  width: 52%;
  min-width: 9.5rem;
  margin-top: -30%;
  margin-left: auto;
  margin-right: -3%;
}

.hero__portrait .plate__core { overflow: hidden; }

.hero__portrait img {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: 50% 18%;      /* keep his head in frame as the box narrows */
}

@media (min-width: 1000px) {
  .hero__portrait { width: 46%; margin-top: -34%; margin-right: -7%; }
}

/* No bleed past the gutter on a phone. The negative right margin is what gives
   the collage its overlap on a wide screen, but at 375px it pushed the
   portrait to 458px inside a 375px viewport. The document then scrolled
   sideways, which is also why the hamburger became unclickable: the header had
   moved out from under the pointer. The overlap is kept vertically. */
@media (max-width: 700px) {
  .hero__portrait { width: 56%; margin-right: 0; margin-top: -26%; }
}

/* A quiet plate rather than a caption bar: this is a portfolio, so the name
   should be readable without leaning on the wordmark in the header. */
.hero__portrait-cap {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: var(--s-7) var(--s-4) var(--s-3);
  background: linear-gradient(to top,
    rgba(10, 9, 7, 0.88) 0%, rgba(10, 9, 7, 0.66) 46%, rgba(10, 9, 7, 0) 100%);
  color: #F7F4ED;
}

.hero__portrait-cap b {
  font-family: var(--font-display);
  font-size: var(--t--1);
  font-weight: 700;
  letter-spacing: -0.01em;
}

.hero__portrait-cap span {
  font-size: 0.5625rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: rgba(247, 244, 237, 0.76);
}

.hero__badge {
  position: absolute;
  z-index: 3;
  left: -1.5%;
  bottom: 12%;
  display: grid;
  gap: 0.15rem;
  padding: 0.85rem 1.1rem;
  border-radius: var(--r-md);
  background: var(--glass-bg-2);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  backdrop-filter: blur(16px) saturate(1.4);
  box-shadow: var(--shadow-2);
  max-width: 12rem;
}

.hero__badge::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(150deg, var(--glass-edge-1), transparent 50%, var(--glass-edge-2));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
}

.hero__badge b {
  font-family: var(--font-display);
  font-size: var(--t-1);
  font-weight: 400;
  letter-spacing: -0.022em;
  font-variant-numeric: tabular-nums;
}

.hero__badge span {
  font-size: 0.5625rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ---------- Marquee of service areas ----------
   Reads as a divider between sections, so it carries its own tone rather than
   sitting on whatever is behind it. */
.areas-strip {
  overflow: hidden;
  padding-block: var(--s-4);
  background: var(--divider);
  border-block: 1px solid var(--line);
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
}

/* No gap on the track, on purpose. With `gap`, n items carry n-1 gaps, so
   translating by -50% lands half a gap out and the loop visibly jumps every
   pass. The spacing lives inside each item instead, which makes the second
   half byte for byte the same width as the first. */
.areas-strip__track {
  display: flex;
  width: max-content;
  animation: drift 52s linear infinite;
  will-change: transform;
}

.areas-strip__item {
  display: inline-flex;
  align-items: center;
  gap: var(--s-6);
  padding-right: var(--s-6);
  font-family: var(--font-display);
  font-size: var(--t-1);
  letter-spacing: -0.015em;
  color: var(--ink-2);
  white-space: nowrap;
}

.areas-strip__item::after {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--brass);
  opacity: 0.6;
}

@keyframes drift {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(-50%, 0, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .areas-strip__track { animation: none; flex-wrap: wrap; width: 100%; }
  .areas-strip { -webkit-mask-image: none; mask-image: none; }
}

/* ---------- Portfolio grid (asymmetric bento on wide screens) ---------- */
.portfolio {
  display: grid;
  gap: var(--s-6) var(--s-5);
  grid-template-columns: 1fr;
}

@media (min-width: 680px) {
  .portfolio { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 1080px) {
  .portfolio { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: var(--s-8) var(--s-5); }
  .portfolio > * { grid-column: span 2; }
}

/* ---------- Editorial split block ---------- */
.split {
  display: grid;
  gap: var(--s-8);
  align-items: center;
}

@media (min-width: 940px) {
  .split { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--s-10); }
  .split--wide-left { grid-template-columns: minmax(0, 1.02fr) minmax(0, 0.98fr); }
  .split--wide-right { grid-template-columns: minmax(0, 0.8fr) minmax(0, 1.2fr); }
  .split--flip > *:first-child { order: 2; }
}

.split__body { display: grid; gap: var(--s-5); align-content: center; }
.split__body p { color: var(--ink-2); max-width: 52ch; }

/* ---------- Process steps ---------- */
.steps {
  display: grid;
  gap: var(--s-7) var(--s-6);
  grid-template-columns: 1fr;
}

@media (min-width: 700px) { .steps { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .steps { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.steps--4 { grid-template-columns: 1fr; }
@media (min-width: 700px) { .steps--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .steps--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* The tallest step sets the row height and every step stretches to it. With
   the default align-content the slack is shared out between the rows, so the
   step with the shortest body pushes its own number and heading down and the
   headings stop lining up. Packing to the start keeps every heading on one
   baseline and lets the slack fall to the bottom where nobody sees it. */
.step { align-content: start; }

.step--ruled { padding-top: var(--s-4); border-top: 1px solid var(--line); }

/* ---------- Case study ---------- */
.case { display: grid; gap: var(--s-6); }

@media (min-width: 940px) {
  .case { grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr); gap: var(--s-9); align-items: center; }
  .case:nth-of-type(even) > *:first-child { order: 2; }
}

.case + .case { margin-top: var(--section-y); padding-top: var(--section-y); border-top: 1px solid var(--line); }

.case__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2) var(--s-5);
  font-size: var(--t--2);
  color: var(--ink-3);
  letter-spacing: 0.02em;
}

.case__results {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-4);
  margin-top: var(--s-2);
  padding-top: var(--s-5);
  border-top: 1px solid var(--line);
}

.case__result { display: grid; gap: 0.2rem; }

.case__result b {
  font-family: var(--font-display);
  font-size: var(--t-2);
  font-weight: 400;
  line-height: 1;
  letter-spacing: -0.028em;
  color: var(--brass);
  font-variant-numeric: tabular-nums;
}

.case__result span { font-size: var(--t--2); color: var(--ink-3); }

/* ---------- Service area map ---------- */

@media (min-width: 940px) {
  .areas { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: var(--s-9); align-items: center; }
}





.map-plate svg { width: 100%; height: auto; display: block; }
.map-node { transition: opacity 400ms var(--ease-out); }

/* ---------- Portrait slot ---------- */
.portrait {
  position: relative;
  display: grid;
  place-items: center;
  aspect-ratio: 4 / 5;
  background:
    radial-gradient(120% 90% at 50% 0%, var(--surface-3), transparent 62%),
    var(--surface-2);
  overflow: hidden;
}

.portrait img { width: 100%; height: 100%; object-fit: cover; }




/* ---------- Contact block ---------- */
.contact { display: grid; gap: var(--s-8); }

@media (min-width: 940px) {
  .contact { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); gap: var(--s-10); align-items: start; }
}

.contact__lines { display: grid; gap: var(--s-5); margin-top: var(--s-6); }

.contact__line {
  display: flex;
  align-items: flex-start;
  gap: var(--s-3);
  text-decoration: none;
}

.contact__line svg { width: 1.05rem; height: 1.05rem; color: var(--brass); flex: 0 0 auto; margin-top: 0.28rem; }

.contact__line b {
  display: block;
  font-size: 0.5625rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.15rem;
}

.contact__line span { font-size: var(--t-0); color: var(--ink); transition: color var(--d-hover) var(--ease-out); }

@media (hover: hover) and (pointer: fine) {
  a.contact__line:hover span { color: var(--brass); }
}

.form-grid { display: grid; gap: var(--s-4); }
@media (min-width: 600px) { .form-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ---------- CTA band ---------- */
.cta-band {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xl);
  padding: clamp(2rem, 1.4rem + 3vw, 3.5rem);
  background:
    radial-gradient(96% 150% at 8% 0%, var(--brass-wash), transparent 56%),
    var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-2);
  display: grid;
  gap: var(--s-5);
}

@media (min-width: 880px) {
  .cta-band { grid-template-columns: minmax(0, 1.3fr) minmax(0, 0.7fr); align-items: center; gap: var(--s-8); }
}

.cta-band::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(150deg, var(--glass-edge-1), transparent 52%, var(--glass-edge-2));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
}

.cta-band__title { font-size: var(--t-3); max-width: 16ch; }
.cta-band__actions { display: flex; flex-wrap: wrap; gap: var(--s-3); }

/* ---------- Page masthead (inner pages) ----------

   Three compositions, one skeleton.

   Every masthead is the same height and shares the same left column: crumbs,
   then the headline. The right column is where they diverge, and it is built
   from the page's own material rather than from an ornament placed beside the
   text:

     Listings  a live index of what is currently on the market
     Work      a ledger of completed sales, figures counting up
     About     a survey stamp: coordinates, dates, territory

   An earlier version put the same line drawing on all three and changed only
   the picture. It read as decoration because it was.
   ========================================================================== */
.masthead {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  min-height: clamp(21rem, 16rem + 15vw, 29rem);
  padding-top: clamp(8rem, 6.5rem + 6vw, 11rem);
  padding-bottom: var(--s-8);
  background: var(--paper-2);
  border-bottom: 1px solid var(--line);
  isolation: isolate;
}

.masthead > .wrap { width: 100%; position: relative; z-index: 1; }

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

/* On desktop the masthead is a fixed height and its content sits on the
   bottom edge. Chasing equality by tuning the headline measure does not hold:
   the three headlines are different lengths, so any measure that gives one of
   them two lines gives another three, and the mastheads drift apart by 65px.
   Fixing the box and aligning to its floor makes them identical by
   construction. Below 940px it goes back to content height, where a fixed box
   would clip. */
@media (min-width: 940px) {
  /* min-height, not height. A fixed box plus align-items:flex-end plus
     overflow:hidden means any headline taller than the box overflows UPWARD
     and gets cut off at the top, which is exactly what happened when the
     Commercial and About headlines got longer: five lines in a box sized for
     three, with the first two clipped off the screen. A minimum still makes
     every short masthead identical, which was the point, and lets a long one
     grow instead of losing its first line. */
  /* 46rem, not 37rem. All three mastheads now carry content taller than the
     old floor, so each one sized to its own content and they came out 691,
     731 and 733: close enough to look like a mistake. A floor above the
     tallest makes them identical again, and it stays a MINIMUM so a future
     headline grows the box instead of being clipped by it. */
  .masthead { min-height: 46rem; }

  .hero-index,
  .hero-ledger,
  .hero-stamp { align-content: end; }
}

@media (min-width: 940px) {
  .masthead__grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.82fr);
    align-items: end;
    gap: var(--s-10);
  }
}

/* Wide enough that all three headlines set on two lines. At a narrower
   measure two of them took three lines and those mastheads ran 65px taller
   than the third. */
.masthead__title { max-width: 24ch; text-wrap: balance; }

/* A display size is a function of how much text it has to carry. The headline
   size was set for "Three moves, start to finish"; the repositioning replaced
   those with full positioning statements, and at the original size "Your
   trusted BC commercial and residential real estate advisor" ran to seven
   lines and swallowed the whole masthead. Long headlines drop a step and take
   a wider measure, which puts them back to three or four lines without
   touching the short ones. */
.masthead__title--long {
  font-size: var(--t-4);
  max-width: 30ch;
}

.masthead__lede {
  max-width: 40ch;
  margin-top: var(--s-4);
  font-size: var(--t-0);
  line-height: 1.6;
  color: var(--ink-3);
}

/* ---------- Shared quiet field ----------
   One soft bloom per page, tinted differently, and nothing else. */
.masthead::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    radial-gradient(64% 100% at 88% 12%, var(--masthead-bloom, var(--brass-wash)), transparent 66%),
    linear-gradient(180deg, var(--surface) 0%, var(--paper-2) 100%);
}

.masthead--listings { --masthead-bloom: var(--brass-wash); }
.masthead--work { --masthead-bloom: rgba(126, 86, 31, 0.11); }
.masthead--about { --masthead-bloom: rgba(138, 42, 51, 0.08); }

:root[data-theme="dark"] .masthead--work { --masthead-bloom: rgba(220, 172, 99, 0.11); }
:root[data-theme="dark"] .masthead--about { --masthead-bloom: rgba(219, 123, 131, 0.09); }

/* ---------- Listings: the live index ---------- */
/* A card, so the index sits on its own ground rather than on the hero's grid
   lines. Same treatment as the About stamp, so the two inner pages read as one
   family. Opaque on purpose: the grid drifts behind it, and a translucent
   panel would make the list's contrast depend on where those lines happened to
   be at the time. */
.hero-index {
  display: grid;
  padding: var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  box-shadow: var(--shadow-1);
}

/* The head rule already separates the head from the rows, and the card edge
   now closes the bottom, so the last row does not need its own. */
.hero-index__row:last-child { border-bottom: 0; padding-bottom: 0; }

.hero-index__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  padding-bottom: var(--s-3);
  border-bottom: 1px solid var(--line-2);
  font-size: var(--t--2);
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.hero-index__head b { color: var(--brass); font-weight: 600; font-variant-numeric: tabular-nums; }

.hero-index__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--s-4);
  padding: var(--s-3) 0;
  border-bottom: 1px solid var(--line);
  opacity: 0;
  transform: translateY(0.5rem);
  animation: index-in 700ms var(--ease-out) forwards;
  animation-delay: calc(220ms + var(--i, 0) * 110ms);
}

@keyframes index-in { to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  .hero-index__row { opacity: 1; transform: none; animation: none; }
}

.hero-index__addr {
  font-size: var(--t--1);
  font-weight: 500;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hero-index__area { display: block; font-size: var(--t--2); font-weight: 400; color: var(--ink-3); margin-top: 0.15rem; }

.hero-index__price {
  font-family: var(--font-display);
  font-size: var(--t-1);
  font-weight: 600;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---------- Work: the ledger ---------- */
.hero-ledger { position: relative; display: grid; gap: 0; }

/* The count of documented sales, set large and quiet behind the rows */
.hero-ledger__ghost {
  position: absolute;
  top: 50%;
  right: -0.06em;
  transform: translateY(-54%);
  z-index: -1;
  font-family: var(--font-display);
  font-size: clamp(7rem, 5rem + 9vw, 13rem);
  font-weight: 700;
  line-height: 0.8;
  letter-spacing: -0.05em;
  color: var(--brass);
  opacity: 0.09;
  pointer-events: none;
  user-select: none;
}

:root[data-theme="dark"] .hero-ledger__ghost { opacity: 0.13; }

.hero-ledger__row {
  display: grid;
  grid-template-columns: 2rem minmax(0, 1fr) auto;
  align-items: baseline;
  gap: var(--s-3);
  padding: var(--s-3) 0;
  position: relative;
}

/* Each row's rule draws in rather than simply appearing */
.hero-ledger__row::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--line);
  transform: scaleX(0);
  transform-origin: left center;
  animation: rule-draw 800ms var(--ease-out) forwards;
  animation-delay: calc(200ms + var(--i, 0) * 140ms);
}

@keyframes rule-draw { to { transform: scaleX(1); } }

@media (prefers-reduced-motion: reduce) {
  .hero-ledger__row::after { transform: scaleX(1); animation: none; }
}

.hero-ledger__n {
  font-size: var(--t--2);
  font-weight: 600;
  letter-spacing: 0.16em;
  color: var(--brass);
  font-variant-numeric: tabular-nums;
}

.hero-ledger__place { font-size: var(--t--1); font-weight: 500; color: var(--ink); }

.hero-ledger__fig {
  font-size: var(--t--1);
  font-weight: 600;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.hero-ledger__fig span { color: var(--ink-3); font-weight: 400; margin-left: 0.3rem; }

/* ---------- About: the survey stamp ---------- */
.hero-stamp {
  position: relative;
  display: grid;
  gap: var(--s-4);
  padding: var(--s-5) var(--s-5) var(--s-5) var(--s-6);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  /* Opaque, not glass. Sitting at 62% over the masthead's bloom made the
     panel's contrast depend on where the gradient happened to be behind it,
     and the label text measured 1.21:1. */
  background: var(--surface);
  box-shadow: var(--shadow-1);
  overflow: hidden;
}

@keyframes motif-draw { to { stroke-dashoffset: 0; } }

.hero-stamp__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s-4);
  position: relative;
}

.hero-stamp__row + .hero-stamp__row { padding-top: var(--s-4); border-top: 1px solid var(--line); }

.hero-stamp__k {
  font-size: var(--t--2);
  font-weight: 600;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.hero-stamp__v {
  font-family: var(--font-display);
  font-size: var(--t-1);
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.hero-stamp__v small { display: block; font-family: var(--font-ui); font-size: var(--t--2); font-weight: 400; letter-spacing: 0; color: var(--ink-3); margin-top: 0.15rem; }

@media (max-width: 939px) {
  .hero-ledger__ghost { font-size: 7rem; opacity: 0.07; }
  .hero-stamp { padding: var(--s-4); }
}

/* The section that follows a masthead opens its own space again */
.masthead + .section { padding-top: var(--section-y); }

.crumbs {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 2.75rem;
  font-size: var(--t--2);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: var(--s-4);
}

.crumbs a {
  display: inline-flex;
  align-items: center;
  min-height: 2.75rem;
  text-decoration: none;
  transition: color var(--d-hover) var(--ease-out);
}
.crumbs svg { width: 0.6rem; height: 0.6rem; opacity: 0.6; }

@media (hover: hover) and (pointer: fine) { .crumbs a:hover { color: var(--brass); } }

/* ---------- Credential list ---------- */
.creds { display: grid; gap: 0; }

.cred {
  display: grid;
  gap: 0.35rem var(--s-5);
  padding: var(--s-5) 0;
  border-bottom: 1px solid var(--line);
}

/* The year column was removed with the licensing dates. Left as a two column
   grid the single remaining child would sit in the 9rem track meant for a
   date, so the entry is one column now. */

.cred__year {
  font-size: var(--t--2);
  font-weight: 700;
  letter-spacing: 0.16em;
  color: var(--brass);
  font-variant-numeric: tabular-nums;
}

.cred__body h4 { margin-bottom: 0.25rem; }
.cred__body p { font-size: var(--t--1); color: var(--ink-2); max-width: 56ch; }

/* ---------- Testimonial cards ---------- */
.tcards {
  display: grid;
  gap: var(--s-5);
  grid-template-columns: 1fr;
}

@media (min-width: 720px) { .tcards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1080px) { .tcards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* ---------- Review cards: an alternating brown and white run ----------
   Each card carries its OWN palette rather than inheriting the page's, so the
   text, stars, quote mark and rule all follow the card it sits on instead of
   the theme behind it. Setting only the background would leave dark text on a
   brown card.

   The run alternates, and the two themes start on opposite feet: dark mode
   opens brown, light mode opens white. */
.tcard {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--s-4);
  padding: var(--s-6) var(--s-5) var(--s-5);
  border-radius: var(--r-lg);
  background: var(--c-bg);
  color: var(--c-ink);
  border: 1px solid var(--c-edge);
  box-shadow: var(--shadow-1);
  transition: transform 420ms var(--ease-out), box-shadow 420ms var(--ease-out);
}

/* The two card palettes. Each is self contained and legible on its own
   ground, so a card can be dropped into either theme unchanged. */
.tcard--brown {
  --c-bg: #2E2318;
  --c-ink: #F6F1E6;
  --c-mute: #CFC1A9;
  --c-accent: #E0AE68;
  --c-rule: rgba(246, 241, 230, 0.20);
  --c-edge: rgba(246, 241, 230, 0.12);
}

.tcard--white {
  --c-bg: #FBF8F1;
  --c-ink: #17140F;
  --c-mute: #574F43;
  --c-accent: #7E561F;
  --c-rule: rgba(23, 20, 15, 0.16);
  --c-edge: rgba(23, 20, 15, 0.10);
}

/* Light mode opens white, dark mode opens brown. Assigned here rather than in
   the markup so the sequence cannot drift if a card is added or reordered. */
.tcard:nth-child(odd) { --c-bg: #FBF8F1; --c-ink: #17140F; --c-mute: #574F43; --c-accent: #7E561F; --c-rule: rgba(23, 20, 15, 0.16); --c-edge: rgba(23, 20, 15, 0.10); }
.tcard:nth-child(even) { --c-bg: #2E2318; --c-ink: #F6F1E6; --c-mute: #CFC1A9; --c-accent: #E0AE68; --c-rule: rgba(246, 241, 230, 0.20); --c-edge: rgba(246, 241, 230, 0.12); }

:root[data-theme="dark"] .tcard:nth-child(odd) { --c-bg: #2E2318; --c-ink: #F6F1E6; --c-mute: #CFC1A9; --c-accent: #E0AE68; --c-rule: rgba(246, 241, 230, 0.20); --c-edge: rgba(246, 241, 230, 0.12); }
:root[data-theme="dark"] .tcard:nth-child(even) { --c-bg: #FBF8F1; --c-ink: #17140F; --c-mute: #574F43; --c-accent: #7E561F; --c-rule: rgba(23, 20, 15, 0.16); --c-edge: rgba(23, 20, 15, 0.10); }

@media (hover: hover) and (pointer: fine) {
  .tcard:hover { transform: translateY(-3px); box-shadow: var(--shadow-2); }
}

/* ---------- Rating ----------
   A rating is a caption, not a graphic. Kept to caption size and on one line,
   above the quote it belongs to.

   The explicit display:flex and the fixed icon box are both load bearing: the
   base reset sets svg { display: block }, so without them each star becomes a
   full-width block and the row renders as five enormous stacked glyphs. */
.stars {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0.15rem;
  color: var(--brass);
  line-height: 1;
}

.stars__s {
  display: block;
  width: 0.8rem;
  height: 0.8rem;
  flex: 0 0 auto;
}

.stars__s--empty { color: var(--c-rule, var(--line-2)); }

/* The quote mark is drawn from the display face rather than an icon, so it
   carries the same personality as the headlines. */
.tcard::before {
  content: "\201C";
  font-family: var(--font-display);
  font-size: 3.25rem;
  line-height: 0.72;
  color: var(--c-accent);
  opacity: 0.55;
}

.tcard__text {
  flex: 1 1 auto;
  font-size: var(--t-0);
  line-height: 1.6;
  color: var(--c-ink);
}

.tcard__by {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem var(--s-3);
  padding-top: var(--s-4);
  border-top: 1px solid var(--c-rule);
  font-size: var(--t--2);
  color: var(--c-mute);
}

.tcard__by b { font-weight: 600; color: var(--c-ink); font-size: var(--t--1); }

/* ---------- 404 ---------- */
.notfound {
  display: grid;
  place-items: center;
  min-height: 70dvh;
  text-align: center;
  gap: var(--s-5);
}

.notfound__code {
  font-family: var(--font-display);
  font-size: var(--t-6);
  line-height: 0.9;
  letter-spacing: -0.04em;
  color: var(--brass);
}


/* ---------- Office map ----------
   Leaflet's own chrome is grey-on-white by default, which fights both themes.
   Everything visible here is re-skinned onto the site's tokens. */
/* ---------- Areas map ----------
   One large map replaces both the bulleted list of neighbourhoods and the
   small office map that used to sit beside it. */
.areas-map-wrap { display: grid; gap: var(--s-6); }

.areas-map__lede {
  max-width: 62ch;
  color: var(--ink-2);
}

.areas-map {
  position: relative;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: #EAE4D8;
  box-shadow: var(--shadow-1);
}

.areas-map__key {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem var(--s-4);
  padding: var(--s-3) var(--s-4);
  border-top: 1px solid var(--line);
  background: var(--surface);
  font-size: var(--t--2);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.areas-map__k {
  width: 0.7rem;
  height: 0.7rem;
  border-radius: 50%;
  flex: 0 0 auto;
  margin-right: -0.15rem;
}

.areas-map__k--office { background: var(--claret); }
.areas-map__k--area { background: var(--brass); margin-left: var(--s-3); }

.map {
  height: clamp(24rem, 18rem + 22vw, 40rem);
  width: 100%;
  background: #EAE4D8;
  z-index: 0;                       /* keep Leaflet's panes under the header */
}

/* ---------- Map markers ----------
   Areas are dots, the office is a pin in a different colour. Shape and colour
   both carry the distinction, so it does not rest on colour alone. */
.area-pin { background: none; border: 0; }

.area-pin__dot {
  display: block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--brass);
  border: 2px solid #FCFAF5;
  box-shadow: 0 1px 5px rgba(22, 19, 14, 0.35);
  transition: transform 220ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .area-pin:hover .area-pin__dot { transform: scale(1.32); }
}

.office-pin { background: none; border: 0; }
.office-pin svg path { fill: var(--claret); stroke: #FCFAF5; stroke-width: 2.4; }
.office-pin svg .office-pin__eye { fill: #FCFAF5; }
.office-pin svg { filter: drop-shadow(0 6px 12px rgba(22, 19, 14, 0.34)); }

/* Labels sit on light tiles in both themes, so they take fixed light colours */
.area-label {
  background: rgba(252, 250, 245, 0.92);
  color: #453F35;
  border: 1px solid rgba(22, 19, 14, 0.12);
  border-radius: var(--r-xs);
  box-shadow: 0 1px 4px rgba(22, 19, 14, 0.12);
  padding: 0.14rem 0.4rem;
  font-family: var(--font-ui);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  white-space: nowrap;
}

.area-label::before { display: none; }


.map__fallback { padding: var(--s-6); text-align: center; }

/* Tiles carry the cartography; this only softens them into the page */
.leaflet-container {
  font-family: var(--font-ui);
  background: #EAE4D8;
}

/* The basemap stays light in both themes, so the tiles are toned to the paper
   rather than inverted. */
.leaflet-tile-pane { filter: saturate(0.82) contrast(0.96); }

/* Chrome sits ON the light map in both themes, so it takes fixed light-map
   colours rather than the page tokens, which would vanish in dark mode. */
.map-label {
  background: #FCFAF5;
  color: #16130E;
  border: 1px solid rgba(22, 19, 14, 0.16);
  border-radius: var(--r-sm);
  box-shadow: 0 2px 10px rgba(22, 19, 14, 0.18);
  padding: 0.3rem 0.6rem;
  font-family: var(--font-ui);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  white-space: nowrap;
}

.map-label::before { border-right-color: rgba(22, 19, 14, 0.16) !important; }

.map-pin { background: none; border: 0; }
.map-pin svg { filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.28)); }

/* Controls */
.leaflet-bar,
.leaflet-bar a {
  border: 0;
  background: #FCFAF5;
  color: #16130E;
  border-radius: var(--r-sm);
  box-shadow: 0 2px 10px rgba(22, 19, 14, 0.18);
}

  .leaflet-bar a {
  border-bottom: 1px solid rgba(22, 19, 14, 0.14);
  transition: background-color var(--d-hover) var(--ease-out), color var(--d-hover) var(--ease-out);
}

.leaflet-bar a:last-child { border-bottom: 0; }

@media (hover: hover) and (pointer: fine) {
  .leaflet-bar a:hover { background: #FFFFFF; color: #7E561F; }
}

.leaflet-bar a.leaflet-disabled { opacity: 0.45; }

/* Attribution is a legal requirement for OSM and CARTO, so it stays visible
   and legible rather than being hidden to tidy the corner. */
/* Opaque, not glass. The credits sit over map imagery whose luminance is
   unknown and changes as you pan, so a translucent plate makes legibility a
   matter of where you happen to be looking. */
/* Selector matched to Leaflet's own `.leaflet-container .leaflet-control-attribution`.
   At one class this rule lost on specificity no matter where it sat, so the
   inset below never applied and the rounded corner really was clipping the
   CARTO credit, on both maps. Vendor CSS now loads above the site's, so equal
   specificity is enough to win and the !important this needed is gone. */
.leaflet-container .leaflet-control-attribution {
  background: #FCFAF5;
  color: #453F35;
  font-size: 0.625rem;
  padding: 0.2rem 0.5rem;
  /* Inset from the corner: the plate is rounded and clips anything flush to
     the edge, which was cutting the CARTO credit in half. */
  margin: 0 0.5rem 0.5rem 0;
  border-radius: var(--r-xs);
}

.leaflet-control-attribution a,
.leaflet-container .leaflet-control-attribution a {
  color: #453F35;
  text-decoration: none;
}

.leaflet-container .leaflet-control-attribution a:hover { color: var(--brass); }

/* Leaflet 1.9 appends a flag graphic to the attribution. It is not part of the
   OSM or CARTO licence requirement, and unconstrained it renders as a large
   coloured block over the map. */
.leaflet-attribution-flag { display: none !important; }

/* Popup */
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-2);
  border-radius: var(--r-sm);
}

.leaflet-popup-content { margin: var(--s-4); font-size: var(--t--1); line-height: 1.5; }
.leaflet-popup-content a { color: var(--brass); }
.leaflet-container a.leaflet-popup-close-button { color: var(--ink-3); }
.leaflet-container a.leaflet-popup-close-button:hover { color: var(--ink); }


/* ---------- Built by Codex Corp ----------
   A credit, not a campaign. It sits at the foot of the page, states who built
   the site, names the four companies and gets out of the way. Everything here
   is deliberately one size down from the site's own sections: smaller type,
   small buttons, cards that are a name and a line rather than a pitch. */
.codex { background: var(--surface-2); border-top: 1px solid var(--line); }

.codex__bar {
  display: grid;
  gap: var(--s-5);
}

@media (min-width: 860px) {
  /* Centred against the intro, not bottom aligned. Hanging them off the
     baseline of the last line of copy left them floating at whatever height
     that paragraph happened to end. */
  .codex__bar { grid-template-columns: minmax(0, 1fr) auto; gap: var(--s-8); align-items: center; }
}

.codex__title {
  margin-top: var(--s-3);
  font-size: var(--t-2);
  letter-spacing: -0.028em;
}

.codex__lede { margin-top: var(--s-3); color: var(--ink-2); max-width: 52ch; font-size: var(--t--1); }

.codex__actions { display: flex; flex-wrap: wrap; gap: var(--s-2); }

/* A quieter button than the site's own: this section is a credit, so its
   controls should not compete with Sharry's calls to action. One height, one
   padding, icon and label optically centred together. */
.codex__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  min-height: 2.75rem;
  padding: 0 1.1rem;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-2);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--t--2);
  font-weight: 600;
  letter-spacing: 0.01em;
  text-decoration: none;
  white-space: nowrap;
  cursor: pointer;
  transition: color var(--d-hover) var(--ease-out),
              border-color var(--d-hover) var(--ease-out),
              background-color var(--d-hover) var(--ease-out),
              transform var(--d-press) var(--ease-out);
}

.codex__btn svg { width: 0.85rem; height: 0.85rem; flex: 0 0 auto; color: var(--brass); }
.codex__btn:active { transform: translateY(1px); }

.codex__btn--solid {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.codex__btn--solid svg { color: var(--brass-on-ink); }

@media (hover: hover) and (pointer: fine) {
  .codex__btn:hover { border-color: var(--brass); color: var(--brass); }
  .codex__btn:hover svg { color: var(--brass); }
  .codex__btn--solid:hover { background: var(--brass); border-color: var(--brass); color: var(--paper); }
  .codex__btn--solid:hover svg { color: var(--paper); }
}

.opcos {
  display: grid;
  gap: var(--s-3);
  grid-template-columns: repeat(2, minmax(0, 1fr));
  margin-top: var(--s-6);
}

@media (min-width: 860px) { .opcos { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

.opco {
  position: relative;
  isolation: isolate;
  cursor: pointer;
  padding: var(--s-4) var(--s-4) var(--s-4);
  border-radius: var(--r-sm);
  background: var(--surface);
  border: 1px solid var(--line);
  overflow: hidden;
  transition: transform 320ms var(--ease-out), border-color 320ms var(--ease-out);
}

/* The colour wash sits behind the content rather than the content being
   lifted above it. That matters: making the name and description positioned
   to clear this made the heading the containing block for the card link's
   stretched ::after, so only the top quarter of the tile was clickable.
   isolation keeps the negative index inside the card. */
.opco::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--opco-bg);
  opacity: 0;
  transition: opacity 320ms var(--ease-out);
  pointer-events: none;
}

.opco__bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--opco);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 460ms var(--ease-out);
}

.opco[data-reveal="in"] .opco__bar { transform: scaleX(1); }

@media (prefers-reduced-motion: reduce) { .opco__bar { transform: scaleX(1); } }

@media (hover: hover) and (pointer: fine) {
  .opco:hover { transform: translateY(-2px); border-color: var(--opco); }
  .opco:hover::before { opacity: 1; }
}

.opco__name {
  font-size: var(--t--1);
  font-weight: 700;
  letter-spacing: -0.012em;
  color: var(--opco-ink, var(--opco));
}

/* The link sits on the company name, so that is the accessible name, and its
   ::after covers the card so the whole tile is clickable. Wrapping the <li>
   in an <a> would swallow the description into the link text. */
.opco__link {
  color: inherit;
  text-decoration: none;
  outline: none;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}

.opco__link::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  border-radius: inherit;
}

.opco__out {
  width: 0.6rem;
  height: 0.6rem;
  flex: 0 0 auto;
  opacity: 0;
  transform: translate(-2px, 2px);
  transition: opacity var(--d-hover) var(--ease-out), transform var(--d-hover) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .opco:hover .opco__out { opacity: 1; transform: translate(0, 0); }
}

/* Focus reads as the whole tile, because that is what the click target is */
.opco:has(.opco__link:focus-visible) {
  outline: 2px solid var(--opco);
  outline-offset: 3px;
}

.opco:has(.opco__link:focus-visible) .opco__out { opacity: 1; transform: translate(0, 0); }

.opco__what {
  margin-top: 0.2rem;
  font-size: var(--t--2);
  line-height: 1.5;
  color: var(--ink-3);
}

/* Group colours, lifted from codexcorp.ca. The -ink variants are darker pairs
   for text, because the display colours are tuned for fills and none of them
   clear 4.5:1 as small type. Labs measured 4.33:1 against its own tint and
   needed another two steps down. */
.opco--studios { --opco: #CF7468; --opco-ink: #A8483C; --opco-bg: rgba(207, 116, 104, 0.10); }
.opco--digital { --opco: #A87FD4; --opco-ink: #6F45A4; --opco-bg: rgba(168, 127, 212, 0.10); }
.opco--labs    { --opco: #48BAC8; --opco-ink: #1A737E; --opco-bg: rgba(72, 186, 200, 0.12); }
.opco--systems { --opco: #6A8FCC; --opco-ink: #3C639F; --opco-bg: rgba(106, 143, 204, 0.11); }

:root[data-theme="dark"] .opco--studios { --opco-ink: #E29A90; }
:root[data-theme="dark"] .opco--digital { --opco-ink: #C6A6E6; }
:root[data-theme="dark"] .opco--labs    { --opco-ink: #6FD3DF; }
:root[data-theme="dark"] .opco--systems { --opco-ink: #97B4E2; }

.codex__report {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-3) var(--s-5);
  margin-top: var(--s-5);
  padding-top: var(--s-4);
  border-top: 1px solid var(--line);
}

.codex__report-text { font-size: var(--t--2); color: var(--ink-3); max-width: 58ch; }


/* ---------- Home hero photograph ----------
   Sits under everything: photo, then a black scrim, then the contour line
   work, then the content.

   The scrim is BLACK IN BOTH THEMES by instruction, and that has a
   consequence worth being explicit about: the hero is a dark region in light
   mode too, so its text cannot use the page's ink, which is near black there.
   The hero therefore redefines its own tokens below, the same way an inverted
   section does. Nothing outside the hero is affected.

   The line graphics are kept, not replaced, so this can be reverted by
   deleting one element and one block of CSS. */
/* ---------- Hero backdrop ----------
   One photograph again. The day/night pair moved to the portrait plate, where
   the house is the subject rather than a backdrop, so this is back to a plain
   <picture>: it does not switch on the theme any more, and <picture> gives back
   real srcset sizing and fetchpriority on what is still the LCP element. */
.hero-photo {
  position: absolute;
  inset: 0;
  z-index: -2;
  overflow: hidden;
}

.hero-photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
}

/* One scrim, one colour, both themes.

   Weighted across the diagonal rather than laid flat: heavy enough on the left
   to carry the headline and lede, and light enough on the right that the
   skyline is actually visible. Two stacked heavy gradients was the first
   attempt and it rendered the photograph as a black rectangle, which is the
   same as not having it. */
.hero-photo::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(100deg,
      rgba(8, 7, 6, 0.90) 0%,
      rgba(8, 7, 6, 0.78) 26%,
      rgba(8, 7, 6, 0.46) 54%,
      rgba(8, 7, 6, 0.30) 78%,
      rgba(8, 7, 6, 0.38) 100%),
    linear-gradient(to top, rgba(8, 7, 6, 0.46) 0%, rgba(8, 7, 6, 0) 38%);
}

/* ---------- Hero plate ----------
   The house behind Sharry's portrait, day or night with the theme. A CSS
   background rather than <picture>: the theme is a stored preference on a
   data-attribute, <source media> can only read media queries, and two
   <picture> elements toggled with display:none would fetch both. Only the
   matching one ever loads.

   Cropped to exactly 4:5 when the variants are generated, so the shipped bytes
   are all visible pixels and the framing is decided once instead of being
   re-derived by every browser at every viewport. */
.hero-plate {
  background-repeat: no-repeat;
  background-size: cover;
  background-position: 50% 50%;
}

:root:not([data-theme="dark"]) .hero-plate {
  background-image: url("../img/hero/card-day-960.jpg");
  background-image: image-set(
    url("../img/hero/card-day-960.avif") type("image/avif"),
    url("../img/hero/card-day-960.webp") type("image/webp"),
    url("../img/hero/card-day-960.jpg") type("image/jpeg"));
}

:root[data-theme="dark"] .hero-plate {
  background-image: url("../img/hero/card-night-960.jpg");
  background-image: image-set(
    url("../img/hero/card-night-960.avif") type("image/avif"),
    url("../img/hero/card-night-960.webp") type("image/webp"),
    url("../img/hero/card-night-960.jpg") type("image/jpeg"));
}

@media (max-width: 700px) {
  :root:not([data-theme="dark"]) .hero-plate {
    background-image: url("../img/hero/card-day-560.jpg");
    background-image: image-set(
      url("../img/hero/card-day-560.avif") type("image/avif"),
      url("../img/hero/card-day-560.webp") type("image/webp"),
      url("../img/hero/card-day-560.jpg") type("image/jpeg"));
  }
  :root[data-theme="dark"] .hero-plate {
    background-image: url("../img/hero/card-night-560.jpg");
    background-image: image-set(
      url("../img/hero/card-night-560.avif") type("image/avif"),
      url("../img/hero/card-night-560.webp") type("image/webp"),
      url("../img/hero/card-night-560.jpg") type("image/jpeg"));
  }
}

/* Retina, where a 960 wide plate is asked to fill a ~500px box at 2x. */
@media (min-width: 1000px) and (min-resolution: 1.5dppx) {
  :root:not([data-theme="dark"]) .hero-plate {
    background-image: url("../img/hero/card-day-1280.jpg");
    background-image: image-set(
      url("../img/hero/card-day-1280.avif") type("image/avif"),
      url("../img/hero/card-day-1280.webp") type("image/webp"),
      url("../img/hero/card-day-1280.jpg") type("image/jpeg"));
  }
  :root[data-theme="dark"] .hero-plate {
    background-image: url("../img/hero/card-night-1280.jpg");
    background-image: image-set(
      url("../img/hero/card-night-1280.avif") type("image/avif"),
      url("../img/hero/card-night-1280.webp") type("image/webp"),
      url("../img/hero/card-night-1280.jpg") type("image/jpeg"));
  }
}



/* The hero becomes its own dark region. Redefining the tokens means every
   child follows without being restyled one by one. */
/* The hero used to force a light-on-dark palette in BOTH themes, because it sat
   on the same night photograph either way. It now carries the day photograph in
   the light theme, where cream type measures 1.43:1 and is simply invisible, so
   the override is scoped to dark and the light theme inherits the ordinary page
   palette: dark ink on a bright midday sky, which measures 11.6:1. */
.hero {
  --ink: #F6F2E9;
  --ink-2: rgba(246, 242, 233, 0.86);
  --ink-3: rgba(246, 242, 233, 0.70);
  --line: rgba(246, 242, 233, 0.20);
  --line-2: rgba(246, 242, 233, 0.34);
  --brass: #E3B876;
  --brass-wash: rgba(227, 184, 118, 0.18);
  --surface: rgba(255, 255, 255, 0.10);
  --surface-2: rgba(255, 255, 255, 0.14);
  --cta-bg: #C4454F;
  --cta-fg: #FFF6F0;
  color: var(--ink);
}

.hero {
  /* Required, not cosmetic. Without a stacking context here the photo layer's
     negative z-index escapes to the nearest ancestor that has one and paints
     BEHIND this background, so the hero renders as a flat panel and the
     photograph is never seen. */
  isolation: isolate;
  background: #0A0907;
}

.hero .btn--ghost {
  background: transparent;
  color: #F6F2E9;
  border-color: rgba(246, 242, 233, 0.42);
}

.hero .btn--ghost svg { color: #F6F2E9; }

@media (hover: hover) and (pointer: fine) {
  /* Scoped away from .btn--ghost with :not(). Written as a bare .hero .btn:hover
     it repainted EVERY hero button's label to near-black, and the ghost's own
     hover rule only changed background and border, so the black label survived
     on top of a 12% wash over a dark photograph: invisible on hover.

     The brass swap also predates the claret CTA and no longer belongs on the
     primary, which should deepen rather than change hue. */
  .hero .btn:not(.btn--ghost):hover { background: #8E2833; }

  .hero .btn--ghost:hover {
    background: rgba(246, 242, 233, 0.14);
    border-color: #F6F2E9;
    color: #F6F2E9;
  }
}

/* The badge sits on a dark hero now, so it takes a dark ground in BOTH themes
   rather than the page's light glass. Left as glass it was light text on a
   light panel and measured 2.38:1. Dark, per the same rule as the scrim. */
.hero .hero__badge {
  background: rgba(10, 9, 7, 0.68);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.28), 0 20px 44px -26px rgba(0, 0, 0, 0.7);
}

.hero .hero__badge::before {
  background: linear-gradient(160deg, rgba(246, 242, 233, 0.34), transparent 46%, rgba(0, 0, 0, 0.4));
}

/* The contours read against the photograph rather than against paper */
/* Cream over the night photograph, brass over the day one. A cream line on a
   bright midday sky is invisible, which is the same mistake in reverse. */
.hero .hero-bg { opacity: 0.16; color: #F6F2E9; }

/* ---------- Hero background ----------
   One large drawn form per hero, scaled past the edges of the frame so you
   only ever see part of it. That is the whole idea: a repeated motif reads as
   wallpaper and asks to be looked at, while a single cropped form reads as
   atmosphere and stays behind the words.

   Everything is one hairline weight in the brand's brass at very low opacity,
   with a mask that thins it further behind the text column. Motion is measured
   in minutes, not seconds, so it registers as depth rather than as animation.
   ========================================================================== */
.hero-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
  color: var(--brass);
  opacity: 0.13;
  /* Lightest where the headline sits, fullest out at the far edge */
  -webkit-mask-image: linear-gradient(100deg, transparent 4%, rgba(0,0,0,0.35) 34%, #000 72%);
  mask-image: linear-gradient(100deg, transparent 4%, rgba(0,0,0,0.35) 34%, #000 72%);
}

:root[data-theme="dark"] .hero-bg { opacity: 0.2; }

.hero-bg svg {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 132%;
  height: 132%;
  transform: translate(-50%, -50%);
  /* No overflow: visible here. With it, the paths reported a 6095px wide box
     and the document grew to 459px inside a 375px viewport, which then made
     Playwright scroll sideways to reach the hamburger and click the theme
     toggle instead. preserveAspectRatio="slice" already does the cropping. */
}

.hero-bg [stroke] { vector-effect: non-scaling-stroke; }

/* Content sits above it without needing its own background */
.hero > .wrap, .masthead > .wrap { position: relative; z-index: 1; }

/* ---- Home: contours. Land, read as a survey. ---- */
.hero-bg--contour g { animation: bg-rise 150s ease-in-out infinite alternate; }

@keyframes bg-rise {
  from { transform: translate3d(0, 2.5%, 0) scale(1); }
  to   { transform: translate3d(0, -2.5%, 0) scale(1.05); }
}

/* ---- Listings: a plan grid, drifting sideways ---- */
.hero-bg--grid g { animation: bg-slide 190s linear infinite; }

@keyframes bg-slide {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-12.5%, 0, 0); }
}

/* ---- Work: long ascending sweeps ---- */
.hero-bg--ascent g { animation: bg-lift 170s ease-in-out infinite alternate; }

@keyframes bg-lift {
  from { transform: translate3d(-1.5%, 1.5%, 0); }
  to   { transform: translate3d(1.5%, -1.5%, 0); }
}

/* ---- About: radius. Territory from a point. ---- */
.hero-bg--radius g { transform-origin: 74% 46%; animation: bg-breathe 120s ease-in-out infinite alternate; }

@keyframes bg-breathe {
  from { transform: scale(0.97); }
  to   { transform: scale(1.06); }
}

@media (prefers-reduced-motion: reduce) {
  .hero-bg--contour g,
  .hero-bg--grid g,
  .hero-bg--ascent g,
  .hero-bg--radius g { animation: none; }
}

/* On a phone the form is scaled up further so a legible slice of it fills the
   frame, and dropped in opacity because the text sits right over it. */
@media (max-width: 700px) {
  .hero-bg { opacity: 0.1; -webkit-mask-image: none; mask-image: none; }
  :root[data-theme="dark"] .hero-bg { opacity: 0.16; }
  .hero-bg svg { width: 200%; height: 200%; }
}


/* ---------- Hero collage: hover ----------
   The comment on .hero__stack calls these prints laid on a desk, so they
   behave like it: the one under the pointer lifts and squares up, the other
   settles back a little. Transform and shadow only, so nothing reflows and the
   two plates never shift each other's position.

   The whole hero is the hover target rather than each plate, because the plates
   overlap and per-plate hover made the portrait flicker as the pointer crossed
   the seam. */
@media (hover: hover) and (pointer: fine) {
  .hero__stack-main,
  .hero__portrait {
    transition: transform 620ms var(--ease-out), box-shadow 620ms var(--ease-out);
    will-change: transform;
  }

  .hero__stack-main .plate__core img,
  .hero__portrait img {
    transition: transform 900ms var(--ease-out);
  }

  /* At rest the two sit very slightly off square, like dropped prints */
  .hero__stack-main { transform: rotate(-0.35deg); }
  .hero__portrait { transform: rotate(0.7deg); }

  /* Property plate under the pointer: lifts, straightens, image eases in */
  .hero__stack:has(.hero__stack-main:hover) .hero__stack-main {
    transform: rotate(0deg) translateY(-8px) scale(1.012);
    box-shadow: var(--shadow-3);
  }

  .hero__stack:has(.hero__stack-main:hover) .hero__stack-main .plate__core img {
    transform: scale(1.04);
  }

  .hero__stack:has(.hero__stack-main:hover) .hero__portrait {
    transform: rotate(1.4deg) translateY(4px) scale(0.985);
  }

  /* Portrait under the pointer: comes forward off the property behind it */
  .hero__stack:has(.hero__portrait:hover) .hero__portrait {
    transform: rotate(0deg) translateY(-10px) scale(1.03);
    box-shadow: var(--shadow-3);
  }

  .hero__stack:has(.hero__portrait:hover) .hero__portrait img {
    transform: scale(1.05);
  }

  .hero__stack:has(.hero__portrait:hover) .hero__stack-main {
    transform: rotate(-0.9deg) translateY(3px) scale(0.99);
  }

  /* The badge belongs to the property plate, so it follows it rather than
     sitting still while the plate moves out from under it. */
  .hero__badge { transition: transform 620ms var(--ease-out); }
  .hero__stack:has(.hero__stack-main:hover) .hero__badge { transform: translateY(-5px); }
  .hero__stack:has(.hero__portrait:hover) .hero__badge { transform: translateY(2px); }
}

/* No resting tilt and no motion when reduced motion is asked for: the plates
   sit square and only the shadow answers the pointer. */
@media (prefers-reduced-motion: reduce) {
  .hero__stack-main,
  .hero__portrait,
  .hero__badge { transform: none !important; transition: box-shadow 200ms linear; }
}


/* ---------- Testimonial deck (home) ----------
   The same card as the Work page, one at a time, flipping between them. Cards
   are stacked in one grid cell so the stage keeps the height of the tallest and
   the controls below never jump as you move through them. */
.deck { perspective: 1400px; display: grid; grid-template-rows: 1fr auto; gap: var(--s-4); }

.deck__stage {
  display: grid;
  transform-style: preserve-3d;
  transition: transform 240ms var(--ease-out);
}

.deck__stage > * { grid-area: 1 / 1; }

.deck__card {
  opacity: 0;
  visibility: hidden;
  transform: rotateY(8deg);
  transition: opacity 240ms var(--ease-out), transform 380ms var(--ease-out), visibility 0s linear 240ms;
}

.deck__card.is-current {
  opacity: 1;
  visibility: visible;
  transform: rotateY(0deg);
  transition: opacity 300ms var(--ease-out) 120ms, transform 420ms var(--ease-out) 60ms, visibility 0s;
}

/* The turn itself, driven from the script for the half-beat between cards */
.deck__stage[data-flipping="true"][data-dir="fwd"] { transform: rotateY(-7deg) scale(0.985); }
.deck__stage[data-flipping="true"][data-dir="back"] { transform: rotateY(7deg) scale(0.985); }

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

.deck__nav {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  flex: 0 0 auto;
  border-radius: var(--r-pill);
  border: 1px solid var(--line-2);
  color: var(--ink-2);
  cursor: pointer;
  transition: color var(--d-hover) var(--ease-out), border-color var(--d-hover) var(--ease-out),
              background-color var(--d-hover) var(--ease-out);
}

.deck__nav svg { width: 0.8rem; height: 0.8rem; }
.deck__nav[data-deck-prev] svg { transform: rotate(90deg); }
.deck__nav[data-deck-next] svg { transform: rotate(-90deg); }

@media (hover: hover) and (pointer: fine) {
  .deck__nav:hover { color: var(--brass); border-color: var(--brass); background: var(--brass-wash); }
}

.deck__dots { display: flex; align-items: center; gap: 0.3rem; }

.deck__dot {
  position: relative;
  width: 1.5rem;
  height: 2.75rem;
  padding: 0;
  cursor: pointer;
}

.deck__dot::after {
  content: "";
  position: absolute;
  inset: calc(50% - 1px) 0.3rem auto;
  height: 2px;
  border-radius: 2px;
  background: var(--line-2);
  transition: background-color var(--d-hover) var(--ease-out), transform var(--d-hover) var(--ease-out);
}

.deck__dot[aria-current="true"]::after { background: var(--brass); transform: scaleY(2); }

@media (hover: hover) and (pointer: fine) {
  .deck__dot:hover::after { background: var(--ink-3); }
}



@media (prefers-reduced-motion: reduce) {
  .deck__stage, .deck__card { transition: none; transform: none !important; }
}

/* On the home page the deck sits on the feature ground, so its card takes the
   white palette rather than inheriting the alternating run from the Work grid. */
.deck__card {
  --c-bg: #FBF8F1; --c-ink: #17140F; --c-mute: #574F43;
  --c-accent: #7E561F; --c-rule: rgba(23, 20, 15, 0.16); --c-edge: rgba(23, 20, 15, 0.10);
}

:root[data-theme="dark"] .deck__card {
  --c-bg: #2E2318; --c-ink: #F6F1E6; --c-mute: #CFC1A9;
  --c-accent: #E0AE68; --c-rule: rgba(246, 241, 230, 0.20); --c-edge: rgba(246, 241, 230, 0.12);
}


/* ---------- Mini map (home) ----------
   The same map as the About page, at a glance instead of as a tool. map.js
   reads data-map="mini" and drops the labels, zoom control, panning and
   popups; these are the size changes that go with that. */

/* Half-size dots and no pin label, so twelve markers read as a constellation
   rather than as twelve things to click. */
.map.is-mini .area-pin__dot {
  width: 8px;
  height: 8px;
  border-width: 1.5px;
  box-shadow: 0 1px 3px rgba(22, 19, 14, 0.3);
}

.map.is-mini .office-pin svg { width: 22px; height: 30px; }

/* Leaflet stamps its own attribution styling. On the small map it competes
   with everything around it, so it is shrunk to a credit rather than a
   caption. Still present and still legible, because the tiles are not ours. */
.map.is-mini .leaflet-container .leaflet-control-attribution,
.leaflet-container .map.is-mini .leaflet-control-attribution,
.map.is-mini .leaflet-control-attribution {
  padding: 1px 5px;
  font-size: 9px;
  line-height: 1.4;
  color: #4A443A;
}

.map.is-mini .leaflet-control-attribution a { color: #4A443A; }


/* ==========================================================================
   Clients section

   Three things that belong together: what people said, what the work added up
   to, and where it happened. They were laid out as two loose columns with the
   figures buried inside one of them, so nothing lined up with anything and the
   three parts read as three unrelated blocks.

   It is now one grid, three bands deep, and everything is placed against it:

     head        title | lede            two equal columns
     figures     four cells, ruled       spans the full width, tying the two sides
     pair        panel | panel           two equal columns
                 meta  | meta            same row, so the strips line up
                 action| action          same row, so the buttons line up

   The head, the band and the pair all use one column template, so a vertical
   line drawn down the middle of the section touches the same edge in all three.
   ========================================================================== */

/* The head shipped as 1.15fr / 0.85fr while the body below it was 1fr / 1fr,
   which put the lede on a different left edge from the column underneath it.
   Same template, same gap, same breakpoint as the pair, or they cannot agree. */
@media (min-width: 940px) {
  .head--even {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    column-gap: var(--s-8);
    align-items: end;
  }
}

/* ---------- The figures band ----------
   Full width on purpose. It is the only element that touches both columns, so
   it is what stops the two sides reading as two separate ideas. Hairlines top
   and bottom make it a band rather than four numbers floating in space. */
.figband {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-5) var(--s-6);
  margin-bottom: var(--s-8);
  padding: var(--s-6) 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}

@media (min-width: 760px) {
  .figband { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 0; }

  /* Dividers come from the cells, not from extra elements. The outer two lose
     their inner padding so the band's ends sit flush with the section edges,
     which is what makes the first number line up with the title above it. */
  .figband .stat {
    padding-inline: var(--s-6);
    border-left: 1px solid var(--line);
  }

  .figband .stat:first-child { padding-inline-start: 0; border-left: 0; }
  .figband .stat:last-child { padding-inline-end: 0; }
}

/* ---------- The pair ----------
   Both columns carry the same three things in the same order: a panel, a strip
   of meta under it, and one action. Declaring those rows on the parent and
   letting each column inherit them with subgrid is what guarantees the two
   panels end on the same line, the two strips sit on the same line, and the
   two buttons sit on the same line, at every width and whatever the content
   does. Matching heights by hand would only hold for one viewport. */
.clients__pair { display: grid; gap: var(--s-6); }

@media (min-width: 940px) {
  .clients__pair {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: 1fr auto auto;
    column-gap: var(--s-8);
    row-gap: var(--s-5);
  }

  .clients__col {
    grid-row: 1 / -1;
    display: grid;
    /* Fallback for engines without subgrid: the columns still stretch to the
       same height and end on the same line, they just size their own rows. */
    grid-template-rows: 1fr auto auto;
    row-gap: var(--s-5);
  }

  @supports (grid-template-rows: subgrid) {
    .clients__col { grid-template-rows: subgrid; }

    /* Both wrappers hand their two rows back to the section grid, so the panel
       and the strip below it are placed by the parent rather than by the
       wrapper. This is the whole reason the two sides stay locked together. */
    .deck,
    .minimap { grid-row: span 2; grid-template-rows: subgrid; row-gap: var(--s-5); }
  }
}

/* One action per column, each the width of its own label rather than stretched
   across the grid track. */
.clients__col > .btn { justify-self: start; }

/* ---------- The map panel ----------
   The border used to wrap the map and its legend together, which left the map
   column looking like one tall box against the other column's card plus loose
   controls. The frame now sits on the map alone, matching the testimonial card,
   and the legend became a strip underneath it opposite the deck controls. */
.minimap { margin-top: 0; display: grid; }

.minimap .map {
  height: 100%;
  min-height: clamp(9rem, 7rem + 7vw, 12.5rem);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: #EAE4D8;
  box-shadow: var(--shadow-1);
}

.minimap__cap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem var(--s-5);
  padding: 0;
  border-top: 0;
  background: none;
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.minimap__cap > span { display: inline-flex; align-items: center; gap: 0.45rem; }

.minimap__k { width: 0.55rem; height: 0.55rem; border-radius: 50%; flex: 0 0 auto; }
.minimap__k--office { background: var(--claret); }
.minimap__k--area { background: var(--brass); }

/* Stacked, there is no second column to hold a line with, so the rows go back
   to their own heights and the two halves simply follow each other. */
@media (max-width: 58.6875rem) {
  .minimap .map { height: clamp(9rem, 7rem + 7vw, 12.5rem); }
}


/* ---------- Coverage (home) ----------
   What the Clients section became once the testimonials and the figures came
   out of it. The map used to be one column of a pair, sized to match a
   testimonial card beside it. On its own it is the section, so it gets the
   full width and a taller frame: a province is a wide shape and squeezing it
   into a half column was always the compromise, not the intent. */
.coverage {
  margin: 0;
  display: grid;
  gap: var(--s-5);
}

.coverage .map {
  height: clamp(16rem, 12rem + 18vw, 30rem);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  overflow: hidden;
  background: #EAE4D8;
  box-shadow: var(--shadow-1);
}

.coverage__cap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.6rem var(--s-5);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.coverage__cap > span { display: inline-flex; align-items: center; gap: 0.5rem; }


/* ---------- Property types (commercial) ----------
   Seven short labels. The obvious move is seven cards, and it is the wrong one:
   a card implies there is something inside it worth opening, and there is only
   a noun. A ruled list with a counter reads as an index, which is what this
   actually is, and it stays legible when the list grows or shrinks. */
.ptypes {
  list-style: none;
  margin: var(--s-7) 0 0;
  padding: 0;
  display: grid;
  gap: 0;
  border-top: 1px solid var(--line);
}

@media (min-width: 760px) {
  /* Two columns, and the rule between them is the same hairline as the rows,
     so the grid reads as one ruled table rather than two stacked lists. */
  .ptypes { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--s-8); }
  .ptypes .ptype:nth-child(even) { border-left: 0; }
}

.ptype {
  display: flex;
  align-items: baseline;
  gap: var(--s-4);
  padding: var(--s-5) 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--t-1);
  letter-spacing: -0.01em;
}

.ptype__n {
  flex: 0 0 auto;
  font-family: var(--font-display);
  font-size: var(--t--2);
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--brass);
  font-variant-numeric: tabular-nums;
}


/* ---------- Commercial services ----------
   Four substantial cards on a two by two grid, each carrying an oversized
   outlined numeral as a watermark.

   The first version was a ruled two column row: number, then icon, then title
   on the left and the paragraph on the right. It was legible and completely
   inert, and with a small icon floating above a small number the top left of
   each row read as three unrelated marks rather than one thing. The numeral is
   now the graphic, drawn big and hollow so it reads as ornament rather than as
   data, and the icon sits on the title where it belongs. */
.svcs {
  display: grid;
  gap: var(--s-5);
  margin-top: var(--s-7);
  grid-template-columns: repeat(auto-fit, minmax(min(24rem, 100%), 1fr));
}

.svc {
  position: relative;
  overflow: hidden;
  display: grid;
  gap: var(--s-4);
  align-content: start;
  padding: var(--s-7) var(--s-6) var(--s-6);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--surface);
  transition: border-color var(--d-hover) var(--ease-out),
              transform var(--d-hover) var(--ease-out),
              box-shadow var(--d-hover) var(--ease-out);
}

/* Hollow, oversized, and clipped by the card. Painted with a stroke rather
   than a fill so it stays ornament at any size and never competes with the
   title for attention. */
.svc__num {
  position: absolute;
  top: -0.35em;
  right: 0.08em;
  font-family: var(--font-display);
  font-size: clamp(6rem, 4rem + 8vw, 10rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -0.04em;
  color: transparent;
  -webkit-text-stroke: 1px var(--brass);
  opacity: 0.16;
  pointer-events: none;
  transition: opacity var(--d-hover) var(--ease-out), transform var(--d-hover) var(--ease-out);
}

.svc__ico {
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border-radius: var(--r-md);
  color: var(--brass);
  background: var(--brass-wash, rgba(126, 86, 31, 0.12));
  border: 1px solid var(--line);
  transition: transform var(--d-hover) var(--ease-out), border-color var(--d-hover) var(--ease-out);
}

.svc__ico svg { width: 1.5rem; height: 1.5rem; }

.svc__title {
  font-size: var(--t-2);
  letter-spacing: -0.02em;
  max-width: 18ch;
  text-wrap: balance;
}

.svc__body { color: var(--ink-2); font-size: var(--t--1); }

@media (hover: hover) and (pointer: fine) {
  .svc:hover {
    border-color: var(--brass);
    transform: translateY(-4px);
    box-shadow: var(--shadow-2);
  }
  .svc:hover .svc__num { opacity: 0.3; transform: translateY(4px); }
  .svc:hover .svc__ico { transform: scale(1.06); border-color: var(--brass); }
}

@media (prefers-reduced-motion: reduce) {
  .svc, .svc__num, .svc__ico { transition: none; }
  .svc:hover { transform: none; }
  .svc:hover .svc__num { transform: none; }
  .svc:hover .svc__ico { transform: none; }
}


/* ==========================================================================
   Commercial page

   Four sections, four different shapes on purpose. The page carries a lot of
   prose and almost no photography, so if every section were the same ruled
   list it would read as one long document. Each one gets the treatment its
   content actually wants: a drawn scene, an index, a tile grid, a card set.
   ========================================================================== */

/* ---------- Hero district ---------- */
.district {
  margin: var(--s-7) 0 0;
  color: var(--brass);
}

.district svg { width: 100%; height: auto; overflow: visible; }

/* Drawn on rather than faded in. Each building traces itself, staggered off
   the shared --i, so the skyline assembles left to right the way you would
   draw it. The dash length is generous because these paths differ a lot in
   length and one value has to cover the longest. */
.district__ground,
.district__b,
.district__plot {
  stroke-dasharray: 620;
  stroke-dashoffset: 620;
}

.district__ground { animation: district-draw 900ms var(--ease-out) 120ms forwards; }

.district__b,
.district__plot {
  animation: district-draw 1100ms var(--ease-out) forwards;
  animation-delay: calc(320ms + var(--i) * 150ms);
}

@keyframes district-draw { to { stroke-dashoffset: 0; } }

/* The buildings are the point, not the drawing of them. Anyone who has asked
   for less motion gets the finished skyline immediately. */
@media (prefers-reduced-motion: reduce) {
  .district__ground,
  .district__b,
  .district__plot {
    stroke-dasharray: none;
    stroke-dashoffset: 0;
    animation: none;
  }
}

/* ---------- Service icons ---------- */
.svc__ico {
  display: grid;
  place-items: center;
  width: 2.5rem;
  height: 2.5rem;
  flex: 0 0 auto;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  color: var(--brass);
  background: var(--surface);
  transition: border-color var(--d-hover) var(--ease-out),
              color var(--d-hover) var(--ease-out),
              transform var(--d-hover) var(--ease-out);
}

.svc__ico svg { width: 1.25rem; height: 1.25rem; }

@media (hover: hover) and (pointer: fine) {
  .svc:hover .svc__ico { border-color: var(--brass); transform: translateY(-2px); }
}

/* ---------- Property type tiles ---------- */
.ptiles {
  list-style: none;
  margin: var(--s-7) 0 0;
  padding: 0;
  display: grid;
  gap: var(--s-4);
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
}

.ptile {
  display: grid;
  gap: var(--s-3);
  align-content: start;
  padding: var(--s-5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  transition: border-color var(--d-hover) var(--ease-out),
              transform var(--d-hover) var(--ease-out),
              box-shadow var(--d-hover) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .ptile:hover {
    border-color: var(--brass);
    transform: translateY(-3px);
    box-shadow: var(--shadow-1);
  }
}

.ptile__ico { color: var(--brass); }
.ptile__ico svg { width: 1.65rem; height: 1.65rem; }
.ptile__name { font-size: var(--t-0); letter-spacing: -0.01em; }
.ptile__note { font-size: var(--t--1); color: var(--ink-3); }

/* ---------- Who I work with ---------- */
.whos { margin-top: var(--s-7); }

/* A real scroll container, not a transform track. Scroll snap gives touch
   swipe, trackpad flicks and keyboard scrolling for free, and with JS off this
   is still a row you can scroll by hand rather than a dead strip showing one
   card. The script only adds the three second step. */
.whos__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(17rem, 1fr);
  gap: var(--s-5);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  /* Room for the cards' hover lift and shadow, which a clipping scroll
     container would otherwise cut off along the top edge. */
  padding: 0.5rem 0.25rem 1rem;
  margin: -0.5rem -0.25rem -1rem;
  scrollbar-width: none;
}

.whos__track::-webkit-scrollbar { display: none; }

@media (min-width: 60rem) {
  /* Three at a time on a wide screen, so there is always a partial fourth
     hinting that the row continues. */
  .whos__track { grid-auto-columns: calc((100% - var(--s-5) * 2) / 3); }
}

.whos__track > * { scroll-snap-align: start; }

/* The brass rule across the top is what separates these from the tiles above:
   same family, different weight, so the two sections do not read as one grid
   of sixteen boxes. */
.who {
  display: grid;
  gap: var(--s-3);
  align-content: start;
  padding: var(--s-6) var(--s-5) var(--s-5);
  border: 1px solid var(--line);
  border-top: 2px solid var(--brass);
  border-radius: var(--r-md);
  background: var(--surface);
  transition: transform var(--d-hover) var(--ease-out),
              box-shadow var(--d-hover) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .who:hover { transform: translateY(-3px); box-shadow: var(--shadow-1); }
}

.who__ico { color: var(--brass); }
.who__ico svg { width: 1.5rem; height: 1.5rem; }
.who__name { font-size: var(--t-0); letter-spacing: -0.01em; }
.who__body { font-size: var(--t--1); color: var(--ink-2); }


/* ---------- Listings hero mosaic ----------
   The hero used to carry three addresses and their prices, which read as a
   dashboard on a page whose whole job is to show property. Five photographs on
   an offset grid say "listings" before a word is read.

   Deliberately not a uniform grid: equal tiles read as a contact sheet, and a
   contact sheet is what the grid further down the page already is. */
.mosaic {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  /* Capped so the mosaic still fits inside the masthead floor at 1920px. At
     5.2rem the five rows plus gaps pushed the Listings masthead 10px past the
     other two, which is exactly the kind of near-match that reads as a bug. */
  grid-auto-rows: clamp(3.2rem, 2.4rem + 3vw, 4.9rem);
  gap: clamp(0.4rem, 0.25rem + 0.5vw, 0.7rem);
}

.mosaic__t {
  overflow: hidden;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: var(--surface);
  opacity: 0;
  transform: translateY(10px) scale(0.985);
  animation: mosaic-in 620ms var(--ease-out) forwards;
  animation-delay: calc(220ms + var(--i) * 90ms);
}

.mosaic__t img { width: 100%; height: 100%; object-fit: cover; }

/* Five tiles, three sizes, arranged so no two of the same size touch. */
.mosaic__t--a { grid-column: span 4; grid-row: span 3; }
.mosaic__t--b { grid-column: span 2; grid-row: span 2; }
.mosaic__t--c { grid-column: span 2; grid-row: span 1; }
.mosaic__t--d { grid-column: span 3; grid-row: span 2; }
.mosaic__t--e { grid-column: span 3; grid-row: span 2; }

@keyframes mosaic-in {
  to { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .mosaic__t { opacity: 1; transform: none; animation: none; }
}


/* The bio column is four long paragraphs and the portrait is a single tall
   plate, so the two are never the same height. Centring the plate against the
   text reads as deliberate; top-aligning it left a growing gap under the
   photograph as the copy got longer. */
.split--wide-left > *:last-child { align-self: center; }


/* The Properties head lost its lede paragraph, which used to fill the right
   column and give the link something to sit under. Alone, an inline-flex link
   sits at that column's left edge, which is the middle of the page: it reads
   as something that has come loose rather than as a deliberate action. Pushed
   to the column's right edge, where the eye already goes to leave a section. */
@media (min-width: 900px) {
  .head__action { display: flex; justify-content: flex-end; }
}
