/* ==========================================================================
   Nordu marketing site
   Palette, typeface and radius/shadow language are taken straight from the
   configurator so the two sites read as one product. Light page, white
   surfaces, #323232 ink, #e8502a accent, with the dark glass header the
   configurator also uses.
   ========================================================================== */

:root{
  --paper:#dfdfdf;
  --surface:#ffffff;
  --surface-2:#ececec;
  --ink:#323232;
  --ink-soft:#5a5a5a;
  /* #8a8a8a measured 3.45:1 on --surface and 2.59:1 on --paper, both short of
     the 4.5:1 AA text floor. This is the darkest grey that still reads as a
     step below --ink-soft while clearing AA against both. */
  --ink-muted:#636363;
  --line:#d6d6d6;
  --line-strong:#b8b8b8;

  --accent:#e8502a;
  --accent-soft:rgba(232,80,42,0.10);
  --accent-line:rgba(232,80,42,0.45);
  /* The brand orange measures 3.74:1 on white and 2.81:1 on the page, so as
     small text or as a fill behind white text it fails AA. --accent-text
     covers the former (an eyebrow, a link) and is redefined to plain
     --accent in dark mode, where the same hue already clears AA as text.
     --accent-strong covers the latter (a filled button, a badge) and is not
     redefined for dark, because white-on-fill is the same failing pair in
     either theme: the button itself doesn't change colour with the page. */
  --accent-text:#b63414;
  --accent-strong:#b63414;
  --accent-strong-hover:#a93012;

  --wrap:1240px;
  --radius:10px;
  --card-gap:clamp(16px,2vw,24px);
  --shadow:0 1px 2px rgba(0,0,0,0.04), 0 8px 24px -12px rgba(0,0,0,0.14);

  /* Tells the browser which way to draw the things this stylesheet does not
     paint itself: scrollbars, the form controls' own chrome, the caret. The
     dark counterpart is in the dark mode block at the foot of the file. */
  color-scheme:light;
}

*{box-sizing:border-box;}
html,body{margin:0;padding:0;}
html{scroll-behavior:smooth;}
body{
  background:var(--paper);
  color:var(--ink);
  font-family:'Host Grotesk',-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;
  font-size:16px;
  line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
[hidden]{display:none !important;}
::selection{background:var(--accent-strong);color:#fff;}
img{max-width:100%;display:block;}
button{font-family:inherit;}
a{color:inherit;}
:focus-visible{outline:2px solid var(--accent-strong);outline-offset:3px;border-radius:4px;}

.wrap{max-width:var(--wrap);margin:0 auto;padding:0 28px;}
.eyebrow{
  font-size:11px;font-weight:600;letter-spacing:0.18em;
  text-transform:uppercase;color:var(--ink-muted);margin:0 0 14px;
}
.eyebrow.accent{color:var(--accent-text);}

h1,h2,h3,h4{margin:0;letter-spacing:-0.02em;line-height:1.08;font-weight:800;}
h1{font-size:clamp(38px,6vw,72px);}
h2{font-size:clamp(28px,3.6vw,44px);}
h3{font-size:clamp(20px,2vw,26px);letter-spacing:-0.015em;}
h4{font-size:16px;letter-spacing:0.01em;font-weight:700;}
p{margin:0 0 16px;}
p:last-child{margin-bottom:0;}
.lede{font-size:clamp(17px,1.6vw,21px);color:var(--ink-soft);line-height:1.5;}
.muted{color:var(--ink-muted);}

section{padding:clamp(64px,9vw,120px) 0;}
.section-head{max-width:720px;margin-bottom:clamp(32px,4vw,56px);}
.section-head p{color:var(--ink-soft);font-size:17px;}
.rule{height:1px;background:var(--line);border:0;margin:0;}

/* ---------- carded sections ----------
   A section rendered as a raised white panel rather than a band of the page.
   The section drops its own generous vertical padding and takes half the card
   gap on each side instead, so two adjacent cards sit exactly --card-gap apart
   (padding does not collapse the way margin would) while a card next to an
   ordinary full padding section still gets that section's breathing room.

   Only sections whose content is plain copy or a split are carded. Anything
   built from .card, .material, .band, .project, .panel or .showroom stays a
   band of the page, because those are already white panels and putting them on
   another white panel flattens the hierarchy to nothing. */
.carded{padding:calc(var(--card-gap) / 2) 0;}
/* A carded section straight after a full bleed hero needs more than half a
   card gap above it. .hero-image is padding:0, so it contributes nothing of
   its own and the first card ended up 11px below the photograph while the
   cards below it sat 23px apart — tighter against the one thing on the page
   that most needs room. Adjacent sibling, so it only fires in that one place
   and the card-to-card rhythm underneath is untouched. */
.hero-image + .carded{padding-top:clamp(28px,4vw,56px);}
.section-card{
  background:var(--surface);
  border:1px solid var(--line);
  border-radius:20px;
  padding:clamp(32px,4vw,64px);
  box-shadow:var(--shadow);
}
@media (max-width:620px){
  .section-card{border-radius:16px;padding:28px 22px;}
}

/* ---------- header (dark tinted blur, no hard edge) ---------- */
/* The bar no longer rescopes its ink. It used to force white-on-dark
   regardless of the page, which meant the header had to be maintained as its
   own little theme. Inheriting instead means it simply follows: dark ink on
   white glass in the light theme, light ink on dark glass in the dark one,
   with no second set of colours to keep in step. */
.site-head{
  position:sticky;top:0;z-index:60;
  background:transparent;
  color:var(--ink);
}
/* The blur sits on a pseudo-element rather than the header itself so it can
   be masked. Masking the header directly would fade the logo and links out
   along with it. It extends past the bottom edge to give the gradient room
   to fall off, so there is no visible line where the blur stops. */
/* Frosted white glass. The alpha is not a taste decision: plain blur alone
   could not hold the nav legible over the hero photo, where the links measured
   about 1.2:1 against the dark foliage along the top strip.

   Measured against the real photographs rather than guessed. Sampling the
   darkest 2% of the strip the header covers, including the hero scrim on top
   of it, the worst case is rgb(21,26,8) in the rear-extension hero. Against
   white at this alpha the nav links measure 5.0:1 and the active item 9.4:1.
   At 0.78 the links drop to 4.29:1 and fail AA, so there is not much room
   below this. If a darker hero photograph is ever swapped in, re-measure. */
.site-head::before{
  content:'';position:absolute;inset:0;
  -webkit-backdrop-filter:blur(20px) saturate(160%);
  backdrop-filter:blur(20px) saturate(160%);
  background:rgba(255,255,255,0.85);
  /* The page beneath is only slightly darker than the bar, so without this
     the header has no edge at all once it lifts off the hero. */
  box-shadow:inset 0 -1px 0 rgba(0,0,0,0.09);
  pointer-events:none;
}
.head-inner{
  max-width:var(--wrap);margin:0 auto;padding:16px 28px;
  display:flex;align-items:center;justify-content:space-between;gap:24px;
  /* Keeps the bar content above the absolutely positioned blur layer. */
  position:relative;z-index:1;
}
.logo{display:flex;align-items:center;flex:none;}
.logo svg{height:22px;width:auto;display:block;}
.logo-ink{fill:var(--ink);}
.logo-accent{fill:var(--accent);}

/* margin-left:auto rather than the theme button carrying it: the nav is the
   first thing in the right hand group now, so it is what pushes the group
   across. */
.nav{display:flex;align-items:center;gap:30px;margin-left:auto;}
/* --ink-soft rather than --ink-muted: muted only reaches about 2.4:1 against
   the header backdrop, which fails AA. Soft clears it while still reading as
   secondary to the active item. */
.nav a{
  font-size:14px;font-weight:600;text-decoration:none;color:var(--ink-soft);
  transition:color .15s;white-space:nowrap;
}
.nav a:hover{color:var(--ink);}
.nav a[aria-current="page"]{color:var(--ink);}
/* `.nav a` outweighs `.btn-primary` on specificity, so the button colours
   have to be restated here or the label inherits the muted link colour. */
.nav a.btn-primary,
.nav a.btn-primary[aria-current="page"]{color:#fff;}
.nav a.btn-ghost{color:var(--ink);}
.nav .btn{margin-left:4px;}

/* Sits next to the Enquire button in the mobile header, so it takes the same
   pill shape rather than staying the old 7px rectangle. */
.nav-toggle{
  display:none;background:none;border:1px solid var(--line-strong);
  border-radius:999px;padding:9px 16px;color:var(--ink);cursor:pointer;
  font-size:13px;font-weight:600;
  order:2;
}

/* ---------- theme toggle ----------
   Light and dark, sitting after the Enquire button. It follows the nav in the
   markup too, not just visually, so tab order and reading order agree.

   The order values are for mobile. There the nav collapses to an absolutely
   positioned dropdown and leaves the bar holding the logo, this and the Menu
   button, and without them this would land after Menu instead of before it.

   display:none by default. The inline script in each <head> puts a .js class
   on the document, so the button only appears where it can actually work.
   Doing it this way round rather than un-hiding it from site.js means it is
   never painted and then removed. */
.theme-toggle{
  display:none;order:1;
  align-items:center;justify-content:center;
  width:38px;height:38px;flex:none;
  background:none;border:1px solid var(--line-strong);border-radius:999px;
  color:var(--ink);cursor:pointer;
  transition:border-color .15s,background .15s;
}
.js .theme-toggle{display:inline-flex;}
/* A dark wash now the bar is light. The old white one was invisible on it. */
.theme-toggle:hover{border-color:var(--ink);background:rgba(0,0,0,0.05);}
.theme-toggle svg{width:17px;height:17px;display:block;}
/* Two icons, one shown at a time. The icon names the mode the button switches
   to, so the moon shows while the page is light. The dark variants live in the
   dark mode block at the foot of this file. */
.theme-toggle .icon-light{display:none;}

/* ---------- buttons ---------- */
/* One button shape everywhere: the pill the homepage entry cards use. Ghost
   buttons take it too, so a primary and a ghost sitting together in a .btn-row
   read as a pair rather than two different components. The chips in .tag and
   .entry-cta already use the same 999px radius. */
.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:9px;
  font-size:15px;font-weight:600;text-decoration:none;
  padding:14px 28px;border-radius:999px;border:1px solid transparent;
  cursor:pointer;transition:background .15s,border-color .15s,color .15s,transform .1s;
}
.btn:active{transform:scale(0.985);}
.btn-primary{background:var(--accent-strong);color:#fff;border-color:var(--accent-strong);}
.btn-primary:hover{background:var(--accent-strong-hover);border-color:var(--accent-strong-hover);}
.btn-ghost{background:transparent;color:var(--ink);border-color:var(--line-strong);}
.btn-ghost:hover{border-color:var(--ink);}
.btn-sm{padding:10px 18px;font-size:14px;}
.btn-row{display:flex;flex-wrap:wrap;gap:12px;margin-top:28px;}

.textlink{
  display:inline-flex;align-items:center;gap:7px;
  font-weight:600;text-decoration:none;color:var(--accent-text);
  border-bottom:1px solid var(--accent-line);padding-bottom:2px;
  transition:border-color .15s;
}
.textlink:hover{border-bottom-color:var(--accent-text);}

/* ---------- image placeholders ----------
   Lettered stand-ins. Every one is replaced with real photography before
   launch: see the placeholder index in README.md. */
.ph{
  margin:0;position:relative;background:var(--surface-2);
  border:1px dashed var(--line-strong);border-radius:var(--radius);
  display:flex;align-items:center;justify-content:center;
  overflow:hidden;
}
.ph::after{
  content:'';position:absolute;inset:0;
  background:repeating-linear-gradient(135deg,
    rgba(0,0,0,0.022) 0 12px, transparent 12px 24px);
  pointer-events:none;
}
.ph-letter{
  font-size:clamp(34px,5vw,60px);font-weight:800;letter-spacing:-0.03em;
  color:var(--ink-muted);line-height:1;
}
.ph-note{
  position:absolute;left:14px;bottom:12px;right:14px;
  font-size:11px;letter-spacing:0.06em;text-transform:uppercase;
  color:var(--ink-muted);font-weight:600;
}
.ph-wide{aspect-ratio:16/9;}
.ph-hero{aspect-ratio:21/9;}
.ph-square{aspect-ratio:1/1;}
.ph-tall{aspect-ratio:4/5;}

/* ---------- hero ---------- */
.hero{padding:clamp(56px,7vw,96px) 0 clamp(48px,6vw,80px);}
.hero h1{max-width:14ch;}
.hero .lede{max-width:60ch;margin-top:22px;}
.hero-media{margin-top:clamp(40px,5vw,64px);}

/* FULL BLEED HERO WITH THE COPY ON A CARD.

   No gradient. The photograph is not dimmed anywhere — the copy sits on its
   own opaque card and the picture is only hidden where that card covers it.

   The alternative was type straight on the photograph, which needs the shot to
   carry a large quiet field. Nine rounds of generation showed that does not
   survive contact with real images: page ink measured 0.8:1 where the copy
   goes on this hero, because the black clad building is exactly there. A card
   makes the layout independent of what is in the frame, which matters when the
   heroes get regenerated.

   THE CARD BORROWS .entry's TREATMENT — same surface, same 1px line, same 20px
   radius, same shadow — so the hero card and the two entry cards directly below
   it read as one family rather than two different components stacked. */
.hero-image{
  position:relative;padding:0;overflow:hidden;
  /* THE CAP IS WHAT BIG SCREENS CARE ABOUT, not the 66vh.
     The box is full width and the photograph is 16:9ish, so `cover` fills the
     width and crops the height — which means the wider the viewport, the more
     of the picture is thrown away. At 680px a 2560px screen showed under half
     of it, a 3.76:1 letterbox slot with the roofline and the ground cut off.
     900px gives that screen 2.84:1 and about two thirds of the photograph.

     Laptops are untouched: 66vh on a 800-900px tall window is 528-594px, well
     under either cap, so the old number was never reached there. Only screens
     tall enough to want more than 680px see any difference at all. */
  min-height:clamp(460px,66vh,900px);
  /* Left, and vertically centred. It sat on the bottom edge before, which read
     as a caption pinned under the photograph rather than as the first card in
     the run. Centred it holds its own against the building and lines up with
     the optical middle of the frame.

     Mobile is unaffected: the ≤620px block below sets display:block here, so
     align-items stops applying and the card returns to flowing under the
     photograph. */
  display:flex;align-items:center;
}
.hero-bg{position:absolute;inset:0;}
.hero-bg img{width:100%;height:100%;object-fit:cover;object-position:50% 50%;}

.hero-content{
  position:relative;z-index:1;
  /* width:100% matters. As a flex item this box would otherwise shrink to its
     content, and the auto margins on .wrap would then centre that narrower
     box, pushing the hero copy inwards so it no longer lined up with the
     gutter used by every other section. */
  width:100%;
  /* Equal top and bottom. These were deliberately top-heavy when the card was
     bottom-aligned; left that way under centring they push it below the true
     middle, because it is the padded box that gets centred, not the card. */
  padding-top:clamp(28px,4vh,56px);
  padding-bottom:clamp(28px,4vh,56px);
  /* Right hand side, on the same gutter every other section on the page ends
     at, so the card's outer edge lines up with the content column rather than
     floating loose against the viewport. The card is opaque, so unlike the old
     scrim it does not care what is behind it.

     Mobile ignores this: .hero-image goes display:block below 620px, so the
     card stops being a flex item and returns to full width under the photo. */
  display:flex;justify-content:flex-end;
}

.hero-card{
  background:var(--surface);border:1px solid var(--line);
  border-radius:20px;box-shadow:var(--shadow);
  padding:clamp(22px,2vw,30px) clamp(24px,2.2vw,32px);
  /* 330px when the headline was a step smaller. At the entry-card size it set
     to four lines in that width, so the card carries the type rather than the
     type being shrunk to fit the card. Measured, not guessed: see the note on
     .hero-card h1. */
  width:min(400px,100%);
}
/* The same size as .entry strong, the headings on the two cards below it, so
   the three read as one family rather than the hero being the quiet one.

   It used to be a step smaller, because at a 330px card width this headline
   set to four lines. The card is wider now to carry it — see .hero-card. */
.hero-card h1{margin:0;font-size:clamp(26px,2.6vw,36px);line-height:1.1;max-width:none;}
.hero-card .lede{margin:10px 0 0;font-size:14.5px;line-height:1.5;max-width:none;}
/* Orange, like the entry cards below. The default .eyebrow is --ink-muted
   grey, which is right in a section heading and wrong here: on the card
   this line is the brand mark, the same one .entry-brand carries. */
.hero-card .eyebrow{margin:0 0 9px;font-size:17px;color:var(--accent-text);}

/* ---------- the one hero that opens on the photograph ---------- */
/* about.html only. The note above .hero-image says type straight on a picture
   needs the shot to carry a large quiet field, and that the building heroes do
   not -- page ink measured 0.8:1 on one of them, because the dark cladding is
   exactly where the copy goes. The about header is mist, which IS that field,
   so the card comes off there and nowhere else.

   Everything here is min-width:621px. Below that the existing hero block ends
   the photograph and flows the copy onto --surface underneath it, which is
   already right and must not get white type on it.

   THE INK IS FIXED LIGHT IN BOTH THEMES. A photograph does not change with the
   toggle, so ink that did would be dark type on a dark forest half the time.
   Same reason the accent is pinned to the brighter --accent rather than
   --accent-text: the text token goes to #b63414 in light mode, which is a
   colour chosen for white paper and far too dark for this. */
@media (min-width:621px){
  /* Left, where the frame is darkest and the trees are closest. */
  .hero-over .hero-content{justify-content:flex-start;}
  /* A scrim, angled so it is heaviest at the left where the copy sits and gone
     by the right third, which is the brightest part of the picture and the
     part worth keeping. Sits above .hero-bg and below .hero-content, which
     carries z-index:1. */
  .hero-over::after{
    content:'';position:absolute;inset:0;
    background:linear-gradient(103deg,
      rgba(10,12,14,0.86) 0%, rgba(10,12,14,0.74) 30%,
      rgba(10,12,14,0.34) 62%, rgba(10,12,14,0.04) 100%);
  }
  .hero-over .hero-card{
    width:min(640px,100%);
    background:none;border:0;border-radius:0;box-shadow:none;padding:0;
  }
  /* Page-hero size again, not card size. .hero-card h1 shrank it to 36px to
     fit a 400px card; there is no card here and this is the first thing on the
     page. */
  .hero-over .hero-card h1{
    font-size:clamp(34px,4.6vw,58px);line-height:1.06;max-width:15ch;color:#f7f5f3;
  }
  /* The brand orange, matching every other eyebrow on the site, at Craig's
     call and knowing the cost: measured against the composited photograph it
     is about 2.5:1 here and 11px uppercase wants 4.5:1.

     A lighter tint (#ffa88c) was here first and did clear it, at the price of
     being the only eyebrow on the site in its own colour. Deepening the scrim
     was tried instead and does not pay: +0.08 alpha bought +0.24 of contrast,
     because orange this saturated only pulls away from the background once the
     background is nearly black -- 5.32:1 on a fully opaque scrim. Reaching 4.5
     means alpha ~0.97 behind the copy, which is a black rectangle over the
     part of the photograph the header exists to show.

     The h1 and the lede are unaffected and measure 8.78:1 and 6.79:1. */
  .hero-over .hero-card .eyebrow{margin:0 0 14px;color:#e8502a;}
  .hero-over .hero-card .lede{
    margin-top:20px;font-size:17px;line-height:1.55;max-width:52ch;
    color:rgba(247,245,243,0.88);
  }
}
/* Stacked full width, not wrapped. At 330px a primary and a ghost cannot sit
   side by side, and left to .btn-row's wrap they came out as one long pill
   with a short one orphaned underneath it. */
.hero-card .btn-row{margin-top:18px;gap:8px;flex-direction:column;}
.hero-card .btn{padding:11px 20px;font-size:14px;width:100%;}

/* MOBILE IS A DIFFERENT LAYOUT, not a squeezed version of this one.

   A full bleed hero on a phone is a tall box, and cover has to crop a 16:9
   photograph to fit it — at 375px wide only 44% of the image width survives.
   These heroes exist to show a whole building; cropping away more than half of
   one defeats the picture before the card is even considered.

   So below 620px the photograph is shown at its own 16:9 shape, full width and
   uncropped, and the card sits under it on the page rather than over it. The
   building reads end to end, and the copy gets a proper white ground instead of
   competing with whatever is behind it. */
@media (max-width:620px){
  .hero-image{display:block;min-height:0;}
  .hero-bg{position:relative;inset:auto;}
  .hero-bg img{aspect-ratio:16/9;height:auto;}
  /* White, edge to edge. .wrap is full width at this size with the gutter as
     padding, so the background spans the viewport while the text stays on the
     same margin as every section below. Against the --paper ground of the
     entry band underneath it, that gives a clean edge and reads as its own
     section rather than as copy loose on the page. */
  .hero-content{
    background:var(--surface);
    padding:clamp(26px,7vw,38px) 0 clamp(24px,6vw,34px);
  }
  /* NOT A CARD HERE. A card is a thing sitting on something else, and on a
     phone there is nothing behind it to sit on — the photograph has already
     ended. So the treatment comes off entirely and this becomes an ordinary
     full width section on the page ground, in the same gutter as everything
     below it. */
  .hero-card{
    width:100%;
    background:none;border:0;border-radius:0;box-shadow:none;padding:0;
  }
  /* And it can carry a proper headline again: at card size this was set to
     21px, which is right inside a 330px block and undersized as the first
     thing on the page. */
  .hero-card h1{font-size:clamp(27px,7.6vw,32px);}
  .hero-card .lede{font-size:15.5px;margin-top:12px;}
  .hero-card .btn-row{margin-top:22px;}
}

/* A lettered placeholder standing in for the hero photograph. It fills the
   bleed area and drops the card treatment, so the dashed box previews the crop
   the real shot has to fill. Its own letter and note are restated in white
   because the dark scrim above sits over them. */
.hero-bg .ph{
  width:100%;height:100%;
  aspect-ratio:auto;border:0;border-radius:0;
  /* Darker than the usual placeholder grey, and deliberately so. The scrim
     above is tuned for a photograph, not for a near white box: over
     var(--surface-2) the eyebrow measured 2.3:1 and the headline 3.4:1. At
     this tone they measure 5.7:1 and 6.9:1. NOTE when the real photograph
     lands: it has to be dark enough behind the copy to hold those numbers, or
     the scrim needs strengthening for that page. */
  background:#8f8f8f;
}
/* The letter needs position:relative to take a z-index and clear the hatch.
   The note must NOT get one: it is already absolutely positioned to the bottom
   of the figure, and relative here outweighs that and drops it back into the
   flex row beside the letter. */
.hero-bg .ph-letter{color:rgba(255,255,255,0.45);position:relative;z-index:1;}
.hero-bg .ph-note{color:rgba(255,255,255,0.45);z-index:1;}

/* No type modes any more: the copy is on --surface, so it uses the page ink
   like every other section. */

/* ---------- form status ----------
   The line under a submit button that says what happened. role="status" on
   the element, so a screen reader announces it without the focus moving. */
.form-msg{
  margin:14px 0 0;font-size:14.5px;font-weight:500;line-height:1.45;
}
.form-msg.ok{color:var(--ink-soft);}
/* --replace is the site's "this needs attention" red and already clears AA on
   both grounds; a second failure colour would be one more thing to keep in
   step with dark mode. */
.form-msg.bad{color:var(--replace);}

/* ---------- entry points ----------
   The two routes into the range. These were dark translucent cards laid over
   the hero photograph; they now run underneath it as a pair of light panels,
   centred, with the link rendered as a pill. */
/* Bottom gap matches the gap between the carded sections that follow, so the
   entry cards and those panels read as one continuous run. */
.entry-band{padding:clamp(28px,3.5vw,48px) 0 calc(var(--card-gap) / 2);}
.entry-points{
  display:grid;grid-template-columns:repeat(2,1fr);gap:clamp(16px,2vw,24px);
}
.entry{
  display:flex;flex-direction:column;align-items:center;text-align:center;
  gap:10px;text-decoration:none;
  background:var(--surface);border:1px solid var(--line);
  /* Softer than the site radius on purpose. These are the largest panels on
     the page and the standard 10px reads as a hard corner at this size. */
  border-radius:20px;
  padding:clamp(40px,4.5vw,64px) clamp(24px,3vw,48px);
  box-shadow:var(--shadow);
  transition:border-color .15s,box-shadow .15s;
}
.entry:hover{
  border-color:var(--accent-line);
  box-shadow:0 1px 2px rgba(0,0,0,0.05), 0 18px 40px -20px rgba(0,0,0,0.22);
}
/* The brand line above the card heading. Selector has to be .entry .entry-brand,
   not .entry-brand on its own: .entry span below is (0,1,1) on specificity and
   would otherwise win on both size and colour. The negative margin works against
   the card's 10px flex gap to sit the line tight under the heading, the way an
   eyebrow should. */
/* 17px, not the base rule's 11px: every other eyebrow on this page is scaled
   up by its wrapper (.section-head p, .split-body p, .statement-copy) and the
   two card eyebrows were the ones left behind. */
.entry .entry-brand{
  font-size:17px;font-weight:600;letter-spacing:0.18em;
  text-transform:uppercase;color:var(--accent-text);
  max-width:none;margin-bottom:-6px;
}
.entry strong{
  font-size:clamp(26px,2.6vw,36px);font-weight:800;
  letter-spacing:-0.02em;line-height:1.1;
}
.entry span{
  font-size:clamp(16px,1.4vw,19px);color:var(--ink-soft);
  /* Wide enough that the taglines sit on one line on desktop, which is what
     stops the two cards looking ragged next to each other. */
  line-height:1.4;max-width:42ch;
}
.entry .entry-cta{
  margin-top:16px;
  display:inline-flex;align-items:center;justify-content:center;
  padding:12px 28px;border-radius:999px;
  background:var(--accent-strong);color:#fff;
  font-size:15px;font-weight:600;max-width:none;
  transition:background .15s;
}
.entry:hover .entry-cta{background:var(--accent-strong-hover);}

/* The image is the last thing in the card. margin-top:auto pushes it to the
   base, so cards whose taglines wrap to different depths still line their
   images up with each other across the row. The gap below the pill comes from
   the pill rather than the figure, because an auto margin cannot also hold a
   minimum. */
.entry-cta:not(:last-child){margin-bottom:22px;}
.entry-ph{
  width:100%;margin:auto 0 0;
  /* A slightly tighter radius than the card, so the two curves nest. */
  border-radius:14px;
}

/* ---------- what is Nordu ---------- */
/* Not a grid. Every h2 on the site is the same 44px, but boxed into a column
   this 94 character heading wrapped over four lines and read as a smaller
   heading than the full width ones above and below it. Across the full measure
   it sets in two and matches them, with the body copy beneath at a normal
   reading width. */
/* The intro reads as a statement, like home.html's "Why extend": eyebrow, then
   the copy itself, with no heading between them. .statement-copy carries the
   17px eyebrow and lifts the bold opener to --ink; the two rules below are the
   part that block does not need, having only one paragraph and no button. */
.statement-copy .lede + .lede{margin-top:clamp(16px,1.8vw,22px);}
.statement-copy .btn-row{margin-top:24px;}
/* 54ch is the measure for a paragraph sitting beside a photograph. This card
   has nothing in the other half, so the copy takes the width it is given --
   still capped, or the line runs too long to read on a wide screen. */
.intro .statement-copy .lede{max-width:78ch;}
/* ---------- jump tags ----------
   In-page anchors styled as chips. Deliberately not the .filter buttons from
   the projects page: those change what is on screen, these navigate, so they
   are anchors and behave like links for middle click and sharing. */
.tag-row{display:flex;flex-wrap:wrap;gap:10px;margin-top:clamp(24px,3vw,34px);}
/* A second row directly under the first is part of the same block, so it takes
   the chip gap rather than the full space that separates the row from copy. */
.tag-row + .tag-row{margin-top:10px;}
/* A line saying what the chips do. It takes the row's top margin and the row
   then tucks up under it, so hint and chips read as one unit. */
/* --accent-text, not --accent. At 14.5px this is small text, and the brand
   orange measures 2.81:1 on the page, so plain --accent would fail AA here for
   the sake of half a shade. --accent-text is already redefined to --accent in
   dark mode, where the hue clears AA on its own, so the line is the brighter
   orange there without a second rule. */
.tag-hint{
  margin:clamp(24px,3vw,34px) 0 0;
  font-size:14.5px;color:var(--accent-text);max-width:58ch;
}
.tag-hint + .tag-row{margin-top:12px;}
.tag{
  display:inline-flex;align-items:center;
  padding:9px 18px;border-radius:999px;
  border:1px solid var(--line-strong);
  font-size:14px;font-weight:600;text-decoration:none;color:var(--ink-soft);
  transition:border-color .15s,color .15s,background .15s;
}
/* Scoped to a: the same chip is also used as a plain, non-navigating label on
   the size sections, and those must not offer a hover affordance. */
a.tag:hover{border-color:var(--accent);color:var(--ink);background:var(--accent-soft);}
/* The "and above" in 30m2+. It carries meaning the number does not -- this is
   the only band with no upper bound -- so it is worth marking.

   --accent-text for the same reason .tag-hint uses it: 14px is small text and
   plain --accent fails AA on white. It stays orange through a.tag:hover, which
   recolours the anchor to --ink; the span sets its own colour, so the plus
   holds while the rest of the chip darkens. On the hover fill (--accent-soft,
   a 10% orange tint) it still clears AA comfortably. */
.tag-plus{color:var(--accent-text);}
.tag-plain{cursor:default;}
/* A tighter row where the chips are labels rather than navigation. */
.tag-row-sm{gap:8px;margin-top:10px;}
.tag-row-sm .tag{padding:7px 15px;font-size:13.5px;}
/* Matches the .spec dt above it, so "Suits" reads as one more spec row. */
.spec-label{
  margin:22px 0 0;font-size:14.5px;color:var(--ink-muted);
}

/* Without this a tag jump lands with the heading tucked under the sticky
   header. Roughly header height plus a little air. */
.type-section{scroll-margin-top:86px;}

/* ---------- spec list ----------
   Small definition list under the copy in each type section. dl rather than a
   table: it is label and value pairs, not tabular data. */
.spec{
  margin:26px 0 0;padding:18px 0 0;border-top:1px solid var(--line);
  display:grid;gap:11px;
}
.spec > div{display:flex;gap:14px;align-items:baseline;justify-content:space-between;}
.spec dt{font-size:14.5px;color:var(--ink-muted);margin:0;}
.spec dd{
  margin:0;font-size:15px;font-weight:600;color:var(--ink);
  text-align:right;
}

/* ---------- floor plans ----------
   The SVG comes from the configurator's plans.js, which sizes itself by
   viewBox, so the figure just needs to give it a box and let it scale. */
.plan{
  margin:0;background:var(--surface-2);
  border:1px solid var(--line);border-radius:14px;
  padding:clamp(18px,2.2vw,28px);
  display:flex;flex-direction:column;gap:14px;
}
.plan-svg{display:flex;align-items:center;justify-content:center;}
.plan-svg svg{width:100%;height:auto;max-height:340px;display:block;}
.plan figcaption{
  font-size:12px;letter-spacing:0.04em;text-transform:uppercase;
  color:var(--ink-muted);font-weight:600;line-height:1.45;
}
/* Shown until the plan is drawn, and left in place on the one type that has no
   band in the configurator yet. */
.plan-empty{
  display:flex;align-items:center;justify-content:center;
  min-height:200px;border:1px dashed var(--line-strong);border-radius:10px;
  font-size:13px;letter-spacing:0.06em;text-transform:uppercase;
  color:var(--ink-muted);font-weight:600;text-align:center;padding:20px;
}

/* ---------- generic split ---------- */
.split{
  display:grid;grid-template-columns:1fr 1fr;
  gap:clamp(32px,5vw,72px);align-items:center;
}
.split.reverse .split-media{order:-1;}
/* A split that follows a section head starts at the top, not centred.
   align-items:center is right when the split is the whole section and the two
   columns introduce themselves. It is wrong once a heading has already been
   set above them: on about.html the square photograph makes the row 504px
   tall against 374px of copy, so centring dropped the copy 65px and left the
   heading 118px above the paragraph it introduces, level with the middle of
   the picture. Aligned to the top the gap is the section head's own 54px and
   the slack falls under the copy, where it reads as the photograph being the
   taller of the two.

   Adjacent sibling, so it needs the head and the split to be neighbours --
   which is true in exactly one place on the site today. */
.section-head + .split{align-items:start;}
.split-body h2{margin-bottom:18px;}
.split-body p{color:var(--ink-soft);font-size:17px;}
/* A .split-body standing on its own, with no photograph beside it. It is no
   longer half a grid, so without this the paragraphs set right across the card
   at well over a hundred characters a line. The column it lost was doing the
   measure's job; this does it instead. */
.split-body--full{max-width:58ch;}

.tick-list{list-style:none;margin:22px 0 0;padding:0;display:grid;gap:11px;}
.tick-list li{
  position:relative;padding-left:26px;font-size:15.5px;color:var(--ink-soft);
}
.tick-list li::before{
  content:'';position:absolute;left:0;top:9px;
  width:9px;height:9px;border-radius:2px;background:var(--accent);
}

.note-strip{
  margin-top:26px;padding:18px 20px;
  background:var(--surface);border:1px solid var(--line);border-radius:6px;
  font-size:15px;color:var(--ink-soft);
}
.note-strip strong{color:var(--ink);}
/* Ink rather than the accent. Over the strip's own accent wash the accent
   orange measures 3.3:1, which fails AA at this size; ink measures 10.7:1, and
   the underline is what marks it as a link. */
.note-strip a{
  color:var(--ink);font-weight:600;
  text-decoration:underline;text-underline-offset:2px;
}

/* ---------- topic block ----------
   An icon beside a heading and body copy, in the page's ordinary type sizes.
   Used where a section has one subject rather than a numbered sequence. */
.topic{display:flex;gap:clamp(16px,1.6vw,22px);align-items:flex-start;}
.topic-body{max-width:72ch;}
.topic-body h3{margin-bottom:12px;}
.topic-body p{color:var(--ink-soft);font-size:16.5px;}
@media (max-width:620px){
  .topic{gap:14px;}
}

/* ---------- the five, summarised ----------
   The homepage strip. Five short columns, each an icon over a name and a line
   of copy. The full version is the components grid on system.html. */
.five{
  list-style:none;margin:0;padding:0;
  display:grid;grid-template-columns:repeat(5,1fr);gap:clamp(20px,2.4vw,32px);
}
.five-item h3{font-size:17px;font-weight:700;letter-spacing:-0.01em;line-height:1.25;margin-bottom:8px;}
.five-item p{font-size:14.5px;color:var(--ink-muted);line-height:1.5;}
/* One icon treatment, two users: the five strip stacks it above its heading,
   .topic sets it beside one. Only the margin differs, so the rest is shared. */
.icon-badge,
.five-icon{
  display:flex;align-items:center;justify-content:center;
  width:46px;height:46px;flex:none;
  border-radius:12px;
  background:var(--accent-soft);color:var(--accent);
}
.icon-badge svg,
.five-icon svg{width:24px;height:24px;display:block;}
.five-icon{margin-bottom:16px;}

/* ---------- statement card ----------
   A single paragraph with a mark beside it, used once on home.html and once on
   garden.html between the hero and the first real section.

   Two fractions, not `1fr auto`. `auto` was right when the second column was
   an icon sized by its own content; a photograph has no intrinsic size to be
   sized by, so it needs a share of the card instead. The copy keeps the larger
   share -- the paragraph is the section and the photograph is the evidence. */
.statement-card{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,0.85fr);
  gap:clamp(28px,4vw,72px);align-items:center;
}
/* Most eyebrows on this site are not 11px. `.split-body p` and `.section-head
   p` both set 17px, and one class plus one type outweighs the lone class on
   `.eyebrow`, so every eyebrow sitting inside one of those containers is
   quietly scaled up -- which is all of them except the hero card's and this
   one. The statement card has no such rule, so it kept the 11px the base rule
   asks for and ended up the odd one out next to NORDU 1, NORDU 2 and NORDU 3.

   Matching the larger size rather than pulling the others down: those six are
   what the page looks like today and what Craig is comparing against. The
   0.18em tracking scales with the size on its own, so this is the only
   declaration needed. */
.statement-copy .eyebrow{font-size:17px;}

/* The same problem as the rule above, in the case with no wrapper at all.
   "What is Nordu?" on index and "What we build" on home sit straight in a
   .section-card -- no .split-body, .section-head or .statement-copy between
   them and it -- and those wrappers are exactly what quietly supplies the
   17px. So both kept the base 11px and read a size down from every other
   eyebrow on their own page, which is what Craig spotted on index.

   Direct children only, so nothing already sized by a wrapper is touched.
   Inspiration's hero is a card rather than a photograph and is included: the
   hero cards on every other page are 17px now too, so holding it at 11px would
   make it the odd one out rather than the consistent one. */
.carded > .wrap > .section-card > .eyebrow{font-size:17px;}
/* system.html's hero is the one that sits straight in the .wrap, with no card
   and no wrapper of any kind between it and the section, so neither the rule
   above nor .hero-card reaches it. Same 17px, for the same reason. */
.hero > .wrap > .eyebrow{font-size:17px;}
.statement-copy .lede{margin:0;max-width:54ch;}
/* Bold inside a .lede would otherwise be heavy grey: .lede is --ink-soft, so
   the weight lands but the emphasis does not. Lifting it to --ink is what
   .note-strip strong already does for the same reason. */
.statement-copy .lede strong{color:var(--ink);}
.statement-photo{margin:0;}
/* Same hairline and same 14px radius as .tg-slide img, which is the other
   photograph sitting inside a card on this page. aspect-ratio matches the
   file, so it only reserves the box while the image loads -- object-fit is
   there to keep the crop centred if the file is ever recut, not to do the
   cropping. */
.statement-photo img{
  width:100%;height:auto;aspect-ratio:16/9;object-fit:cover;
  border:1px solid var(--line);border-radius:14px;
}
/* Stacked, with the photograph first. Beside 54 characters of copy it has no
   room at this width, and above the paragraph it still opens the section. */
@media (max-width:760px){
  .statement-card{grid-template-columns:1fr;gap:22px;}
  .statement-photo{order:-1;}
}
/* The CTA sits under the strip rather than in it, so the row of five reads as
   one block. */
.five + .btn-row{margin-top:clamp(28px,3vw,40px);}
@media (max-width:1040px){
  /* Five across stops working well before the 900px breakpoint: the names are
     two and three words long and start breaking mid word. */
  .five{grid-template-columns:repeat(3,1fr);}
}
@media (max-width:680px){
  .five{grid-template-columns:repeat(2,1fr);}
}
@media (max-width:460px){
  .five{grid-template-columns:1fr;gap:22px;}
}

/* ---------- size bands ---------- */
.bands{display:grid;gap:14px;}
.band{
  display:grid;grid-template-columns:200px 150px 1fr;gap:24px;align-items:baseline;
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--radius);padding:22px 26px;box-shadow:var(--shadow);
}
.band-name{font-size:18px;font-weight:700;}
.band-dims{
  font-size:15px;font-weight:600;color:var(--ink);
  font-variant-numeric:tabular-nums;
}
.band-dims small{display:block;font-weight:400;color:var(--ink-muted);font-size:13px;margin-top:2px;}
.band-eg{font-size:14.5px;color:var(--ink-muted);}

/* ---------- materials ---------- */
.materials{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;}
.material{
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);
}
.material .ph{border:0;border-bottom:1px dashed var(--line-strong);border-radius:0;}
.material-body{padding:22px 24px;}
.material-body h3{margin-bottom:8px;}
.material-body p{font-size:14.5px;color:var(--ink-muted);}

/* Colour swatches. The group carries role="img" and a label, so a screen
   reader hears one meaningful thing rather than three empty spans, and no
   colour names appear in the visible copy. */
.swatches{display:flex;gap:9px;margin-top:16px;}
.swatch{
  width:22px;height:22px;border-radius:50%;
  background:var(--sw);
  /* The render range includes an off-white, which would otherwise vanish
     against the card. The ring is what keeps every tone legible as a chip. */
  border:1px solid rgba(0,0,0,0.18);
  /* An inner highlight so the darker tones read as a surface rather than a
     hole punched in the card. */
  box-shadow:inset 0 1px 0 rgba(255,255,255,0.28);
}

/* ---------- cards / components ---------- */
.cards{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;}
.card{
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--radius);padding:28px;box-shadow:var(--shadow);
}
.card h3{margin-bottom:10px;}
.card p{font-size:15px;color:var(--ink-soft);}
.card .num{
  display:block;font-size:12px;font-weight:700;letter-spacing:0.14em;
  color:var(--accent-text);margin-bottom:14px;
}
/* A mark beside the title, on about.html's two discipline cards only.

   The colour is on the icon, not the words. Orange headings were tried and the
   title is the thing being read -- it wants the page's ink. The mark carries
   the accent instead, which is the same division .card .num above already
   makes on system.html.

   NOT on .card h3 generally, for the same reason: those cards already have
   .num doing this job, and a number and an icon in one 28px box is two marks
   for one purpose.

   Flex on the h3 rather than an inline span, so a title that wraps keeps its
   second line aligned with the first instead of running back under the icon.
   flex:none stops the 22px box being squeezed by a long word beside it. */
.cards-accent .card h3{display:flex;align-items:center;gap:10px;}
.card-icon{flex:none;color:var(--accent-text);display:block;}
.card-icon svg{display:block;width:22px;height:22px;}

.steps{display:grid;gap:0;counter-reset:step;}
.step{
  display:grid;grid-template-columns:64px 1fr;gap:24px;
  padding:26px 0;border-top:1px solid var(--line);
}
.step:last-child{border-bottom:1px solid var(--line);}
.step-n{
  font-size:13px;font-weight:700;letter-spacing:0.1em;color:var(--accent-text);
  padding-top:5px;
}
.step h3{margin-bottom:8px;}
.step p{font-size:15.5px;color:var(--ink-soft);max-width:62ch;}

/* ---------- configurator band ---------- */
/* Dark, so it reads as a distinct step rather than another content section.
   Rescopes the ink variables locally to flip this one band without touching
   the page around it. The header used to do the same and no longer does: it
   inherits the page's ink now, so this is the only place left using the
   trick. */
.cta-band{
  background:#232323;
  --ink:#ffffff;
  --ink-soft:rgba(255,255,255,0.78);
  --ink-muted:rgba(255,255,255,0.62);
  --line-strong:rgba(255,255,255,0.34);
  color:var(--ink);
}
.cta-band-inner{
  display:grid;grid-template-columns:1.25fr 1fr;
  gap:clamp(32px,5vw,64px);align-items:center;
}
.cta-band-copy h2{margin-bottom:16px;}
.cta-band-copy p{color:var(--ink-soft);font-size:17px;max-width:52ch;}
/* Side by side and sized by their labels, like every other .btn on the page --
   no stretch to the column. They stack again below 860px, where the band is
   one column and a row of two would put both labels on two lines each. */
.cta-band-actions{display:flex;flex-wrap:wrap;gap:12px;}
@media (max-width:860px){
  .cta-band-inner{grid-template-columns:1fr;gap:28px;}
  .cta-band-actions{flex-direction:column;align-items:flex-start;}
}

/* ---------- showroom CTA ---------- */
.showroom{
  background:var(--surface);border:1px solid var(--line);
  border-radius:14px;overflow:hidden;box-shadow:var(--shadow);
  display:grid;grid-template-columns:1fr 1fr;align-items:stretch;
}
.showroom .ph{border:0;border-radius:0;height:100%;min-height:340px;}
/* The real photograph, in the same slot the lettered placeholder used. The
   .showroom grid stretches its children, so the image just has to fill and
   crop rather than letterbox. */
.showroom-img{width:100%;height:100%;min-height:340px;object-fit:cover;display:block;}
.showroom-body{padding:clamp(32px,4vw,56px);align-self:center;}
.showroom-body h2{margin-bottom:16px;}
.showroom-body p{color:var(--ink-soft);font-size:17px;}
/* The call to action sits between the body copy and the buttons, so it reads as
   the prompt to act rather than as another line of description. Selector needs
   both classes: `.showroom-body p` above outweighs a lone `.showroom-cta` and
   would drag the colour back to the soft grey. */
.showroom-body .showroom-cta{
  margin-top:14px;color:var(--ink);
  font-weight:700;font-size:18px;
}

/* ---------- projects ---------- */
.filter-row{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:32px;}
/* Inside the Projects header card the filters are the last thing in it, so the
   margin that used to separate them from the grid below becomes dead space at
   the foot of the card. They want room above instead. */
.section-card > .filter-row{margin:clamp(26px,3vw,36px) 0 0;}
.filter{
  background:transparent;border:1px solid var(--line-strong);
  border-radius:999px;padding:9px 20px;color:var(--ink-muted);
  font-size:14px;font-weight:600;cursor:pointer;transition:all .15s;
}
.filter:hover{color:var(--ink);}
/* --accent-strong, the same orange .btn-primary uses: the brand --accent is
   too light behind white text at 14px. */
.filter.active{
  background:var(--accent-strong);border-color:var(--accent-strong);color:#fff;
}

.projects{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;}
.project{
  background:var(--surface);border:1px solid var(--line);
  border-radius:var(--radius);overflow:hidden;box-shadow:var(--shadow);
  /* Column, so the CTA at the foot of each card can be pushed to the bottom
     and the buttons across a row line up whatever length the meta list is. */
  display:flex;flex-direction:column;
}
.project .ph{border:0;border-bottom:1px dashed var(--line-strong);border-radius:0;}
.project-body{padding:22px 24px;display:flex;flex-direction:column;flex:1;}
.project-line{
  font-size:11px;font-weight:700;letter-spacing:0.14em;text-transform:uppercase;
  color:var(--accent-text);margin-bottom:10px;
}
.project-body h3{font-size:20px;margin-bottom:12px;}
.project-meta{
  list-style:none;margin:0;padding:0;display:grid;gap:5px;
  font-size:14px;color:var(--ink-muted);
}
.project-meta b{color:var(--ink-soft);font-weight:600;}
/* margin-top:auto is what does the aligning: the meta list takes the slack in
   a short card, so every CTA sits on the same line as its neighbours'. */
.project-cta{margin:auto 0 0;padding-top:20px;}
.project-cta .btn{width:100%;}

/* ---------- forms ---------- */
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;}
.field{display:flex;flex-direction:column;}
.field.wide{grid-column:1 / -1;}
.field label{
  font-size:11px;font-weight:600;letter-spacing:0.1em;text-transform:uppercase;
  color:var(--ink-muted);margin-bottom:8px;
}
.field input,.field select,.field textarea{
  background:var(--surface);border:1px solid var(--line-strong);border-radius:7px;
  padding:13px 15px;font-family:inherit;font-size:15px;color:var(--ink);
  outline:none;transition:border-color .15s,box-shadow .15s;
}
.field textarea{resize:vertical;min-height:120px;line-height:1.5;}
.field input:focus,.field select:focus,.field textarea:focus{
  border-color:var(--accent);box-shadow:0 0 0 3px var(--accent-soft);
}
.consent{
  display:flex;gap:12px;align-items:flex-start;margin-top:22px;
  font-size:14px;color:var(--ink-soft);line-height:1.5;
}
.consent input{
  margin:3px 0 0;width:17px;height:17px;accent-color:var(--accent);flex:none;
}
/* The visit box, orange. It is the one line in the form that is an offer
   rather than an instruction, and .consent's --ink-soft grey filed it with
   the privacy line underneath as though both were small print.

   --accent-text, not --accent: 14px is small text, where the brand orange
   measures 3.74:1 on white and fails AA. This gives 6.02:1 there and resolves
   to the brighter --accent in dark mode, which clears AA on its own at 4.55:1.
   Scoped to the span so the tick itself keeps accent-color above. */
.consent-visit span{color:var(--accent-text);}
/* The visit box now sits inside the form grid, above the timing field it
   governs. Full width like the fields around it, and without .consent's own
   top margin, which was spacing against a paragraph rather than against an
   18px grid gap. */
.form-grid > .consent{grid-column:1 / -1;margin-top:0;}

.contact-grid{display:grid;grid-template-columns:1.15fr 1fr;gap:clamp(32px,5vw,64px);align-items:start;}
.panel{
  background:var(--surface);border:1px solid var(--line);
  border-radius:14px;padding:clamp(28px,3.5vw,44px);box-shadow:var(--shadow);
}
.panel.accent{border-color:var(--accent-line);}
/* h1 too. The enquire card's heading became an h1 when the page lost its hero
   -- the page needs exactly one and that was the only one -- and this rule
   named h2 alone, so the headline ended up sitting flat on its paragraph with
   no gap at all. Whichever level the card's heading carries, it needs the same
   14px underneath it. */
.panel h1,
.panel h2{margin-bottom:14px;}
.panel > p{color:var(--ink-soft);}

.contact-list{list-style:none;margin:0;padding:0;display:grid;gap:22px;}
.contact-list h4{margin-bottom:5px;}
.contact-list p{font-size:14.5px;color:var(--ink-muted);}
.contact-list a{color:var(--accent-text);text-decoration:none;font-weight:600;}
.contact-list a:hover{text-decoration:underline;}
/* The showroom map. line-height:0 on the figure because an iframe is inline by
   default and would otherwise sit on a text baseline, leaving a few pixels of
   background under it inside the rounded corner.

   NOT FILTERED FOR DARK MODE. A map is a map: inverting or dimming it makes the
   road names and the pin harder to read, which is the one thing it is here to
   do. The border and the 12px radius are what stop a bright panel looking
   pasted onto a dark page. */
.map{
  margin:12px 0 0;line-height:0;
  border:1px solid var(--line);border-radius:12px;overflow:hidden;
}
.map iframe{display:block;width:100%;height:220px;border:0;}

/* ---------- footer ---------- */
.site-foot{
  border-top:1px solid var(--line);padding:64px 0 40px;margin-top:40px;
}
.foot-grid{
  display:grid;grid-template-columns:2fr 1fr 1fr 1fr;gap:40px;margin-bottom:48px;
}
.foot-grid h4{
  font-size:11px;letter-spacing:0.14em;text-transform:uppercase;
  color:var(--ink-muted);margin-bottom:16px;font-weight:600;
}
.foot-grid ul{list-style:none;margin:0;padding:0;display:grid;gap:10px;}
.foot-grid a{
  font-size:14.5px;text-decoration:none;color:var(--ink-soft);transition:color .15s;
}
.foot-grid a:hover{color:var(--ink);}
.foot-blurb{font-size:14.5px;color:var(--ink-muted);max-width:38ch;margin-top:16px;}
.foot-base{
  display:flex;justify-content:space-between;gap:20px;flex-wrap:wrap;
  padding-top:26px;border-top:1px solid var(--line);
  font-size:13px;color:var(--ink-muted);
}

/* ---------- responsive ---------- */
@media (max-width:1000px){
  .materials,.cards,.projects{grid-template-columns:repeat(2,1fr);}
  .foot-grid{grid-template-columns:1fr 1fr;gap:32px;}
}
@media (max-width:860px){
  .nav{
    display:none;position:absolute;left:0;right:0;top:100%;
    flex-direction:column;align-items:stretch;gap:0;
    /* Follows the bar above it rather than carrying its own dark scheme, so
       the two read as one surface in both themes. It needs a solid background
       rather than the header's glass because page content scrolls underneath
       it, and a translucent dropdown over moving text is unreadable. */
    background:var(--surface);
    border-bottom:1px solid var(--line);
    box-shadow:0 12px 24px -14px rgba(0,0,0,0.30);
    padding:12px 28px 22px;
  }
  .nav.open{display:flex;}
  .nav a{padding:13px 0;font-size:16px;border-bottom:1px solid var(--line);}
  .nav .btn{margin-top:16px;border-bottom:0;justify-content:center;}
  .nav-toggle{display:block;}
  /* The nav is out of flow here, so its auto margin does nothing and the job
     of pushing the right hand group across falls to this. */
  .theme-toggle{margin-left:auto;}
  .site-head{position:relative;}
  .split,.showroom,.contact-grid{grid-template-columns:1fr;}
  .split.reverse .split-media{order:0;}
  .showroom .ph{min-height:260px;}
  .band{grid-template-columns:1fr;gap:10px;}
  .band-dims small{display:inline;margin-left:8px;}
  .entry-points{grid-template-columns:1fr;}
}
@media (max-width:620px){
  .wrap,.head-inner{padding-left:20px;padding-right:20px;}
  .materials,.cards,.projects{grid-template-columns:1fr;}
  .form-grid{grid-template-columns:1fr;}
  .foot-grid{grid-template-columns:1fr;}
  .step{grid-template-columns:1fr;gap:8px;}
  .btn{width:100%;}
  .btn-row{flex-direction:column;}
}
@media (prefers-reduced-motion:reduce){
  html,.tg-track{scroll-behavior:auto;}
  *,*::before,*::after{animation-duration:0.01ms !important;transition-duration:0.01ms !important;}
}


/* ---------- the type gallery ----------
   Two finishes and the plan, in the media column of each product card on the
   Extensions and Garden pages. The point is that the same building comes in
   more than one skin, which a single photograph cannot say.

   A SCROLL SNAP TRACK, not a scripted carousel. The whole thing works with
   JavaScript switched off: it is a horizontally scrollable strip that snaps,
   so a touch swipe or a trackpad already moves it and the other two slides
   are reachable. site.js only adds the arrows and the dots on top, and if it
   never runs nothing is lost but the buttons.

   That ordering matters on this site, where the nav and the footer are static
   HTML for the same reason. A carousel built the usual way, with slides
   positioned by script, shows one image and hides two from anyone without it.

   .plan keeps its own class alongside .tg-plan so plans.js still finds it by
   [data-plan] and its panel styling is untouched. */
.type-gallery{position:relative;}

.tg-track{
  display:flex;gap:var(--card-gap);
  overflow-x:auto;overscroll-behavior-x:contain;
  scroll-snap-type:x mandatory;scroll-behavior:smooth;
  /* The scrollbar is redundant once the dots are there, and on a track this
     short it sits awkwardly under the caption. Swipe and the buttons both
     still work without it. */
  scrollbar-width:none;-ms-overflow-style:none;
}
.tg-track::-webkit-scrollbar{display:none;}

.tg-slide{
  flex:0 0 100%;scroll-snap-align:start;margin:0;
  /* min-width:0 stops a flex item refusing to shrink below its content, which
     is what makes a 100% basis actually mean 100% of the track. */
  min-width:0;
}
.tg-slide img{
  width:100%;height:auto;display:block;
  border:1px solid var(--line);border-radius:14px;
}
.tg-slide figcaption{
  margin-top:9px;font-size:12px;letter-spacing:0.04em;text-transform:uppercase;
  color:var(--ink-muted);font-weight:600;line-height:1.45;
}
/* The plan now has the column to itself on its own slide, so it can be drawn
   at a readable size again. */
.tg-plan .plan-svg svg{max-height:250px;}

/* ---- controls, added by site.js and absent without it ---- */
.tg-controls{
  display:flex;align-items:center;gap:14px;
  margin-top:14px;
}
.tg-btn{
  flex:none;width:34px;height:34px;border-radius:999px;
  border:1px solid var(--line-strong);background:var(--surface);
  color:var(--ink);cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  transition:border-color .15s,color .15s,opacity .15s;
}
.tg-btn:hover:not(:disabled){border-color:var(--ink);}
.tg-btn:disabled{opacity:0.35;cursor:default;}
.tg-btn svg{width:15px;height:15px;}

.tg-dots{display:flex;gap:7px;margin-left:auto;}
.tg-dot{
  width:7px;height:7px;padding:0;border:0;border-radius:999px;
  background:var(--line-strong);cursor:pointer;transition:background .15s;
}
.tg-dot[aria-current="true"]{background:var(--accent);}
.tg-dot:hover{background:var(--ink-muted);}

@media (max-width:900px){
  .tg-plan .plan-svg svg{max-height:280px;}
}

/* ---------- a placeholder with a picture in it ----------
   The .ph classes stay on the figure so every contextual override keeps
   working (.project .ph, .material .ph, .showroom .ph each restyle the box).
   This only strips the things that exist to say "no photograph yet": the
   dashed rule, the tinted panel and the diagonal hatch.

   Deliberately late in the file. Those context rules are (0,2,0) and set
   borders of their own, so .ph.ph-filled has to match on specificity and win
   on order rather than out specify them.

   NOTE: everything currently in a .ph-filled is AI generated placeholder
   imagery for the demo. See the warning at the top of inspiration.html. */
.ph.ph-filled{background:none;border:0;}
.ph.ph-filled::after{content:none;}
.ph-filled > img{
  width:100%;height:100%;display:block;object-fit:cover;
}

/* ==========================================================================
   DARK MODE
   Everything here hangs off one attribute on <html>. There is deliberately no
   `@media (prefers-color-scheme:dark)` copy of this block: the inline script in
   each page's <head> resolves the operating system preference into
   data-theme before the first paint, so the media query would only ever be a
   second, drift-prone statement of the same thing. Without JavaScript the site
   stays light, which is what it did before dark mode existed and is coherent
   with the toggle itself being hidden there.

   Almost all of it is the token block. Below that are the handful of places
   where a literal colour was baked into a component because the light page was
   the only page there was.
   ========================================================================== */
:root[data-theme="dark"]{
  /* Warm neutral, to match the warm neutral greys of the light palette rather
     than dropping to a blue black. Three steps: page, card, recessed. */
  --paper:#121212;
  --surface:#1c1c1c;
  --surface-2:#262626;
  /* Not pure white. #f2f0ee at 15:1 on a card is already far past AA, and pure
     white on near black is what makes long paragraphs shimmer. */
  --ink:#f2f0ee;
  --ink-soft:#b8b5b1;   /* 8.3:1 on --surface */
  --ink-muted:#8f8c88;  /* 5.1:1 on --surface, so captions still clear AA */
  --line:#2f2f2f;
  --line-strong:#454545;

  /* The accent does not move. It is the logo colour, and it measures 4.6:1 as
     text on --surface and 5.0:1 on --paper, so it still clears AA where it is
     used for the small uppercase eyebrows and link text. The washes have to
     open up though: a 10% tint of anything is invisible against #1c1c1c. */
  --accent-soft:rgba(232,80,42,0.16);
  --accent-line:rgba(232,80,42,0.55);
  /* --accent-strong is not redefined here: see the note on it in the light
     :root. --accent-text reverts to the plain accent, which already clears
     AA as text against both dark surfaces. */
  --accent-text:var(--accent);

  /* A shadow tuned for a light page all but disappears here. Deeper and wider,
     with the borders doing more of the work of separating a card from the
     page than they do in light mode. */
  --shadow:0 1px 2px rgba(0,0,0,0.5), 0 10px 28px -14px rgba(0,0,0,0.9);

  color-scheme:dark;
}

/* The sun replaces the moon once the page is dark. */
:root[data-theme="dark"] .theme-toggle .icon-dark{display:none;}
:root[data-theme="dark"] .theme-toggle .icon-light{display:block;}

/* Dark glass rather than white. The ink inside the bar needs no override: the
   header inherits the page's, which has already flipped by this point. Same
   alpha as the light bar, and the worst case is easier here because light text
   over a dark tint gains contrast from exactly the dark photo areas that cost
   the light bar its margin. */
:root[data-theme="dark"] .site-head::before{
  background:rgba(18,18,18,0.85);
  box-shadow:inset 0 -1px 0 rgba(255,255,255,0.12);
}
:root[data-theme="dark"] .theme-toggle:hover{background:rgba(255,255,255,0.08);}

/* The hatch is a black wash over a light box. Inverted, at a slightly higher
   alpha because a light film over near black reads weaker than the reverse. */
:root[data-theme="dark"] .ph::after{
  background:repeating-linear-gradient(135deg,
    rgba(255,255,255,0.035) 0 12px, transparent 12px 24px);
}

/* Light mode puts a dark band on a light page to mark the configurator as a
   step out of the reading flow. Here it goes the other way and sinks below the
   page instead, held apart by hairlines rather than by tone.

   Not lighter than the page, which was the first attempt. The band carries an
   accent eyebrow, and against a lifted #2b2b2b that measured 3.78:1 and failed
   AA. Against this it measures 5.2:1, which is better than the 4.2:1 the same
   line manages on the light page's own band. */
:root[data-theme="dark"] .cta-band{
  background:#0b0b0b;
  border-top:1px solid var(--line);
  border-bottom:1px solid var(--line);
}

/* The floor plans are drawn by the configurator's plans.js as black line work
   on a white sheet, and those colours are baked into the SVG. Rather than
   fork the drawing code, the plan is given a paper backdrop and reads as a
   drawing pinned to a dark card, which is what it is. */
:root[data-theme="dark"] .plan-svg{
  background:#e6e4e1;border-radius:8px;padding:14px;
}

/* The ring keeps the palest render tone legible against the card. On a dark
   card it is the darkest timber tone that needs the ring instead. */
:root[data-theme="dark"] .swatch{
  border-color:rgba(255,255,255,0.24);
  box-shadow:inset 0 1px 0 rgba(255,255,255,0.18);
}

:root[data-theme="dark"] .entry:hover{
  box-shadow:0 1px 2px rgba(0,0,0,0.5), 0 20px 44px -22px rgba(0,0,0,1);
}

/* ---------- first visit hint ----------
   A one-off nudge at the theme button, shown to someone who has never set a
   preference. Built by script and never present otherwise, so it cannot be
   left behind in the markup.

   Blue on purpose, and the only blue on either site. It reads as chrome
   rather than as content, which is what a one-off onboarding nudge is: in the
   brand orange it would look like a promotion. Change --hint-bg if you would
   rather it were the accent.

   Positioned against .head-inner, which is already position:relative, with
   the exact offsets set by script. The button sits at the end of the row on
   desktop and in the middle of it on mobile, so a fixed right value cannot
   serve both. */
.theme-hint{
  /* Measured, not picked. At #1d6fe0 the softer second line came out at
     3.96:1 and failed AA. This tone puts the heading at 6.0:1, the second
     line at 4.9:1 and the close control at 4.1:1, all clear. */
  --hint-bg:#1a5fc4;
  position:absolute;top:100%;z-index:70;
  margin-top:11px;max-width:min(268px,calc(100vw - 32px));
  background:var(--hint-bg);color:#fff;
  border-radius:11px;padding:13px 15px;
  box-shadow:0 10px 28px -10px rgba(0,0,0,0.45);
  font-size:13.5px;line-height:1.45;
  display:flex;gap:11px;align-items:flex-start;
  animation:hint-in .28s ease both;
}
@keyframes hint-in{from{opacity:0;transform:translateY(-5px);}to{opacity:1;transform:none;}}
/* The pointer. --arrow is the distance from the bubble's right edge to the
   centre of the button, set by script. */
.theme-hint::before{
  content:'';position:absolute;top:-6px;right:var(--arrow,20px);
  width:12px;height:12px;background:var(--hint-bg);
  transform:rotate(45deg);border-radius:2px;
}
.theme-hint b{display:block;font-weight:700;margin-bottom:2px;}
.theme-hint span{color:rgba(255,255,255,0.86);}
.theme-hint-close{
  flex:none;background:none;border:0;padding:2px;margin:-2px -3px 0 0;
  color:rgba(255,255,255,0.75);cursor:pointer;line-height:0;
  border-radius:4px;
}
.theme-hint-close:hover{color:#fff;}
.theme-hint-close svg{width:14px;height:14px;display:block;}
@media (prefers-reduced-motion:reduce){
  .theme-hint{animation:none;}
}
