/* ===== FONTS =====
   Self-hosted from R2 rather than fetched from Google. Three reasons, in order
   of how much they cost: a third-party font host is a second DNS lookup, a
   second TLS handshake and a dependency whose downtime is invisible to us; the
   files are served with max-age=31536000, immutable, which Google caps far
   lower; and nothing about a visitor is announced to a third party.

   These are the same subsetted WOFF2 files Google serves - same unicode-range
   splits, so a page still downloads only the latin range it needs - copied to
   c21-branding/fonts/ and served from our own domain. Regenerate them with the
   manifest rather than editing by hand: the file names encode style, weight
   and subset, and a mismatch here fails silently as a fallback face.
   ================= */
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 300 500;
  font-display: swap;
  src: url(https://library.c21masters.com/c21-branding/fonts/fraunces/fraunces-italic-300-500-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: italic;
  font-weight: 300 500;
  font-display: swap;
  src: url(https://library.c21masters.com/c21-branding/fonts/fraunces/fraunces-italic-300-500-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(https://library.c21masters.com/c21-branding/fonts/fraunces/fraunces-normal-300-700-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Fraunces';
  font-style: normal;
  font-weight: 300 700;
  font-display: swap;
  src: url(https://library.c21masters.com/c21-branding/fonts/fraunces/fraunces-normal-300-700-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(https://library.c21masters.com/c21-branding/fonts/manrope/manrope-normal-400-700-latin.woff2) format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url(https://library.c21masters.com/c21-branding/fonts/manrope/manrope-normal-400-700-latin-ext.woff2) format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* =====================================================================
   TERRA — Century 21 Masters agent template
   Mirror of https://library.c21masters.com/global-css/terra.css
   --------------------------------------------------------------------
   Converted from the "Terra Luxury" source template. Warm ink on a clean
   canvas, one disciplined brass accent, deep estate green for the
   high-contrast bands. Fraunces over Manrope. Near-square 2px radii —
   architectural rather than friendly.

   Fonts are loaded by head-meta.njk, not here.

   Dropped from the source stylesheet, because nothing renders them any
   more: stats/counters, listing cards, awards, the about timeline, the
   services grid, the FAQ accordion, and the hand-written footer. The
   footer is injected by global-js/footer.js and carries its own
   presentation, the same as it does on starter, signature and brand.

   Contents
   1.  Design tokens
   2.  Reset & base
   3.  Typography
   4.  Layout primitives
   5.  Header / navigation
   6.  Buttons & links
   7.  Icons
   8.  Hero
   9.  Page hero & shared blocks
   10. Communities
   11. Ylopo IDX widget
   12. Process steps
   13. Contact
   14. QA build notices
   15. Animation utilities
   16. Responsive
   17. Reduced motion
   ===================================================================== */

/* 1. DESIGN TOKENS ---------------------------------------------------- */
:root {
  --ink:        #16140f;
  --ink-2:      #221f18;
  --canvas:     #fbfaf6;
  --canvas-2:   #f1ede3;
  --estate:     #1b2a24;
  --estate-2:   #263a31;
  --brass:      #b0904f;   /* decorative gold accent */
  --brass-deep: #8a6e45;   /* text-bearing gold, kept darker for AA contrast */
  --champagne:  #d9c7a7;

  --text:       #2a2620;
  --text-soft:  #5d564a;
  --muted:      #8b8475;
  --line:       rgba(22, 20, 15, 0.12);
  --line-soft:  rgba(22, 20, 15, 0.07);
  --on-dark:    #f3efe6;
  --on-dark-mut:rgba(243, 239, 230, 0.66);

  --display: "Fraunces", "Iowan Old Style", Georgia, "Times New Roman", serif;
  --sans: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;

  --fs-eyebrow: 0.78rem;
  --fs-sm:   clamp(0.85rem, 0.82rem + 0.15vw, 0.95rem);
  --fs-base: clamp(1rem, 0.96rem + 0.2vw, 1.08rem);
  --fs-lg:   clamp(1.15rem, 1.05rem + 0.5vw, 1.4rem);
  --fs-xl:   clamp(1.5rem, 1.2rem + 1.4vw, 2.2rem);
  --fs-2xl:  clamp(2rem, 1.5rem + 2.4vw, 3.2rem);
  --fs-3xl:  clamp(2.6rem, 1.8rem + 4vw, 5rem);

  --space-section: clamp(4.5rem, 3rem + 7vw, 9rem);
  --container: 1240px;
  --gutter: clamp(1.25rem, 0.6rem + 3vw, 3rem);
  --radius: 2px;
  --radius-lg: 4px;

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
  --ease-out: cubic-bezier(0.16, 1, 0.3, 1);
  --t-fast: 0.25s;
  --t-mid: 0.5s;
  --t-slow: 0.9s;

  --shadow-card: 0 1px 2px rgba(22,20,15,0.04), 0 18px 40px -28px rgba(22,20,15,0.35);
  --shadow-lift: 0 1px 2px rgba(22,20,15,0.05), 0 32px 60px -34px rgba(22,20,15,0.45);
}

/* 2. RESET & BASE ----------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; overflow-x: clip; }

body {
  font-family: var(--sans);
  font-size: var(--fs-base);
  line-height: 1.65;
  color: var(--text);
  background: var(--canvas);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img, svg, picture, video { display: block; max-width: 100%; }
ul[class], ol[class] { list-style: none; padding: 0; }
a { color: inherit; }
button { font: inherit; color: inherit; cursor: pointer; background: none; border: none; }
:focus-visible { outline: 2px solid var(--brass); outline-offset: 3px; border-radius: 1px; }

.skip-link {
  position: absolute;
  left: 50%;
  top: -100px;
  transform: translateX(-50%);
  background: var(--ink);
  color: var(--on-dark);
  padding: 0.7rem 1.4rem;
  z-index: 1000;
  letter-spacing: 0.04em;
  transition: top var(--t-fast) var(--ease);
}
.skip-link:focus { top: 8px; }

/* 3. TYPOGRAPHY ------------------------------------------------------- */
h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 380;
  line-height: 1.04;
  letter-spacing: -0.01em;
  color: var(--ink);
  font-variation-settings: "opsz" 40;
}
h1 { font-size: var(--fs-3xl); }
h2 { font-size: var(--fs-2xl); }
h3 { font-size: var(--fs-xl); }
h4 { font-size: var(--fs-lg); font-weight: 500; }
p { color: var(--text-soft); }
strong { color: var(--text); font-weight: 600; }
em { font-style: italic; }

.eyebrow {
  font-family: var(--sans);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: 0.26em;
  text-transform: uppercase;
  color: var(--brass-deep);
  display: inline-flex;
  align-items: center;
  gap: 0.7rem;
}
.eyebrow.is-centered { justify-content: center; }
.lede { font-size: var(--fs-lg); color: var(--text-soft); line-height: 1.55; }
.serif-accent { font-family: var(--display); font-style: italic; color: var(--brass-deep); }

/* 4. LAYOUT PRIMITIVES ------------------------------------------------ */
.container {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.section { padding-block: var(--space-section); }
.section--tight { padding-block: clamp(3rem, 2rem + 4vw, 5rem); }
.section--dark { background: var(--estate); color: var(--on-dark); }
.section--dark h1, .section--dark h2, .section--dark h3, .section--dark h4 { color: var(--on-dark); }
.section--dark p { color: var(--on-dark-mut); }
.section--alt { background: var(--canvas-2); }

.section-head { max-width: 62ch; }
.section-head.is-centered { margin-inline: auto; text-align: center; }
.section-head h2 { margin-top: 0.9rem; }
.section-head p { margin-top: 1rem; }

.split {
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  gap: clamp(2rem, 1rem + 4vw, 5rem);
  align-items: center;
}

/* 5. HEADER / NAV ----------------------------------------------------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 800;
  border-bottom: 1px solid transparent;
  transition: border-color var(--t-mid) var(--ease), padding var(--t-mid) var(--ease);
}
/* The frosted bar lives on a pseudo-element, NOT the header itself.
   backdrop-filter on the header would make it the containing block for the
   fixed off-canvas menu, trapping the menu at header height. */
.site-header::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: color-mix(in srgb, var(--canvas) 78%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  transition: background var(--t-mid) var(--ease);
}
.site-header.is-scrolled { border-bottom-color: var(--line); }
.site-header.is-scrolled::before { background: color-mix(in srgb, var(--canvas) 94%, transparent); }

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2rem;
  padding-block: clamp(1rem, 0.7rem + 0.9vw, 1.6rem);
  transition: padding var(--t-mid) var(--ease);
}
.is-scrolled .nav { padding-block: 0.85rem; }

/* The wordmark is the agent's own name, first line and remainder. */
.brand { display: inline-flex; flex-direction: column; line-height: 1; text-decoration: none; }
.brand__name { font-family: var(--display); font-size: 1.45rem; letter-spacing: 0.04em; color: var(--ink); }
.brand__sub {
  font-size: 0.62rem;
  letter-spacing: 0.42em;
  text-transform: uppercase;
  color: var(--brass-deep);
  margin-top: 0.35rem;
  padding-left: 0.1em;
}

.nav__links { display: flex; align-items: center; gap: clamp(0.9rem, 0.3rem + 1.4vw, 2rem); }
.nav__link {
  position: relative;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--text);
  padding-block: 0.4rem;
  transition: color var(--t-fast) var(--ease);
}
.nav__link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--brass);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--t-mid) var(--ease-out);
}
.nav__link:hover { color: var(--ink); }
.nav__link:hover::after,
.nav__link[aria-current="page"]::after { transform: scaleX(1); }
.nav__link[aria-current="page"] { color: var(--brass-deep); }

.nav__cta { display: inline-flex; }
.nav__toggle { display: none; width: 42px; height: 42px; position: relative; }
.nav__toggle span {
  position: absolute;
  left: 9px;
  right: 9px;
  height: 1.5px;
  background: var(--ink);
  transition: transform var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease);
}
.nav__toggle span:nth-child(1) { top: 15px; }
.nav__toggle span:nth-child(2) { top: 21px; }
.nav__toggle span:nth-child(3) { top: 27px; }
.nav__toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav__toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* 6. BUTTONS & LINKS -------------------------------------------------- */
.btn {
  --bg: var(--ink);
  --fg: var(--on-dark);
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.95rem 1.7rem;
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--fg);
  background: var(--bg);
  border: 1px solid var(--bg);
  border-radius: var(--radius);
  position: relative;
  overflow: hidden;
  transition: color var(--t-mid) var(--ease), border-color var(--t-mid) var(--ease);
  isolation: isolate;
}
/* Hover is a brass panel sliding up behind the label, not a background swap. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--brass);
  transform: translateY(101%);
  transition: transform var(--t-mid) var(--ease-out);
  z-index: -1;
}
.btn:hover { color: var(--ink); border-color: var(--brass); }
.btn:hover::before { transform: translateY(0); }

.btn--ghost { --bg: transparent; --fg: var(--ink); border-color: var(--line); }
.btn--ghost:hover { color: var(--ink); border-color: var(--brass); }
.btn--light { --bg: var(--on-dark); --fg: var(--ink); }
.btn--light:hover { color: var(--ink); }

.text-link {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.82rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--brass-deep);
}
.text-link svg { transition: transform var(--t-fast) var(--ease); }
.text-link:hover svg { transform: translateX(5px); }

/* 7. ICONS ------------------------------------------------------------
   The source hydrated <i data-lucide> from a CDN on every page. The icons
   this template uses are inlined as SVG in the markup instead, so nothing
   is fetched at runtime on 582 sites. global-js/terra.js carries the same
   set for anything injected after load. */
.lucide { stroke-width: 1.5; }
.text-link svg { width: 16px; height: 16px; }

/* 8. HERO ------------------------------------------------------------- */
.hero {
  position: relative;
  padding-top: clamp(3rem, 1rem + 6vw, 6rem);
  padding-bottom: var(--space-section);
}
.hero__grid {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: clamp(2rem, 1rem + 4vw, 4.5rem);
  align-items: end;
}
.hero__title {
  margin: 1.4rem 0 1.6rem;
  /* The headline is agent copy, so its longest word is not known at design
     time. A word with no break opportunity has to break rather than run out
     of the hero, which clips it. Same guard as timeless and aurelia. */
  overflow-wrap: break-word;
}
.hero__title .line { display: block; overflow: initial; }
.hero__title .line > span { display: block; }
.hero__actions { display: flex; gap: 1rem; margin-top: 2.2rem; flex-wrap: wrap; }

.media { position: relative; overflow: hidden; border-radius: var(--radius-lg); background: var(--estate); }
.media__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 1.4s var(--ease-out);
}
.media:hover .media__img { transform: scale(1.05); }
.hero__visual { aspect-ratio: 4 / 5; box-shadow: var(--shadow-lift); }

/* 9. PAGE HERO & SHARED BLOCKS ---------------------------------------- */
.page-hero { padding-top: clamp(3rem, 1.5rem + 5vw, 6rem); padding-bottom: clamp(2.5rem, 1.5rem + 4vw, 5rem); }
.page-hero h1 { margin: 1.1rem 0 1.1rem; max-width: 18ch; }
.page-hero p { max-width: 56ch; }

.pill-row { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1.6rem; }
.pill {
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--text-soft);
  border: 1px solid var(--line);
  border-radius: 100px;
  padding: 0.45rem 1rem;
}

/* 10. COMMUNITIES -----------------------------------------------------
   New for the conversion; the source template had no communities section.
   Built on the same card language as the source's listing card so the two
   read as one family. Reference: widgets/terra/communities.html */
.communities { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gutter); }

.community {
  display: flex;
  flex-direction: column;
  background: var(--canvas);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: transform var(--t-mid) var(--ease-out), box-shadow var(--t-mid) var(--ease-out),
              border-color var(--t-mid) var(--ease);
}
.community:hover { transform: translateY(-6px); box-shadow: var(--shadow-lift); border-color: var(--line); }

.community__media { position: relative; aspect-ratio: 4 / 3; overflow: hidden; background: var(--estate); }
.community__media .media__img { transition: transform 1.2s var(--ease-out); }
.community:hover .community__media .media__img { transform: scale(1.06); }

.community__tag {
  position: absolute; top: 1rem; left: 1rem;
  background: var(--canvas);
  color: var(--ink);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  font-weight: 600;
  padding: 0.4rem 0.8rem;
  border-radius: 100px;
}

.community__body { padding: 1.5rem 1.5rem 1.7rem; display: flex; flex-direction: column; gap: 0.9rem; flex: 1; }
.community__name { font-family: var(--display); font-size: 1.6rem; line-height: 1.1; color: var(--ink); }
/* The brass rule hangs off the name rather than being its own element, so a
   card with no description keeps the same rhythm as one that has copy. */
.community__name::after {
  content: "";
  display: block;
  width: 34px;
  height: 1px;
  background: var(--brass);
  margin-top: 0.9rem;
}
.community__desc { font-size: 0.9rem; color: var(--text-soft); line-height: 1.6; }
/* margin-top:auto holds the card floor whether or not there is a description */
.community__more {
  margin-top: auto;
  padding-top: 0.4rem;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--brass-deep);
}
.community__more svg { width: 16px; height: 16px; transition: transform var(--t-fast) var(--ease); }
.community:hover .community__more svg { transform: translateX(5px); }

/* 11. YLOPO IDX WIDGET ------------------------------------------------
   The widget renders its own cards; all this does is hold the space and
   show a shimmer until it hydrates, so the page does not jump. */
.ylopo-easy-widget { min-height: 420px; position: relative; }
.ylopo-easy-widget.ylopo-loading::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
  background: linear-gradient(90deg, var(--canvas-2) 25%, var(--canvas) 37%, var(--canvas-2) 63%);
  background-size: 400% 100%;
  animation: terra-shimmer 1.4s ease infinite;
}
.ylopo-easy-widget.ylopo-ready::before { display: none; }
@keyframes terra-shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }

/* ---- Market trends host (Gold, one page per community) ----------------
   Same job as the rail above, and it was not doing it: this host reserved
   no height at all, so on a Market Trends page the listings section and the
   two below it sat ~750px too high and jumped down the moment the chart
   landed. It gets the listings widget's own floor and shimmer, so the two
   read as one system.

   The hook is :not(:has(*)) rather than the .ylopo-loading class the rail
   uses - ylopo-bundle.js only sets that class on the listings widget. :empty
   would not work either: the Nunjucks source leaves a newline inside this
   div, and a text node is enough to fail :empty.

   The floor is deliberately BELOW the widget's real height (measured 715px at
   1440, 750px at 390). min-height only reserves, so a low floor absorbs most
   of the shift and can never leave a hole under a widget that came in
   shorter. */
.ylopo-market-trends { position: relative; }
.ylopo-market-trends:not(:has(*)) { min-height: 420px; }
.ylopo-market-trends:not(:has(*))::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius-lg);
  background: linear-gradient(90deg, var(--canvas-2) 25%, var(--canvas) 37%, var(--canvas-2) 63%);
  background-size: 400% 100%;
  animation: terra-shimmer 1.4s ease infinite;
}

/* ---- Chart clearance under the metric tabs ---------------------------
   The four metric tabs carry 0.6rem below them and the chart no margin at
   all, and the chart's top axis label is drawn ABOVE its own box: the skin
   gives that svg overflow:visible so the vendor's fixed 600x200 viewBox is
   not clipped. Measured, the top label lands 5px under the tab row at 1440
   and 7px at 768 and 390 - close enough to read as a collision, and it is
   the gap the QA screenshot marks. 1.75rem puts it in proportion with the
   2.75rem the skin sets between the stat tiles and the tabs.

   WHY THE SELECTOR IS SHAPED LIKE THIS. The rule it corrects lives in the
   OTHER project - ylopo-grid's templates/terra.css - which ylopo-bundle.js
   appends AFTER this stylesheet, so an equal-specificity rule here loses on
   cascade order. That skin selects with six classes; this restates the same
   six and adds one element, which outranks it without !important. It is a
   copy of a selector this repo does not own: if the tab or chart spacing is
   ever set deliberately in the skin, delete this block rather than tuning
   it, and make the change there where it belongs. Same reasoning as the
   sold-date override further down this file. */
.ylopo-market-trends .YLOPO-widget .market-trends .market-trends-content .market-trends-area div.market-trends-desktop {
  margin-top: 1.75rem;
}

/* 12. PROCESS STEPS ---------------------------------------------------
   Only the Buyers and Sellers pages use these; they are the tips those
   pages exist to carry. */
.steps { counter-reset: step; display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--gutter); }
.step { position: relative; padding-top: 2.6rem; }
.step::before {
  counter-increment: step;
  content: counter(step, decimal-leading-zero);
  position: absolute;
  top: 0; left: 0;
  font-family: var(--display);
  font-size: 1.6rem;
  color: var(--brass);
}
/* The hairline runs from the numeral to the next step and is suppressed on
   the last one, so the row reads as a sequence rather than four boxes. */
.step::after {
  content: "";
  position: absolute;
  top: 0.6rem; left: 3.2rem; right: -0.5rem;
  height: 1px;
  background: var(--line);
}
.step:last-child::after { display: none; }
.step h4 { margin-bottom: 0.5rem; }
.step p { font-size: 0.92rem; }

/* 13. CONTACT ---------------------------------------------------------
   The form itself is injected by form-inject.js and brings its own markup,
   so there is nothing here to style it with. Once that injector supports
   the terra template name, its styling lands there — the target look is
   preserved in widgets/terra/contact-form.html. */
.contact-grid { display: grid; grid-template-columns: 1fr 1.15fr; gap: clamp(2rem, 1rem + 4vw, 5rem); align-items: start; }
.info-list { display: grid; gap: 1.6rem; margin-top: 2rem; }
.info-item__label { font-size: 0.74rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--muted); }
.info-item__value { font-family: var(--display); font-size: 1.25rem; color: var(--ink); margin-top: 0.3rem; }
.info-item a { text-decoration: none; }
.info-item a:hover { color: var(--brass-deep); }

/* 14. QA BUILD NOTICES ------------------------------------------------
   Deliberately visible while the reviews and sold-properties widgets are
   being built, so leadership reviewing the site can see what is coming
   rather than an empty section. Remove this block once both widgets ship. */
.qa-note {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  flex-wrap: wrap;
  max-width: 60ch;
  margin-inline: auto;
  padding: 1.4rem 1.6rem;
  border: 1px dashed var(--brass);
  border-radius: var(--radius-lg);
  background: color-mix(in srgb, var(--brass) 7%, transparent);
}
.qa-note__tag {
  flex: none;
  font-size: 0.66rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--brass);
  border-radius: 100px;
  padding: 0.35rem 0.8rem;
}
.qa-note p { font-size: 0.9rem; color: var(--text-soft); flex: 1 1 22ch; }
.section--dark .qa-note p { color: var(--on-dark-mut); }

/* Sold page with nothing to show.
   The visitor-facing counterpart to .qa-note: an agent whose Ylopo agent ids
   are not on file yet gets this instead of an empty band. Which of the two
   renders is decided in Nunjucks - the widget has to be absent from the page,
   not hidden here. */
.sold-empty {
  max-width: 46ch; margin-inline: auto;
  padding: 2rem 1rem;
  text-align: center;
  border-top: 1px solid var(--line, rgba(22, 20, 15, 0.12));
  border-bottom: 1px solid var(--line, rgba(22, 20, 15, 0.12));
}
.sold-empty .sold-empty__title {
  font-family: var(--display, Georgia, serif);
  font-size: var(--fs-xl, 1.6rem); font-weight: 400;
  margin: 0 0 0.75rem;
  color: var(--ink, #16140f);
}
.sold-empty p { margin: 0; font-size: var(--fs-base, 1rem); color: var(--text-soft, #5d564a); }
.sold-empty a { color: var(--brass, #b0904f); text-decoration: underline; text-underline-offset: 0.2em; }
.sold-empty a:hover { color: var(--brass-deep, #8a6e45); }

/* Reviews fallback. Same shape and voice as .sold-empty above, and for the same
   reason: an empty band under a "Client Reviews" heading reads as broken, so the
   section always has something true to say.

   It carries two states that look identical to a visitor but arrive
   differently. With no RealSatisfied key on file this is the only thing
   rendered, decided at build time. With a key, both this and .rs-reviews ship
   `hidden` and rs-reviews.js unhides whichever the feed turned out to support -
   which is this one for 165 of the 434 resolved agents, since only approved
   testimonials reach the feed.

   The two-class selector on the title is deliberate: `.rs-empty p` is (0,1,1)
   and would otherwise win the font-size over a single-class `.rs-empty__title`.
   That exact collision flattened the title during QA. */
.rs-empty {
  max-width: 46ch; margin-inline: auto;
  padding: 2rem 1rem;
  text-align: center;
  border-top: 1px solid var(--line, rgba(22, 20, 15, 0.12));
  border-bottom: 1px solid var(--line, rgba(22, 20, 15, 0.12));
}
.rs-empty .rs-empty__title {
  font-family: var(--display, Georgia, serif);
  font-size: var(--fs-xl, 1.6rem); font-weight: 400;
  margin: 0 0 0.75rem;
  color: var(--ink, #16140f);
}
.rs-empty p { margin: 0; font-size: var(--fs-base, 1rem); color: var(--text-soft, #5d564a); }
.rs-empty a { color: var(--brass, #b0904f); text-decoration: underline; text-underline-offset: 0.2em; }
.rs-empty a:hover { color: var(--brass-deep, #8a6e45); }

/* Sale date on sold cards - OFF unless SHOW_SOLD_DATE is on in
   scripts/feature-flags.mjs, which is what emits data-sold-date on the widget.

   The Ylopo widget puts the date in .gallery-card-b-generate-sort-block, which
   this template's card CSS in the ylopo-grid project hides outright. Overriding
   that !important needs both the flag attribute and this specificity, so the
   rule lives here rather than there - and so the per-template design of the bar,
   when it happens, sits with the rest of this template's tokens.

   :has(.sold-date) is load-bearing, not decoration. For-sale cards reuse the
   same container for "Listed 4 Months Ago", so revealing it unscoped would put
   that strip on every listing card on every home and search page. Only a sold
   card has .sold-date inside.

   Two selectors, not one: the card redesign takes .property-details-wrap out of
   layout with display:none, and on these templates that wrapper holds the sort
   block and nothing else - so the block alone computes to a 0x0 box inside a
   hidden parent. Revealing the wrapper duplicates no content precisely because
   it is otherwise empty; check that still holds before copying this to a new
   template.

   Still wearing the widget's own grey bar styling: designing it per template is
   the work that has to happen before the flag is turned on. */
.ylopo-easy-widget[data-sold-date="true"] .property-details-wrap:has(.sold-date),
.ylopo-easy-widget[data-sold-date="true"] .gallery-card-b-generate-sort-block:has(.sold-date) {
  display: flex !important;
}


/* 15. ANIMATION UTILITIES --------------------------------------------- */
.reveal {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity var(--t-slow) var(--ease-out), transform var(--t-slow) var(--ease-out);
  transition-delay: var(--reveal-delay, 0s);
}
.reveal.is-visible { opacity: 1; transform: none; }

.mask { clip-path: inset(0 0 100% 0); transition: clip-path 1.1s var(--ease-out); }
.mask.is-visible { clip-path: inset(0 0 0 0); }

.line > span { transform: translateY(110%); transition: transform var(--t-slow) var(--ease-out); }
.is-loaded .line > span { transform: translateY(0); }

/* 16. RESPONSIVE ------------------------------------------------------ */
@media (max-width: 1024px) {
  .communities, .steps { grid-template-columns: repeat(2, 1fr); }
  .step::after { display: none; }
}

/* Burger panel — the overflow menu, and the phone menu, and the same thing at
   every width in between. This used to be @media (max-width: 900px), itself a
   hand-raised replacement for the source template's 860px. Both were one
   number standing in for 586 different agent-name lengths, since the wordmark
   shares this row. global-js/nav-fit.js keeps as many items inline as the row
   can hold and nests the rest in here instead. */
.nav-panel {
  position: fixed;
  inset: 0 0 0 auto;
  /* Half the window from 442px up, the whole window at 441 and below. A width
     tied to the design's own taste (this was min(82vw, 360px)) made the same
     menu a different size on every template; a proportion reproduces.

     The cap is Terra's own, and only the extreme end of the range feels it:
     half of a 1024 screen is 512px, so nothing below ~1040px changes. Past
     that the proportion stops being a drawer - 1280px of paper on a 2560
     monitor holds eight words of menu - and a capped panel reads as a
     considered object instead. */
  width: min(50%, 520px);
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
  /* The rhythm is on the rows now, not on this gap. Every item used to be
     1.6rem from the next, which on a 390x844 phone pushed the CTA 155px past
     the fold; the rows carry their own padding and a hairline instead, so the
     whole menu including the CTA fits without scrolling and still has air. */
  gap: 0;
  /* The drawer opens under the header, which keeps its close button on top of
     it, so the top has to clear that band - measured, the X ends at 58-60px.
     There is no header-height token on this template, hence the literal. The
     foot has nothing to clear. */
  padding: 4.75rem var(--gutter) 2rem;
  /* Two layers. On top, a 3px brass spine down the edge the drawer enters
     from - the one disciplined accent this template allows itself - fading out
     downward so it stays an accent rather than becoming a border. Under it,
     warm paper rather than flat white: the wash lands on --canvas-2, the same
     tone the alternate sections use, so the drawer belongs to the same
     material as the page it covers.

     Both are backgrounds and not a pseudo-element on purpose. This panel is
     the scroll container, and a box drawn inside its flow would scroll away
     from the edge; a background paints against the padding box and stays. */
  background:
    linear-gradient(180deg, var(--brass) 0%, var(--champagne) 42%, transparent 100%) left top / 3px 100% no-repeat,
    linear-gradient(180deg, var(--canvas) 0%, var(--canvas) 58%, var(--canvas-2) 100%);
  border-left: 1px solid var(--line);
  box-shadow: -40px 0 80px -40px rgba(22,20,15,0.45);
  transform: translateX(100%);
  opacity: 0;
  /* visibility is in the transition with a delay so it flips instantly on
     open and only once the slide has finished on close. Without the delay the
     panel disappears on the first frame of closing and the animation is never
     seen. */
  transition: transform var(--t-mid) var(--ease-out),
              opacity var(--t-mid) var(--ease-out),
              visibility 0s linear var(--t-mid);
  z-index: 850;
  overflow-y: auto;
  /* A Gold Terra menu is fourteen entries plus a mark, a CTA and a phone
     number: on a 390x844 phone that is taller than the window whatever the
     type does, so the drawer scrolls by design and the rows above are sized to
     keep that scroll short. contain stops it chaining into the page behind,
     which was the part that felt broken - reaching the last link and having
     the market trends page start moving underneath. */
  overscroll-behavior: contain;
  /* The panel sits off-screen rather than hidden, so without this its links
     stay in the tab order while it is closed. */
  visibility: hidden;
}
/* Safe centring for the panel's contents.
   justify-content:center in a scroll container puts the overflow at BOTH ends,
   and the half above the centre cannot be scrolled to - scrollTop is already 0
   there. It went unnoticed while the panel held only the links that did not
   fit; now that the burger means EVERY item is in here, a short screen put the
   mark and the first two links in that unreachable half (measured: 153px cut
   off at 360x640, 228px at 320x480).
   Auto margins centre exactly the same way when there is free space - two of
   them split it evenly - and collapse to zero when there is none, so the menu
   is centred when it fits and scrolls from its true top when it does not.
   Nothing here is a media query: the condition is whether the content fits,
   which no breakpoint knows. */
/* No auto margins here, and that is the point.
   The panel used to centre its contents with justify-content, which put the
   overflow at both ends and made the top half unreachable. Auto margins fixed
   the reachability but kept the drift: the mark landed 32px from the panel top
   on a short screen and 180px on a tall one, and on a tall drawer it left a
   168px void between the mark and the first link - the logo read as stranded
   rather than as the head of the menu.
   So the stack is simply top-aligned: the mark sits at the panel's top padding,
   the links follow one gap below it, and both are the same on every screen. It
   costs nothing in emptiness, because the panels that could look sparse
   (classic, california, timeless) are height:auto and shrink to their content;
   the two side drawers are full-height by design, where a menu that starts at
   the top is the ordinary thing. justify-content: flex-start above is what
   keeps the top scrollable when the content is taller than the screen. */
.nav-panel.is-open { transform: translateX(0); visibility: visible; }
.nav-panel.is-open {
  transform: translateX(0);
  opacity: 1;
  visibility: visible;
  transition-delay: 0s;
}

/* Phones take the whole window: half of 400px is a column of menu beside a
   column of page nobody can read. */
@media (max-width: 441px) {
  .nav-panel { width: 100%; }
}

/* The rows settle in behind the drawer, a beat apart. Deliberately short and
   deliberately capped at six steps: past that the last item waits long enough
   to be noticed as waiting. The closed state is not a hiding place - the panel
   itself is off-screen and visibility:hidden - so the reduced-motion block at
   the foot of this file, which drops every transition to 0.001ms, lands every
   row at its end state instantly rather than leaving anything invisible. */
.nav-panel__brand,
.nav-panel [data-nav-fit-panel-list] > *,
.nav-panel__foot {
  opacity: 0;
  transform: translateX(14px);
  transition: opacity var(--t-mid) var(--ease-out), transform var(--t-mid) var(--ease-out);
}
.nav-panel.is-open .nav-panel__brand,
.nav-panel.is-open [data-nav-fit-panel-list] > *,
.nav-panel.is-open .nav-panel__foot {
  opacity: 1;
  transform: none;
}
.nav-panel.is-open [data-nav-fit-panel-list] > *:nth-child(1) { transition-delay: 0.06s; }
.nav-panel.is-open [data-nav-fit-panel-list] > *:nth-child(2) { transition-delay: 0.10s; }
.nav-panel.is-open [data-nav-fit-panel-list] > *:nth-child(3) { transition-delay: 0.14s; }
.nav-panel.is-open [data-nav-fit-panel-list] > *:nth-child(4) { transition-delay: 0.18s; }
.nav-panel.is-open [data-nav-fit-panel-list] > *:nth-child(5) { transition-delay: 0.22s; }
.nav-panel.is-open [data-nav-fit-panel-list] > *:nth-child(n+6) { transition-delay: 0.26s; }
.nav-panel.is-open .nav-panel__foot { transition-delay: 0.30s; }
.nav-panel .nav__links {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 0; width: 100%;
}

/* ---- The head ---------------------------------------------------------
   The mark used to float above the links with nothing tying it to them. A
   hairline under it makes it the head of the menu, which is what it is. The
   name and number sit in .nav-panel__foot at the other end - see the layout,
   where both are rendered outside [data-nav-fit-panel-list] so nav-fit.js
   cannot promote them into the bar. */
.nav-panel__brand {
  width: 100%;
  padding-bottom: 1.35rem;
  border-bottom: 1px solid var(--line);
  margin-bottom: 1.35rem;
}

/* ---- Top-level rows --------------------------------------------------
   Fraunces, sentence case, at reading size. The bar's 0.82rem uppercase
   Manrope is right for a row of eight items in a 60px band and wrong for a
   column with a whole drawer to itself: at that size, in that tracking, the
   menu read as a table of contents for a form. Both faces are still doing
   their own job - the display face names the pages, and the sub-rows below
   stay in Manrope's uppercase, which is what makes them read as belonging
   to the row above rather than competing with it. */
.nav-panel [data-nav-fit-panel-list] > .nav__link,
.nav-panel .nav__group > .nav__link {
  display: block;
  width: 100%;
  font-family: var(--display);
  font-size: clamp(1.35rem, 1.15rem + 0.7vw, 1.7rem);
  font-weight: 400;
  letter-spacing: 0.005em;
  text-transform: none;
  color: var(--ink);
  /* 1.2, against the 1.6 the body sets. A display face at 22-27px in a stack
     of fourteen entries does not want body leading: it cost 12px a row, the
     rows read as table cells, and it pushed the CTA further past the fold on
     every screen. */
  line-height: 1.2;
  padding: 0.62rem 0 0.62rem 1.15rem;
  transition: color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}

/* The brass indicator, in the row's own left padding. It grows from the
   centre rather than sliding, and it is a ::before because the base
   .nav__link::after is the bar's sliding underline - which is switched off in
   here: under a serif row it read as an ink correction, not an accent. */
.nav-panel .nav__link::after { display: none; }
.nav-panel [data-nav-fit-panel-list] > .nav__link::before,
.nav-panel .nav__group > .nav__link::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 2px;
  height: 0;
  background: var(--brass);
  transform: translateY(-50%);
  transition: height var(--t-fast) var(--ease-out);
}
.nav-panel [data-nav-fit-panel-list] > .nav__link:hover,
.nav-panel .nav__group > .nav__link:hover {
  color: var(--brass-deep);
  transform: translateX(3px);
}
.nav-panel [data-nav-fit-panel-list] > .nav__link:hover::before,
.nav-panel .nav__group > .nav__link:hover::before,
.nav-panel [data-nav-fit-panel-list] > .nav__link:focus-visible::before,
.nav-panel .nav__group > .nav__link:focus-visible::before { height: 1.35em; }

/* The page you are on: brass ink and the indicator already lit, so the menu
   answers "where am I" before anything is hovered. */
.nav-panel .nav__link[aria-current="page"] { color: var(--brass-deep); }
.nav-panel .nav__link[aria-current="page"]::before { height: 1.35em; }

/* Hairlines between the top-level entries. --line-soft, not --line: at this
   count a full-strength rule turns the menu into a table.

   :not(.nav__cta) rather than a second rule undoing it on the CTA. The CTA is
   not a row of the list and takes no rule above it, and a rule that set
   padding-top:0 there carried three classes against the CTA's own two - so it
   won on specificity at any order and flattened the button to a 40px slab with
   no padding above its label. Excluding it here is the version that cannot
   come back. */
.nav-panel [data-nav-fit-panel-list] > * + *:not(.nav__cta) {
  border-top: 1px solid var(--line-soft);
  padding-top: 0.15rem;
}

/* ---- The CTA ---------------------------------------------------------
   On --estate rather than --ink: the drawer ends on the warm side of the wash,
   and estate green is the ground this template uses for its closing bands.
   .btn's own brass panel still slides up behind the label on hover.

   Full width with the label centred is what this was, and it read as a slab:
   a 375px bar of dark green holding 120px of 0.8rem type in the middle, with
   the two ends doing nothing. Full width is right for the last thing in the
   drawer - it closes the column the menu made - so the fix is to use the
   width rather than to shrink it. The label sits at the left on the SAME line
   as every menu row above it (1.15rem is the rows' own text indent), the
   chevron takes the right end, and the block is tall enough to hold both. */
.nav-panel .nav__cta {
  --bg: var(--estate);
  width: 100%;
  justify-content: space-between;
  padding: 1.1rem 1.3rem 1.1rem 1.15rem;
  margin-top: 1.75rem;
}

/* Drawn from borders rather than an icon, the same way .nav__caret is: Lucide
   is inlined in global-js/terra.js and runs after first paint, so a
   <i data-lucide> here would be an empty box until the script landed.

   It is a pseudo-element and not markup because this element is
   data-nav-fit-last: nav-fit.js promotes it into the header bar when the row
   has space, and an arrow in the markup would follow it there and change a bar
   this task was not asked to touch. Scoped to .nav-panel, it cannot. */
.nav-panel .nav__cta::after {
  content: "";
  flex: none;
  width: 0.5rem;
  height: 0.5rem;
  border-top: 1.5px solid currentColor;
  border-right: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform var(--t-fast) var(--ease);
}
.nav-panel .nav__cta:hover::after,
.nav-panel .nav__cta:focus-visible::after { transform: translateX(3px) rotate(45deg); }

/* ---- The foot --------------------------------------------------------
   Who the menu belongs to, and the one number that reaches them. Quiet by
   construction: this is the last thing in the drawer, not a second CTA. */
.nav-panel__foot {
  width: 100%;
  margin-top: 1.6rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--line);
}
.nav-panel__who {
  display: block;
  font-family: var(--display);
  font-size: 1rem;
  color: var(--ink);
}
.nav-panel__tel {
  display: inline-block;
  margin-top: 0.4rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-decoration: none;
  color: var(--text-soft);
  transition: color var(--t-fast) var(--ease);
}
.nav-panel__tel:hover { color: var(--brass-deep); }

/* The close control is the burger, which sits in the header above the panel.
   While the drawer is open it is the only thing to press up there, so it gets
   a target of its own rather than three floating strokes.

   position:fixed is the fix for a defect, not a style. body.menu-open sets
   overflow:hidden, which makes the body the scroll container the sticky header
   sticks to - and the body's own scrollTop is 0, so on a page scrolled 447px
   the header sat 447px above the viewport and took the X with it. Measured on
   a Market Trends page at 1024x768. Escape and a click on the backdrop still
   closed the drawer, but on a phone the drawer is the whole window: no
   backdrop to press and no keyboard, so nothing was left to press at all.

   The 9px is the burger's own stroke inset, so the X's ink lines up with the
   drawer's text edge rather than its box. nav-fit.js counts this element's
   width when it measures the row; out of flow that over-reserves 42px, which
   can only keep an item in the panel that would have fitted, and only while
   the menu is open. */
body.menu-open .nav__toggle {
  position: fixed;
  top: 0.85rem;
  right: calc(var(--gutter) - 9px);
  border-radius: 50%;
  transition: background var(--t-fast) var(--ease);
}
body.menu-open .nav__toggle:hover { background: var(--canvas-2); }

body.menu-open { overflow: hidden; }

/* The burger appears only when the panel has something in it. nav-fit.js sets
   this to "false" when every item fits inline; it is absent until the script
   runs, and absent has to mean visible - that is the state a phone loads in,
   with every item still in the panel. */
.nav__toggle { display: block; z-index: 860; }
[data-nav-overflow="false"] .nav__toggle { display: none; }

/* An inline nav link that wraps to a second line is the thing overflow is
   there to prevent, so it must not be an option the row can take instead. */
#primary-nav .nav__link, #primary-nav .nav__cta { white-space: nowrap; }

/* Nav submenus — Buyers carries Calculator, Sellers carries Home Valuation
   and Sold. A group is one element, so nav-fit.js moves it whole and knows
   nothing about the nesting; all that is left to decide is how it reads in
   each of the two places it can land.

   Inline it is an absolutely positioned dropdown, and that is not a styling
   preference: a submenu in flow would make the group as wide as its widest
   child, nav-fit would measure that width and nest the group at every screen
   size, so the dropdown would never once be seen.

   It opens on :hover and on :focus-within, and no script owns either. Focus
   reaches the parent link - which is always visible - before it can reach
   into the submenu, so :focus-within is already true by the time the next Tab
   lands inside. That is why the closed state can use visibility:hidden and
   keep the submenu out of the tab order until it is on screen. */
.nav__group { position: relative; display: flex; align-items: center; }
.nav__group > .nav__link { display: inline-flex; align-items: center; gap: 0.45em; }
.nav__sub { display: flex; flex-direction: column; }

/* Drawn from borders, not from an icon: Lucide is inlined in
   global-js/terra.js and runs after first paint, so an <i data-lucide> here
   would be an empty box in the bar until the script landed. */
.nav__caret {
  width: 0.36em;
  height: 0.36em;
  flex: none;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: translateY(-0.12em) rotate(45deg);
  transition: transform var(--t-fast) var(--ease);
}

/* ---- Inline: the dropdown ---- */
[data-nav-fit-inline] .nav__sub {
  position: absolute;
  top: 100%;
  left: -1rem;
  min-width: max-content;
  gap: 0.15rem;
  margin-top: 0.55rem;
  padding-block: 0.55rem;
  background: var(--canvas);
  border: 1px solid var(--line);
  border-radius: 2px;
  box-shadow: 0 20px 44px -26px rgba(22, 20, 15, 0.5);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease),
              visibility var(--t-fast) var(--ease);
  z-index: 860;
}
/* The gap between the label and the dropdown is dead ground the pointer has
   to cross, and crossing it would close the menu. This bridges it. */
[data-nav-fit-inline] .nav__sub::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  top: -0.55rem;
  height: 0.55rem;
}
[data-nav-fit-inline] .nav__group:hover .nav__sub,
[data-nav-fit-inline] .nav__group:focus-within .nav__sub {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
[data-nav-fit-inline] .nav__group:hover .nav__caret,
[data-nav-fit-inline] .nav__group:focus-within .nav__caret {
  transform: translateY(0.1em) rotate(-135deg);
}
[data-nav-fit-inline] .nav__sub .nav__link { padding: 0.5rem 1rem; }
/* The sliding brass rule belongs to the bar. Inside a card it reads as a
   border on the wrong edge. */
[data-nav-fit-inline] .nav__sub .nav__link::after { display: none; }
[data-nav-fit-inline] .nav__sub .nav__link:hover { background: var(--canvas-2); color: var(--ink); }

/* ---- Panel: an indented block, always open ---- */
/* No disclosure to tap here. The panel exists to show everything that did not
   fit, so hiding half of it behind a second interaction would defeat it. */
[data-nav-fit-panel] .nav__group {
  flex-direction: column;
  align-items: flex-start;
  /* Tight to its parent: the group is one entry with its pages under it, and
     1.1rem here made the children read as siblings of the row above. */
  gap: 0.2rem;
  width: 100%;
}
[data-nav-fit-panel] .nav__sub {
  gap: 0;
  /* No width:100% here. With the left margin below, a full parent width put
     this box 18px past the panel's content edge and the drawer grew a
     horizontal scrollbar - 12px of it, showing up only once the panel was tall
     enough to take a vertical scrollbar and lose that much width. The rows
     inside only change colour on hover, so they need no box to fill: shrink to
     fit is both correct and the version that cannot overflow. */
  /* Indented from the parent row's own left padding, so the rule lines up
     under the brass indicator rather than starting a second column. */
  margin: 0.15rem 0 0.55rem 1.15rem;
  padding-left: 1.1rem;
  border-left: 1px solid var(--line-soft);
}
[data-nav-fit-panel] .nav__sub .nav__link {
  display: block;
  font-family: var(--sans);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text-soft);
  line-height: 1.2;
  padding-block: 0.45rem;
}
[data-nav-fit-panel] .nav__sub .nav__link:hover { color: var(--ink); }
[data-nav-fit-panel] .nav__sub .nav__link[aria-current="page"] { color: var(--brass-deep); }
[data-nav-fit-panel] .nav__caret { display: none; }

/* Estate green rather than neutral ink, and a touch of blur: the page behind
   the drawer should read as set down, not greyed out. The blur is on the
   backdrop and not on the panel - backdrop-filter on the panel would make it
   the containing block for its own fixed spine. */
.nav-backdrop {
  position: fixed; inset: 0;
  background: rgba(27, 42, 36, 0.46);
  backdrop-filter: blur(2px);
  -webkit-backdrop-filter: blur(2px);
  opacity: 0; visibility: hidden; transition: opacity var(--t-mid) var(--ease); z-index: 840;
}
.nav-backdrop.is-open { opacity: 1; visibility: visible; }

@media (max-width: 900px) {
  .hero__grid, .split, .contact-grid { grid-template-columns: 1fr; }
  .hero__visual { aspect-ratio: 16 / 11; }
}

@media (max-width: 620px) {
  .communities, .steps { grid-template-columns: 1fr; }
  .qa-note { flex-direction: column; gap: 0.8rem; }

  /* On a phone a standalone CTA goes full width with its label centred. At
     this size an inline-flex button sized to its text leaves a ragged left
     edge against the copy above it, and the shorter of two stacked buttons
     reads as the less important one purely because it is narrower.

     Scoped to Terra's own .btn, and excluded inside a card: the community
     tiles size their own actions to the tile. Widget-injected markup - the
     Ylopo cards and the contact form - carries its own classes and is not
     touched by this. */
  .btn {
    width: 100%;
    justify-content: center;
    text-align: center;
  }
  .community .btn,
  .ylopo-easy-widget .btn {
    width: auto;
  }

  /* The row already wraps; making it a column stops a trailing button
     stretching oddly and keeps the gap even between them. */
  .hero__actions { flex-direction: column; align-items: stretch; }

  /* "See every listing" is a text link, not a CTA, so it is centred without
     being stretched. fit-content keeps the hover underline tight to the
     label - .text-link::after spans the element, so a full-width link would
     drag a rule across the column. */
  .text-link { display: flex; width: fit-content; margin-inline: auto; }
}

/* 17. REDUCED MOTION -------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .reveal, .mask, .line > span { opacity: 1; transform: none; clip-path: none; }
}

/* 18. INJECTED FOOTER -------------------------------------------------
   footer.js owns the footer's markup and its styles; this does not restyle
   it, it stops this stylesheet from reaching into it.

   Section 3's `p { color: var(--text-soft) }` is right for body copy on
   Terra's light grounds, but it also hits the injected footer, which is dark.
   footer.js colours most of its paragraphs explicitly and those win on
   specificity; the copyright line is the one that does not, and it inherits
   its colour from .footer-bottom. The leak overrode that inheritance and
   rendered it #5d564a on a #252526 ground - near-invisible. Restoring
   `inherit` hands it back to .footer-bottom without touching the paragraphs
   footer.js does colour. */
footer p { color: inherit; }
