﻿/*
Theme Name: OBILIS House
Theme URI: https://obilis.co/
Author: Obilis Consultant and Development
Description: A responsive, SEO-ready WordPress theme for OBILIS House and residential construction businesses.
Version: 2.50.1
Requires at least: 6.0
Tested up to: 6.6
Requires PHP: 7.4
License: GNU General Public License v2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: obilis-homes
Tags: custom-logo, custom-menu, featured-images, translation-ready, blog, portfolio
*/

/* ==========================================================================
   1. Tokens
   ========================================================================== */

:root {
  --forest: #1b3a2a;
  --forest-dark: #122b1e;
  --forest-deep: #0e2418;
  --ink: #18312b;
  --gold: #c9a24a;
  --gold-light: #e0c872;
  --gold-dark: #ab8636;
  --cream: #f7f3e9;
  --cream-soft: #fbf8f1;
  --sand: #efe8da;
  --white: #fff;
  --muted: #6b7873;
  --line: #e4e9e5;

  /* Sampled from the mark itself, not guessed from --forest: the logo's field
     is an olive green, a good deal warmer and darker than the theme's forest.
     Deliberately outside the style presets — the brand colour is the brand's,
     whichever palette the site is wearing. */
  --brand-green: #273c04;

  /* Newsreader has no Thai or CJK glyphs, so each script gets its own serif
     before the generic fallback. */
  --serif: "Newsreader", "Noto Serif Thai", "Songti SC", "Noto Serif SC", Georgia, serif;
  --sans: "Inter", "Noto Sans Thai", -apple-system, BlinkMacSystemFont, "Segoe UI", Tahoma, sans-serif;

  --radius: 4px;
  --radius-lg: 10px;
  --shadow: 0 18px 45px rgba(20, 48, 41, .09);
  --header-h: 78px;
  --topbar-h: 34px;
  --container: 1360px;

  /* Design tuning ---------------------------------------------------------
     Six knobs, all in Customize → Design. The values below are the design as
     drawn; inc/design-tuning.php re-declares only the ones that were moved.

     They are multipliers rather than replacement sizes on purpose: every
     spacing they touch keeps its ratio to every other one, so a slider can
     make the page tighter or airier but cannot pull the layout out of
     proportion. Each is clamped to a range that still looks like this theme. */
  --tune-text: 1;      /* type scale, .9–1.15                */
  --tune-leading: 1.7; /* body line-height, 1.45–1.9         */
  --tune-space: 1;     /* space between sections, .7–1.2     */
  --tune-density: 1;   /* padding inside cards, .7–1.2       */
  --tune-hero: 82;     /* hero height in vh, 55–90           */
}

/* ==========================================================================
   2. Base
   ========================================================================== */

* { box-sizing: border-box; }

/* Every font size in this file bar one is in rem, so the whole type scale —
   body copy, headings and the clamp() minimums alike — moves together from
   this single line. */
html { scroll-behavior: smooth; font-size: calc(100% * var(--tune-text)); }

body {
  margin: 0;
  background: var(--white);
  color: var(--ink);
  font-family: var(--sans);
  font-size: .9375rem;   /* 15px at the default text scale */
  line-height: var(--tune-leading);
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }
a { color: inherit; }
button { font: inherit; }

h1, h2, h3, h4 {
  font-family: var(--serif);
  font-weight: 400;
  line-height: 1.12;
  letter-spacing: -.01em;
  margin: .3em 0;
}

h1 { font-size: clamp(2.8rem, 6vw, 5rem); }
h2 { font-size: clamp(2rem, 3.6vw, 3.1rem); }
h3 { font-size: 1.3rem; }

.container { width: min(var(--container), calc(100% - 56px)); margin-inline: auto; }
.center { text-align: center; }

.eyebrow {
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: .22em;
  font-weight: 700;
  font-size: .68rem;
  color: var(--gold);
  margin: 0 0 14px;
}

.screen-reader-text {
  position: absolute !important;
  width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0);
  white-space: nowrap;
}

:where(a, button, [tabindex]):focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 3px;
}

/* ==========================================================================
   3. Buttons
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 13px 26px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--forest);
  color: var(--white);
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-decoration: none;
  transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.btn:hover { background: var(--forest-dark); }

.btn-gold { background: var(--gold); color: var(--forest-deep); }
.btn-gold:hover { background: var(--gold-dark); color: var(--white); }

.btn-ghost {
  background: rgba(255, 255, 255, .82);
  color: var(--ink);
  border-color: rgba(24, 49, 43, .18);
  padding-left: 8px;
}
.btn-ghost:hover { background: var(--white); }

.btn-ghost .play {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--forest);
  color: var(--white);
}

.link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: .8rem;
  font-weight: 600;
  letter-spacing: .04em;
  text-decoration: none;
  color: var(--forest);
  border-bottom: 1px solid var(--gold);
  padding-bottom: 3px;
}
.link-arrow:hover { color: var(--gold-dark); }

/* ==========================================================================
   4. Top bar
   ========================================================================== */

.topbar {
  background: var(--forest-deep);
  color: rgba(255, 255, 255, .82);
  font-size: .7rem;
  letter-spacing: .04em;
}
.topbar .container {
  min-height: var(--topbar-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}
.topbar-note { margin: 0; }
.topbar-values { margin: 0; color: rgba(255, 255, 255, .62); }

/* ==========================================================================
   5. Header
   ========================================================================== */

/* --- The bar, and when it draws itself -----------------------------------
   The header is clear at the top of every page and paints itself only once the
   page has been scrolled — `.is-stuck`, set by theme.js. Where a page opens on
   a photograph it is pulled up behind the bar (see `.hero` and `.style-hero`)
   so the clear state has something to be clear against; the pages that used to
   open on a cream band no longer carry one, and open on white.

   The pull-up is measured, not assumed. --header-real is written by theme.js
   from the header's actual height, because at narrow widths the row wraps and
   the Thai menu makes it wrap sooner than the English one; using the nominal
   --header-h would leave a seam above the picture. */

.site-header {
  position: sticky;
  top: 0;
  z-index: 40;
  background: transparent;
  border-bottom: 1px solid transparent;
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
  transition: background .3s ease, border-color .3s ease, backdrop-filter .3s ease;
}

/* Solid once it is a bar. Half-white with a blur behind it let the photograph
   read straight through the row — worse still with the inset hero, where the
   picture slides under the bar and the page shows white either side of it, so
   the bar looked like a gap. The page's own white, and nothing behind it. */
.site-header.is-stuck {
  background: var(--white);
  border-bottom-color: var(--line);
  -webkit-backdrop-filter: none;
          backdrop-filter: none;
}

/* A whisper of a scrim rather than a bar. The hero photograph is editable, and
   the menu is dark green: this is what keeps it legible if someone uploads a
   picture that is dark across the top. Delete this rule for a header with
   nothing at all behind it. */
.site-header::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg, rgba(255, 255, 255, .38), rgba(255, 255, 255, 0));
  transition: opacity .3s ease;
}
.site-header.is-stuck::after { opacity: 0; }

.has-hero .hero { margin-top: calc(var(--header-real, var(--header-h)) * -1); }
.has-hero .hero-copy { padding-top: calc(52px + var(--header-real, var(--header-h))); }

/* An opened mobile menu needs something to sit on, wherever the page is — and
   on every page, not just the ones with a hero: at the top of the page the bar
   is clear, and a column of links over the article behind it is unreadable. */
.site-header:has(.main-nav.is-open) {
  background: var(--white);
  border-bottom-color: var(--line);
}
.site-header:has(.main-nav.is-open)::after { opacity: 0; }

.nav-wrap {
  min-height: var(--header-h);
  display: flex;
  align-items: center;
  gap: 26px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
  text-decoration: none;
  margin-right: auto;
  transition: opacity .35s ease, transform .35s ease;
}

/* --- The brand over the opening photograph -------------------------------
   On the front page the logo sits out of the first slide and comes back the
   moment anything moves — the page being scrolled, or the slideshow going on
   to the next slide. `.is-over-hero` is set by theme.js and only ever added,
   so a visitor with JS switched off gets the logo where it has always been.

   Hidden by visibility rather than display: the row keeps the space, so
   nothing else in the header shifts as it comes and goes — and a hidden brand
   drops out of the tab order rather than sitting there as an invisible link. */
.site-header.is-over-hero .brand {
  opacity: 0;
  visibility: hidden;
  transform: translateY(-6px);
  transition: opacity .35s ease, transform .35s ease, visibility 0s linear .35s;
}
.brand img, .brand .custom-logo { width: 34px; height: auto; }
.brand-text { display: grid; }
.brand-name {
  font-family: var(--serif);
  font-size: 1.22rem;
  font-weight: 600;
  letter-spacing: .14em;
  line-height: 1.1;
}
.brand-sub {
  font-size: .52rem;
  font-weight: 600;
  letter-spacing: .2em;
  color: var(--muted);
  text-transform: uppercase;
}

.main-nav ul {
  display: flex;
  align-items: center;
  gap: 26px;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Thai labels carry no spaces, so the browser breaks them mid-word and a menu
   item silently becomes two lines. Hold every item on one line instead and
   take the room back from the gaps as the window narrows. */
.main-nav a {
  text-decoration: none;
  white-space: nowrap;
  font-size: .78rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--ink);
  padding: 6px 0;
  border-bottom: 1px solid transparent;
  transition: color .2s ease, border-color .2s ease;
}
.main-nav a:hover { color: var(--forest); border-bottom-color: var(--gold); }

.btn-arrow { display: none; }        /* the phone mock's arrow; see the hero rules */

.header-cta { padding: 12px 22px; white-space: nowrap; }

/* The glyph carries the button on its own: a box around it is one more line on
   a bar that already has a logo, a language switcher and a rule under it. The
   tap target is held at 44px by the padding rather than by a border. */
.menu-toggle {
  display: none;
  border: 0;
  background: none;
  padding: 8px 10px;
  margin-right: -10px;         /* the padding, pulled back off the container edge */
  font-size: 1.15rem;
  line-height: 1;
  color: var(--ink);
  cursor: pointer;
  transition: color .2s ease;
}
.menu-toggle:hover { color: var(--forest); }

/* Open and closed are the same button, so the mark swaps rather than the label:
   the ✕ is the only affordance that says the sheet closes again. Both glyphs are
   aria-hidden and the button keeps its "Menu" name for screen readers. */
.menu-toggle-icon { display: inline-block; width: 1em; text-align: center; }
.menu-toggle-icon.is-close { display: none; }
.menu-toggle[aria-expanded="true"] .menu-toggle-icon { display: none; }
.menu-toggle[aria-expanded="true"] .menu-toggle-icon.is-close { display: inline-block; }

/* The menu sheet's entrance; it is only ever used under 980px. Kept here beside
   the button that triggers it rather than inside the media query. */
@keyframes obilis-nav-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .main-nav.is-open { animation: none; }
}

/* ==========================================================================
   6. Hero
   ========================================================================== */

.hero {
  position: relative;
  min-height: min(calc(var(--tune-hero) * 1vh), 720px);
  display: grid;
  /* The copy rides in the sky at the top of the frame, so the house itself is
     never behind the headline. */
  align-items: start;
  background: var(--hero, none) center / cover no-repeat;
}
/* The wash only has to carry the dark-green headline, so it is kept to a soft
   halo: narrower across (gone by 56% instead of 74%) and masked away towards
   the bottom, which hands the garden and the front of the house back to the
   photo. */
.hero::before,
.hero-slide::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    rgba(249, 246, 238, .93) 0%,
    rgba(249, 246, 238, .84) 18%,
    rgba(249, 246, 238, .52) 32%,
    rgba(249, 246, 238, .16) 45%,
    rgba(249, 246, 238, 0) 56%);
  -webkit-mask-image: linear-gradient(180deg,
    #000 0%, #000 52%, rgba(0, 0, 0, .42) 74%, rgba(0, 0, 0, 0) 92%);
          mask-image: linear-gradient(180deg,
    #000 0%, #000 52%, rgba(0, 0, 0, .42) 74%, rgba(0, 0, 0, 0) 92%);
}
.hero .container { position: relative; }
.hero-copy { max-width: 560px; padding: 52px 0 96px; }
/* The size of a hero headline follows the script it is written in, not the
   language of the page: the brand's English slogan sits on the Thai front page,
   and one slideshow serves all three languages, so Thai headlines appear on the
   English page too. obilis_text_script() picks the class. */
/* The nine steps below hand their size to --title-size rather than setting
   font-size, so one rule can multiply all of them by a slide's own text size.
   --title-scale is only ever set on desktop: on a phone the size comes from the
   mobile slider, which is already folded into --title-size there. */
.hero-title {
  color: var(--copy-color, var(--forest));
  margin: 0 0 22px;
  font-family: var(--copy-font-head, var(--serif));
  --title-size: clamp(2.8rem, 6vw, 5rem);
  font-size: calc(var(--title-size) * var(--title-scale, 1));
}
.hero-title.is-latin { --title-size: clamp(2.8rem, 6vw, 5rem);   line-height: 1.12; }
.hero-title.is-thai  { --title-size: clamp(1.9rem, 3.8vw, 3.2rem); line-height: 1.26; }
.hero-title.is-cjk   { --title-size: clamp(2.2rem, 4.4vw, 3.7rem); line-height: 1.26; }

/* …and steps down as the line gets longer. The rules above are the 'lg' step,
   drawn for a three-word slogan; a translated headline is usually a sentence
   and at that size wraps to five lines, which made the hero — and so the
   photograph in it — visibly taller on one language than another.
   obilis_headline_size() picks the step from the length of the longer line. */
.hero-title.is-latin.size-md { --title-size: clamp(2.2rem, 4.6vw, 3.7rem); line-height: 1.14; }
.hero-title.is-latin.size-sm { --title-size: clamp(1.8rem, 3.6vw, 2.9rem); line-height: 1.18; }
.hero-title.is-thai.size-md  { --title-size: clamp(1.7rem, 3.3vw, 2.8rem); line-height: 1.26; }
.hero-title.is-thai.size-sm  { --title-size: clamp(1.5rem, 2.9vw, 2.4rem); line-height: 1.3; }
.hero-title.is-cjk.size-md   { --title-size: clamp(1.9rem, 3.8vw, 3.1rem); line-height: 1.26; }
.hero-title.is-cjk.size-sm   { --title-size: clamp(1.7rem, 3.3vw, 2.6rem); line-height: 1.3; }
.hero-title .accent { display: block; font-style: italic; }
.hero-lead {
  font-size: calc(.95rem * var(--title-scale, 1));
  color: var(--copy-color-text, #33463f);
  font-family: var(--copy-font-body, var(--sans));
  margin: 0 0 30px;
  max-width: 380px;
}
.hero-buttons { display: flex; flex-wrap: wrap; gap: 12px; }
.hero-copy .btn, .hero-eyebrow { font-family: var(--copy-font-body, var(--sans)); }

/* --- What a slide sets for itself ----------------------------------------
   Position, face, colour and size are entered per slide under "Slide text —
   place and style"; obilis_slide_style() writes them onto the slide as custom
   properties. Every one has a fallback here, so a slide that sets none of them
   is drawn exactly as the theme always drew it. */

@media (min-width: 621px) {
  /* Desktop only: on a phone the size comes from the mobile slider, which is
     already folded into --title-size there. */
  .hero-slide { --title-scale: var(--copy-scale, 1); }

  /* Placed freely. The block is anchored by the same percentage twice — as an
     offset from the container's edge, and as a pull-back on its own width — so
     0% sets its left edge on the left edge, 100% its right edge on the right,
     and no value can push it out of the frame. */
  .hero-slide.has-place > .container {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 50%;
    transform: translateX(-50%);
  }
  .hero-slide.has-place .hero-copy {
    position: absolute;
    left: var(--copy-x, 0%);
    top: var(--copy-y, 50%);
    transform: translate(calc(var(--copy-x, 0%) * -1), calc(var(--copy-y, 50%) * -1));
    padding: 0;
  }
}

.hero-slide.align-center .hero-copy { text-align: center; }
.hero-slide.align-center .hero-lead { margin-inline: auto; }
.hero-slide.align-center .hero-buttons { justify-content: center; }
.hero-slide.align-right .hero-copy { text-align: right; }
.hero-slide.align-right .hero-lead { margin-left: auto; margin-right: 0; }
.hero-slide.align-right .hero-buttons { justify-content: flex-end; }

/* Somewhere to stand when the words are moved over a busy part of the picture.
   Drawn behind the copy rather than around it, so it never changes the layout. */
.hero-slide.has-scrim .hero-copy { position: relative; }
.hero-slide.has-scrim .hero-copy::before {
  content: "";
  position: absolute;
  inset: -24px -30px;
  z-index: -1;
  border-radius: var(--radius-lg);
  background: rgba(249, 246, 238, var(--copy-scrim, 0));
}

/* --- Hero slideshow ------------------------------------------------------
   Slides are stacked in one grid cell rather than positioned absolutely, so
   the hero grows to fit its tallest slide instead of clipping it. */

/* align-items on .hero pins the copy to the top of the frame; the slide stack
   itself has to fill the frame instead, or the photo stops short of it. */
.hero.is-slider { display: grid; align-items: stretch; }
.hero.is-slider::before { content: none; }   /* each slide carries its own wash */

/* The three languages carry different words, so their copy is never exactly the
   same height, and a hero sized by its content shows the same photograph at a
   different height on each front page. A definite height settles it: the
   picture is identical in Thai, English and Chinese, and the copy — which is
   top-aligned and comfortably shorter — simply sits inside it.
   Left as content-sized below 620px, where the column is narrow, the copy is
   proportionally much taller, and clipping would be the worse trade. */
@media (min-width: 621px) {
  .hero.is-slider { height: min(calc(var(--tune-hero) * 1vh), 720px); }
}

.hero-slides { display: grid; grid-template-areas: "stack"; }

.hero-slide {
  grid-area: stack;
  position: relative;
  display: grid;
  align-items: start;
  background: var(--hero, none) center / cover no-repeat;
  opacity: 0;
  /* Delaying visibility until the fade is over keeps the outgoing slide from
     swallowing clicks, and keeps its buttons out of the tab order. */
  visibility: hidden;
  transition: opacity .8s ease, visibility 0s linear .8s;
}
.hero-slide.is-active {
  opacity: 1;
  visibility: visible;
  transition: opacity .8s ease;
}

/* A photo can be any colour behind the controls, so they sit on a cream pill
   of their own rather than trusting the picture to stay light. */
.hero-controls {
  position: absolute;
  z-index: 3;
  left: 50%;
  bottom: 26px;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 5px 7px;
  border-radius: 999px;
  background: rgba(249, 246, 238, .82);
  -webkit-backdrop-filter: blur(4px);
          backdrop-filter: blur(4px);
}

.hero-arrow {
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--forest);
  cursor: pointer;
  transition: background .2s ease;
}
.hero-arrow:hover { background: rgba(255, 255, 255, .95); }

.hero-dots {
  display: flex;
  align-items: center;
  gap: 9px;
  padding-inline: 6px;
}
.hero-dot {
  width: 9px; height: 9px;
  padding: 0;
  border: 1px solid var(--forest);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition: background .2s ease, width .2s ease, border-radius .2s ease;
}
.hero-dot.is-active { width: 26px; border-radius: 5px; background: var(--forest); }

/* A visitor who asked their system for less motion gets no cross-fade either;
   theme.js also stops the slideshow advancing on its own. */
@media (prefers-reduced-motion: reduce) {
  .hero-slide { transition: none; }
}

/* ==========================================================================
   7. Sections
   ========================================================================== */

.section { padding: calc(96px * var(--tune-space)) 0; }
.section-cream { background: var(--cream-soft); }
.section-sand { background: var(--cream); }

.section-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 30px;
  margin-bottom: calc(44px * var(--tune-space));
}
.section-head h2 { margin: 0; }
.section-tools {
  display: flex;
  align-items: center;
  gap: 22px;
  flex-shrink: 0;
}

/* The rail has no arrows: it is dragged. The cursor is the only thing that says
   so before a visitor tries, which is why it is here rather than left to the
   default. `is-dragging` is set by theme.js for as long as a press is being
   pulled along; the grabbing hand and the suspended text selection both belong
   to that moment and to nothing else. */
[data-rail] { cursor: grab; }
[data-rail].is-dragging {
  cursor: grabbing;
  -webkit-user-select: none;
          user-select: none;
  scroll-behavior: auto;   /* a drag is not a jump; nothing to animate */
}
[data-rail].is-dragging a,
[data-rail].is-dragging img { pointer-events: none; }
.section-head.is-center {
  flex-direction: column;
  align-items: center;
  text-align: center;
  margin-bottom: calc(54px * var(--tune-space));
}

/* ==========================================================================
   8. Home style cards
   ========================================================================== */

/* A rail that slides left to right rather than a static row of four.
   --cards is how many fit in view; the fraction is a deliberate sliver of the
   next card, which is what tells a reader there is more to the right without
   needing a label to say so. The scrolling is the browser's own, so a swipe, a
   trackpad, the arrow buttons and the keyboard all drive the same thing and it
   still works with JavaScript off. */
.style-grid {
  --cards: 3.25;
  --gap: 22px;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--cards) - 1) * var(--gap)) / var(--cards));
  gap: var(--gap);

  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  /* Setting overflow on one axis makes the other one scroll too, so the card's
     4px lift on hover and its 18/45 shadow would both be sliced off at the
     edge. The padding gives them room inside the scroller and the matching
     negative margin takes that room back out of the layout, leaving the
     spacing around the rail exactly as it was. */
  padding: 8px 0 46px;
  margin: -8px 0 -46px;

  scrollbar-width: none;
  -ms-overflow-style: none;
}
.style-grid::-webkit-scrollbar { width: 0; height: 0; display: none; }

.style-card {
  position: relative;
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
  text-decoration: none;
  transition: transform .25s ease, box-shadow .25s ease;
}
.style-card:hover { transform: translateY(-4px); box-shadow: var(--shadow); }
.style-card img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }

/* The title link covers the whole card so the card is clickable, while the
   save button stays on top of it as a separate control. */
.style-link { text-decoration: none; }
.style-link::after { content: ""; position: absolute; inset: 0; }

.style-body { padding: calc(13px * var(--tune-density)) 22px calc(15px * var(--tune-density)); }
.style-title {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.style-title h3 { margin: 0; font-size: 1.25rem; }
.style-title .arrow { color: var(--muted); transition: transform .2s ease, color .2s ease; }
.style-card:hover .arrow { transform: translateX(4px); color: var(--forest); }
.style-desc { font-size: .78rem; color: var(--muted); margin: 10px 0 0; }

.save-style {
  position: absolute;
  top: 12px; right: 12px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 34px; height: 34px;
  border: 0;
  border-radius: 50%;
  background: rgba(255, 255, 255, .9);
  color: var(--forest);
  cursor: pointer;
  transition: background .2s ease, color .2s ease;
}
.save-style:hover { background: var(--white); }
.save-style[aria-pressed="true"] { background: var(--gold); color: var(--forest-deep); }
.save-style[aria-pressed="true"] svg { fill: currentColor; }

/* ==========================================================================
   9. Story
   ========================================================================== */

.story {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
  background: var(--cream);
}
.story-copy {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 96px clamp(28px, 5vw, 90px) 96px;
  margin-left: auto;
  max-width: 680px;
}
.story-copy h2 { margin: 0 0 18px; }
.story-copy h2 span { display: block; }
.story-copy p { font-size: .85rem; color: #3d514a; max-width: 380px; margin: 0 0 28px; }
.story-copy .btn { align-self: flex-start; }
.story-media { min-height: 520px; }
.story-media img { width: 100%; height: 100%; object-fit: cover; }

/* ==========================================================================
   10. Testimonials
   ========================================================================== */

.reviews {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 22px;
}
.review {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "mark mark" "quote quote" "who thumb";
  align-items: end;
  gap: 6px 16px;
  margin: 0;
  padding: calc(26px * var(--tune-density)) 28px;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.review-mark {
  grid-area: mark;
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--gold);
  font-size: .8rem;
  letter-spacing: .1em;
}
.review-mark .quote-glyph { font-family: var(--serif); font-size: 1.7rem; line-height: 1; }
.review p { grid-area: quote; font-size: .82rem; color: #3d514a; margin: 12px 0 16px; }
.review-who { grid-area: who; }
.review-who cite { display: block; font-style: normal; font-weight: 700; font-size: .78rem; }
.review-house { font-size: .7rem; color: var(--muted); }
.review img {
  grid-area: thumb;
  width: 48px; height: 34px;
  border-radius: 999px;
  object-fit: cover;
}

/* The dots only steer the carousel, so they stay hidden while the three
   reviews sit side by side and there is nothing to scroll. */
.review-dots { display: none; }
.review-dots span {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--sand);
  cursor: pointer;
}
.review-dots span.is-active { background: var(--gold); }

/* ==========================================================================
   11. CTA band
   ========================================================================== */

.cta-band {
  background: linear-gradient(100deg, var(--forest-deep), var(--forest));
  color: var(--white);
}
.cta-band .container {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: center;
  gap: 40px;
  padding: 44px 0;
}
.cta-band h2 { margin: 0; font-size: clamp(1.5rem, 2.4vw, 2.1rem); }
.cta-visit { display: flex; align-items: center; gap: 16px; }
.cta-visit .badge {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--gold);
  color: var(--forest-deep);
  flex: none;
}
.cta-visit strong { display: block; font-size: .82rem; }
.cta-visit p { margin: 2px 0 0; font-size: .72rem; color: rgba(255, 255, 255, .74); }

/* ==========================================================================
   12. Footer
   ========================================================================== */

.site-footer {
  background: var(--white);
  border-top: 1px solid var(--line);
  padding: 56px 0 24px;
}
.footer-grid {
  display: grid;
  grid-template-columns: 1.7fr 1fr 1fr 1fr 1.3fr;
  gap: 36px;
}
.footer-brand .brand { margin: 0 0 16px; }
.footer-tagline { font-size: .72rem; color: var(--muted); margin: 0; max-width: 250px; }

.site-footer h3 {
  font-family: var(--sans);
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .04em;
  margin: 0 0 14px;
}
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { margin-bottom: 8px; }
.site-footer li a {
  font-size: .74rem;
  color: var(--muted);
  text-decoration: none;
}
.site-footer li a:hover { color: var(--forest); }

.footer-connect p { font-size: .72rem; color: var(--muted); margin: 0 0 12px; }
.footer-mail {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 10px 14px;
  font-size: .74rem;
  text-decoration: none;
  color: var(--ink);
}
.footer-mail:hover { border-color: var(--gold); }

.copyright {
  margin-top: 40px;
  padding-top: 18px;
  border-top: 1px solid var(--line);
  font-size: .68rem;
  color: var(--muted);
}

/* ==========================================================================
   13. Inner pages (blog, single, 404, catalog)
   ========================================================================== */

.content-area { padding: 70px 0; max-width: 820px; }

/* Nothing to list. Sized and centred so the band reads as a deliberate message
   rather than a page that stopped halfway. */
.empty-state {
  max-width: 560px;
  margin-inline: auto;
  padding: 30px 0 10px;
  text-align: center;
  color: var(--muted);
}
.empty-title {
  margin: 0 0 10px;
  font-family: var(--serif);
  font-size: clamp(1.4rem, 2.4vw, 1.9rem);
  color: var(--ink);
}
.empty-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 26px;
}

.entry-title { font-size: clamp(2.2rem, 5vw, 3.6rem); }
.post-card { margin-bottom: 36px; padding-bottom: 30px; border-bottom: 1px solid var(--line); }
.wp-caption, .aligncenter { max-width: 100%; margin-inline: auto; }
.alignwide { width: min(var(--container), calc(100vw - 56px)); margin-left: 50%; transform: translateX(-50%); }


/* ==========================================================================
   14. Responsive
   ========================================================================== */

/* The header row carries seven menu items plus the brand, the language switch
   and the CTA. The Thai menu runs about a tenth wider than the English one, so
   the row runs out of width well above the mobile breakpoint. Hand space back
   in two steps — widest items first — so nothing has to wrap. */
@media (max-width: 1340px) {
  .nav-wrap { gap: 20px; }
  .main-nav ul { gap: 20px; }
  .brand-sub { display: none; }          /* the widest thing we can spare */
  .header-cta { padding: 12px 18px; }
}

@media (max-width: 1180px) {
  .nav-wrap { gap: 14px; }
  .main-nav ul { gap: 14px; }
  .main-nav a { font-size: .72rem; letter-spacing: 0; }
  .header-cta { padding: 11px 15px; font-size: .74rem; }

  .style-grid { --cards: 2.3; }
  .footer-grid { grid-template-columns: 1.6fr 1fr 1fr; }
}

/* Last stop before the menu folds into the toggle at 980px. */
@media (max-width: 1080px) {
  .nav-wrap { gap: 10px; }
  .main-nav ul { gap: 11px; }
  .main-nav a { font-size: .68rem; }
  .header-cta { padding: 10px 13px; font-size: .7rem; }
}

@media (max-width: 980px) {
  .topbar-values { display: none; }
  .menu-toggle { display: block; order: 3; }
  .header-cta { display: none; }

  /* The sheet that drops out of the toggle. One hairline separates it from the
     bar; everything else in it is type and space. It opens with a short fade
     rather than appearing at once, which is what stops the page from looking
     like it jumped. */
  .main-nav {
    display: none;
    order: 5;
    width: 100%;
    padding: 4px 0 14px;
    border-top: 1px solid var(--line);
  }
  .main-nav.is-open {
    display: block;
    animation: obilis-nav-in .18s ease-out both;
  }

  /* Once the menu folds into the toggle the bar becomes one line: brand
     centred, toggle on the right. Three grid columns rather than a flex row,
     because a flex centre would sit off by half the toggle's width; the two
     `1fr` tracks are equal, so the brand lands on the middle of the page even
     though only the right one is filled. The menu that drops out of the toggle
     spans the lot on a row of its own. `margin-right: auto` on the brand is
     what held the row apart, so it goes. */
  .nav-wrap {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 10px;
    padding-block: 14px;
  }
  /* Placed by column rather than left to fall into place: with only two things
     on the row, auto-placement would drop the brand into the first track and
     the toggle into the middle one, and the brand would stop being centred.
     Naming the columns keeps the left track deliberately empty. */
  .brand { grid-column: 2; margin-right: 0; justify-self: center; gap: 10px; }

  /* Bigger than the desktop mark: the brand is the only thing in the middle of
     a phone's bar and can afford the room. 48px is the most it can take without
     making the header itself taller — the row is 78px with 14px of padding above
     and below, so anything past 50px starts pushing the bar down the screen. */
  .brand img, .brand .custom-logo { width: 48px; }
  .menu-toggle { grid-column: 3; justify-self: end; }
  .brand-text { text-align: center; }
  .brand-sub { display: block; }         /* the menu has left the row, so it fits again */
  .main-nav { grid-column: 1 / -1; }

  /* 44px square: the smallest target a thumb hits reliably, and the reason the
     padding is on the button rather than on a border around it. */
  .menu-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    padding: 0;
    margin-right: -10px;
    font-size: 1.15rem;
    line-height: 1;
  }

  /* Ranged left, not centred: eight items on a ragged centre axis give the eye
     nothing to run down, and every label here is a different length. */
  .main-nav ul { flex-direction: column; align-items: stretch; gap: 0; }

  /* Lighter and larger than the desktop bar. A menu opened on purpose is being
     read, not scanned past, so it drops the bold and the tight tracking and
     takes the room instead — 15px over 46px of row. */
  .main-nav a {
    display: block;
    padding: 13px 0;
    font-size: .95rem;
    font-weight: 500;
    letter-spacing: 0;
    text-align: left;
    color: var(--ink);
  }

  /* The rule sits inside the row rather than on the link's own border, which
     the desktop hover still owns — and it is drawn between items only, so the
     list ends on space instead of on a line. */
  .main-nav li + li a { box-shadow: inset 0 1px 0 var(--line); }

  /* No gold underline down here: on a touch screen :hover sticks after a tap
     and the mark would be left behind on whichever item was pressed last. */
  .main-nav a:hover,
  .main-nav a:focus-visible { color: var(--forest); border-bottom-color: transparent; }
  .main-nav a:active { color: var(--forest); }

  .story { grid-template-columns: 1fr; }
  .story-copy { margin: 0; max-width: none; padding: 64px 28px; }
  .story-media { min-height: 340px; }

  .reviews {
    grid-auto-flow: column;
    /* Both bounds are 86%: a min of 0 lets the tracks shrink to fit the row,
       which collapses the three cards instead of letting them overflow and scroll. */
    grid-auto-columns: minmax(86%, 86%);
    grid-template-columns: none;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    padding-bottom: 4px;
  }
  .reviews::-webkit-scrollbar { display: none; }
  .review { scroll-snap-align: start; }

  .review-dots {
    display: flex;
    justify-content: center;
    gap: 8px;
    margin-top: 22px;
  }

  .cta-band .container { grid-template-columns: 1fr; gap: 24px; padding: 40px 0; }
}

@media (max-width: 620px) {
  .container { width: calc(100% - 32px); }
  .brand-sub { display: none; }          /* no room beside the toggle and the switch */
  .section { padding: calc(64px * var(--tune-space)) 0; }
  .footer-grid { grid-template-columns: 1fr; }
  /* One card at a time, with a thumb's width of the next one showing. */
  .style-grid { --cards: 1.12; --gap: 14px; }
  /* The arrows sit under the heading on a phone, where the row would otherwise
     squeeze the title into three lines. */
  .section-head { flex-wrap: wrap; }
  .section-tools { gap: 16px; }
  /* --- The hero on a phone ----------------------------------------------
     One column has no room for copy beside the house, so the photograph keeps
     the whole frame and the words drop to the foot of it, over a dark gradient
     that rises out of the bottom edge. The sky and the roofline are left
     untouched; only the ground the type sits on is darkened.

     Everything below is white-on-dark, which is also why the headline switches
     from the theme's serif to Noto Sans Thai: the design this follows is set in
     the one sans throughout. */
  /* The frame is a phone's own 9:16 rather than a share of the window, so the
     photograph is cropped the same way on every handset instead of following
     whatever height each one happens to have. A minimum rather than a fixed
     height: a slide carrying long copy grows the frame instead of spilling its
     words out of the bottom of it. */
  .hero,
  .hero.is-slider { min-height: calc(100vw * 16 / 9); }

  /* `.hero.is-slider` is deliberately left alone above: it has to keep
     stretching so the slide stack fills the frame. It is the slide inside it
     that end-aligns its own copy. */
  .hero,
  .hero-slide { align-items: end; }

  /* The frame's own wash goes: the darkening belongs to the copy, not to a
     fixed percentage of the picture. A slide with three lines of text and two
     buttons is twice the height of one with a single line, and a gradient
     pinned to the frame leaves the top of the taller one stranded on bright
     sky. Carrying it on .hero-copy instead means the fade always begins the
     same distance above the first word, whatever the slide says. */
  /* The photographs are 16:9 and this frame is 9:16, so `cover` fills it by
     showing barely a third of their width — the house gets cropped out of its
     own picture. Instead the picture is laid across the top at its full width,
     whole, and the rest of the frame is the same photograph blurred and
     enlarged behind it. Nothing is letterboxed and there is no seam.

     ::before carries the blur (backdrop-filter works on the slide's own
     background, which is still the cover-sized photo), ::after carries the
     sharp picture, and the copy is lifted above both. */
  .hero::before,
  .hero-slide::before {
    background: none;
    -webkit-mask-image: none;
            mask-image: none;
    z-index: 0;
    -webkit-backdrop-filter: blur(34px) brightness(.9) saturate(1.05);
            backdrop-filter: blur(34px) brightness(.9) saturate(1.05);
  }

  .hero::after,
  .hero-slide::after {
    content: "";
    position: absolute;
    top: 0; left: 0; right: 0;
    z-index: 0;
    aspect-ratio: 16 / 9;
    /* --hero-zoom and --hero-y are the two sliders on the slide (Slide
       content → Mobile photo — fit). At 100% the picture is exactly as wide
       as the strip, which is where it has always sat; above that it grows and
       the strip crops it, and --hero-y decides which part of the taller
       picture shows. */
    background: var(--hero, none) center var(--hero-y, 50%) / var(--hero-zoom, 100%) auto no-repeat;
  }

  .hero > .container,
  .hero-slide > .container { position: relative; z-index: 1; }

  /* Given an upright picture of its own there is nothing to work around: it is
     already the shape of the frame, so it simply fills it and both the blur and
     the strip along the top are switched off. The two sliders still apply, the
     way this shape needs them: height is what fills the frame here, so zoom
     grows the picture past it and --hero-y picks the part that shows. */
  .hero-slide.has-upright {
    background: var(--hero-upright) center var(--hero-y, 50%) / auto var(--hero-zoom, 100%) no-repeat;
  }
  .hero-slide.has-upright::before {
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
  }
  .hero-slide.has-upright::after { content: none; }

  /* The copy is drawn to the phone mock, and every size in it is a share of
     the screen's width rather than a fixed number of pixels — so the block
     keeps the mock's proportions from a 360px handset up to a 430px one.
     --hero-text, the Text size slider on the slide, multiplies all four sizes
     at once, which is why they are written as calc(clamp(…) * ratio): a long
     headline can be pulled down without the eyebrow, the lead and the button
     drifting out of proportion with it.

     Bled to the full width by cancelling the container's 16px gutters; the
     30px that replaces them is the mock's margin, and the foot padding is what
     keeps the button clear of the slideshow dots. */
  .hero-copy {
    max-width: none;
    margin-inline: calc(50% - 50vw);   /* bleeds to the screen edge whatever gutter the container is on */
    padding: 130px 8.9vw 96px;
    background: linear-gradient(180deg,
      rgba(9, 22, 15, 0) 0%,
      rgba(9, 22, 15, .40) 26%,
      rgba(9, 22, 15, .76) 52%,
      rgba(9, 22, 15, .91) 78%,
      rgba(9, 22, 15, .94) 100%);
  }
  .has-hero .hero-copy { padding-top: 130px; }

  .hero-eyebrow {
    color: var(--gold-light);
    font-size: calc(clamp(.54rem, 2.35vw, .7rem) * var(--hero-text, 1));
    margin-bottom: 16px;
  }

  .hero-title {
    font-family: var(--sans);
    font-weight: 700;
    color: #fff;
    margin: 0 0 16px;
  }
  /* One size per script on a phone: the desktop step-down exists to keep three
     languages the same height in a fixed frame, and here the frame grows. */
  .hero-title.is-thai,
  .hero-title.is-thai.size-md,
  .hero-title.is-thai.size-sm { --title-size: calc(clamp(1.7rem, 8.15vw, 2.6rem) * var(--hero-text, 1)); line-height: 1.16; }
  .hero-title.is-latin,
  .hero-title.is-latin.size-md,
  .hero-title.is-latin.size-sm { --title-size: calc(clamp(1.85rem, 8.9vw, 2.85rem) * var(--hero-text, 1)); line-height: 1.08; }
  .hero-title.is-cjk,
  .hero-title.is-cjk.size-md,
  .hero-title.is-cjk.size-sm { --title-size: calc(clamp(1.75rem, 8.5vw, 2.7rem) * var(--hero-text, 1)); line-height: 1.16; }
  .hero-title .accent { font-style: normal; }

  /* Customize → Fonts → Hero headline on mobile, set to the heading face. It
     undoes exactly the three things the mobile design changed: the family, the
     weight and the upright second line. The weight has to travel with the
     family — the serif pairings are loaded at 400/500/600 only, so a 700 here
     would be a browser-synthesised bold and would not match the desktop hero
     it is meant to agree with. Colour stays white: the words are on a dark
     photograph either way. */
  .hero-font-serif .hero-title { font-family: var(--serif); font-weight: 400; }
  .hero-font-serif .hero-title .accent { font-style: italic; }

  .hero-lead {
    max-width: none;
    font-size: calc(clamp(.78rem, 3.5vw, 1rem) * var(--hero-text, 1));
    font-weight: 400;
    line-height: 1.47;
    color: rgba(255, 255, 255, .9);
    margin: 0 0 24px;
  }

  /* The line height is spelled out because the body carries 1.7: left to
     inherit it the button stands half again as tall as the mock's. */
  .hero-buttons .btn {
    font-size: calc(clamp(.76rem, 3.4vw, .92rem) * var(--hero-text, 1));
    font-weight: 600;
    line-height: 1.2;
    padding: 12px 28px;
  }
  .hero-buttons .btn:not(.btn-ghost) .btn-arrow {
    display: inline;
    margin-left: 12px;    /* on top of the button's own 10px gap */
    font-size: 1.05em;
  }

  /* Gold, as the design has it — and necessary rather than decorative: the
     forest green the button carries elsewhere all but disappears against the
     dark ground the copy now sits on. */
  .hero-buttons .btn:not(.btn-ghost) { background: var(--gold); color: var(--forest-deep); }
  .hero-buttons .btn:not(.btn-ghost):hover { background: var(--gold-dark); color: #fff; }

  .hero-buttons .btn-ghost {
    background: rgba(255, 255, 255, .14);
    border-color: rgba(255, 255, 255, .55);
    color: #fff;
  }
  .hero-buttons .btn-ghost .play { background: #fff; color: var(--forest); }

  /* On the dark gradient the controls no longer need their cream pill: the
     dots sit straight on the photograph, and the arrows go — a phone swipes. */
  .hero-controls {
    bottom: 26px;
    gap: 10px;
    padding: 0;
    background: none;
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
  }
  .hero-arrow { display: none; }
  .hero-dot { border-color: rgba(255, 255, 255, .8); }
  .hero-dot.is-active { background: #fff; border-color: #fff; }
}

/* These two steps shrank the brand back when the language switch shared the
   header row with it and the three would not fit. The switch has since left the
   bar, so the mark and the name keep their full size and only the toggle gives
   any width back. */
@media (max-width: 440px) {
  .nav-wrap { gap: 6px; }
  .menu-toggle { padding: 0 10px; }
}

@media (max-width: 360px) {
  .menu-toggle { padding: 0 8px; }
}

/* ==========================================================================
   15. Catalogue
   ========================================================================== */

/* --- The styles, as the way into the models ------------------------------
   The catalogue used to be a list of styles, each with its own carousel, which
   made it a second copy of the style pages. The styles are now a filter: the
   card sets ?style=<slug> and the models below are re-queried on the server, so
   every filtered view has an address of its own that can be sent to someone and
   indexed. The arrow beside the name still goes to the style's own page, for
   the long version. */

/* Three at a time on a rail rather than every style at once. --cards is how
   many fit in view; the card width is worked out from it, so changing that one
   number is the whole of the change. Same mechanism as the front page's style
   rail — see section 8 — and the same reason: the browser does the scrolling,
   so the buttons, a swipe and the keyboard all drive one thing. */
.type-rail { position: relative; }

.type-grid {
  --cards: 3;
  --gap: 20px;

  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--cards) - 1) * var(--gap)) / var(--cards));
  gap: var(--gap);

  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;

  /* Room inside the scroller for the card's hover lift, taken back out of the
     layout again so the spacing around the rail is unchanged. */
  padding: 6px 0 10px;
  margin: -6px 0 -10px;

  scrollbar-width: none;
  -ms-overflow-style: none;
}
.type-grid::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* The round buttons that move the rail. They sit over the photographs rather
   than outside the container, which has no room to spare at 1200px, and each
   is taken off the page at the end it can no longer reach — a button that does
   nothing is worse than no button, and the rail is still draggable. */
.rail-btn {
  position: absolute;
  top: calc(50% - 26px);
  z-index: 3;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  padding: 0;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 50%;
  box-shadow: 0 6px 18px rgba(20, 24, 20, .14);
  cursor: pointer;
  transition: background .2s ease, color .2s ease, border-color .2s ease;
}
.rail-btn:hover { color: var(--white); background: var(--forest); border-color: var(--forest); }
.rail-btn.is-prev { left: -14px; }
.rail-btn.is-next { right: -14px; }
.rail-btn[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .type-grid { scroll-behavior: auto; }
}

.type-card {
  scroll-snap-align: start;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  transition: border-color .2s ease, transform .2s ease;
}
.type-card:hover { border-color: var(--forest); transform: translateY(-2px); }

/* The chosen style is marked on the card as well as on its chip, because the
   card is where the eye was when the choice was made. */
.type-card.is-active { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); }

.type-media {
  display: block;
  aspect-ratio: 4 / 3;
  background: var(--cream);
}
.type-media img { width: 100%; height: 100%; object-fit: cover; }

.type-copy {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: calc(18px * var(--tune-density)) calc(18px * var(--tune-density)) calc(16px * var(--tune-density));
}

/* An h2 for the outline, but a label on the page — written as two classes so it
   outranks the per-language h2 sizes further down, which are set on the <html>
   element and would otherwise make a Thai style name three times this size. */
.type-card .type-name {
  margin: 0;
  font-family: var(--serif);
  font-size: 1rem;
  line-height: 1.35;
}
.type-name a { text-decoration: none; }
.type-name a:hover { color: var(--forest); }

.type-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px 12px;
  margin: auto 0 0;
  padding-top: 8px;
  font-size: .72rem;
  color: var(--muted);
}
.type-meta a { color: var(--forest); text-decoration: none; font-weight: 600; }
.type-meta a:hover { border-bottom: 1px solid var(--gold); }

/* --- The filter bar ------------------------------------------------------
   Chips repeat the styles in one line so the filter is still in reach once the
   cards have scrolled away, and the search box shares the row with them. Both
   are plain links and a plain GET form: they work with JavaScript off, and the
   back button walks back through the filters the way a visitor expects. */

/* The styles and the models are one thought, so the two bands are not spaced
   apart as if they were separate sections. */
.catalog-types { padding-bottom: 0; }
.catalog-models { scroll-margin-top: 96px; }

.catalog-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px 24px;
  margin-bottom: 30px;
  padding-bottom: 22px;
  border-bottom: 1px solid var(--line);
}

.filter-chips { display: flex; flex-wrap: wrap; gap: 8px; }

.chip {
  display: inline-block;
  padding: 8px 16px;
  font-size: .78rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 999px;
  transition: border-color .2s ease, background .2s ease, color .2s ease;
}
.chip:hover { border-color: var(--forest); color: var(--forest); }
.chip.is-active {
  color: var(--white);
  background: var(--forest);
  border-color: var(--forest);
}

.catalog-search { display: flex; gap: 8px; }

.catalog-search input[type="search"] {
  min-width: 220px;
  padding: 9px 16px;
  font: inherit;
  font-size: .82rem;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: 999px;
}
.catalog-search input[type="search"]:focus {
  outline: none;
  border-color: var(--forest);
}

.catalog-search button {
  padding: 9px 20px;
  font: inherit;
  font-size: .78rem;
  font-weight: 600;
  color: var(--white);
  background: var(--forest);
  border: 1px solid var(--forest);
  border-radius: 999px;
  cursor: pointer;
}
.catalog-search button:hover { background: var(--ink); border-color: var(--ink); }

/* What the grid below is showing, in words — the filter is in the address bar
   and on a chip, but a count is what tells a visitor the page did something. */
.catalog-result {
  margin: 0 0 22px;
  font-size: .78rem;
  letter-spacing: .04em;
  color: var(--muted);
}

/* Nothing found is a dead end unless it offers the way out. */
.catalog-empty {
  padding: 60px 28px;
  text-align: center;
  background: var(--cream);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.catalog-empty p { margin: 0 auto; max-width: 46ch; color: var(--muted); }
.catalog-empty-title {
  margin-bottom: 10px !important;
  font-family: var(--serif);
  font-size: 1.3rem;
  color: var(--ink) !important;
}
.catalog-empty-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
  margin-top: 26px !important;
}

@media (max-width: 760px) {
  .catalog-bar { flex-direction: column; align-items: stretch; }
  .catalog-search input[type="search"] { flex: 1; min-width: 0; }
  /* The chips run off the edge rather than stacking into a wall of pills. */
  .filter-chips {
    flex-wrap: nowrap;
    overflow-x: auto;
    padding-bottom: 4px;
    scrollbar-width: none;
  }
  .filter-chips::-webkit-scrollbar { display: none; }
  .chip { flex: 0 0 auto; }
}

/* Fewer cards in view as the room runs out, and no buttons on a touch screen —
   there the rail is swiped, which is what a finger reaches for first anyway.
   The fraction is a deliberate sliver of the next card: it says there is more
   to the right without needing a label to say so. */
@media (max-width: 900px) {
  .type-grid { --cards: 2.2; }
}

@media (max-width: 620px) {
  .type-grid { --cards: 1.35; --gap: 14px; }
  .rail-btn { display: none; }
}

/* --- Photo card deck ----------------------------------------------------- */

/* The photos are a deck of prints: the top card is dragged aside and the ones
   behind it ease forward. Every card is placed from its own `--depth` (0 = on
   top), so advancing the deck is only a renumbering — see assets/js/theme.js.
   `--drag` runs 0 → 1 while the top card is being pulled away, which slides
   the rest of the deck up behind it. */

.style-gallery {
  position: relative;
  --deck-shift: 11px;   /* how far each card behind peeks out below */
  --deck-scale: .05;    /* and how much narrower it gets */
  --deck-visible: 3;    /* cards drawn at once; the rest wait out of sight */
}

/* The drag is watched on the stage, because the floor-plan button covers the
   deck and would otherwise catch every press meant for the top card. */
.style-stage {
  position: relative;
  touch-action: pan-y;  /* sideways is ours to drag, upwards still scrolls */
  -webkit-user-select: none;
  user-select: none;
}

.style-slides {
  position: relative;
  isolation: isolate;   /* keep the cards' z-order away from the plan button */
  z-index: 1;
  aspect-ratio: 4 / 3;
  /* Room below for the cards that fan out — none for a style with one photo,
     which is what `--deck-back` on the gallery counts. */
  margin-bottom: calc(var(--deck-shift) * var(--deck-back, 2));
  cursor: grab;
}
.style-slides.is-dragging,
.style-stage:has(.is-dragging) .plan-open { cursor: grabbing; }

.style-slide {
  --d: max(0, calc(var(--depth) - var(--drag, 0)));

  position: absolute;
  inset: 0;
  z-index: calc(20 - var(--depth));
  margin: 0;
  transform-origin: 50% 100%;
  transform: translateY(calc(var(--d) * var(--deck-shift)))
             scale(calc(1 - var(--d) * var(--deck-scale)));
  opacity: clamp(0, calc(var(--deck-visible) - var(--d)), 1);
  background: var(--white);
  box-shadow: 0 10px 26px rgba(14, 36, 24, .16);
  transition: transform .5s cubic-bezier(.22, .61, .36, 1), opacity .4s ease;
  outline: 6px solid rgba(255, 255, 255, .75);
  outline-offset: -6px;
}

/* Nothing to animate against while a finger is on the card, and the card on
   its way off the deck needs to clear the ones it is uncovering. */
.style-slides.is-dragging .style-slide { transition: none; }
.style-slide.is-leaving { z-index: 30; transition: transform .42s ease-in, opacity .42s ease-in; }
.style-slide.is-resetting { transition: none; }

/* The photo itself must not swallow the drag as a native image drag. */
.style-slide img {
  width: 100%; height: 100%;
  object-fit: cover;
  pointer-events: none;
  -webkit-user-drag: none;
}

.style-slide figcaption {
  position: absolute;
  left: 0; bottom: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 9px 16px;
  background: rgba(14, 36, 24, .82);
  color: #fff;
  font-size: .66rem;
  letter-spacing: .04em;
}
.slide-count { font-weight: 700; color: var(--gold-light); }

/* Only the top card is captioned; on the ones behind it, the strip that peeks
   out below should read as a photo edge, not as a sliver of dark bar. */
.style-slide:not(.is-active) figcaption { opacity: 0; transition: opacity .25s ease; }

.style-gallery-controls {
  display: flex;
  align-items: center;
  margin-top: 8px;  /* the deck already reserves room for the cards it fans */
}

.gallery-dots {
  display: flex;
  flex: 1;
  gap: 8px;
}

/* The dots are the only thing left to click besides the deck itself, so each
   one is a 19px target painting a 3px bar. */
.gallery-dot {
  flex: 1;
  height: 19px;
  padding: 8px 0;
  border: 0;
  background: var(--sand);
  background-clip: content-box;
  cursor: pointer;
  transition: background .2s ease;
}
.gallery-dot.is-active { background: var(--gold); }

/* --- Floor plans ---------------------------------------------------------- */

/* The whole photo opens the plans, with a pill to say so. */
.plan-open {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  padding: 14px;
  border: 0;
  background: none;
  cursor: pointer;
}

.plan-open-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 8px 14px;
  border-radius: 999px;
  background: rgba(255, 255, 255, .94);
  box-shadow: 0 6px 18px rgba(14, 36, 24, .22);
  color: var(--forest);
  font-size: .7rem;
  font-weight: 600;
  letter-spacing: .02em;
  transition: background .2s ease, color .2s ease, transform .2s ease;
}

.plan-open:hover .plan-open-pill,
.plan-open:focus-visible .plan-open-pill {
  background: var(--forest);
  color: var(--gold-light);
  transform: translateY(-2px);
}

.plan-open:focus { outline: none; }
.plan-open:focus-visible { outline: 2px solid var(--gold); outline-offset: -4px; }

/* --- Floor plan lightbox -------------------------------------------------- */

body.has-dialog { overflow: hidden; }

.plan-dialog {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: grid;
  place-items: center;
  padding: 24px;
}
.plan-dialog[hidden] { display: none; }

.plan-backdrop {
  position: absolute;
  inset: 0;
  background: rgba(14, 36, 24, .72);
}

.plan-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  width: min(980px, 100%);
  max-height: 100%;
  padding: 24px 26px 26px;
  border-radius: var(--radius-lg);
  background: var(--white);
  box-shadow: 0 30px 70px rgba(14, 36, 24, .35);
}

.plan-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  flex: none;
}
.plan-head .eyebrow { margin: 0 0 4px; }
.plan-head h2 { margin: 0; font-size: clamp(1.5rem, 2.4vw, 2rem); }

.plan-close {
  display: grid;
  place-items: center;
  width: 38px; height: 38px;
  flex: none;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: none;
  color: var(--ink);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color .2s ease, color .2s ease;
}
.plan-close:hover { border-color: var(--gold); color: var(--forest); }

.plan-stage {
  position: relative;
  min-height: 0;
  margin-top: 20px;
}

.plan-slide { display: none; margin: 0; }
.plan-slide.is-active { display: block; }

/* Plans are line drawings — fit the whole sheet rather than cropping it. */
.plan-slide img {
  width: 100%;
  max-height: min(62vh, 620px);
  object-fit: contain;
  background: var(--cream-soft);
  border: 1px solid var(--line);
  /* Its own value, not --radius: that token is the one the controls ride, and a
     style preset is free to turn the controls into capsules. A floor plan bent
     into an ellipse is not a rounding, it is a mistake. */
  border-radius: 4px;
}

.plan-slide figcaption {
  margin-top: 10px;
  font-size: .74rem;
  color: var(--muted);
}

.plan-controls {
  display: flex;
  align-items: center;
  gap: 14px;
  flex: none;
  margin-top: 20px;
}

/* Kept for the lightbox; the photo deck itself is swiped rather than stepped. */
.gallery-arrow {
  display: grid;
  place-items: center;
  width: 38px; height: 34px;
  flex: none;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--white);
  color: var(--ink);
  cursor: pointer;
  transition: border-color .2s ease, color .2s ease;
}
.gallery-arrow:hover { border-color: var(--gold); color: var(--forest); }

.plan-dots {
  display: flex;
  flex: 1;
  gap: 8px;
}
.plan-dot {
  flex: 1;
  height: 3px;
  padding: 0;
  border: 0;
  background: var(--sand);
  cursor: pointer;
  transition: background .2s ease;
}
.plan-dot.is-active { background: var(--gold); }

.plan-counter {
  margin: 0;
  flex: none;
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .04em;
  color: var(--muted);
}

/* --- Single style page --------------------------------------------------- */

/* Likewise the style and project photographs — see section 5. */
.style-hero {
  position: relative;
  margin-top: calc(var(--header-real, var(--header-h)) * -1);
  padding: calc(84px + var(--header-real, var(--header-h))) 0 84px;
  background: var(--hero, var(--cream)) center / cover no-repeat;
}
.style-hero::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
    rgba(249, 246, 238, .97) 0%,
    rgba(249, 246, 238, .93) 32%,
    rgba(249, 246, 238, .6) 52%,
    rgba(249, 246, 238, .08) 74%);
}
/* Keep the page gutters, but hold the copy in a narrow left-aligned column. */
.style-hero .container { position: relative; }
.style-hero .container > * { max-width: 760px; }
.style-hero h1 { color: var(--forest); margin: 0 0 8px; font-size: clamp(2.6rem, 5.5vw, 4.4rem); }
.style-lead { margin: 0 0 28px; max-width: 480px; font-size: .88rem; color: #33463f; }

.breadcrumb {
  display: flex;
  gap: 10px;
  margin-bottom: 22px;
  font-size: .72rem;
  color: var(--muted);
}
.breadcrumb a { text-decoration: none; }
.breadcrumb a:hover { color: var(--forest); }

.photo-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 24px;
}
.photo-item { margin: 0; }
.photo-item img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: var(--radius-lg);
}
.photo-item:first-child { grid-column: 1 / -1; }
.photo-item:first-child img { aspect-ratio: 16 / 9; }

.style-nav { border-top: 1px solid var(--line); }
.style-nav .container {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 24px;
  padding: 30px 0;
}
.style-nav-link { text-decoration: none; display: grid; gap: 4px; }
.style-nav-link.is-next { text-align: right; }
.style-nav-link .eyebrow { margin: 0; }
.style-nav-name { font-family: var(--serif); font-size: 1.2rem; }
.style-nav-link:hover .style-nav-name { color: var(--forest); }
.style-nav-all {
  font-size: .74rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--muted);
  border-bottom: 1px solid var(--gold);
  padding-bottom: 3px;
}

.cta-band-simple {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 30px;
  padding: 46px 0;
}
.cta-band-simple h2 { margin: 0; font-size: clamp(1.5rem, 2.6vw, 2.3rem); }
.cta-band-simple .eyebrow { margin-bottom: 8px; }

@media (max-width: 760px) {
  .photo-grid { grid-template-columns: 1fr; }
  .photo-item:first-child img { aspect-ratio: 4 / 3; }
  .style-nav .container { grid-template-columns: 1fr; text-align: center; }
  .style-nav-link.is-next { text-align: center; }
  .cta-band-simple { flex-direction: column; align-items: flex-start; }
  .style-gallery { --deck-visible: 2; }  /* one card behind is enough on a phone */
  .plan-dialog { padding: 12px; }
  .plan-panel { padding: 20px 18px 22px; }
  .plan-slide img { max-height: 52vh; }
  .plan-open { padding: 10px; }
}

/* ==========================================================================
   16. Per-script typography
   ========================================================================== */

/* Thai and Chinese words run wider than English at the same point size, and
   Thai needs vertical room for its tone marks. */
html[lang^="th"] h1,
html[lang^="zh"] h1 { line-height: 1.28; }
html[lang^="th"] h2,
html[lang^="zh"] h2 { line-height: 1.32; }

html[lang^="th"] h1 { font-size: clamp(1.9rem, 3.5vw, 3.1rem); }
html[lang^="zh"] h1 { font-size: clamp(2.3rem, 4.6vw, 3.9rem); }

html[lang^="th"] h2 { font-size: clamp(1.7rem, 3vw, 2.5rem); }
html[lang^="zh"] h2 { font-size: clamp(1.8rem, 3.2vw, 2.7rem); }

/* Hero headline sizing is set by script beside .hero-title, not by page
   language, so there is deliberately no html[lang] rule for it here. */

html[lang^="th"] .hero-lead,
html[lang^="zh"] .hero-lead { max-width: 440px; }

/* Thai has no letter case and no word spaces, so wide tracking hurts it. */
html[lang^="th"] .eyebrow { letter-spacing: .08em; }

/* ==========================================================================
   17. Specification, reviews, questions and projects
   ========================================================================== */

.specs-heading { margin: 0 0 26px; font-size: clamp(1.6rem, 2.6vw, 2.2rem); }

.spec-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1px;
  margin: 0;
  background: var(--line);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.spec-item { background: var(--white); padding: 22px 20px; }
.spec-item dt {
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}
.spec-item dd { margin: 0; font-family: var(--serif); font-size: 1.5rem; color: var(--forest); }
.spec-item.is-price { background: var(--cream); }
.spec-item.is-price dd { color: var(--gold-dark); }
.spec-note { margin: 16px 0 0; font-size: .74rem; color: var(--muted); }

/* The four headline numbers on a catalogue card. */
.spec-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 20px;
  list-style: none;
  margin: 14px 0 0;
  padding: 14px 0 0;
  border-top: 1px solid var(--line);
}
.spec-strip li { display: flex; flex-direction: column; }
.spec-value { font-family: var(--serif); font-size: 1.05rem; color: var(--forest); line-height: 1.2; }
.spec-label { font-size: .62rem; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.spec-price { display: flex; align-items: baseline; gap: 8px; margin: 12px 0 0; }
.spec-price span { font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); }
.spec-price b { font-family: var(--serif); font-size: 1.35rem; font-weight: 500; color: var(--gold-dark); }

/* --- Reviews ------------------------------------------------------------- */

.review-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 22px;
  margin-top: 40px;
}
.review-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin: 0;
  padding: 28px 26px;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
}
.review-stars { color: var(--gold); letter-spacing: .12em; font-size: .9rem; }
.review-card blockquote { margin: 0; font-size: .88rem; line-height: 1.75; color: #33463f; }
.review-card blockquote p { margin: 0 0 .7em; }
.review-card blockquote p:last-child { margin-bottom: 0; }
.review-card figcaption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  margin-top: auto;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: .72rem;
}
.review-author { font-weight: 600; }
.review-source a { color: var(--gold-dark); text-decoration: none; border-bottom: 1px solid var(--sand); }
.review-source a:hover { border-bottom-color: var(--gold); }
.review-date { color: var(--muted); margin-left: auto; }

/* --- Questions ----------------------------------------------------------- */

.faq-list {
  margin-top: 34px;
  border-top: 1px solid var(--line);
}
.faq-item { border-bottom: 1px solid var(--line); padding: 20px 0; }
.faq-item summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: baseline;
  gap: 12px;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: "−";
  margin-left: auto;
  color: var(--gold-dark);
  font-size: 1.2rem;
  line-height: 1;
}
.faq-item:not([open]) summary::after { content: "+"; }
.faq-item summary h3 { margin: 0; font-size: 1.12rem; color: var(--forest); }
.faq-answer { padding-top: 12px; max-width: 68ch; font-size: .86rem; line-height: 1.8; color: #33463f; }
.faq-answer p { margin: 0 0 .9em; }

/* The lead answer: the sentence a search engine or assistant quotes. */
.answer-first {
  font-size: .94rem;
  font-weight: 500;
  color: var(--ink);
  padding-left: 14px;
  border-left: 2px solid var(--gold);
}
.faq-intro { max-width: 62ch; font-size: .88rem; color: var(--muted); }

/* --- Projects ------------------------------------------------------------ */

.area-filter { border-bottom: 1px solid var(--line); background: var(--cream-soft); }
.area-filter .container {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding-block: 16px;
}
.area-chip {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 7px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--white);
  font-size: .74rem;
  font-weight: 600;
  text-decoration: none;
  color: var(--ink);
  transition: background .2s ease, border-color .2s ease, color .2s ease;
}
.area-chip:hover { border-color: var(--gold); }
.area-chip.is-active { background: var(--forest); border-color: var(--forest); color: var(--white); }
.area-count { font-size: .64rem; color: var(--muted); }
.area-chip.is-active .area-count { color: rgba(255, 255, 255, .7); }

.project-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 28px;
}
@media (max-width: 1080px) { .project-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 700px)  { .project-grid { grid-template-columns: 1fr; } }
.project-card {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.project-media { display: block; aspect-ratio: 4 / 3; overflow: hidden; }
.project-media img { width: 100%; height: 100%; object-fit: cover; transition: transform .4s ease; }
.project-card:hover .project-media img { transform: scale(1.03); }
.project-copy { display: flex; flex-direction: column; gap: 8px; padding: 24px 24px 26px; }
.project-title { margin: 0; font-size: 1.4rem; }
.project-title a { text-decoration: none; color: var(--forest); }
.project-title a:hover { color: var(--gold-dark); }
.project-meta { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0; font-size: .74rem; color: var(--muted); }
.project-desc { margin: 0; font-size: .84rem; color: #33463f; }
.project-copy .link-arrow { margin-top: 6px; align-self: flex-start; }

.style-hero.has-no-cover { background: var(--cream); }
.style-hero.has-no-cover::before { display: none; }

@media (max-width: 620px) {
  .spec-item { padding: 18px 16px; }
  .spec-item dd { font-size: 1.25rem; }
  .review-card { padding: 22px 20px; }
}

/* ==========================================================================
   18. Articles
   ========================================================================== */

/* A fixed column count, not auto-fit: with one article published, auto-fit
   stretches the single card across the whole page and it reads as a mistake. */
.article-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 30px;
}
@media (max-width: 1080px) { .article-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 700px)  { .article-grid { grid-template-columns: 1fr; } }
.article-card {
  display: flex;
  flex-direction: column;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.article-media { display: block; aspect-ratio: 16 / 10; overflow: hidden; }
.article-media img { width: 100%; height: 100%; object-fit: cover; transition: transform .4s ease; }
.article-card:hover .article-media img { transform: scale(1.03); }
.article-copy { display: flex; flex-direction: column; gap: 8px; padding: 26px 24px 28px; }
.article-copy .eyebrow { margin: 0; }
.article-title { margin: 0; font-size: 1.32rem; line-height: 1.25; }
.article-title a { text-decoration: none; color: var(--forest); }
.article-title a:hover { color: var(--gold-dark); }
.article-excerpt { margin: 0; font-size: .84rem; color: #33463f; }
.article-meta { margin: 0; font-size: .72rem; color: var(--muted); display: flex; flex-wrap: wrap; gap: 8px; }

/* The title and date of one article. It used to be a cream band across the top
   of the page; the band went, the title stayed. */
.article-head { padding: calc(56px * var(--tune-space)) 0 calc(24px * var(--tune-space)); }
.article-head .eyebrow { margin-bottom: 10px; }
.article-head h1 { margin: 0; color: var(--forest); font-size: clamp(2rem, 4vw, 3.2rem); }
/* The card rule further down spreads its meta line edge to edge, which is
   right in a card and wrong under a title: here the date and the reading time
   belong together at the left. */
.article-head .article-meta { margin-top: 16px; justify-content: flex-start; }

/* Words an editor typed where a page's opening band used to hold them. */
.section-intro { padding-bottom: 0; }
.archive-intro { margin-bottom: 34px; }

.article-cover { margin: 0; }
.article-cover img { width: 100%; border-radius: var(--radius-lg); }

/* The reading column: long lines are what make an article hard to finish. */
.article-body {
  max-width: 68ch;
  margin-inline: auto;
  font-size: .95rem;
  line-height: 1.85;
  color: #2c3f39;
}
.article-body > * { margin-block: 0 1.15em; }
.article-body h2 { margin-block: 1.8em .5em; font-size: clamp(1.5rem, 2.4vw, 2rem); color: var(--forest); }
.article-body h3 { margin-block: 1.5em .4em; font-size: 1.18rem; color: var(--forest); }
.article-body ul, .article-body ol { padding-left: 1.3em; }
.article-body li { margin-bottom: .5em; }
.article-body img, .article-body figure { border-radius: var(--radius-lg); }
.article-body figcaption { font-size: .74rem; color: var(--muted); margin-top: 8px; }
.article-body blockquote {
  margin: 1.6em 0;
  padding: 4px 0 4px 22px;
  border-left: 2px solid var(--gold);
  font-family: var(--serif);
  font-size: 1.15rem;
  color: var(--ink);
}
.article-body table { width: 100%; border-collapse: collapse; font-size: .84rem; }
.article-body th, .article-body td { padding: 10px 12px; border: 1px solid var(--line); text-align: left; }
.article-body th { background: var(--cream); font-weight: 600; }

/* The lead answer, in an article as on the FAQ page. */
.article-body .answer-first {
  font-size: 1.02rem;
  font-weight: 500;
  color: var(--ink);
  background: var(--cream-soft);
  border-left: 3px solid var(--gold);
  border-radius: 0 var(--radius) var(--radius) 0;
  padding: 18px 22px;
}

.page-links { display: flex; gap: 8px; justify-content: center; margin-top: 30px; font-size: .8rem; }

/* ==========================================================================
   19. Contact dock
   ========================================================================== */

.contact-dock {
  position: fixed;
  right: 18px;
  bottom: 18px;
  z-index: 60;
  display: flex;
  flex-direction: column;
  gap: 10px;
  align-items: flex-end;
  transition: opacity .3s ease, transform .3s ease, visibility 0s linear 0s;
}

/* Out of the way until the hero has been scrolled past — see theme.js, and the
   <noscript> beside the markup that hands the buttons back without it.
   visibility as well as opacity, so a dock that is only faded out cannot still
   be tabbed into. */
.contact-dock.is-waiting {
  opacity: 0;
  visibility: hidden;
  transform: translateY(16px);
  pointer-events: none;
  transition: opacity .3s ease, transform .3s ease, visibility 0s linear .3s;
}
.contact-dock-btn {
  display: inline-flex;
  align-items: center;
  gap: 0;
  height: 52px;
  padding: 0 16px;
  border-radius: 999px;
  background: var(--forest);
  color: var(--white);
  text-decoration: none;
  font-size: .8rem;
  font-weight: 600;
  box-shadow: 0 8px 24px rgba(20, 48, 41, .22);
  transition: gap .25s ease, padding .25s ease, background .2s ease;
}
.contact-dock-btn.is-line { background: #06c755; }
.contact-dock-btn.is-facebook { background: #1877f2; }
.contact-dock-btn:hover { background: var(--forest-dark); }
.contact-dock-btn.is-line:hover { background: #05a948; }
.contact-dock-btn.is-facebook:hover { background: #1462c9; }

/* The label stays collapsed until the button is wanted, so the dock never
   covers what someone is reading. */
.contact-dock-label {
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  opacity: 0;
  transition: max-width .25s ease, opacity .2s ease, margin .25s ease;
}
.contact-dock-btn:hover .contact-dock-label,
.contact-dock-btn:focus-visible .contact-dock-label {
  max-width: 220px;
  opacity: 1;
  margin-left: 10px;
}
.contact-dock-btn:hover .screen-reader-text,
.contact-dock-btn:focus-visible .screen-reader-text { display: none; }
.channel-icon { flex-shrink: 0; }

@media (max-width: 620px) {
  .contact-dock { right: 14px; bottom: 14px; gap: 8px; }
  .contact-dock-btn { height: 48px; padding: 0 14px; }
}

/* A phone in landscape has almost no height to spare. */
@media (max-height: 420px) {
  .contact-dock { display: none; }
}

@media print {
  .contact-dock { display: none; }
}

/* ==========================================================================
   20. WordPress admin bar
   ========================================================================== */

/* The bar is only fixed to the window on a wide screen. Below 782px WordPress
   makes it absolute — it scrolls away with the page — so an offset held here
   would leave the sticky header parked 46px down the window with the page
   sliding through the gap above it. Nothing offsets it there. */
body.admin-bar .site-header { top: 32px; }
@media screen and (max-width: 782px) {
  body.admin-bar .site-header { top: 0; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { transition-duration: .01ms !important; }
}

/* ==========================================================================
   21. Language popup
   ========================================================================== */

/* The first-visit language card, and the only language switch on the page.
   The contact dock owns the bottom-right, so this one rests on the left — and
   slides in from the right to get there. theme.js unhides it, then adds .is-in
   a frame later so the slide runs. Everything about it is sized to be read in
   a second and dismissed: one line of prompt, one row of buttons. */
.lang-popup {
  position: fixed;
  left: 18px;
  bottom: 18px;
  z-index: 70;
  max-width: calc(100vw - 36px);
  padding: 11px 13px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--white);
  box-shadow: 0 12px 34px rgba(20, 48, 41, .16);
  opacity: 0;
  transform: translateX(72px);
  transition: opacity .35s ease, transform .5s cubic-bezier(.22, .61, .36, 1);
}
.lang-popup[hidden] { display: none; }
.lang-popup.is-in { opacity: 1; transform: translateX(0); }

.lang-popup-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.lang-popup-title {
  margin: 0;
  font-size: .66rem;
  letter-spacing: .02em;
  color: var(--muted);
}

/* The three sit in a row, the way the header switch used to read. */
.lang-popup-options { display: flex; gap: 6px; }
.lang-popup-btn {
  padding: 6px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: .74rem;
  font-weight: 600;
  white-space: nowrap;
  color: var(--muted);
  text-decoration: none;
  transition: border-color .2s ease, background .2s ease, color .2s ease;
}
.lang-popup-btn:hover,
.lang-popup-btn:focus-visible { border-color: var(--forest); color: var(--ink); background: var(--cream-soft); }
.lang-popup-btn.is-current {
  border-color: var(--forest);
  background: var(--forest);
  color: var(--white);
}

.lang-popup-close {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--muted);
  font-size: .95rem;
  line-height: 1;
  cursor: pointer;
  transition: background .2s ease, color .2s ease;
}
.lang-popup-close:hover { background: var(--sand); color: var(--ink); }

/* On a phone it keeps clear of the contact dock's icons rather than sitting
   on top of them. */
@media (max-width: 620px) {
  .lang-popup { left: 14px; bottom: 14px; max-width: calc(100% - 92px); }
}

@media print {
  .lang-popup { display: none; }
}

/* ==========================================================================
   22. Style presets — the shapes a palette cannot carry
   ==========================================================================
   Everything a preset can say in colours and rounding it says through :root,
   from inc/skins.php, and those tokens reach the drawn rules without anything
   here. What is left is shape: the AXIOMA reference sets its navigation as a
   column of capsules rather than a list of links, and no value of --radius on
   its own can turn one into the other.

   Scoped to .skin-axioma, which inc/skins.php only puts on the body when that
   preset is the one chosen. The default preset adds no class, so none of this
   can reach a site running the theme as drawn. */

@media (max-width: 980px) {
  /* The sheet loses its dividing hairline: with the items drawn as capsules the
     line reads as a second border stacked on the first. */
  .skin-axioma .main-nav { border-top-color: transparent; padding-bottom: 18px; }

  /* The drawn theme stacks the menu as full-width rows, stretched edge to edge
     and divided by an inset hairline. Capsules want the opposite on all three
     counts: shrink to their own width, sit centred, and carry no divider — a
     line drawn across the top of a pill reads as a mistake, not a separator. */
  .skin-axioma .main-nav ul { align-items: center; gap: 9px; }
  .skin-axioma .main-nav li + li a { box-shadow: none; }

  .skin-axioma .main-nav a {
    text-align: center;
    /* A fixed width rather than one per label, so the capsules stack as an even
       column instead of pulsing in and out with the length of each word. */
    width: min(230px, 72vw);
    padding: 11px 20px;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--white);
    transition: background .2s ease, border-color .2s ease, color .2s ease;
  }

  .skin-axioma .main-nav a:hover,
  .skin-axioma .main-nav a:focus-visible {
    background: var(--forest);
    border-color: var(--forest);
    color: var(--white);
  }

  /* The page you are on is the filled one, the way the reference marks it. */
  .skin-axioma .main-nav .current-menu-item > a,
  .skin-axioma .main-nav .current_page_item > a {
    background: var(--forest);
    border-color: var(--forest);
    color: var(--white);
  }
}

/* --- The figures on the opening picture ----------------------------------
   Drawn whenever the Customizer holds a number, in either preset: the panel is
   frosted glass, which the AXIOMA reference uses and which the drawn theme's
   own header already speaks. Positioned rather than placed in the flow so it
   cannot push the copy around, and kept above the hero's own gradient. */

/* Lifted out of the copy it is written into and pinned to the corner of the
   content column — the copy is capped at 560px, so anchoring to the container
   is what lets the panel reach the far side of the frame. */
.hero-figures {
  position: absolute;
  right: 0;
  bottom: 96px;
  z-index: 2;
  display: grid;
  gap: 12px;
  width: min(280px, 42vw);
}

.hero-figure {
  padding: 16px 20px;
  border: 1px solid rgba(255, 255, 255, .22);
  border-radius: var(--radius-lg);
  background: rgba(20, 20, 20, .38);
  -webkit-backdrop-filter: blur(14px);
          backdrop-filter: blur(14px);
  color: #fff;
}
.hero-figure-value { margin: 0; font-size: 2rem; line-height: 1.1; font-weight: 600; }
.hero-figure-label { margin: 6px 0 0; font-size: .74rem; line-height: 1.45; color: rgba(255, 255, 255, .82); }

/* On a phone the panel would sit on top of the words, so the two cards go side
   by side above them and the copy keeps the foot of the frame to itself. */
@media (max-width: 620px) {
  .hero-figures {
    position: static;
    width: auto;
    grid-template-columns: 1fr 1fr;
    gap: 10px;
    margin: 0 0 18px;
  }
  .hero-figure { padding: 13px 15px; }
  .hero-figure-value { font-size: 1.5rem; }
  .hero-figure-label { font-size: .68rem; }
}

/* --- AXIOMA: the rest of the shapes -------------------------------------- */

/* The frame. The reference insets the opening picture from the page edge and
   rounds it hard, so the white of the page reads as a mount around it. */
.skin-axioma .hero,
.skin-axioma .hero.is-slider {
  border-radius: 26px;
  overflow: hidden;
}
.skin-axioma.has-hero .hero { margin-inline: 14px; }

/* An overflow-hidden hero clips the header that is pulled up over it, so the
   inset is given back to the bar rather than taken from it. */
.skin-axioma.has-hero .site-header > .container { padding-inline: 14px; }

/* On a phone the picture takes the whole width. The mount reads as a frame on
   a wide screen; on a 390px one it is 16px of white either side of the only
   photograph on the page, and the hero's copy needs every pixel of it. The
   header gives its inset back at the same breakpoint. */
@media (max-width: 620px) {
  .skin-axioma .hero,
  .skin-axioma .hero.is-slider { border-radius: 0; }
  .skin-axioma.has-hero .hero { margin-inline: 0; }
  .skin-axioma.has-hero .site-header > .container { padding-inline: 0; }
}

/* The arrow badge. A filled disc at the end of the label, the way the
   reference draws every call to action — built from the span the hero buttons
   already carry, and from a pseudo-element on the header's own button, which
   has no span of its own to use. */
.skin-axioma .btn:not(.btn-ghost) .btn-arrow,
.skin-axioma .header-cta::after {
  display: inline-grid;
  place-items: center;
  width: 30px; height: 30px;
  margin-left: 12px;
  margin-right: -10px;
  border-radius: 50%;
  background: var(--forest);
  color: var(--white);
  font-size: .85rem;
  line-height: 1;
  flex: none;
}
.skin-axioma .header-cta::after { content: "↗"; }
.skin-axioma .btn:not(.btn-ghost) .btn-arrow { content: none; }

/* The gold button carries a dark disc; the dark buttons need the reverse or the
   badge disappears into the fill. */
.skin-axioma .btn:not(.btn-gold):not(.btn-ghost) .btn-arrow {
  background: var(--white);
  color: var(--forest);
}

/* Icon buttons become discs with a hairline, not rounded squares. */

.skin-axioma .gallery-arrow,
.skin-axioma .plan-close,
.skin-axioma .hero-arrow,
.skin-axioma .save-style,
.skin-axioma .menu-toggle {
  border-radius: 50%;
  aspect-ratio: 1;
  width: auto;
  min-width: 40px;
  padding: 0;
}

@media (min-width: 981px) {
  /* The desktop bar: every item its own capsule, the one you are on filled. */
  .skin-axioma .main-nav ul { gap: 8px; }

  .skin-axioma .main-nav a {
    padding: 8px 16px;
    border: 1px solid var(--line);
    border-bottom-color: var(--line);   /* the drawn theme's underline, retired */
    border-radius: var(--radius);
    transition: background .2s ease, border-color .2s ease, color .2s ease;
  }

  .skin-axioma .main-nav a:hover {
    background: var(--forest);
    border-color: var(--forest);
    color: var(--white);
  }

  .skin-axioma .main-nav .current-menu-item > a,
  .skin-axioma .main-nav .current_page_item > a {
    background: var(--forest);
    border-color: var(--forest);
    color: var(--white);
  }

  /* Over the clear header the hairlines would vanish against a photograph. */
  .skin-axioma .site-header:not(.is-stuck) .main-nav a {
    border-color: rgba(255, 255, 255, .55);
    background: rgba(255, 255, 255, .14);
  }
}

/* --- AXIOMA: the call to action at the foot of the frame -----------------
   The reference floats one pill at the bottom centre of the opening picture
   rather than tucking it under the copy. Taking it out of the flow needs an
   anchor that is the whole frame, and the only positioned ancestor between the
   button and the slide is the content column — which stops where the copy
   stops. So the column goes static and the lift it was carrying moves onto the
   copy itself, which is what actually needed to sit above the picture. */

/* The column keeps the lift it always had — the copy has to sit above the
   picture layers — and is stretched to the height of the frame, so an absolute
   child measured from its bottom is measured from the bottom of the picture.
   Anchoring the button to .hero-copy instead looks like it would work and does
   not: the copy stops where the words stop, and the button lands halfway up. */
.skin-axioma .hero > .container,
.skin-axioma .hero-slide > .container {
  position: relative;
  z-index: 1;
  min-height: 100%;
}

.skin-axioma .hero-buttons {
  position: absolute;
  left: 50%;
  bottom: 78px;
  transform: translateX(-50%);
  z-index: 3;
  width: max-content;
  max-width: calc(100% - 40px);
  justify-content: center;
}

/* Re-anchored with the column: measured from the frame now, not from the
   content edge, which is where the reference puts it anyway. */
.skin-axioma .hero-figures { right: 26px; }

@media (max-width: 620px) {
  /* Stretching the column undid what put the copy at the foot of the frame —
     that was the slide's own end-alignment, and a column filling the height has
     nothing left to align. The column takes the job over. */
  .skin-axioma .hero-slide > .container,
  .skin-axioma .hero > .container {
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
  }

  /* The picture is the point, so the scrim stops being a floor and becomes a
     wash: enough to carry white type, not enough to black out the bottom half
     of the photograph the way it was doing. */
  .skin-axioma .hero-copy {
    padding-bottom: 132px;
    background: linear-gradient(180deg,
      rgba(9, 22, 15, 0) 0%,
      rgba(9, 22, 15, .22) 30%,
      rgba(9, 22, 15, .46) 58%,
      rgba(9, 22, 15, .60) 82%,
      rgba(9, 22, 15, .64) 100%);
  }

  .skin-axioma .hero-buttons { bottom: 62px; }

  /* The pill is the widest thing at the foot of a phone frame; the dots need
     to clear it rather than share its line. */
  .skin-axioma .hero-controls { bottom: 18px; }
}

/* --- AXIOMA: the category bar, and a wordmark without its picture ---------
   The pills read as glass over the photograph — a wash of white with a hairline
   on it — and the one you are on fills with the logo's own green. The hairline
   has to change with the bar behind it: white over the picture, where white is
   the only thing that shows; the ordinary line colour once the bar has gone
   opaque, where a white edge would disappear into it. */

@media (min-width: 981px) {
  .skin-axioma .main-nav a {
    border-color: var(--line);
    background: rgba(255, 255, 255, .55);
    -webkit-backdrop-filter: blur(8px);
            backdrop-filter: blur(8px);
  }

  .skin-axioma .site-header:not(.is-stuck) .main-nav a {
    border-color: rgba(255, 255, 255, .62);
    background: rgba(255, 255, 255, .16);
  }

  /* Selected — and hovered, which is the same statement made early. */
  .skin-axioma .main-nav a:hover,
  .skin-axioma .main-nav a:focus-visible,
  .skin-axioma .main-nav .current-menu-item > a,
  .skin-axioma .main-nav .current_page_item > a,
  .skin-axioma .site-header:not(.is-stuck) .main-nav a:hover,
  .skin-axioma .site-header:not(.is-stuck) .main-nav .current-menu-item > a,
  .skin-axioma .site-header:not(.is-stuck) .main-nav .current_page_item > a {
    background: var(--brand-green);
    border-color: var(--brand-green);
    color: var(--white);
  }
}

/* The same green marks the page you are on in the sheet the toggle drops, so
   the two menus agree. Glass is not borrowed with it: those pills sit on a
   white sheet, where a wash of white on white would leave nothing to see. */
@media (max-width: 980px) {
  .skin-axioma .main-nav a:hover,
  .skin-axioma .main-nav a:focus-visible,
  .skin-axioma .main-nav .current-menu-item > a,
  .skin-axioma .main-nav .current_page_item > a {
    background: var(--brand-green);
    border-color: var(--brand-green);
    color: var(--white);
  }
}

/* Over the picture the wordmark carries the brand on its own; once the bar has
   drawn itself white the mark comes back to sit on it. Hidden rather than
   removed from the template, so switching back to the Obilis preset brings the
   picture with it and nothing has to be re-uploaded.

   An open menu counts as a white bar too: it fills to 97% without ever being
   stuck, and a wordmark floating alone above an opaque sheet reads as something
   that failed to load. */
/* It fades and folds away rather than being switched off, so the wordmark
   slides across instead of jumping the width of the mark. The gap between the
   two moves onto the mark itself: `gap` cannot be animated away, and a flex gap
   left standing would hold 10px of daylight open beside a mark of zero width. */
.skin-axioma .brand { gap: 0; }

.skin-axioma .brand img,
.skin-axioma .brand .custom-logo {
  max-width: 48px;      /* above its width at either breakpoint, so it never clamps */
  margin-right: 10px;
  transition: max-width .32s ease, margin-right .32s ease, opacity .24s ease;
}

.skin-axioma .site-header:not(.is-stuck):not(:has(.main-nav.is-open)) .brand img,
.skin-axioma .site-header:not(.is-stuck):not(:has(.main-nav.is-open)) .brand .custom-logo {
  max-width: 0;
  margin-right: 0;
  opacity: 0;
}

/* --- AXIOMA: the wash runs to the top of the frame -----------------------
   It used to stop short. The mask here held the cream ramp away from the band
   the header bar occupies and let it arrive about 54px under it, on the reading
   that milk behind a transparent bar looked like the bar's own scrim.

   Seen on the page it read as a mistake instead: the wash began a third of the
   way down the picture with a visible edge, and the top-left corner — the one
   place the headline actually starts — was left on bare photograph. The ramp is
   a corner of light, so it has to own its corner.

   The base rule already runs it from 0, so this override only has to stop
   subtracting the top band. What it still does is keep the horizontal reach and
   the bottom fade the skin was tuned for, and hand the bar's own legibility back
   to the wordmark's text-shadow below, which is what it was always doing under
   the old mask anyway. */
@media (min-width: 621px) {
.skin-axioma .hero::before,
.skin-axioma .hero-slide::before {
  -webkit-mask-image: linear-gradient(180deg,
    #000 0%,
    #000 52%,
    rgba(0, 0, 0, .42) 74%,
    rgba(0, 0, 0, 0) 92%);
          mask-image: linear-gradient(180deg,
    #000 0%,
    #000 52%,
    rgba(0, 0, 0, .42) 74%,
    rgba(0, 0, 0, 0) 92%);
}
}

/* The bar's own scrim goes with it: the menu items carry their own glass, so
   the only thing left needing help is the wordmark, which gets a shadow of its
   own instead of a band of white across the whole frame. Both come back the
   moment the bar draws itself, where a shadow on an opaque bar would be grubby. */
.skin-axioma .site-header:not(.is-stuck)::after { opacity: 0; }

.skin-axioma .site-header:not(.is-stuck) .brand-name,
.skin-axioma .site-header:not(.is-stuck) .brand-sub {
  text-shadow: 0 1px 14px rgba(255, 255, 255, .85), 0 1px 3px rgba(255, 255, 255, .9);
}

/* --- AXIOMA: clear rather than frosted, over the picture ------------------
   The bar sitting on the hero carries nothing behind it now: no wash on the
   pills, no blur, no glow behind the wordmark — only the hairline that draws
   each capsule. The frosted treatment stays where it was asked for, on the bar
   that appears once the page is scrolled. */

@media (min-width: 981px) {
  .skin-axioma .site-header:not(.is-stuck) .main-nav a {
    background: transparent;
    border-color: rgba(255, 255, 255, .7);
    -webkit-backdrop-filter: none;
            backdrop-filter: none;
  }

  /* The selected capsule keeps its green: it is the one thing on the bar that
     is meant to be solid. */
  .skin-axioma .site-header:not(.is-stuck) .main-nav a:hover,
  .skin-axioma .site-header:not(.is-stuck) .main-nav .current-menu-item > a,
  .skin-axioma .site-header:not(.is-stuck) .main-nav .current_page_item > a {
    background: var(--brand-green);
    border-color: var(--brand-green);
  }
}

.skin-axioma .site-header:not(.is-stuck) .brand-name,
.skin-axioma .site-header:not(.is-stuck) .brand-sub { text-shadow: none; }

/* The bar keeps its dark type over the picture, by choice. The outlines stay
   white, so the capsules still draw themselves against the photograph; only the
   words inside them are the theme's ink. */
.skin-axioma .site-header:not(.is-stuck) .main-nav a,
.skin-axioma .site-header:not(.is-stuck) .brand,
.skin-axioma .site-header:not(.is-stuck) .brand-name { color: var(--ink); }

.skin-axioma .site-header:not(.is-stuck) .brand-sub { color: var(--muted); }

.skin-axioma .site-header:not(.is-stuck) .menu-toggle {
  color: var(--ink);
  border-color: rgba(255, 255, 255, .7);
}

/* The selected capsule stays green with white on it, so it still reads as the
   one that is filled rather than as one more outline. */
.skin-axioma .site-header:not(.is-stuck) .main-nav a:hover,
.skin-axioma .site-header:not(.is-stuck) .main-nav .current-menu-item > a,
.skin-axioma .site-header:not(.is-stuck) .main-nav .current_page_item > a { color: #fff; }

/* --- Landing on an anchor, under a sticky bar ----------------------------
   The bar is fixed to the top of the window, so a jump to #homes puts the top
   of that section exactly where the bar is standing and its heading arrives
   already covered. scroll-margin-top is the browser's own answer: it tells the
   scroll to stop short by the height of whatever is in the way.

   Measured from --header-real, the height theme.js publishes, so it follows the
   bar when the row wraps on a phone; --header-h is the fallback for a first
   paint before the script has run. Not part of any style preset — the menu
   links do this on every theme. */
section[id],
.story[id],
.cta-band[id] {
  scroll-margin-top: calc(var(--header-real, var(--header-h)) + 26px);
}


/* ==========================================================================
   22. House models on a style page
   ==========================================================================

   The catalogue card is a style; the models drawn in that style live behind it,
   on the style's own page. The card here is deliberately quieter than the
   project card — photograph, name, price, one line — because everything else
   about a model is on the model's own page, one click away. */

.style-models { padding-top: calc(70px * var(--tune-space)); }

/* Three across at most, and the card takes whatever width that leaves.

   This used to be auto-fill at a fixed minimum, which counts how many cards of
   that width fit rather than how many should: at 1360px it laid out five 250px
   tracks, so a catalogue holding two models drew two small cards against the
   left edge and three columns of nothing. Counting the columns instead means
   the photograph is always as large as the row allows — 436px on a desktop
   against the 250px it was — and the count steps down with the viewport. */
.model-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 30px 26px;
}

/* The catalogue and a style page hold the same card and now the same grid;
   .is-wide is kept because both templates and the editor's saved markup use
   it, and because it still marks which of the two this is. */
.model-grid.is-wide { grid-template-columns: repeat(3, minmax(0, 1fr)); }

/* Below a laptop three cards each fall under about 260px, which is narrower
   than the photograph deserves; two is the better trade from here down. */
@media (max-width: 900px) {
  .model-grid,
  .model-grid.is-wide { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.model-card {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  transition: border-color .2s ease, transform .2s ease;
}
.model-card:hover { border-color: var(--forest); transform: translateY(-2px); }

.model-media {
  display: block;
  aspect-ratio: 4 / 3;
  background: var(--cream);
}
.model-media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.model-copy {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 8px;
  padding: calc(22px * var(--tune-density)) calc(22px * var(--tune-density)) calc(20px * var(--tune-density));
}

/* Which style a model is drawn in. Shown only in the catalogue, where the grid
   holds every style at once and that is the question the eye is asking; on a
   style page the answer is the page itself, so the tag would be noise. */
.model-style {
  margin: 0;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--gold-dark);
}

.model-title {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.25rem;
  line-height: 1.25;
}
.model-title a { text-decoration: none; }
.model-title a:hover { color: var(--forest); }

.model-desc {
  margin: 0;
  font-size: .82rem;
  line-height: 1.75;
  color: var(--muted);
}

/* The price is the one number a buyer scans a grid of models for, so it is set
   apart from the sentence above it rather than folded into it. */
.model-price {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 2px 0 0;
}
.model-price span { font-size: .72rem; letter-spacing: .06em; color: var(--muted); }
.model-price b { font-family: var(--serif); font-size: 1.1rem; color: var(--gold-dark); }

.model-card .link-arrow { margin-top: 6px; font-size: .76rem; }

/* The models on the front page: a sample under the styles that sort them, not
   the catalogue. Six at most, and the grid above already stops at three across,
   so what is left to say here is the room around them and the way out. */
.home-models { margin-top: calc(46px * var(--tune-space)); }

.home-models-more {
  margin: calc(34px * var(--tune-space)) 0 0;
  text-align: center;
}

@media (max-width: 620px) {
  /* .is-wide is two classes and would otherwise outrank this one. */
  .model-grid, .model-grid.is-wide { grid-template-columns: 1fr; gap: 22px; }
  .model-title { font-size: 1.15rem; }
}

/* The models behind a style card — see front-page.php and page-catalog.php,
   which both ask the same question. On the front page it is a line of text
   inside a card that is itself one big link; in the catalogue, where the card
   is not, it is a link of its own. */
.style-count {
  margin: 10px 0 0;
  font-size: .76rem;
  color: var(--forest);
  border-bottom: 1px solid var(--gold);
  padding-bottom: 2px;
  display: inline-block;
}

/* ==========================================================================
   The enquiry form
   ==========================================================================
   Rendered by obilis_render_lead_form() in inc/leads.php. Two columns on a
   desktop, one on a phone; the message box and the buttons always span the
   full width so the eye finishes each row before the next begins. */

.lead-form {
  max-width: 860px;
  margin-inline: auto;
  text-align: left;
}

.lead-heading {
  margin: 0 0 8px;
}

.lead-lead {
  margin: 0 0 24px;
  color: var(--muted);
}

.lead-fields {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 16px 20px;
}

.lead-row {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin: 0;
}

.lead-row.is-wide,
.lead-actions {
  grid-column: 1 / -1;
}

.lead-row label {
  font-size: .9rem;
  font-weight: 600;
  color: var(--ink);
}

.lead-row .req {
  color: var(--gold-dark);
}

.lead-row input,
.lead-row select,
.lead-row textarea {
  width: 100%;
  padding: 12px 14px;
  font: inherit;
  font-size: 1rem;
  color: var(--ink);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.lead-row input:focus-visible,
.lead-row select:focus-visible,
.lead-row textarea:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 1px;
  border-color: var(--gold);
}

.lead-row textarea {
  resize: vertical;
  min-height: 110px;
}

.lead-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 18px;
  margin: 8px 0 0;
}

.lead-privacy {
  font-size: .85rem;
  color: var(--muted);
}

/* The honeypot: off-screen rather than display:none, which some bots read as
   a field they are meant to skip. */
.lead-trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.lead-note {
  margin: 0 0 20px;
  padding: 14px 16px;
  border-radius: var(--radius);
  border-inline-start: 4px solid;
  font-weight: 500;
}

.lead-note.is-good {
  background: #eef6f0;
  border-color: var(--forest);
  color: var(--forest-dark);
}

.lead-note.is-bad {
  background: #fdf3e6;
  border-color: var(--gold-dark);
  color: #7a4a12;
}

/* The closing band on the front page is dark; the form sits on a card so the
   fields keep the contrast a form needs. */
.cta-band .lead-form {
  margin-top: 34px;
  padding: 28px;
  background: var(--cream-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

.cta-band .lead-heading,
.cta-band .lead-row label {
  color: var(--ink);
}

@media (max-width: 720px) {
  .lead-fields {
    grid-template-columns: minmax(0, 1fr);
  }

  .cta-band .lead-form {
    padding: 20px;
  }
}

/* The contact page: details on the left, the form on the right. */
.contact-layout {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: 40px;
  align-items: start;
}

.contact-details h2 {
  margin: 0 0 20px;
}

.contact-line {
  margin: 0 0 18px;
  line-height: 1.6;
}

.contact-line strong {
  font-size: .85rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.contact-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 26px 0 0;
}

.contact-form .lead-form {
  margin: 0;
  padding: 28px;
  background: var(--cream-soft);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

@media (max-width: 900px) {
  .contact-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: 32px;
  }

  .contact-form .lead-form {
    padding: 20px;
  }
}

/* ==========================================================================
   The article index
   ==========================================================================
   Laid out as a magazine rather than a uniform grid — see page-articles.php.
   Three shapes of one card do the work: the standard card, a `.is-feature`
   that lays its words over the photograph, and a short, wide `.is-compact`
   with the photograph beside them. */

/* The masthead this page used to open with went the way of the grey band on
   every other page. What is left of it is the lead the editor wrote, set above
   the list as a line of copy rather than a band. */
.article-lead {
  max-width: 620px;
  color: var(--muted);
  font-size: .92rem;
  line-height: 1.75;
}

/* The three newest, side by side. */
.article-feature-row {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 26px;
}

/* The mosaic. The first card spans two columns and two rows, so the two
   compact cards after it stack in the third column and the block closes as a
   rectangle; everything after falls into single cells. */
.article-bento {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-auto-rows: minmax(0, auto);
  gap: 26px;
  margin-top: 30px;
}

.article-bento > .is-feature {
  grid-column: span 2;
  grid-row: span 2;
}

/* --- The card, in its three shapes ------------------------------------- */

.article-tags {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0;
}

.article-tag {
  padding: 4px 12px;
  font-size: .68rem;
  font-weight: 600;
  letter-spacing: .04em;
  color: var(--forest);
  background: var(--sand);
  border-radius: 999px;
}

.article-time {
  font-size: .7rem;
  color: var(--muted);
}
.article-time::before {
  content: '·';
  margin-inline-end: 8px;
}
.article-tags > .article-time:first-child::before { content: none; }

.article-meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: auto 0 0;
  padding-top: 4px;
  font-size: .72rem;
  color: var(--muted);
}

.article-more {
  color: var(--gold-dark);
  transition: transform .2s ease;
}
.article-card:hover .article-more { transform: translateX(3px); }

/* The feature card: photograph behind, words in front, a wash between the two
   so the type keeps its contrast whatever the photograph is doing. */
.article-card.is-feature {
  position: relative;
  min-height: 420px;
  border: 0;
}

.article-card.is-feature .article-media {
  position: absolute;
  inset: 0;
  aspect-ratio: auto;
}

.article-card.is-feature .article-media::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(14, 36, 24, .88) 0%, rgba(14, 36, 24, .45) 42%, rgba(14, 36, 24, 0) 72%);
}

.article-card.is-feature .article-copy {
  position: relative;
  justify-content: flex-end;
  min-height: 420px;
  padding: 30px 30px 28px;
}

/* The standard card pushes its date to the bottom with an automatic top
   margin, which on this card swallowed the free space and left the headline
   stranded at the top of the photograph, over the clear part of the wash.
   Here the whole block is pushed down instead, by justify-content above. */
.article-card.is-feature .article-meta { margin-top: 0; }

.article-card.is-feature .article-title { font-size: clamp(1.5rem, 2.4vw, 2rem); }
.article-card.is-feature .article-title a { color: var(--white); }
.article-card.is-feature .article-title a:hover { color: var(--gold-light); }
.article-card.is-feature .article-excerpt,
.article-card.is-feature .article-meta { color: rgba(255, 255, 255, .78); }
.article-card.is-feature .article-tag { color: var(--forest-deep); background: var(--gold-light); }
.article-card.is-feature .article-time { color: rgba(255, 255, 255, .72); }
.article-card.is-feature .article-more { color: var(--gold-light); }

/* The compact card: photograph beside the words, and no excerpt — at this
   height a third line of type would be cropped rather than read. */
.article-card.is-compact {
  display: grid;
  grid-template-columns: 40% minmax(0, 1fr);
  align-items: stretch;
}

.article-card.is-compact .article-media {
  height: 100%;
  aspect-ratio: auto;
}

.article-card.is-compact .article-copy {
  gap: 8px;
  padding: 18px 20px;
}

.article-card.is-compact .article-title { font-size: 1.05rem; }

/* --- The bar above the mosaic ------------------------------------------ */

.article-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 16px 24px;
  padding: 18px 22px;
  background: var(--cream-soft);
  border: 1px solid var(--line);
  border-radius: 999px;
}

.article-bar-copy { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 14px; }

.article-bar-title {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-weight: 700;
  color: var(--forest);
}

.article-bar-mark { color: var(--gold-dark); }

.article-bar-sub {
  margin: 0;
  font-size: .78rem;
  color: var(--muted);
}

/* The pill on the right of the bar, with its mark in a circle. */
.btn-round {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 6px 6px 6px 20px;
  border-radius: 999px;
}

.btn-round-mark {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  color: var(--forest);
  background: var(--white);
  border-radius: 50%;
}

/* The count beside a topic on a filter chip. */
.chip-count {
  margin-inline-start: 6px;
  font-size: .68rem;
  font-weight: 600;
  color: var(--muted);
}
.chip.is-active .chip-count { color: rgba(255, 255, 255, .7); }

/* --- Narrower screens --------------------------------------------------- */

/* The mosaic gives up its third column first. Three columns leaves the feature
   card two of them and the compact cards one, and a compact card with the
   photograph beside the words needs more width than a third of a laptop. */
@media (max-width: 1080px) {
  .article-bento { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .article-bento > .is-feature { grid-column: 1 / -1; grid-row: auto; }
  .article-card.is-feature,
  .article-card.is-feature .article-copy { min-height: 360px; }
}

/* The row of three at the top is plain cards, so it holds three across for as
   long as the model grid does — the two read as the same site at the same
   widths. */
@media (max-width: 900px) {
  .article-feature-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 700px) {

  .article-feature-row,
  .article-bento { grid-template-columns: minmax(0, 1fr); gap: 22px; }

  .article-bento > .is-feature { grid-column: auto; }

  /* Side by side, the photograph on a compact card is barely a thumbnail. */
  .article-card.is-compact { grid-template-columns: minmax(0, 1fr); }
  .article-card.is-compact .article-media { aspect-ratio: 16 / 10; }
  .article-card.is-compact .article-copy { padding: 22px 22px 24px; }

  .article-bar { border-radius: var(--radius-lg); }
}

/* The band of figures on a house-model card ------------------------------
   Land size and width as headed figures, the counted rooms as icons, and the
   price opposite them — see obilis_render_model_specs(). */

.model-specs {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: end;
  gap: 10px 16px;
  width: 100%;
  margin: 4px 0 2px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

.model-figures { display: flex; flex-direction: column; gap: 10px; min-width: 0; }

.model-land {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 22px;
  margin: 0;
}

.model-land dt {
  margin: 0 0 1px;
  font-size: .68rem;
  font-weight: 600;
  color: var(--muted);
}

.model-land dd {
  margin: 0;
  font-family: var(--serif);
  font-size: 1.05rem;
  line-height: 1.2;
  color: var(--ink);
}
.model-land dd span { font-family: var(--sans); font-size: .72rem; color: var(--muted); }

.model-counts {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 14px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.model-counts li {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: .8rem;
  font-weight: 600;
  color: var(--ink);
}

.model-count-icon { display: inline-flex; color: var(--muted); }

/* The price. Set against the figures rather than under them, so a row of
   cards can be scanned down the right-hand edge. */
.model-money {
  margin: 0;
  text-align: end;
  line-height: 1.05;
}

.model-money b {
  display: block;
  font-family: var(--serif);
  font-size: 1.75rem;
  font-weight: 700;
  color: var(--forest);
}

.model-money span {
  display: block;
  margin-top: 2px;
  font-size: .74rem;
  color: var(--muted);
}

/* On a narrow card the price under the figures reads better than a squeezed
   column beside them. */
@media (max-width: 520px) {
  .model-specs { grid-template-columns: minmax(0, 1fr); }
  .model-money { text-align: start; }
}
