/* ===========================================================================
   Alexandra Marie Studio — shared layer
   ---------------------------------------------------------------------------
   Scope, deliberately narrow:
     1. design tokens (mirroring _ds/tokens.css)
     2. base element styles
     3. site chrome: skip link, header, footer
     4. the handful of components that exist because the static site needs them
        and the prototype did not have them

   Everything page-specific lives in css/pages/<page>.css, generated by
   build/port_captured.py from the prototype's own declarations. Do not add
   page styles here: a rule here can out-specify a ported class and silently
   change a page. Type and spacing are changed from the tokens below.
   Type tokens (--serif/--sans) live in fonts.css and only there.
   =========================================================================== */
@layer base {

:root{
  /* color */
  --paper:#F4F0E8;        /* backgrounds */
  --cream:#EAE3D6;        /* secondary surfaces */
  --ink:#3B3128;          /* primary text — never black */
  --brown:#494134;        /* secondary text, accents */
  --gray:#6B6459;         /* muted text */
  --slate:#6B6459;        /* alias the project pages use; same value as --gray */
  --line:#DCD4C5;         /* borders, dividers */
  --mist:#9DACB5;         /* rare accent only */
  --warm-gray:#55483C;    /* secondary body text on cream/dark */

  /* type scale */
  --h1:clamp(2.6rem, 6vw, 4.4rem);
  --h2:clamp(1.9rem, 3.6vw, 2.8rem);
  --h3:1.3rem;
  --h4:1.25rem;
  --body:0.9375rem;
  --small:0.8125rem;
  --eyebrow:0.6875rem;

  /* spacing — whitespace is part of the brand */
  --space-1:8px; --space-2:16px; --space-3:28px; --space-4:44px;
  --space-5:72px; --space-6:112px;
  --section-gap:clamp(96px, 13vw, 180px);
  --margin-page:clamp(24px, 6vw, 96px);
  --measure:34em;
  --container:1280px;

  /* motion */
  --fade-dur:700ms;
  --fade-dist:16px;
  --ease-out:cubic-bezier(.25,.46,.45,.94);
  --hero-scale-from:1.03;
  --hero-scale-dur:6.5s;
  --lift:-2px;
}

/* --- base ---------------------------------------------------------------- */
*{box-sizing:border-box;margin:0}
html{scroll-behavior:smooth}
body{background:var(--paper);color:var(--ink);font-family:var(--sans);
     font-size:var(--body);line-height:1.7;-webkit-font-smoothing:antialiased}
h1,h2,h3,h4{font-family:var(--serif);font-weight:400;letter-spacing:-0.01em}
h1{font-size:var(--h1);line-height:1.08}
h2{font-size:var(--h2);line-height:1.15}
h3{font-size:var(--h3);line-height:1.3}
h4{font-size:var(--h4);line-height:1.4}
a{color:var(--ink)}
section[id]{scroll-margin-top:100px}

@keyframes fadeUp{from{opacity:0;transform:translateY(var(--fade-dist))}to{opacity:1;transform:none}}
@keyframes softIn{from{opacity:0}to{opacity:1}}
@media(prefers-reduced-motion:reduce){
  *{animation:none!important;transition:none!important;scroll-behavior:auto!important}
}

/* --- skip link — keyboard users land here first -------------------------- */
.skip{position:absolute;left:-9999px;top:0;z-index:100;background:var(--ink);
      color:var(--paper);padding:12px 20px;text-decoration:none;font-size:var(--small)}
.skip:focus{left:0}

/* --- hero video: poster still for prefers-reduced-motion ------------------ */
.hero-still{display:none}
@media(prefers-reduced-motion:reduce){
  .hero-video{display:none}
  .hero-still{display:block}
}

}

@layer overrides {

/* --- disclosure: FAQ answers and service descriptions --------------------
   The prototype's accordions are single-open and JS-driven, so only one panel
   is ever in the DOM. These are native <details> so every word ships in the
   HTML while the page still renders collapsed, as the design does. The ported
   classes supply all the visual styling; these rules only remove the native
   marker and swap the +/- indicator. */
.faq-item summary,.svc-item summary{list-style:none;cursor:pointer}
.faq-item summary::-webkit-details-marker,
.svc-item summary::-webkit-details-marker{display:none}
.faq-item[open] summary span:last-child,
.svc-item[open] summary span:last-child{font-size:0}
.faq-item[open] summary span:last-child::after,
.svc-item[open] summary span:last-child::after{content:"\2013";font-size:1.1rem}
.faq-answer,.svc-answer{padding:0 0 26px;max-width:var(--measure)}
.faq-answer p,.svc-answer p{color:var(--brown);margin-bottom:14px}
.faq-answer p:last-child,.svc-answer p:last-child{margin-bottom:0}

/* --- pre-designed home packages ------------------------------------------
   The prototype shows one tier at a time behind a selector; all three are
   rendered so the copy is in the HTML. */
.tier-list{list-style:none;padding:0;margin:0 0 var(--space-3);
           display:flex;flex-direction:column;gap:var(--space-3)}
.tier{border-top:1px solid var(--line);padding-top:var(--space-2)}
.tier:first-child{border-top:0;padding-top:0}
.tier-name{font-family:var(--serif);font-size:var(--h4);margin:0 0 4px}
.tier ul{margin:0;padding-left:1.1em;font-size:var(--small);color:var(--brown);
         display:flex;flex-direction:column;gap:6px}
.tier-upgrade{font-size:var(--small);color:var(--gray);margin:12px 0 0;line-height:1.6}
/* once JS has collapsed the list to a single tier the dividers between them are
   meaningless, so the visible tier sits flush under the selector */
.tier-list[data-collapsed]{gap:0}
.tier-list[data-collapsed] .tier{border-top:0;padding-top:0}

/* --- Schedule a Consultation: two-column split (client request) -----------
   Deliberate deviation from the prototype, which stacks these in a single
   720px column. Collapses at the design system's existing 860px breakpoint,
   the same one the ported [data-mq] rules use. */
main [data-split="contact"]{display:grid;
                       grid-template-columns:minmax(260px,0.85fr) minmax(0,1.15fr);
                       gap:var(--space-4) var(--space-5);align-items:start;
                       max-width:var(--container);width:100%}
.contact-intro{max-width:38ch}
@media(max-width:860px){
  main [data-split="contact"]{grid-template-columns:1fr;gap:var(--space-4)}
}
/* HubSpot native embed container. Spacing only, from existing tokens - HubSpot
   renders and styles the fields inside. */
.hs-form-frame{margin-top:var(--space-3);width:100%}

/* Post-submit confirmation. Hidden until js/enhance.js gets a 2xx from HubSpot,
   then shown in the form's own slot. The three declarations are the same ones
   the surrounding paragraphs already use in both sections - var(--measure) and
   var(--gray) - plus the form's own 8px top margin, which is var(--space-1).
   Nothing here is a literal value and no new token is introduced. */
/* [hidden] is a UA rule, and ANY author rule beats it. The inquiry form carries
   display:grid from its ported class, so setting form.hidden faithfully set the
   attribute while the form stayed 1028px tall on screen - a successful submit
   looked identical to nothing happening. My own check asserted form.hidden, the
   IDL attribute, instead of asserting the form had actually stopped rendering,
   which is why it passed.

   !important is deliberate. Being in the last layer is not enough: .studio-form
   also sets display:grid, at the same specificity and later in the same layer,
   so it won the cascade and the waitlist form stayed on screen after a
   successful submit - the identical bug, one form along. [hidden] means "not
   rendered"; nothing below should be able to outrank it by source order. */
[hidden]{display:none !important}

/* Honeypot. Present in the markup for bots, never shown to a person. It had no
   rule at all, so it rendered as a plain unlabelled text input above the submit
   button. Not display:none and not visibility:hidden - some bots skip both. */
.hp{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;
    clip-path:inset(50%);white-space:nowrap;border:0}

/* Post-submit confirmation. A state change, so it reads louder than the body
   copy around it: the export's serif face at its --h4 step in full --ink,
   against the muted --gray body copy beside it. Tokens only. */
.form-sent{margin:var(--space-1) 0 0;max-width:var(--measure)}
.form-sent p{font-family:var(--serif);font-size:var(--h4);line-height:1.4;
             color:var(--ink);margin:0}

/* Chrome paints autofilled fields pale blue over the form's own transparent
   background. There is no property that turns that off, so the standard inset
   shadow stands in for it: both contact sections are var(--cream), and the
   fields are transparent on top of it. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
select:-webkit-autofill,
textarea:-webkit-autofill{
  -webkit-box-shadow:0 0 0 100px var(--cream) inset;
  box-shadow:0 0 0 100px var(--cream) inset;
  -webkit-text-fill-color:var(--ink);
  caret-color:var(--ink);
}

/* --- waitlist form -------------------------------------------------------
   The export has no waitlist form - only an anchor out to a hosted HubSpot
   page - so this component is built here rather than ported. Every declaration
   mirrors the inquiry form's own ported rules so the two read as one design:
   same 1fr 1fr grid and 18px/44px gaps, same 40px underlined fields, same
   eyebrow labels, same solid submit button. The select arrow is the export's
   own base64 SVG, taken from the homepage where it survives intact. */
.studio-form{margin-top:var(--space-1);display:grid;grid-template-columns:1fr 1fr;
             gap:18px 44px;align-items:start}
.studio-form .span-2{grid-column:1/-1}
.studio-form label{display:block;font-size:var(--eyebrow);letter-spacing:.18em;
                   text-transform:uppercase;color:var(--gray);margin-bottom:8px}
.studio-form input,.studio-form select{width:100%;box-sizing:border-box;height:40px;
  border:0;border-bottom:1px solid var(--line);background:transparent;
  font-family:var(--sans);font-size:var(--body);line-height:1.4;color:var(--ink);
  padding:10px 0;border-radius:0}
.studio-form select{appearance:none;-webkit-appearance:none;padding-right:26px;cursor:pointer;
  background-image:url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMCIgaGVpZ2h0PSI2Ij48cGF0aCBkPSJNMCAwbDUgNiA1LTZ6IiBmaWxsPSIjOEE3QTZBIi8+PC9zdmc+);
  background-repeat:no-repeat;background-position:right center}
.studio-form button{grid-column:1/-1;justify-self:start;margin-top:var(--space-1);
  font-family:var(--sans);font-size:var(--small);letter-spacing:.06em;
  background:var(--ink);color:var(--paper);border:1px solid var(--ink);
  padding:15px 34px;cursor:pointer;
  transition:background 250ms cubic-bezier(.25,.46,.45,.94)}
.studio-form button:hover{background:var(--brown);border-color:var(--brown)}
@media(max-width:600px){.studio-form{grid-template-columns:1fr}}

/* --- eyebrows promoted to real headings ----------------------------------
   Several pages skip a heading level or have no <h1>. Promoting the styled
   eyebrow keeps the outline valid; these rules cancel only the heading face
   and leading the global h1..h4 rule would otherwise apply. */
.eyebrow-head,.eyebrow-h2{font-family:var(--sans);line-height:inherit;letter-spacing:inherit}

/* The logo carries width/height attributes, which the brief requires for CLS.
   The export sizes it by height with the width left to follow, so restore that
   and let the attributes serve only as the aspect-ratio hint. */
nav img, footer img{width:auto}

/* The nav logo scales down with the viewport instead of being clipped or
   squashed. The export fixes its height at 26px and lets `img{width:100%}`
   crush the wordmark to 2.77:1 against its true 4.73:1 when the nav runs out of
   room; with width:auto it holds its shape but pushes the last link off the
   right edge at 375px. Scaling the HEIGHT keeps the ratio exact and shrinks the
   logo as the screen narrows, which is what the client asked for: "i dont
   understand why it cant just move in as the screen moves".

   26px / 4.2vw = 619px, so at any viewport at or above 620px this computes to
   exactly the export's 26px and nothing changes. Below that it shrinks
   proportionally: 15.75px tall and 74.5px wide at 375px, against the export's
   distorted 72px. Recorded in build/diffconfig.json. */
nav img{height:clamp(18px, 4.2vw, 26px)}

/* Footer column headings are promoted from <h3> to <h2> for the document
   outline. The export renders them with the <h3> leading, so keep that rather
   than picking up the <h2> value. */
footer h2{line-height:1.3}

/* NOTE: the export's own responsive footer rule is written against h3, which
   the promotion above orphans. It is NOT restated here - port_captured.py and
   port.py rewrite the selector to h2 in each page's own CSS, so every page
   keeps its own breakpoint exactly as the export wrote it. */

/* The pre-designed homes headline, sized to match "Selected Work" on
   /projects/ at the client's request. The export sets it at var(--h1), 70.4px -
   the only h1 on the site at that step - and the client's replacement headline
   runs to three lines there instead of the original's two, 241px against 161px.

   Every value below is taken from the /projects/ h1 as measured in the browser,
   so the two headings are the same object: var(--h2), line-height 1.15,
   letter-spacing -0.01em. /about/ already matches these. Recorded as a
   deviation from the export in build/diffconfig.json.

   max-width goes from the export's 20ch to 28ch for the same reason. 20ch was
   measured for the original two-word-shorter headline; against the client's it
   is 443px while the line after the <br> needs 619px, so the headline broke into
   three lines instead of the two she specified. 28ch is the smallest ch value
   that fits it, and because ch scales with the font it holds two lines at 1280,
   1024 and 671. Below that the viewport is simply narrower than the text and it
   wraps further, which is correct - it never overflows. */
h1.plans-headline{font-size:var(--h2);line-height:1.15;letter-spacing:-0.01em;
                  max-width:28ch}

/* The gap above the floor plan title. The export sets 56px on the Ranch plan's
   wrapper, which the client asked to reduce; --space-3 is 28px and is the token
   nearest half of it. Applied to BOTH project sheets from one rule so they
   cannot drift apart - the Ranch wrapper is targeted through the block that
   holds its plan label. */
[data-magnify], .vista-plans{--plan-gap:var(--space-3)}
.rh-s27, .vista-plans{margin-top:var(--space-3)}

/* --- Vista floor plans ----------------------------------------------------
   Built rather than ported: the export has no floor plan on this page. Values
   come from the Ranch plan's own rules so the two project sheets read the same.

   HEIGHT IS CAPPED here and not on Ranch, because the drawings differ in shape.
   Ranch is 5100x3300, ratio 1.545, so filling the page frame renders it
   1267x820 at 1440x900 - 0.91 of a screen. Vista is 3401x3086, nearly square at
   1.102, so the same width rendered 1.28 screens and ran past the fold. Capped,
   it is 0.80 of a screen and centres within the same margins rather than
   spanning them, which is the trade the shape forces. Cropping cannot fix it:
   floor 01's own content is 1.084.

   The cap is on the FRAME, not the image. Capping the image with width:auto and
   height:auto collapsed it to 0x0 until the file loaded - the plan is lazy, so
   before it decodes there is no intrinsic size for auto to resolve against.
   3401 / 3086 = 1.102, the shared aspect of both plans. */
.vista-plans{margin-top:var(--space-3);--plan-aspect:1.102}

/* The title IS the control. The plan in view is bold; the other is greyed until
   hovered, when it comes up to the normal shade to show it is live. */
.plan-switch{margin:0 0 18px;display:flex;align-items:baseline;gap:.6em}
.plan-opt{font:inherit;letter-spacing:inherit;text-transform:inherit;
  background:none;border:0;padding:0;cursor:pointer;color:var(--gray);
  font-weight:400;transition:color 200ms}
.plan-opt:hover{color:var(--brown)}
.plan-opt.is-on{color:var(--brown);font-weight:700;cursor:default}
.plan-sep{color:var(--line)}
.plan-label{margin:0 0 18px}

/* Both sheets occupy the SAME grid cell, so switching swaps in place instead of
   the page reflowing around a differently-positioned block. With JS off they
   are both visible and the grid stacks them vertically instead. */
.plan-stack{display:grid}
.plan-stack > .plan-sheet{grid-area:1 / 1}
.plan-stack > .plan-sheet[hidden]{display:none}

/* NO overflow:hidden. It used to be here, and it was silently slicing the
   magnifier: the lens is centred on the pointer, so near an edge part of it
   falls outside the drawing and the frame cut it off flat, with no border on
   that side. The lens is meant to hang over the sheet and show blank paper
   there. js/enhance.js keeps it inside the viewport so it cannot raise a
   horizontal scrollbar; nothing else needs clipping, since the frame's only
   other child is the image at width:100%. */
.plan-frame{position:relative;cursor:crosshair;
            max-width:calc(80vh * var(--plan-aspect));margin-inline:auto}
.plan-frame img{width:100%;height:auto;display:block}

/* The lens carries the FULL-resolution drawing, not the CDN-resized one.
   background-size is set by js/enhance.js from the displayed width and the
   plan's own data-zoom-factor, so the zoom holds at any size. */
.plan-lens{position:absolute;width:260px;height:260px;
  border:1px solid rgba(26,22,18,.35);box-shadow:0 12px 36px rgba(0,0,0,.25);
  background-color:var(--paper);background-repeat:no-repeat;
  pointer-events:none;display:none;z-index:5}
.plan-lens[data-floor-lens="0"]{background-image:url(/assets/proj/vista-plan-floor-1.png)}
.plan-lens[data-floor-lens="1"]{background-image:url(/assets/proj/vista-plan-floor-2.png)}

/* --- hero crop ------------------------------------------------------------
   Every full-bleed hero is centred, not top-anchored.

   The heroes are object-fit:cover in a box near 2.3:1 fed by sources near
   1.5:1, so about a third of the image height is always cropped - that is
   unavoidable at this box ratio. WHICH third is the question. Ranch was already
   center center; Vista and About were center top, which throws away the bottom
   third: on Vista that is the terrace, the furniture and the dock, and it is
   why it read as too tight. Centring keeps the subject and splits the loss
   between sky and foreground.

   Client is comparing against the Claude Design canvas, not the exported file,
   and the export is demonstrably lossy elsewhere. Recorded in diffconfig. */
.hero img{object-position:center center}

/* Per-hero crop, solved from the images themselves rather than judged by eye.
   Row positions below were measured by scanning each file's centre columns for
   the row where luminance variance departs from flat sky (the roofline) and
   where greenness jumps (the lawn).

   The box is object-fit:cover at roughly 2.3:1 over a roughly 1.5:1 source, so
   about a third of the height is always cropped. v below is the visible
   fraction of the source; it shifts with the window because the hero is 70vh.

   RANCH: roofline measured at 48.6% by scanning the narrow columns at each
   gable apex - above the centre gable the sky sits flat at luminance 252 until
   52.1%, where it drops 139 points in one step; the right gable breaks at 48.6%.
   An earlier 44.5% was the TREE LINE behind the house and put the roof visibly
   low. Solving (roof - s) / v = 0.5 gives 45.9-46.6% across common widths.

   RANCH (calero-v-2.jpg): ROOFLINE AT 48.6% of the source, house base at 80%,
   lawn from 82%. Client instruction 2026-08-21: "roof line at the top of the
   building should be at the halfway height of the canvas."

   Solving (roof - s) / v = 0.5 for the object-position gives 34% at 1280 and
   1440, 32.7% at 1512, 36.6% at 1920. 35% is the middle of that range and lands
   the roof at 48.9-51.1% across all of them.

   The cost, stated because it is unavoidable rather than a mistake: the two
   earlier requests - roof at halfway AND the base uncut - cannot both hold at
   this box ratio. Putting the roof at the middle pushes the base past the
   bottom edge. The house loses 1.9% of its height at 1512, 6.6% at 1280 and
   1440, and 18.6% at 1920x1080, where the hero is proportionally shortest.
   Only a taller hero satisfies both, and the height was to stay as it is.

   VISTA (sunrise-hero-web-1600.jpg): roof peak at 8% of the source, terrace
   base at 78%. That span is 70%, wider than v, so the whole house CANNOT fit -
   the client asked for the maximum house without losing the roof, so the band
   is pinned just above the roofline and the loss is taken at the bottom. 
   UPDATE 2026-08-21: 46% put the roof exactly on the midline and the client
   said it was still too low, so "halfway" described the intent rather than the
   arithmetic. Now 68%, which lifts the roof to 35-40% of the canvas.

   Raising this value moves the house UP and keeps MORE lawn at the same time: a
   larger object-position slides the visible band DOWN the source, so it ends
   later and the foreground survives while the roof rises in frame. The base is
   comfortably inside at every common width again - 86% at 1440x900, 88% at
   1920x1080 - which 46% could not manage at 1920.

   The knob, at 1440x900: every +10 points lifts the roof 5.2% of canvas height.
   46% -> 50.0%   55% -> 45.2%   62% -> 41.6%   68% -> 38.4%   75% -> 34.8%
*/
.hero img[src*="calero-v-2"]{object-position:50% 68%}
.hero img[src*="sunrise-hero"]{object-position:50% 15%}

/* --- nav: current page, and anchor landing ------------------------------
   The export has no active state to copy: every prototype is a single page, so
   its nav links look identical everywhere. aria-current="page" is the standard
   hook and build.py sets it, including on the child pages - a visitor on a
   project page still sees Projects marked.

   The treatment is the export's own hover colour, var(--ink) against the
   resting var(--brown), plus a hairline in the same colour. Nothing invented. */
nav a[aria-current="page"]{color:var(--ink);border-bottom:1px solid var(--ink);
                           padding-bottom:2px}

/* Anchor links land below the fixed bar rather than under it. The bar is 76px
   in the export's own markup; --space-2 is the breathing room. Without this,
   the Services link from the nav put the target section's heading behind the
   bar. Applies to every in-page target, not just that one. */
[id]{scroll-margin-top:calc(76px + var(--space-2))}

/* --- the page container ---------------------------------------------------
   The single definition of the page border. The nav, every body wrapper, the
   footer grid and the footer bottom bar all carry this class, applied by
   page_frame() in the porters, and nothing else sets a horizontal page inset.

   No max-width: the border is var(--margin-page) from the viewport edge at
   every width, continuously, so the logo, body content and footer columns share
   one left edge and one right edge at any size. A capped container cannot do
   that - above the cap it is inset by (viewport - cap)/2 + padding while an
   uncapped sibling is inset by padding alone, which is the bug that shipped.

   Inner measures (the 820px quote, --measure on body copy) are unaffected;
   they constrain text inside the frame, they do not set the frame. */
.page{padding-inline:var(--margin-page)}
/* A frame inside a frame must not add a second border. The export nests them -
   a <section> carrying the page padding wrapping a max-width wrapper that
   carries it again - which put /projects/ content at 48px instead of 24px. */
.page .page{padding-inline:0}

/* The nav links wrap. Seven of the fourteen exports already say so; the other
   seven set only a gap, and on those the row is wider than the bar at 375px, so
   the last link crosses the page's right edge (364.3px against an edge at
   351px). A single right edge at every width is the requirement, so this is not
   optional any more - it applies on all 13 pages. Above the point where the row
   fits, wrapping has no effect and nothing moves. */
nav [data-mq="nav-links"]{flex-wrap:wrap;justify-content:flex-end}

/* --- phone: stop three pages scrolling sideways -------------------------
   At 375px /projects/, ranch-house and vista-house all overflowed - 439, 457
   and 477px of content in a 375px viewport. The export does exactly the same,
   which means it was never tested at that width, not that it is right. Most of
   this site's traffic is phones.

   Two unrelated causes:

   1. The projects grid. The export collapses it to `1fr 1fr` below 860px, but
      an `fr` track has an automatic minimum of min-content, and each card's
      min-content is set by a `white-space: nowrap` title. So the two tracks
      refused to shrink and measured 177.8 + 223.2 = 401px inside a 327px box.
      min-width:0 lets them shrink, which fixes the whole 600-860px band as
      well; below 600px they stack, per the client's instruction.

   2. A nowrap line in the closing section on both plan pages - "Mirroring |
      Resizing | Site-Specific Adjustments" - 433px of unbreakable text in a
      327px box. It is allowed to wrap below 600px only; above that the box is
      at least 529px and the line fits on one row as designed. */
[data-mq="projects"]>a{min-width:0}

@media(max-width:600px){
  [data-mq="projects"]{grid-template-columns:1fr!important;gap:44px!important}
  [data-mq="projects"]>a{grid-column:1/-1!important}
  .custom p{white-space:normal}
}

/* --- footer: the page border wins over the export's optical nudge ---------
   Every export page carries transform:translateX(-18px) on the footer's third
   column. In the four-column desktop footer that is an internal optical tweak:
   columns one and four set the page border, so nothing moves. Stacked on a
   phone, that third column IS the left border, and it sat 18px outside it -
   measured at 375px, lefts were [24, 24, 6, 24] where every other page gave
   [24, 24, 24, 24].

   Neutralised at 900px, the widest of the three footer-stacking breakpoints in
   the ported CSS (520, 860 and 900 across the 13 pages). Honest edge: on the
   860px pages this also drops the nudge between 861 and 900px, where the
   footer is still four columns. That is a 40px band in which one heading sits
   18px further right than the canvas - visible only side by side, and the
   alternative is per-page rules that would drift apart. */
@media(max-width:900px){
  [data-mq="footer"]>*{transform:none!important}
}

/* --- the "What You Receive" chart scrolls instead of crushing -------------
   The export gives this table four columns at 2.2fr 1fr 1fr 1fr and wraps it
   in overflow-x:auto, which says plainly that it is meant to SCROLL when it
   does not fit. With fr units and no minimum it does the opposite: at 375px
   the columns collapsed to 30/75/117/80px and scrollWidth equalled clientWidth,
   so there was nothing to scroll and the headings wrapped instead - "Design
   Set" and "Complete Home" onto two lines, "Build Set" on one.

   That, not the eyebrow labels, is why the three headings sat at different
   heights. The export's own align-items:end is correct and untouched: it
   bottom-aligns the columns so eyebrow wrapping cannot move the headings, and
   measurement confirms all three already share a bottom edge.

   min-width restores the intent. The value is measured, not guessed: at a row
   width of 600px "Complete Home" still wrapped to two lines while the other
   two stayed on one; it fits at 676px. 700px is the next round number clear of
   that, and it is the widest of the three headings that sets it. */
@media(max-width:900px){
  [data-mq="chart"]>div{min-width:700px!important}

  /* The scroll cue. A table that scrolls with no sign it scrolls reads as
     broken or as clipped content. These are the classic CSS-only scroll
     shadows: two solid gradients pinned to the content box (background-
     attachment:local) sit UNDER two soft shadows pinned to the viewport box
     (scroll). At rest the solid pair covers the shadows; as soon as there is
     content out of frame on a side, that side's solid gradient scrolls away
     and its shadow shows through. So the cue appears only when there is
     somewhere to go and fades out at each end, with no JS and no scroll
     listener. */
  [data-mq="chart"]{
    background:
      linear-gradient(to right, var(--paper) 30%, rgba(255,255,255,0)) left center,
      linear-gradient(to left,  var(--paper) 30%, rgba(255,255,255,0)) right center,
      radial-gradient(farthest-side at 0 50%,
                      rgba(26,22,18,.20), rgba(26,22,18,0)) left center,
      radial-gradient(farthest-side at 100% 50%,
                      rgba(26,22,18,.20), rgba(26,22,18,0)) right center;
    background-repeat:no-repeat;
    background-size:44px 100%, 44px 100%, 15px 100%, 15px 100%;
    background-attachment:local, local, scroll, scroll;
    overscroll-behavior-x:contain;
  }
}

/* --- click-to-enlarge, built by js/enhance.js ---------------------------- */
[data-zoom]{cursor:zoom-in}
.lightbox{position:fixed;inset:0;z-index:200;background:rgba(26,22,18,.92);display:flex;
          align-items:center;justify-content:center;padding:48px;cursor:zoom-out;border:0}
.lightbox img{max-width:100%;max-height:100%;object-fit:contain;
              box-shadow:0 30px 80px rgba(0,0,0,.5)}
.lightbox-close{position:absolute;top:24px;right:32px;background:none;border:0;
                color:rgba(234,227,214,.8);font-size:28px;line-height:1;cursor:pointer;
                font-family:var(--sans)}

}
