/* =========================================================================
   The neighborhood section's component library.

   WHY THIS FILE EXISTS. Six pages are built by six separate sessions running at
   the same time. Without a finished component set, each one invents its own card,
   its own distance chip and its own pinned band, and the section ends up looking
   like six pages by six people. It also meant five of them had to WAIT for the
   hub to be built first, which is the whole reason they could not run in parallel.

   So the components are finished, here, before any page is written. A page-build
   session writes CONTENT and uses these class names. It does not restyle them.

   Everything here obeys DESIGN.md plus Ryan's two amendments of 2026-08-30:
   cards are allowed on content pages but carry no box, no rounded corner and no
   shadow; and the background effect is the homepage's PINNED reveal, never the
   5.5 percent parallax drift.

   The palette tokens are NOT defined here. Every page already carries them.
   ========================================================================= */

/* ---------- section rhythm -------------------------------------------------
   The homepage rhythm is lopsided on purpose: full-height image, thin strip of
   breath, full-height image. The white sections are short. `.nb-band` is the
   short one; the pinned bands supply the tall ones. Never alternate evenly. */
.nb-band{padding:clamp(64px,9vw,132px) 0}
.nb-band--tight{padding:clamp(38px,5vw,68px) 0}
/* Flat 45px, not a clamp. Ryan's call 2026-08-31 for the split band: it sits
   between two full-bleed image bands, and the breathing room they already carry
   makes the default 129.6px read as a hole rather than as rhythm. A clamp here
   would scale it back up on a wide screen, which is the opposite of the ask. */
.nb-band--thin{padding:45px 0}
/* No cap by default. A 60ch cap here was pinning every heading and its intro to
   the left third of a wide screen. The cap belongs on RUNNING PROSE, which still
   has it via `.nb-prose`, not on headings. */
.nb-head{max-width:none}
.nb-head h2{margin:0;font-size:clamp(28px,4.2vw,58px)}
.nb-head p{margin:18px 0 0;color:var(--ink-2);font-size:clamp(15px,1.3vw,17.5px)}
/* There is deliberately NO eyebrow or kicker component. DESIGN.md refuses them
   and Ryan reaffirmed it 2026-08-31: a section heading carries itself. The
   Starlight page this section's structure came from uses plain H2s throughout,
   and it reads fine. Do not add one back. */

/* ---------- the pinned band ------------------------------------------------
   The homepage's reveal, reused. The image holds still and the page rides over
   it. This is NOT parallax and must not be turned back into it.

   Three things it depends on, all learned the hard way:
     1. `.nb-roll` needs an OPAQUE ground or the image shows through the page.
     2. `body` must use `overflow-x:clip`, never `hidden`. `hidden` computes the
        other axis to `auto`, which makes body a scroll container and kills
        sticky on its children.
     3. Under prefers-reduced-motion the pin reverts to static flow. */
/* `min-height:min(460px,100svh)`, not a bare 460px, and the difference is a real
   bug found by review 2026-08-31 and reproduced at 844x390.

   `min-height` always beats a smaller `height`. A bare 460px floor therefore
   forced a 460px box inside a 390px landscape-phone viewport, and `.nb-pin-copy`
   is anchored to the BOX's bottom edge rather than the viewport's. The headline
   and its paragraph rendered 30px below the fold and stayed invisible for the
   entire time the band was stuck. Exactly the failure this page has now shipped
   three times: content written correctly and positioned where nobody scrolls.

   Wrapping the floor in min() says what was actually meant: at least 460px, but
   never taller than the viewport. */
.nb-pin{position:sticky;top:0;z-index:0;height:100svh;min-height:min(460px,100svh);
  overflow:hidden;background:var(--deep)}
.nb-pin>img{width:100%;height:100%;object-fit:cover}
.nb-pin::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(16,20,22,.42) 0%,rgba(16,20,22,0) 30%,
    rgba(16,20,22,0) 46%,rgba(16,20,22,.74) 100%)}
.nb-pin-copy{position:absolute;left:0;right:0;bottom:clamp(40px,8vh,92px);z-index:2;
  color:var(--on-dark);text-shadow:0 1px 20px rgba(10,13,15,.9)}
.nb-pin-copy h2{margin:0;font-size:clamp(26px,3.6vw,52px);max-width:20ch}
.nb-pin-copy p{margin:14px 0 0;font-size:clamp(13px,1.2vw,15.5px);
  letter-spacing:.05em;color:var(--on-dark-2);max-width:44ch}
/* Everything after a pinned band lives in one of these. */
/* Anything that follows a pinned or sticky band must carry BOTH an opaque
   ground and a higher stacking order, or the pinned image shows through it. */
.nb-roll{position:relative;z-index:1;background:var(--paper)}
@media(prefers-reduced-motion:reduce){ .nb-pin{position:relative;top:auto} }

/* ---------- the measured-distance chip -------------------------------------
   The section's one repeated non-photographic device, the way the diagonal seam
   is the homepage's. A 1px rule, never a filled pill, because a filled pill is a
   badge and this site has no badges.

   The off-peak label is part of the component and not optional. Every minute
   figure on this site is a free-flow best case, and a prospect who checks it at
   5pm and finds it oversold stops trusting every other number we publish. */
.nb-chip{display:inline-flex;align-items:baseline;gap:9px;
  padding:7px 13px;border:1px solid var(--rule-2);
  font:400 10.5px/1 Jost,sans-serif;letter-spacing:.15em;text-transform:uppercase;
  color:var(--ink-2);white-space:nowrap}
.nb-chip b{font-weight:500;color:var(--ink);font-variant-numeric:tabular-nums}
.nb-chip .nb-chip-sep{color:var(--rule-2)}
.nb-chip--dark{border-color:rgba(246,242,234,.3);color:var(--on-dark-2)}
.nb-chip--dark b{color:var(--on-dark)}

/* ---------- the card -------------------------------------------------------
   REBUILT 2026-08-31 to Starlight's layout, on Ryan's instruction, after
   measuring the reference rather than eyeballing it. His words: cards were
   against the design law, "but this time they are okay."

   What was measured on starlighthorizon.com/things-to-do-canyon-lake/ and is
   reproduced here:
     panel   rgba(12,60,70,.55) + backdrop-filter blur(9px)
     border  1px solid rgba(247,235,225,.28), no shadow
     padding 28px 24px
     layout  two independent columns, ~28px apart, RIGHT COLUMN OFFSET 56px DOWN
     image   inside the panel, not bleeding to its edge
     CTA     a real button at the BOTTOM of the card
     copy    65 to 176 characters. Short. One or two sentences.

   What is deliberately NOT reproduced, because Ryan asked for their layout in
   OUR style: their 14px corner radius (ours stay square), and their filled gold
   pill button (ours is the site's ghost control). Those two are the whole
   difference between borrowing a layout and copying a website. */
/* CAPPED AT 1180px, not the page's full 1560px measure. Ryan, 2026-08-31: the
   cards were so tall that one or two would not fit on screen. The cause was
   width, not padding. On a wide monitor the 1560px measure gave each card 766px,
   and an image at 16/9 is then 431px tall BEFORE any text. Starlight's cards,
   which this layout is copied from, are 561px wide inside a ~1200px content
   area. Matching that width is most of the fix. */
.nb-cards{display:grid;gap:clamp(20px,2.4vw,28px);grid-template-columns:1fr;
  align-items:start;max-width:1180px;margin-inline:auto}
@media(min-width:820px){
  .nb-cards{grid-template-columns:repeat(2,1fr)}
  /* The stagger. Starlight offsets its right column by exactly 56px, which is
     what stops a two-column grid reading as a table of boxes. Applied to the
     column rather than to alternate cards so the offset survives cards of
     unequal height. */
  .nb-cards > *:nth-child(even){margin-top:56px}
}

.nb-card{display:flex;flex-direction:column;text-decoration:none;color:inherit;
  border:1px solid rgba(246,242,234,.28);border-radius:0;box-shadow:none;
  /* CARD GROUND, 0.90. Ryan set this on his contrast tuner 2026-09-02, reading
     the three figures it reports against the parks band 3 backdrop: card body
     text 8.20 against a 4.5 floor, card against backdrop 3.02 against a 3.0
     floor, section heading 5.59 against a 3.0 floor. It shipped at .55.

     0.90 is past the point the tuner marks as the end of glass, and that is a
     deliberate trade rather than an oversight: the panel now reads as a tinted
     surface with the picture faintly behind it rather than as clear glass. What
     it buys is that the "card against backdrop" figure, the panel edge, is the
     binding constraint at 3.02, and every text tier on top of it clears with
     room to spare on every backdrop in the section. */
  background:rgba(22,27,29,.90);
  -webkit-backdrop-filter:blur(9px);backdrop-filter:blur(9px);
  padding:20px;
  transition:background .5s var(--ease),border-color .5s var(--ease)}
/* SCOPED UNDER `.nb-cards` DELIBERATELY, and this is a specificity fight rather
   than a style choice.

   Every card also carries `.rv`, the site-wide reveal class, and `.rv` lives in
   each page's INLINE <style>, which the browser reads AFTER this external sheet.
   `.rv` sets `transition:opacity 1.1s, transform 1.3s` and `.rv.in` sets
   `transform:none`. Both are the same specificity as a bare `.nb-card` rule, so
   source order decides and the inline one wins.

   Left unscoped, the result is a hover that does nothing: `.rv.in{transform:none}`
   cancels the lift outright, and the background and border stop animating because
   `.rv`'s transition list replaces theirs. Adding the parent takes these to 0-2-0
   and 0-3-0, which clears both.

   Verified by reading the live cascade, not by assuming it. */
/* `opacity` IS IN THIS LIST ON PURPOSE, added 2026-09-02. Raising this rule to
   0-2-0 to beat `.rv` wins the whole `transition` SHORTHAND, not just the
   properties named in it, so anything `.rv` was transitioning and this rule does
   not name simply stops transitioning. `.rv` fades the card in with
   `opacity 1.1s`. Leaving opacity out of this list therefore deleted the fade on
   every page that does not restate it, and the cards popped in at full opacity
   instead of arriving. Two of the six page builds spotted that and restated the
   list locally; four did not, which is what a silent shorthand override looks
   like. Naming it here fixes all six. */
.nb-cards .nb-card{transition:background .5s var(--ease),
  border-color .5s var(--ease),transform .45s var(--ease),
  opacity 1.1s var(--ease)}
/* THE CARD MOVES, NOT THE PICTURE INSIDE IT. Ryan's call 2026-09-01.
   The card used to scale its own image on hover, which reads as the photograph
   creeping inside a frame that is standing still: two objects where there should
   be one. Lifting the whole panel a few pixels treats the card as the single
   thing the pointer is actually over, which is what it is.

   A lift and not a scale, because the panel has a 1px hairline and square
   corners; scaling would resample that border and soften the one edge the
   design depends on. Translation moves it without touching its geometry.

   No shadow under the lift. DESIGN.md refuses shadows, and the depth cue here is
   the background deepening and the border brightening, which is quieter and
   already in the language. */
.nb-cards .nb-card:hover,.nb-cards .nb-card:focus-visible{
  background:rgba(22,27,29,.96);border-color:rgba(246,242,234,.45);
  transform:translateY(-5px)}
/* The image sits INSIDE the panel with the padding around it, which is what
   makes it read as part of a card rather than as a card that happens to have a
   picture stuck on top. */
.nb-card figure{margin:0;overflow:hidden;background:var(--limestone)}
/* A FIXED HEIGHT, not an aspect ratio. `aspect-ratio` ties the image's height to
   the card's WIDTH, so a wider screen makes every card taller and fewer fit on
   it. A viewport-relative height does the opposite: the taller the screen, the
   taller the image, so the count visible at once stays roughly constant.

   Raised from 200px to 268px on 2026-08-31. At 200 the frame was 2.9:1 and no
   crop of a 4:3 photograph survives that: Ryan rejected the whole set because
   the subjects had been sliced out. 268 gives 2.2:1, which the source frames can
   actually fill, and the cards still come to about 450px so four are visible at
   once on a 1000px viewport. The images are now pre-cropped to that exact ratio
   by `assets/neighborhood-cards.py`, so `cover` has nothing left to throw away. */
.nb-card figure img{width:100%;height:clamp(170px,24vh,240px);object-fit:cover;
  display:block}
.nb-card-body{padding:13px 0 0;display:flex;flex-direction:column;flex:1}
.nb-card h3{margin:0;font-size:clamp(19px,1.45vw,23px);color:var(--on-dark);
  transition:color .4s var(--ease)}
.nb-card:hover h3,.nb-card:focus-visible h3{color:var(--cedar)}
.nb-card p{margin:10px 0 0;font-size:14.5px;line-height:1.58;color:var(--on-dark-2);
  max-width:42ch}
.nb-card .nb-chip{margin-top:12px;align-self:flex-start}
/* `margin-top:auto` is what pins the control to the bottom of the panel however
   long the copy above it runs, so a row of cards has its buttons on one line
   instead of wherever each paragraph happened to end. */
.nb-card .nb-cta{margin-top:auto;padding-top:15px;align-self:flex-start}
/* A card with no licence-clean photograph. Typographic on purpose: the manifest
   is the only source of imagery and a wrong picture is worse than none. */
.nb-card--type .nb-rule-figure{height:clamp(170px,24vh,240px);
  border:1px solid var(--rule);display:flex;align-items:center;justify-content:center}
.nb-card--type .nb-rule-figure span{font-family:"Bodoni Moda",Georgia,serif;
  font-variation-settings:'opsz' 12,'wght' 500;
  font-size:clamp(30px,3.4vw,50px);color:var(--rule-2);line-height:1}

/* ---------- the comparison table -------------------------------------------
   Real markup, never script-built: this is the answer-engine payload and a
   crawler has to be able to read it. Hairlines only. No zebra striping, no
   boxes, no rounded corners. Tabular numerals so the columns actually align. */
.nb-table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
.nb-table{width:100%;border-collapse:collapse;font-size:15px;min-width:520px}
.nb-table caption{text-align:left;padding:0 0 20px;color:var(--ink-3);
  font:400 11px/1.6 Jost,sans-serif;letter-spacing:.15em;text-transform:uppercase}
.nb-table th,.nb-table td{padding:15px 18px 15px 0;text-align:left;
  border-bottom:1px solid var(--rule);vertical-align:baseline}
.nb-table thead th{border-bottom-color:var(--rule-2);color:var(--ink-3);
  font:500 10.5px/1 Jost,sans-serif;letter-spacing:.19em;text-transform:uppercase;
  padding-bottom:13px}
.nb-table tbody tr:last-child th,.nb-table tbody tr:last-child td{border-bottom:0}
.nb-table .num{text-align:right;font-variant-numeric:tabular-nums;
  white-space:nowrap;padding-right:0}
.nb-table tbody th{font-weight:400;color:var(--ink)}
.nb-table td{color:var(--ink-2)}

/* ---------- the off-peak notice --------------------------------------------
   A visible design element, never fine print. It is the honest caveat that
   protects every number on the site. */
.nb-offpeak{display:flex;gap:14px;align-items:flex-start;
  border-left:1px solid var(--cedar);padding:3px 0 3px 18px;
  margin:0 0 30px;max-width:56ch;
  font-size:14px;line-height:1.65;color:var(--ink-2)}

/* ---------- the scenic backdrop --------------------------------------------
   RESTORED 2026-08-31 after I deleted it by accident. A later edit sliced the
   stylesheet between two section markers to replace the link stack, and this
   block sat between them, so it went with it. The symptom was the band losing
   its height, its ground and its stacking order all at once: the image fell back
   to `object-fit:fill` at its intrinsic 498px, and the page's pinned hero showed
   through where the backdrop should have been.

   The lesson, since I have now made this mistake once: slicing a stylesheet
   between two `s.index()` markers deletes EVERYTHING between them, including
   whatever was appended there since. Replace a named block, do not slice a range.

   This is the effect Ryan pointed at on the Starlight page: a scenic photograph
   staying put behind a card grid while it scrolls. NOT `background-attachment:
   fixed`, which is how Starlight does it and which is unreliable on iOS Safari.
   A sticky image layer gives the same result everywhere. */
/* CHANGED 2026-08-31 from a sticky layer to a clipped fixed one, and the
   difference is exactly what Ryan described.

   STICKY was wrong. A sticky element scrolls WITH the page until it reaches its
   `top` offset and only then pins, so entering the section you watched the image
   travel upward, catch at the top, and stop. The bottom edge behaved correctly
   because the section was already clipping it; the top edge did not.

   FIXED is the behaviour he wanted: the image is locked to the viewport and the
   section is a window onto it. Scrolling down, the window opens from the bottom
   of the frame upward, so the first thing you see is the bottom of the picture
   and it uncrops as you go. That is `background-attachment: fixed` semantics
   without the property itself, which is unreliable on iOS Safari.

   `clip-path:inset(0)` is what confines the fixed layer to this section. It also
   creates a stacking context, so `isolation:isolate` is no longer needed. */
.nb-backdrop{position:relative;z-index:1;background:var(--deep);
  clip-path:inset(0)}
.nb-backdrop>.nb-backdrop-img{position:fixed;inset:0;z-index:0;overflow:hidden;
  pointer-events:none}
.nb-backdrop>.nb-backdrop-img img{width:100%;height:100%;object-fit:cover}
/* Flat .20, computed rather than guessed: this photograph's mean luminance is
   115 and the scrim colour's is 23, so .20 lands at 97, the band Starlight's own
   backdrop reaches under its rgba(12,60,70,.55) over a much brighter frame.
   THE HEADING IS PROTECTED SEPARATELY, on `.nb-head` below, because this layer
   is sticky and its scrim is pinned to the viewport while the heading scrolls
   through it. */
.nb-backdrop>.nb-backdrop-img::after{content:"";position:absolute;inset:0;
  background:rgba(20,24,26,.20)}
.nb-backdrop>.wrap{position:relative;z-index:1;
  padding-top:clamp(70px,9vw,130px);padding-bottom:clamp(80px,10vw,150px)}
.nb-backdrop .nb-card h3{color:var(--on-dark)}
.nb-backdrop .nb-card p{color:var(--on-dark-2)}
.nb-backdrop .nb-cta{color:var(--on-dark-2)}
.nb-backdrop .nb-head{max-width:none;text-align:center;margin:0 auto;
  padding:26px clamp(20px,4vw,60px);
  background:radial-gradient(ellipse 78% 150% at 50% 50%,
    rgba(20,24,26,.68) 0%, rgba(20,24,26,.52) 42%, rgba(20,24,26,0) 76%)}
.nb-backdrop .nb-head h2{color:var(--on-dark);
  font-family:Jost,system-ui,sans-serif;font-weight:500;
  font-variation-settings:normal;
  text-transform:uppercase;letter-spacing:.19em;
  font-size:clamp(24px,3.1vw,46px);line-height:1.18;
  text-shadow:0 1px 22px rgba(10,13,15,.85)}
@media(prefers-reduced-motion:reduce){
  /* A locked background is disorienting for the same people a parallax is, so it
     reverts to an ordinary band in normal flow. `clip-path` comes off with it or
     the now-relative layer is clipped to nothing. */
  .nb-backdrop{clip-path:none}
  .nb-backdrop>.nb-backdrop-img{position:relative;inset:auto;height:52svh}
}

/* ---------- the FAQ, as an accordion ---------------------------------------
   Ryan 2026-08-31, asked for a disclosure accordion "as long as that's still
   good for SEO and AEO". It is, but only because of HOW this is built, and the
   how is the whole point:

   **Native `<details>` and `<summary>`, with no JavaScript.** Every answer is in
   the DOM at page load, fully parsed, whether the row is open or shut. A crawler
   and an answer engine read the document, not the pixels, so nothing is hidden
   from them. Google indexes `<details>` content normally and has said so.

   What WOULD have cost us the ranking is the other way of building this: an
   accordion whose answers are injected by script on click, or one that swaps
   text in and out of the DOM. Then the content genuinely is not there when the
   page is parsed. If anyone ever "improves" this with a JavaScript accordion
   library, that is the regression.

   Two further reasons native beats a custom widget here: it works with
   JavaScript off, and the browser's own find-in-page opens a closed row when the
   match is inside it, which no hand-rolled version does for free.

   The `FAQPage` structured data is unchanged and still carries all five answers
   in full. */
.nb-faq{max-width:none}
.nb-faq-item{border-top:1px solid var(--rule-2)}
.nb-faq-item:last-child{border-bottom:1px solid var(--rule-2)}
.nb-faq-item summary{list-style:none;cursor:pointer;
  display:flex;align-items:baseline;gap:clamp(18px,3vw,40px);
  padding:26px 0;-webkit-tap-highlight-color:transparent}
.nb-faq-item summary::-webkit-details-marker{display:none}
.nb-faq-item summary h3{margin:0;flex:1;
  font-size:clamp(18px,1.55vw,26px);max-width:none;
  transition:color .4s var(--ease)}
.nb-faq-item summary:hover h3{color:var(--cedar)}
.nb-faq-item summary:focus-visible{outline:2px solid var(--cedar);outline-offset:3px}
/* A drawn 1px chevron. DESIGN.md refuses glyphs, and the default disclosure
   triangle is a glyph belonging to no design system, which is why it is
   suppressed above rather than restyled. */
.nb-faq-item summary .nb-chev{flex:none;width:15px;height:9px;overflow:visible;
  fill:none;stroke:currentColor;stroke-width:1;vector-effect:non-scaling-stroke;
  opacity:.6;align-self:center;
  transition:transform .45s var(--ease),opacity .3s var(--ease)}
.nb-faq-item[open] summary .nb-chev{transform:rotate(180deg);opacity:1}
.nb-faq-item[open] summary h3{color:var(--cedar)}
.nb-faq-answer{padding:0 0 30px}
.nb-faq-answer p{margin:0;color:var(--ink-2);max-width:none;
  font-size:clamp(16px,1.25vw,17.5px);line-height:1.7}
.nb-faq-answer p+p{margin-top:12px}
@media(prefers-reduced-motion:reduce){
  .nb-faq-item summary .nb-chev,.nb-faq-item summary h3{transition:none}
}

/* ---------- the sibling link row -------------------------------------------
   Ryan 2026-08-31: across the screen on desktop rather than stacked. It was a
   vertical list hugging the left of a 1560px measure with nothing beside it.

   Still stacked on a phone, where three columns would give each link about a
   hundred pixels and force the labels to wrap. */
/* auto-fit rather than a fixed three. The row carried three links and now
   carries five; a hard `repeat(3,1fr)` left the last two stranded on a second
   row with a hole beside them. auto-fit resolves to five across at 1432px and
   folds down on its own as the viewport narrows, so the count can change again
   without this rule needing to. */
.nb-stack{display:grid;grid-template-columns:1fr;max-width:none;
  margin-top:clamp(40px,4.5vw,64px)}
@media(min-width:820px){
  .nb-stack{grid-template-columns:repeat(auto-fit,minmax(230px,1fr));
    column-gap:clamp(20px,2.2vw,40px)}
}
.nb-stack a{display:flex;align-items:center;justify-content:space-between;gap:24px;
  padding:22px 0;text-decoration:none;border-top:1px solid var(--rule-2);
  font-size:clamp(16px,1.15vw,20px);font-family:"Bodoni Moda",Georgia,serif;
  font-variation-settings:'opsz' 12,'wght' 500;letter-spacing:-.012em;
  transition:color .4s var(--ease)}
/* On one row every link needs its own closing rule; stacked, only the last does,
   or the shared borders double up. */
.nb-stack a{border-bottom:1px solid var(--rule-2)}
@media(max-width:819px){
  .nb-stack a:not(:last-child){border-bottom:0}
}
.nb-stack a:hover{color:var(--cedar)}
.nb-stack .nb-arrow{width:36px;height:9px;flex:none;opacity:.5;
  transition:transform .5s var(--ease),opacity .4s var(--ease)}
.nb-stack a:hover .nb-arrow{transform:translateX(9px);opacity:1}

/* There is deliberately NO pull-quote or testimonial component. 20 Tate has no
   tenant reviews yet (Ryan, 2026-08-31). Starlight's page carries a real Google
   review with a name, a rating and a date, and it does real work there; we have
   nothing true to put in one. An empty component is an invitation to invent a
   quote, so it is not here. If reviews exist later, add it then. */

/* ---------- photo credits under a grid -------------------------------------
   Starlight prints its Creative Commons credits directly under the card grid
   that uses them rather than only in the footer. Every one of our images needs
   attribution too, so this is the honest place for it. */
.nb-credits{margin-top:clamp(34px,4vw,56px);padding-top:20px;
  border-top:1px solid var(--rule);
  font-size:11.5px;line-height:1.8;color:var(--ink-3);max-width:78ch}
.nb-backdrop .nb-credits{border-top-color:rgba(246,242,234,.18);color:var(--on-dark-2)}

/* ---------- the distance dial ----------------------------------------------
   Unique to the hub, but it lives here because the tab row is worth reusing.
   Modelled on the gallery's tab bar so the two read as the same site. */
.nb-dial-tabs{display:flex;flex-wrap:wrap;gap:0;margin:0 0 clamp(26px,3vw,40px);
  border-bottom:1px solid var(--rule)}
.nb-dial-tab{appearance:none;background:none;border:0;cursor:pointer;
  padding:14px 20px 14px 0;margin-right:8px;min-height:44px;
  font:400 11px/1 Jost,sans-serif;letter-spacing:.19em;text-transform:uppercase;
  color:var(--ink-3);position:relative;
  transition:color .35s var(--ease)}
.nb-dial-tab::after{content:"";position:absolute;left:0;right:8px;bottom:-1px;height:1px;
  background:var(--cedar);transform:scaleX(0);transform-origin:left;
  transition:transform .45s var(--ease)}
.nb-dial-tab:hover{color:var(--ink-2)}
.nb-dial-tab[aria-selected="true"]{color:var(--ink)}
.nb-dial-tab[aria-selected="true"]::after{transform:scaleX(1)}
/* Rows are hidden by the filter, never removed. Every row ships in the markup so
   a crawler and an answer engine see the whole table, which is the entire point
   of this element. With JavaScript off the tabs do nothing and all rows show,
   which is the correct fallback rather than a broken one. */
.nb-table tr[hidden]{display:none}
@media(prefers-reduced-motion:reduce){
  .nb-dial-tab,.nb-dial-tab::after{transition:none}
}

/* ---------- the page hero ---------------------------------------------------
   Ported from `floor-plans.html` on Ryan's instruction 2026-08-31 so every
   neighborhood page opens the way the rest of the site does: a half-height band
   with the title centred between two hairlines.

   It lives here rather than in each page because all six need it. `floor-plans`
   keeps its own copy under `.fp-hero`; this stylesheet is only ever loaded on
   neighborhood pages, so the two cannot collide.

   Same pin mechanism as the homepage. The band stays put at the top of the
   viewport and `.nb-roll` scrolls up over it. Depends on `.nb-roll` carrying an
   opaque ground, on `body` using overflow-x:clip rather than hidden, and on the
   pin being off under prefers-reduced-motion. */
.nb-hero{position:sticky;top:0;z-index:0;height:clamp(330px,52svh,600px);
  overflow:hidden;background:var(--deep)}
/* 35%, not centred, and measured against the frame rather than guessed. The
   photograph is 1500x900; at this band's aspect ratio cover keeps about 55% of
   the height, and centring that window starts it below the tower's finial. 35%
   lifts it to keep the roof, the ROUND ROCK lettering, the tank and the sun,
   and gives up only the parked cars along the bottom, which the title's scrim
   would darken anyway. */
.nb-hero img{width:100%;height:100%;object-fit:cover;object-position:center 35%}
/* A portrait crop keeps only half the frame's width. Centred loses the sun
   entirely; 40% keeps the tower whole and the sun's edge with it. */
@media(max-width:719px){ .nb-hero img{object-position:40% 35%} }
/* Dark at the top so the wordmark holds over a bright sky, dark at the bottom so
   the title does. The middle is left alone: a full scrim would flatten the
   photograph, which is the thing the band is for. */
.nb-hero::after{content:"";position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(180deg,rgba(16,20,22,.52) 0%,rgba(16,20,22,0) 28%,
    rgba(16,20,22,0) 44%,rgba(16,20,22,.80) 100%)}
@media(prefers-reduced-motion:reduce){ .nb-hero{position:relative;top:auto} }

/* The title is NOT pinned. It sits in a zero-height block in normal flow right
   after the hero and is anchored upward from it, so it scrolls away like any
   other content while the photograph stays put behind. No JavaScript. */
.nb-hero-stage{position:relative;height:0;z-index:2}
.nb-hero-copy{position:absolute;left:0;right:0;bottom:clamp(30px,5vh,60px);z-index:3;
  display:flex;align-items:center;gap:clamp(14px,3vw,42px);
  color:var(--on-dark);text-shadow:0 1px 22px rgba(10,13,15,.92)}
.nb-hero-copy .r{flex:1 1 auto;height:1px;background:currentColor;opacity:.5}
.nb-hero-copy h1{margin:0;flex:0 0 auto;font-size:clamp(24px,4.2vw,58px);
  white-space:nowrap}
/* "Explore The Neighborhood" is twice the length of "Floor Plans", and nowrap
   pushes it off a phone. Below 720px the rules are dropped and the title is
   allowed to wrap and centre, which is the only way it fits without shrinking
   the type to something apologetic. */
@media(max-width:719px){
  .nb-hero-copy{gap:0;justify-content:center;text-align:center}
  .nb-hero-copy .r{display:none}
  .nb-hero-copy h1{white-space:normal;flex:0 1 auto;max-width:14ch;line-height:1.08}
}
/* floor-plans puts <main> inside the roll; this page wraps the whole thing in
   <main> and puts rolls inside it, so a `.nb-roll > main` selector would match
   nothing. The breathing room the title needs goes on the roll that follows the
   hero instead. */
.nb-hero-stage + .nb-roll{padding-top:clamp(34px,5vh,58px)}

/* ---------- the footer, and why it needs a stacking order -------------------
   Flagged by review 2026-08-31 as fragile rather than broken, and worth closing
   anyway. `.nb-pin` is sticky inside <main>, and neither <main> nor <body>
   establishes a containing block for it, so it stays sticky-reachable for the
   whole remaining height of <main>. It is hidden today only because the final
   `.nb-roll` happens to be long and opaque.

   <footer> is a non-positioned block, so in paint order it sits BEHIND any
   positioned box in the same stacking context, including the pin. Trim enough
   content out of that last roll and the pinned photograph would render on top of
   the footer. Giving the footer its own positive stacking order removes the
   dependency on content length entirely; it already carries an opaque ground. */
footer{position:relative;z-index:1}

/* ---------- the split band: text beside an image -----------------------------
   Ryan, 2026-08-31: the orientation paragraphs needed a picture beside them
   rather than a column of type alone on a dark field.

   The text column keeps the 60ch measure DESIGN.md caps body copy at, so the
   image takes the remainder rather than the two splitting evenly. An even split
   on a 1728px screen would push the measure past 90 characters, which is the
   thing the cap exists to prevent.

   `align-items:start` and a sticky-free image: this band is short enough that
   pinning it would fight the two full-bleed bands either side of it. */
.nb-split{display:grid;gap:clamp(30px,4vw,68px);grid-template-columns:1fr;
  align-items:start;max-width:1320px}
@media(min-width:900px){
  .nb-split{grid-template-columns:minmax(0,60ch) minmax(0,1fr)}
}
.nb-split-copy p{margin:0 0 22px;color:var(--ink-2);
  font-size:clamp(15px,1.3vw,17.5px);line-height:1.68}
.nb-split-copy p:last-child{margin-bottom:0}
.nb-split figure{margin:0;overflow:hidden;background:var(--limestone)}
.nb-split figure img{width:100%;height:100%;display:block;object-fit:cover;
  /* A minimum so the picture stays a picture on a narrow laptop, and a cap so it
     cannot outgrow the prose it is meant to sit beside. */
  min-height:clamp(240px,34vh,320px);max-height:clamp(300px,52vh,560px)}
.nb-split figcaption{margin-top:12px;font-size:11.5px;line-height:1.7;
  color:var(--ink-3)}

/* ---------- photo credits ---------------------------------------------------
   Ryan asked twice for the credit block to come off the page, first from the
   card band and then from the footer. It cannot simply be deleted: every sourced
   photograph here is CC BY or CC BY-SA, and naming the author is a LICENCE
   CONDITION. Deleting it would mean using the images outside the terms they were
   obtained under.

   So the credits moved to their own page and the footer carries a link. This is
   the ordinary way sites handle it and it satisfies the attribution requirement,
   which asks that credit be given in a manner reasonable to the medium, not that
   it sit under the pictures. */
.credit-link{display:inline-block;margin-top:12px;font-size:11px;
  letter-spacing:.14em;text-transform:uppercase;color:var(--on-dark-2);
  text-decoration:none;border-bottom:1px solid rgba(246,242,234,.28);
  padding-bottom:2px;opacity:.72;transition:opacity .35s var(--ease)}
.credit-link:hover{opacity:1}

/* ---------- full-width content blocks ---------------------------------------
   Added 2026-08-31. Everything below the card band was constrained to a narrow
   left-hand column on a wide screen. These fill the measure instead.

   The prose does NOT simply run edge to edge: at 1728px a single column would be
   about 140 characters a line, roughly double what is comfortable, and DESIGN.md
   caps body measure at 60ch for exactly that reason. Multiple columns fill the
   width while every LINE stays the length it should be, which is the typographic
   answer rather than the lazy one. */
.nb-points{list-style:none;margin:clamp(30px,3.4vw,48px) 0 0;padding:0;
  display:grid;gap:0 clamp(28px,3.4vw,56px);grid-template-columns:1fr}
@media(min-width:760px){ .nb-points{grid-template-columns:repeat(2,1fr)} }
@media(min-width:1240px){ .nb-points{grid-template-columns:repeat(3,1fr)} }
.nb-points li{border-top:1px solid var(--rule);padding:18px 0;
  font-size:15.5px;line-height:1.62;color:var(--ink-2)}
.nb-points li b{color:var(--ink);font-weight:400}

/* Running prose, filling the width in columns rather than one long measure. */
.nb-prose{margin-top:clamp(26px,3vw,42px);
  column-gap:clamp(34px,4vw,72px);column-rule:1px solid var(--rule)}
@media(min-width:900px){ .nb-prose{columns:2} }
@media(min-width:1500px){ .nb-prose{columns:3} }
.nb-prose p{margin:0 0 20px;color:var(--ink-2);
  font-size:clamp(15px,1.3vw,17px);line-height:1.68;
  /* Stops a paragraph being split across a column break mid-sentence. */
  break-inside:avoid}
.nb-prose p:last-child{margin-bottom:0}


/* ---------- the timeline ----------------------------------------------------
   Added 2026-08-31. Ryan wanted the Saturday narrative as a timeline with
   Morning, Afternoon and Evening as disclosure groups, modelled on an itinerary
   layout he supplied.

   Native `<details>` again, for the same reason the FAQ uses it: every entry is
   in the DOM whether the group is open or shut, so nothing is hidden from a
   crawler or an answer engine, it works with JavaScript off, and find-in-page
   opens a closed group on a match. */
.nb-tl-group{border-top:1px solid rgba(246,242,234,.22)}
.nb-tl-group:last-of-type{border-bottom:1px solid rgba(246,242,234,.22)}
.nb-tl-group>summary{list-style:none;cursor:pointer;display:flex;
  align-items:center;gap:20px;padding:22px 0;
  -webkit-tap-highlight-color:transparent}
.nb-tl-group>summary::-webkit-details-marker{display:none}
.nb-tl-group>summary h3{margin:0;flex:1;color:var(--on-dark);
  font-family:Jost,system-ui,sans-serif;font-weight:500;
  font-variation-settings:normal;text-transform:uppercase;
  letter-spacing:.19em;font-size:clamp(15px,1.35vw,19px);
  transition:color .4s var(--ease)}
.nb-tl-group>summary:hover h3{color:var(--cedar)}
.nb-tl-group[open]>summary h3{color:var(--cedar)}
.nb-tl-group>summary:focus-visible{outline:2px solid var(--cedar);outline-offset:3px}
.nb-tl-group>summary .nb-chev{flex:none;width:15px;height:9px;overflow:visible;
  fill:none;stroke:currentColor;stroke-width:1;vector-effect:non-scaling-stroke;
  color:var(--on-dark-2);
  transition:transform .45s var(--ease)}
.nb-tl-group[open]>summary .nb-chev{transform:rotate(180deg)}

.nb-tl{list-style:none;margin:0;padding:0 0 24px}
.nb-tl li{display:flex;gap:clamp(14px,1.8vw,26px);align-items:flex-start;
  padding:19px 0;border-top:1px solid rgba(246,242,234,.10)}
.nb-tl li:first-child{border-top:0}
/* A drawn clock, not an emoji or an icon font. DESIGN.md refuses glyphs and
   every mark on this site is a 1px path. */
.nb-tl-clock{flex:none;width:19px;height:19px;margin-top:4px;overflow:visible;
  fill:none;stroke:currentColor;stroke-width:1;vector-effect:non-scaling-stroke;
  color:var(--cedar);opacity:.85}
.nb-tl-when{flex:none;width:clamp(96px,8.5vw,142px);color:var(--on-dark);
  font:400 14px/1.5 Jost,sans-serif;letter-spacing:.14em;text-transform:uppercase;
  margin-top:4px}
.nb-tl-what{flex:1;color:var(--on-dark-2);font-size:clamp(18px,1.5vw,21px);
  line-height:1.62;max-width:none}
.nb-tl-what b{color:var(--on-dark);font-weight:400}
.nb-tl-note{margin:10px 0 0;font-size:clamp(16px,1.3vw,18.5px);line-height:1.6;
  color:var(--on-dark-2)}
@media(max-width:620px){
  .nb-tl li{flex-wrap:wrap}
  .nb-tl-when{width:auto}
  .nb-tl-what{flex:1 1 100%;margin-top:4px}
}
@media(prefers-reduced-motion:reduce){
  .nb-tl-group>summary .nb-chev,.nb-tl-group>summary h3{transition:none}
}

/* A deeper scrim variant. Ryan asked for 25% on the community photograph, where
   the type sitting over it is denser than the card band's. */
.nb-backdrop--25>.nb-backdrop-img::after{background:rgba(20,24,26,.25)}

/* ---------- the timeline's glass panel --------------------------------------
   Ryan 2026-08-31: the timeline was unreadable over the community photograph.
   He was right and the numbers say why. That frame has a mean luminance of 167,
   bright limestone and sky, and after the .25 scrim it sits at 131. Against the
   near-white type that is a contrast ratio of 3.37:1 AT THE MEAN, which fails
   the 4.5 floor for body text, and the timeline's copy is 15.5px body text. Over
   the white porch columns it is far worse than the mean.

   A panel at .68 brings it to 10.2:1, with headroom for the bright patches
   rather than sitting on the line.

   Same language as the cards, one step deeper: no radius, no shadow, a single
   hairline, and the blur that makes it read as glass rather than as a slab. */
/* .80, not .68. CORRECTED 2026-08-31 after review caught two errors in my own
   maths. I validated the panel against `--on-dark`, the near-white token, but
   the timeline's body copy is `--on-dark-2` and the caption tier is that token
   again at .72 opacity. Measured from the live computed styles rather than
   assumed: at .68 the body copy came to 4.62:1 over the photograph's brightest
   regions, passing by hundredths rather than with the headroom I claimed, and
   THE CAPTION TIER CAME TO 2.99:1, which fails outright and carries real content
   including the parks-bond caveat.

   At .80 both clear it with margin: about 6.4:1 for each. The caption's opacity
   comes off in the same change, since dimming a tier that was already the
   dimmest token was the actual cause. */
.nb-tl-panel{background:rgba(20,24,26,.80);
  -webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);
  border:1px solid rgba(246,242,234,.20);
  padding:clamp(20px,2.4vw,38px) clamp(20px,2.6vw,44px)}
/* The group rules sit inside a panel now, so their own top border on the first
   child would double against the panel's edge. */
.nb-tl-panel>.nb-tl-group:first-of-type{border-top:0}
.nb-tl-panel>.nb-tl-group:last-of-type{border-bottom:0}
/* Blur is expensive over a fixed layer on some mobile GPUs. Where it is not
   supported the panel simply sits at a higher alpha, which is the safe direction
   to fail in: more contrast, not less. */
@supports not ((backdrop-filter:blur(1px)) or (-webkit-backdrop-filter:blur(1px))){
  .nb-tl-panel{background:rgba(20,24,26,.86)}
  /* Same reasoning for the card. At a .90 ground the blur is doing very little
     work anyway, so the step up is small. */
  .nb-card{background:rgba(22,27,29,.94)}
}

/* ---------- drawn marks, and the card CTA button ----------------------------
   BOTH RESTORED 2026-08-31, and both were casualties of one mistake: an edit
   that replaced a range of the stylesheet by slicing between two section
   markers took out everything that had been appended between them.

   `.nb-arrow` is the base rule every drawn arrow depends on. Without it the SVG
   inherits `fill:currentColor` and no stroke, so the path paints as a small
   filled wedge instead of a 1px line with a head. That is exactly what Ryan saw
   on the sibling links. The per-context rules setting size and opacity all
   survived, which is why the arrows sat in the right place and simply looked
   wrong, and why this took a second look to spot.

   `.nb-cta` is worse: without it the card CTAs have been rendering as plain text
   rather than the ghost buttons that were approved, and they shipped that way.

   The lesson, now learned twice: replace a NAMED block, never slice a range
   between two `s.index()` markers. A range takes whatever was appended into it
   since, and the loss is silent. */
.nb-arrow{overflow:visible;fill:none;stroke:currentColor;stroke-width:1;
  vector-effect:non-scaling-stroke}

.nb-cta{display:inline-flex;align-items:center;gap:11px;
  padding:11px 19px;border:1px solid rgba(246,242,234,.42);
  font:400 10.5px/1 Jost,sans-serif;letter-spacing:.19em;text-transform:uppercase;
  color:var(--on-dark);background:none;
  transition:background .5s var(--ease),color .5s var(--ease),border-color .5s var(--ease)}
.nb-card:hover .nb-cta,.nb-card:focus-visible .nb-cta{
  background:var(--on-dark);color:var(--on-invert);border-color:var(--on-dark)}
.nb-cta .nb-arrow{width:24px;height:8px;transition:transform .5s var(--ease)}
.nb-card:hover .nb-cta .nb-arrow{transform:translateX(6px)}
/* An outbound link says so: a small drawn corner rather than a word, since the
   label already carries the destination's name. */
.nb-cta[data-ext]::after{content:"";width:7px;height:7px;margin-left:-4px;
  border-top:1px solid currentColor;border-right:1px solid currentColor;
  transform:rotate(-45deg) translateY(-1px)}
@media(prefers-reduced-motion:reduce){
  .nb-cta,.nb-cta .nb-arrow,.nb-stack .nb-arrow{transition:none}
  /* The lift comes off; the background and border change stays, so the card
     still answers the pointer without anything moving. */
  .nb-cards .nb-card{transition:background .5s var(--ease),
    border-color .5s var(--ease)}
  .nb-cards .nb-card:hover,.nb-cards .nb-card:focus-visible{transform:none}
}

/* The community backdrop's heading scrim, 40% more opaque than the default.
   Ryan 2026-08-31, and scoped to THIS backdrop rather than applied to both.
   The two photographs are not equally bright: the creek frame behind "Where to
   Start" measures 115 mean luminance, the community frame here measures 168.
   Pushing both to .95 would leave the darker one reading as a solid black bar
   rather than as the picture deepening behind the words, which is the same
   mistake as matching Starlight's alpha over a different photograph. */
.nb-backdrop--25 .nb-head{
  background:radial-gradient(ellipse 78% 150% at 50% 50%,
    rgba(20,24,26,.95) 0%, rgba(20,24,26,.73) 42%, rgba(20,24,26,0) 76%)}

/* ---------- fair housing marks ---------------------------------------------
   The official artwork, supplied by Ryan 2026-08-31 and knocked out of its white
   background by `assets/fairhousing-prep.py`. It replaced a drawn approximation
   I had made, and replacing it was right: this is a regulatory mark, and a
   hand-drawn lookalike of one is wrong in the same way a redrawn trademark is.

   It is a PNG rather than an SVG because the source is raster. That means the
   colour is baked in, so it cannot follow a CSS token; the prep script bakes
   `--on-dark` and the opacity below does the rest. If the palette ever changes,
   re-run the script rather than filtering the image in CSS. */
.fair-housing{display:flex;align-items:center;justify-content:center;
  margin-top:28px}
.fair-housing img{width:clamp(70px,7.5vw,89px);height:auto;opacity:.82;
  transition:opacity .35s var(--ease)}
.fair-housing img:hover{opacity:1}

/* ---------- the credits page ------------------------------------------------ */
.cr-page{padding:clamp(150px,20vh,240px) 0 clamp(80px,10vw,140px)}
.cr-page h1{margin:0;font-size:clamp(34px,5.6vw,72px)}
.cr-intro{margin:26px 0 0;max-width:70ch;color:var(--ink-2);
  font-size:clamp(15px,1.3vw,17px);line-height:1.7}
.cr-list{list-style:none;margin:clamp(34px,4vw,56px) 0 0;padding:0}
.cr-list li{display:flex;gap:clamp(16px,2.4vw,40px);align-items:baseline;
  justify-content:space-between;flex-wrap:wrap;
  border-top:1px solid var(--rule);padding:18px 0}
.cr-list li:last-child{border-bottom:1px solid var(--rule)}
.cr-what{flex:1 1 30ch;color:var(--ink-2);font-size:15px;line-height:1.6}
.cr-lic{flex:none;font:400 11px/1.6 Jost,sans-serif;letter-spacing:.15em;
  text-transform:uppercase;color:var(--ink-3);white-space:nowrap}
.cr-lic a{color:inherit}
