/* ======================================================================
   maison.css — Aurémo & Co shared design layer
   ======================================================================

   Scoped entirely under .page-maison (set on <body> in index.html), and
   loaded after styles.css so it wins on specificity where it needs to.

   Why a separate file rather than edits inside styles.css: that file is
   215 KB shared by 29 pages — the portal, the admin panel, the product
   pages and the legal pages all depend on its generic selectors (h2,
   .split-section, .outline-button, .lifestyle). Rebuilding the homepage
   in place would have meant touching rules those pages rely on. Here,
   nothing outside .page-maison can be reached.

   Every value comes from the --a-* tokens defined at the end of
   styles.css. There are no loose colours, spacings or durations in this
   file by design — if a value is needed that isn't a token, the token
   set is wrong and gets extended there, not worked around here.

   Naming: .hm-* (homepage). Deliberately shares no class name with the
   old homepage markup, so the legacy rules in styles.css — including
   script.js's parallax selector (.lifestyle, .floating-wrap,
   .rough-crystals) and its .reveal observer — simply never match. No
   overrides, no !important, no fighting.
   ====================================================================== */

/* ----------------------------------------------------------------------
   1. Page shell
   Resets the handful of global rules that would otherwise reach in:
   the body background, and the site-wide uppercase serif headings.
   ---------------------------------------------------------------------- */
.page-maison {
  background: var(--a-ivory);
  color: var(--a-ink);
  font-family: var(--sans);
  font-size: var(--a-body);
  line-height: var(--a-lh-body);
  -webkit-font-smoothing: antialiased;
}

.page-maison h1,
.page-maison h2,
.page-maison h3 {
  /* The global rule sets uppercase + weight 400 for every heading on the
     site. Display type here is sentence case at weight 300 — the single
     biggest difference between "webshop" and "maison". */
  font-family: var(--serif);
  font-weight: var(--a-w-display);
  text-transform: none;
  letter-spacing: normal;
}

/* Skip link. Off-screen until focused rather than display:none, because a
   hidden element cannot receive focus at all. */
.hm-skip {
  position: absolute;
  z-index: 100;
  top: var(--a-s2);
  left: var(--a-s2);
  padding: var(--a-s2) var(--a-s3);
  border: 1px solid var(--a-gold);
  background: var(--a-ivory);
  color: var(--a-ink);
  font-size: var(--a-label);
  letter-spacing: var(--a-ls-label);
  text-transform: uppercase;
  transform: translateY(-200%);
  transition: transform var(--a-d-fast) var(--a-ease);
}
/* :focus, not :focus-visible. Any focus landing on a skip link should
   reveal it — :focus-visible depends on the browser's heuristics for
   whether the interaction "deserves" a ring, and a skip link that stays
   off-screen is useless to the one person who needs it. */
.hm-skip:focus { transform: translateY(0); }

/* ----------------------------------------------------------------------
   2. Layout primitives
   ---------------------------------------------------------------------- */
.hm-shell {
  width: 100%;
  max-width: var(--a-container);
  margin-inline: auto;
  padding-inline: var(--a-gutter);
}

.hm-section {
  position: relative;
  padding-block: var(--a-section-y);
}
.hm-section--shell { background: var(--a-shell); }
.hm-section--night {
  color: var(--a-night-ink);
  background: var(--a-night);
}

/* Editorial split: image one side, copy the other. The --hm-flip modifier
   swaps them so consecutive sections alternate without a second rule. */
.hm-split {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: clamp(var(--a-s5), 6vw, var(--a-s9));
  align-items: center;
}
.hm-split--flip > :first-child { order: 2; }

/* --- Page hero ---------------------------------------------------------
   Every page below the homepage opens with this instead of .hm-hero: no
   footage, no full viewport height. It states where you are and stops,
   because on an interior page the content underneath is the reason anyone
   arrived. The top padding clears the fixed masthead.

   Promoted here from collection.css once appointment.html needed the same
   opening — a second page copying it would have made the listing's copy
   the de facto shared version without saying so. */
.hm-page-hero {
  padding-block: clamp(140px, 12vh + 60px, 200px) var(--a-s8);
}

.hm-breadcrumb {
  display: flex;
  align-items: center;
  gap: var(--a-s2);
  margin: 0 0 var(--a-s5);
  font-size: var(--a-label);
  letter-spacing: var(--a-ls-label);
  text-transform: uppercase;
  color: var(--a-ink-mute);
}
/* The separator was set in the light --a-gold, on the reasoning that a
   slash is punctuation rather than type. It is still a character the
   contrast sweep measures and a screen reader reaches, and it measured
   2.59:1 — so it takes the darkened gold like every other gold character
   on the site (5.40:1 on ivory). This also fixes the same character on
   gemstones.html, which carried the rule before it moved here. */
.hm-breadcrumb span { color: var(--a-gold-ink); }
.hm-breadcrumb b { font-weight: 500; color: var(--a-ink); }

/* Screen-reader-only. For labels that name a control which the page
   already names visually — and for headings a section needs structurally
   but does not need to show. Not display:none: that removes it from the
   accessibility tree as well, which defeats the purpose. */
.hm-visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The only divider on the page. 1px at 8% — a rule this faint reads as a
   change of subject rather than a border, which is the point. */
.hm-rule {
  height: 1px;
  margin: 0;
  border: 0;
  background: var(--a-hairline);
}

/* ----------------------------------------------------------------------
   3. Typography
   ---------------------------------------------------------------------- */
.hm-label {
  display: flex;
  align-items: center;
  gap: var(--a-s3);
  margin: 0 0 var(--a-s4);
  font-family: var(--sans);
  font-size: var(--a-label);
  font-weight: 500;
  letter-spacing: var(--a-ls-label);
  text-transform: uppercase;
  color: var(--a-ink-mute);
}
/* The section number sits in gold; the rule after it carries the eye into
   the heading. */
/* The number is type, so it takes the darkened gold; the rule beside it
   is not, so it keeps the light one. On the dark band the logic inverts —
   the darkened gold measures 3.28:1 on --a-night — so there the original
   lighter gold is the accessible one. */
.hm-label span { color: var(--a-gold-ink); }
.hm-section--night .hm-label span { color: var(--a-gold); }
.hm-label i {
  flex: 0 0 auto;
  width: var(--a-s6);
  height: 1px;
  background: var(--a-gold-soft);
}
.hm-section--night .hm-label { color: var(--a-night-mute); }

.hm-title {
  margin: 0;
  font-size: var(--a-h2);
  line-height: var(--a-lh-display);
  max-width: 20ch;
}
.hm-title--display { font-size: var(--a-display); }

.hm-lead {
  margin: var(--a-s4) 0 0;
  max-width: var(--a-measure);
  font-size: var(--a-lead);
  line-height: var(--a-lh-body);
  color: var(--a-ink-soft);
}
.hm-section--night .hm-lead { color: var(--a-night-mute); }

.hm-body {
  margin: var(--a-s3) 0 0;
  max-width: var(--a-measure);
  color: var(--a-ink-soft);
}

/* ----------------------------------------------------------------------
   4. Links and calls to action
   Both are hairline-driven. Neither moves the layout on hover: the button
   shifts its tracking and its background by a hair, the link grows a gold
   rule left to right. Nothing jumps.
   ---------------------------------------------------------------------- */
.hm-link {
  position: relative;
  display: inline-block;
  padding-bottom: var(--a-s1);
  font-size: var(--a-label);
  letter-spacing: var(--a-ls-label);
  text-transform: uppercase;
  color: var(--a-ink);
  transition: color var(--a-d-fast) var(--a-ease-soft);
}
.hm-link::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1px;
  background: var(--a-gold);
  transform: scaleX(0);
  transform-origin: right;
  transition: transform var(--a-d-fast) var(--a-ease);
}
.hm-link:hover::after,
.hm-link:focus-visible::after {
  transform: scaleX(1);
  transform-origin: left;
}
.hm-section--night .hm-link { color: var(--a-night-ink); }

.hm-cta {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: var(--a-s6);
  padding-inline: var(--a-s5);
  border: 1px solid var(--a-hairline-strong);
  border-radius: var(--a-r0);
  font-size: var(--a-label);
  letter-spacing: var(--a-ls-label);
  text-transform: uppercase;
  color: var(--a-ink);
  background: transparent;
  transition: border-color var(--a-d-mid) var(--a-ease-soft),
              background var(--a-d-mid) var(--a-ease-soft),
              letter-spacing var(--a-d-mid) var(--a-ease);
}
.hm-cta:hover,
.hm-cta:focus-visible {
  border-color: var(--a-gold);
  background: var(--a-gold-wash);
  letter-spacing: 0.22em;
}
.hm-section--night .hm-cta {
  border-color: var(--a-night-hairline);
  color: var(--a-night-ink);
}
.hm-section--night .hm-cta:hover,
.hm-section--night .hm-cta:focus-visible { border-color: var(--a-gold); }

.hm-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--a-s4) var(--a-s5);
  margin-top: var(--a-s6);
}

/* ----------------------------------------------------------------------
   4b. Forms
   Written for appointment.html, moved here the moment professional-login
   needed the same fields. One form vocabulary for the whole site:

     .hm-form         the panel
     .hm-form__grid   two columns, one at 560px
     .hm-field        label above control, hairline under it
     .hm-select       wrapper that owns the chevron and the open list
     .hm-form__actions / .hm-form__note

   Underline fields, no boxes. styles.css draws form controls as bordered
   pills on a tinted fill with the label visually hidden and the
   placeholder carrying the field name — which leaves a half-filled form
   with no visible labels at all. Here every field keeps a real label and
   there are no placeholders.
   ---------------------------------------------------------------------- */
.hm-form {
  padding: clamp(var(--a-s5), 4vw, var(--a-s7));
  border: 1px solid var(--a-hairline);
  background: var(--a-ivory);
}
.hm-section--night .hm-form {
  border-color: var(--a-night-hairline);
  background: var(--a-night-wash);
}

.hm-form__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--a-s5) var(--a-s4);
}
.hm-field--full { grid-column: 1 / -1; }

.hm-field {
  display: grid;
  gap: var(--a-s1);
}
/* Two shapes for the same thing. Normally the whole field is one <label>
   wrapping a <span> and the control, which needs no id. Where the field
   also holds a button — a password toggle — that shape breaks: a button
   inside a label contributes its text to the control's accessible name,
   so the field announces itself as "Password Show". Those fields use a
   separate <label for> and take .hm-field__label instead. */
.hm-field > span,
.hm-field__label {
  font-size: var(--a-label);
  letter-spacing: var(--a-ls-label);
  text-transform: uppercase;
  color: var(--a-ink-mute);
}
/* "(optional)" rather than an asterisk on the required ones: an asterisk
   needs a legend somewhere to explain it, and on a form where most fields
   are optional it would mark the majority. */
.hm-field em {
  font-style: normal;
  text-transform: none;
  letter-spacing: 0.02em;
  color: var(--a-ink-mute);
}

.hm-field input,
.hm-field select,
.hm-field textarea {
  width: 100%;
  min-height: var(--a-s5);
  padding: var(--a-s1) 0;
  border: 0;
  border-bottom: 1px solid var(--a-hairline-strong);
  border-radius: var(--a-r0);
  background: none;
  font-family: var(--sans);
  font-size: var(--a-body);
  line-height: var(--a-lh-body);
  letter-spacing: normal;
  text-transform: none;
  color: var(--a-ink);
  transition: border-color var(--a-d-fast) var(--a-ease-soft);
}
.hm-field textarea {
  min-height: calc(var(--a-s8) + var(--a-s2));
  resize: vertical;
}
.hm-field input:hover,
.hm-field select:hover,
.hm-field textarea:hover { border-bottom-color: var(--a-ink-mute); }
/* :focus, not only :focus-visible — a field the pointer put the cursor in
   should show it too, or a click-filled form has no visible position. The
   gold ring from styles.css still handles the keyboard case on top. */
.hm-field input:focus,
.hm-field select:focus,
.hm-field textarea:focus { border-bottom-color: var(--a-gold); }

/* Autofill. Chrome paints its own background and text colour straight over
   the field; the inset shadow is the only way to put the page's surface
   back, because background-color itself is overridden. */
.hm-field input:-webkit-autofill {
  -webkit-text-fill-color: var(--a-ink);
  box-shadow: inset 0 0 0 100px var(--a-ivory);
}

/* The hint line under a field, and the same element in its error state.
   One element for both because that is what the validators write to:
   setFieldError() in script.js swaps the text and toggles .field-error on
   the <small> inside the field's label. Reserving the height means a
   message appearing does not shove the rest of the form down. */
.hm-field small {
  min-height: 1.2em;
  font-size: var(--a-label);
  line-height: 1.6;
  letter-spacing: 0.02em;
  color: var(--a-ink-mute);
}
.hm-field small.field-error { color: var(--a-alert); }
/* Marked by the browser and by the validators. A gold rule would say
   "active"; this one has to say "wrong", so it takes the alert token —
   alongside the message, never instead of it. */
.hm-field input[aria-invalid],
.hm-field select[aria-invalid],
.hm-field textarea[aria-invalid] { border-bottom-color: var(--a-alert); }

.hm-form__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--a-s3) var(--a-s5);
  margin-top: var(--a-s6);
}
/* .hm-cta is written for anchors; a <button> does not inherit either. */
button.hm-cta {
  font-family: var(--sans);
  cursor: pointer;
}
button.hm-cta:disabled {
  cursor: wait;
  opacity: 0.6;
}

/* --- Submitted confirmation --------------------------------------------
   .form-confirmation keeps its name because script.js scrolls to it by that
   name. styles.css hides it and reveals it via .appointment-form.is-submitted,
   a class none of the rebuilt forms carry, so both halves of that switch are
   restated here. Overriding the hidden state matters more than it looks:
   without it the element stays display:none for ever and a submitted form
   gives no feedback at all.

   This lived under .page-appointment until 15 August. It moved here the
   moment a second form needed it — the sourcing request — rather than being
   copied, so there is one definition and not two that can drift. Add the new
   form's data attribute to the reveal selector below when a third arrives. */
.page-maison .form-confirmation {
  display: none;
  margin: var(--a-s4) 0 0;
  padding: var(--a-s3) var(--a-s4);
  border: 0;
  border-left: 1px solid var(--a-gold);
  background: var(--a-gold-wash);
  font-family: var(--serif);
  font-weight: var(--a-w-display);
  font-size: var(--a-h3);
  line-height: 1.4;
  color: var(--a-ink);
}
.page-maison [data-appointment-form].is-submitted .form-confirmation,
.page-maison [data-sourcing-form].is-submitted .form-confirmation { display: block; }

.hm-form__note {
  margin: var(--a-s4) 0 0;
  max-width: var(--a-measure);
  font-size: var(--a-small);
  color: var(--a-ink-mute);
}

/* --- Select ------------------------------------------------------------
   appearance:none strips the platform chrome so the closed control can be
   a line of type over a hairline like every other field. The chevron is
   drawn on the wrapper, not the control: a background-image on the select
   itself lands under the platform's own arrow on some engines. */
.hm-select { position: relative; }
.hm-select::after {
  content: "";
  position: absolute;
  right: 2px;
  top: 50%;
  width: 5px;
  height: 5px;
  margin-top: -4px;
  border-right: 1px solid var(--a-ink-mute);
  border-bottom: 1px solid var(--a-ink-mute);
  transform: rotate(45deg);
  pointer-events: none;
}
.hm-select select {
  appearance: none;
  -webkit-appearance: none;
  padding-right: var(--a-s3);
  cursor: pointer;
}
.hm-select option { color: var(--a-ink); background: var(--a-ivory); }

/* --- The open list -----------------------------------------------------
   By default the popup is drawn by the operating system, which is where
   the blue highlight comes from — that is the OS accent colour, not
   anything a stylesheet set.

   appearance: base-select hands the popup back to CSS so it can be dressed
   like the rest of the page. Guarded by @supports: where it is unavailable
   the browser keeps its own picker, which is correct and usable, just not
   ours. Nothing below is required for the control to work.

   Type is inherited rather than declared, so one block serves both sizes
   the site uses: the collection filters open in 11px small caps like their
   closed state, the appointment form opens in 16px sentence case like
   its fields. Declaring --a-label here would have made the form's open
   list a different size from the value it is showing. */
@supports (appearance: base-select) {
  .hm-select select,
  .hm-select select::picker(select) {
    appearance: base-select;
  }

  /* base-select supplies its own arrow; ours is already on the wrapper. */
  .hm-select select::picker-icon { display: none; }

  .hm-select select::picker(select) {
    padding: var(--a-s1) 0;
    border: 1px solid var(--a-hairline-strong);
    border-radius: var(--a-r1);
    background: var(--a-ivory);
    /* The one shadow allowed off a gemstone: a menu floating over the page
       has to read as being above it, and a hairline cannot do that. */
    box-shadow: 0 18px 40px -18px rgba(40, 30, 18, 0.3);
    /* Sits clear of the control instead of covering it. */
    margin-block-start: var(--a-s1);
  }

  .hm-select option {
    display: block;
    padding: var(--a-s2) var(--a-s4);
    font-family: var(--sans);
    font-size: inherit;
    letter-spacing: inherit;
    text-transform: inherit;
    color: var(--a-ink-soft);
    background: transparent;
    transition: background var(--a-d-fast) var(--a-ease-soft),
                color var(--a-d-fast) var(--a-ease-soft);
  }

  /* Replaces the blue. :hover for the mouse, :focus for the keyboard —
     arrow keys move focus through the list without hovering anything. */
  .hm-select option:hover,
  .hm-select option:focus {
    background: var(--a-gold-highlight);
    color: var(--a-ink);
    outline: none;
  }

  .hm-select option:checked { color: var(--a-ink); }

  /* base-select marks the current option with a tick. A gold rule down the
     left edge is quieter and matches how the rest of the page marks state. */
  .hm-select option::checkmark {
    content: "";
    display: block;
    width: 1px;
    align-self: stretch;
    margin-inline-end: var(--a-s2);
    background: var(--a-gold);
  }
  .hm-select option:not(:checked)::checkmark { background: transparent; }
}

/* The native date picker button. Left in place — it is how a date field is
   opened on a touch device — but dimmed so it is not the loudest thing in
   the form. */
.hm-field input[type="date"]::-webkit-calendar-picker-indicator {
  opacity: 0.45;
  cursor: pointer;
  transition: opacity var(--a-d-fast) var(--a-ease-soft);
}
.hm-field input[type="date"]:hover::-webkit-calendar-picker-indicator { opacity: 0.8; }

/* ----------------------------------------------------------------------
   5. Scroll reveal
   Driven by maison.js. A separate class from the site's .reveal so the
   observer in script.js (1.1s ease, no stagger) never touches these.

   The hidden starting state is gated behind .hm-js, which the inline
   script in <head> sets. Without that gate a visitor whose JavaScript
   failed to load — blocked, cached badly, offline — would get a page
   where every section is permanently invisible. Gated, the worst case is
   simply a page with no animation. Setting the class from <head> rather
   than from the deferred maison.js also means the state is in place before
   first paint, so there is no flash of visible content collapsing. */
.hm-js .hm-rv {
  opacity: 0;
  transform: translateY(24px);
  transition: opacity var(--a-d-slow) var(--a-ease),
              transform var(--a-d-slow) var(--a-ease);
  transition-delay: var(--hm-rv-delay, 0ms);
}
.hm-js .hm-rv.is-in {
  opacity: 1;
  transform: none;
}

/* ----------------------------------------------------------------------
   6. Collection
   The comps present these as photographs with type beneath, not as cards:
   no panel, no radius, no shadow. A hairline above each caption is the
   only structure. That is what lets the stones be the loudest thing in
   the section.
   ---------------------------------------------------------------------- */
.hm-collection__head {
  display: grid;
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
  gap: clamp(var(--a-s5), 6vw, var(--a-s8));
  align-items: end;
  margin-bottom: var(--a-s8);
}

.hm-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(var(--a-s3), 2.5vw, var(--a-s5));
}

.hm-stone { display: flex; flex-direction: column; }

.hm-stone__figure {
  position: relative;
  margin: 0;
  overflow: hidden;
  background: var(--a-marble);
}
.hm-stone__figure img {
  display: block;
  width: 100%;
  height: auto;
  /* Source files are 636x570 and 636x501 — two ratios. Forcing one square
     box with object-fit keeps the row of stones optically aligned; the
     alternative (native ratios) staggers the captions. */
  aspect-ratio: 1;
  object-fit: cover;
  object-position: center;
  transition: transform 900ms var(--a-ease);
}
.hm-stone:hover .hm-stone__figure img,
.hm-stone:focus-within .hm-stone__figure img { transform: scale(1.04); }

.hm-stone__info {
  padding-top: var(--a-s3);
  margin-top: var(--a-s3);
  border-top: 1px solid var(--a-hairline);
  transition: transform var(--a-d-mid) var(--a-ease);
}
.hm-stone:hover .hm-stone__info,
.hm-stone:focus-within .hm-stone__info { transform: translateY(-4px); }

.hm-stone__name {
  margin: 0;
  font-size: var(--a-h3);
  line-height: 1.2;
}
.hm-stone__spec {
  margin: var(--a-s2) 0 0;
  font-size: var(--a-label);
  line-height: 2;
  letter-spacing: var(--a-ls-label);
  text-transform: uppercase;
  color: var(--a-ink-mute);
}
.hm-stone__link { margin-top: var(--a-s3); }

/* ----------------------------------------------------------------------
   6b. Hero
   The footage keeps its left half deliberately empty, so the copy needs
   no dark scrim over the whole frame — only a soft ivory wash on the side
   it actually occupies. That keeps the stone at full contrast.
   ---------------------------------------------------------------------- */
.hm-hero {
  position: relative;
  display: flex;
  align-items: center;
  overflow: hidden;
  min-height: 100svh;
  /* Top padding clears the fixed masthead. */
  padding-block: clamp(140px, 12vh + 60px, 208px) var(--a-section-y);
  background: var(--a-marble);
}

.hm-hero__media {
  position: absolute;
  /* Bleed top and bottom: the parallax shifts this by up to 6% of its own
     height, and without headroom that would expose the section behind it. */
  inset: -6% 0;
  z-index: 0;
}
.hm-hero__video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

.hm-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    90deg,
    rgba(var(--a-ivory-rgb), 0.94) 0%,
    rgba(var(--a-ivory-rgb), 0.78) 34%,
    rgba(var(--a-ivory-rgb), 0) 66%
  );
  pointer-events: none;
}

.hm-hero__inner {
  position: relative;
  z-index: 2;
}
.hm-hero__copy { max-width: 34rem; }
.hm-hero__copy .hm-title { max-width: none; }

/* Scroll cue, aligned to the container rather than the viewport so it sits
   under the copy instead of floating in the middle of the footage. */
.hm-hero__scroll {
  position: absolute;
  z-index: 2;
  left: max(var(--a-gutter), calc((100% - var(--a-container)) / 2 + var(--a-gutter)));
  bottom: var(--a-s6);
  display: flex;
  align-items: center;
  gap: var(--a-s2);
  font-size: var(--a-label);
  letter-spacing: var(--a-ls-label);
  text-transform: uppercase;
  color: var(--a-ink-mute);
  transition: color var(--a-d-fast) var(--a-ease-soft);
}
.hm-hero__scroll:hover,
.hm-hero__scroll:focus-visible { color: var(--a-ink); }
.hm-hero__scroll i {
  display: block;
  width: var(--a-s6);
  height: 1px;
  background: var(--a-gold-soft);
  transform-origin: left;
  transition: transform var(--a-d-mid) var(--a-ease);
}
.hm-hero__scroll:hover i,
.hm-hero__scroll:focus-visible i { transform: scaleX(1.4); }

/* ----------------------------------------------------------------------
   7. Editorial image slot
   One ratio for every large image on the page. The ratio lives here, not
   in the file, so a replacement photograph of any dimension drops in
   without touching the layout — which is the whole point while the
   Higgsfield assets are still outstanding.
   ---------------------------------------------------------------------- */
.hm-figure {
  position: relative;
  margin: 0;
  overflow: hidden;
  background: var(--a-marble);
}
.hm-figure img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  object-position: center;
  transition: transform 900ms var(--a-ease);
}
.hm-section--night .hm-figure { background: var(--a-night-wash); }

/* --- Full-bleed band ---------------------------------------------------
   The one place the container is abandoned. No section padding either:
   the image runs edge to edge and the copy is positioned inside it, so
   the band reads as a pause rather than another section. */
.hm-band {
  position: relative;
  display: flex;
  align-items: center;
  overflow: hidden;
  min-height: clamp(320px, 40vw, 600px);
  background: var(--a-marble);
}
.hm-band picture,
.hm-band img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
.hm-band__copy {
  position: relative;
  z-index: 1;
  display: flex;
  justify-content: flex-end;
  width: 100%;
  max-width: var(--a-container);
  margin-inline: auto;
  padding-inline: var(--a-gutter);
}
.hm-band__copy p {
  margin: 0;
  max-width: 24ch;
  font-family: var(--serif);
  font-weight: var(--a-w-display);
  font-size: var(--a-h3);
  line-height: 1.35;
  color: var(--a-ink);
}

/* --- Fact list ---------------------------------------------------------
   A description list, because that is what it is: term and value. Rows
   separated by hairlines, no icons — the four circle-drawn icons the old
   sections used were decoration standing in for hierarchy. */
.hm-facts {
  margin: var(--a-s6) 0 0;
  border-top: 1px solid var(--a-hairline);
}
/* As a column of the split rather than a block beneath copy, it starts at
   the top of its own column — the top margin exists to separate it from a
   paragraph that isn't there in that arrangement. */
.hm-split > .hm-facts { margin-top: 0; }
.hm-facts > div {
  display: grid;
  grid-template-columns: minmax(0, 11rem) minmax(0, 1fr);
  gap: var(--a-s2) var(--a-s4);
  padding: var(--a-s3) 0;
  border-bottom: 1px solid var(--a-hairline);
}
.hm-facts dt {
  font-size: var(--a-label);
  letter-spacing: var(--a-ls-label);
  text-transform: uppercase;
  color: var(--a-ink-mute);
}
.hm-facts dd {
  margin: 0;
  font-size: var(--a-small);
  color: var(--a-ink-soft);
}
.hm-section--night .hm-facts { border-top-color: var(--a-night-hairline); }
.hm-section--night .hm-facts > div { border-bottom-color: var(--a-night-hairline); }
.hm-section--night .hm-facts dt { color: var(--a-night-mute); }
.hm-section--night .hm-facts dd { color: var(--a-night-ink); }

/* --- Quotes ------------------------------------------------------------
   No card, no panel, no shadow — a hairline above each is the only
   structure. Three boxed panels read as three boxes; three ruled columns
   read as three opinions. */
.hm-quotes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(var(--a-s5), 4vw, var(--a-s7));
  margin-top: var(--a-s8);
}
.hm-quote {
  margin: 0;
  padding-top: var(--a-s4);
  border-top: 1px solid var(--a-hairline);
}
.hm-quote blockquote {
  margin: 0;
  font-family: var(--serif);
  font-weight: var(--a-w-display);
  font-size: var(--a-h3);
  line-height: 1.45;
  color: var(--a-ink);
}
.hm-quote figcaption {
  margin-top: var(--a-s4);
  font-size: var(--a-label);
  line-height: 1.9;
  letter-spacing: var(--a-ls-label);
  text-transform: uppercase;
  color: var(--a-ink-mute);
}

/* ----------------------------------------------------------------------
   8. Breakpoints — mobile first would mean fewer overrides, but this file
   sits on top of a desktop-first stylesheet, so it follows suit rather
   than mixing two directions in one codebase.
   ---------------------------------------------------------------------- */
@media (max-width: 900px) {
  .hm-split {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--a-s6);
  }
  /* Order must reset too, or the flipped sections put the image below the
     copy on one and above it on the next. */
  .hm-split--flip > :first-child { order: 0; }
  .hm-collection__head {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--a-s5);
    align-items: start;
  }
  .hm-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .hm-quotes { grid-template-columns: minmax(0, 1fr); gap: var(--a-s5); }
  .hm-title { max-width: none; }
}

@media (max-width: 900px) {
  /* Portrait viewports crop a 16:9 frame hard. Biasing the crop right of
     centre keeps the stone in shot instead of showing bare marble, while
     the copy still lands on the softer left side. */
  .hm-hero__video { object-position: 66% center; }
  .hm-hero::after {
    background: linear-gradient(
      180deg,
      rgba(var(--a-ivory-rgb), 0.9) 0%,
      rgba(var(--a-ivory-rgb), 0.74) 46%,
      rgba(var(--a-ivory-rgb), 0.5) 100%
    );
  }
}

@media (max-width: 900px) {
  /* Portrait crops the 21:9 frame to roughly its middle, which is bare
     marble — so the copy can simply centre on it rather than fight the
     stone for space. */
  .hm-band__copy { justify-content: center; }
  .hm-band__copy p { max-width: 20ch; text-align: center; }
}

@media (max-width: 560px) {
  .hm-hero { min-height: 88svh; }
  .hm-grid { grid-template-columns: minmax(0, 1fr); }
  .hm-actions { gap: var(--a-s3); }
  .hm-cta { width: 100%; }
  /* One field per row. Two 11px labels over two half-width date controls
     is narrower than the controls themselves need. */
  .hm-form__grid { grid-template-columns: minmax(0, 1fr); }
  .hm-form {
    /* The panel's inset costs about a fifth of the screen width here, and
       the hairline is doing nothing a section edge doesn't. */
    padding: 0;
    border: 0;
    background: none;
  }
  .hm-form__actions .hm-cta { width: 100%; }
  /* Term over value: at this width an 11rem label column leaves the value
     three or four words wide. */
  .hm-facts > div {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--a-s1);
  }
}

/* Hover-driven motion is meaningless on touch and leaves stones stuck in
   their zoomed state after a tap. */
@media (hover: none) {
  .hm-stone:hover .hm-stone__figure img,
  .hm-stone:hover .hm-stone__info { transform: none; }
}

/* ----------------------------------------------------------------------
   9. Reduced motion — everything stops, nothing disappears. The reveal
   elements must land visible, or a reader with the preference set sees a
   blank page.
   ---------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .page-maison *,
  .page-maison *::before,
  .page-maison *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
  /* Must match .hm-js .hm-rv's specificity (0,2,0) or the gated hidden
     state above would keep winning and the page would stay blank for
     exactly the readers this block is meant to serve. */
  .hm-js .hm-rv {
    opacity: 1;
    transform: none;
  }
}

/* ----------------------------------------------------------------------
   10. Homepage hero — the columns footage
   Third framing of this hero, and the reasoning matters because the
   geometry is not obvious. Read the arithmetic before changing anything
   here or paying for another plate.

   THE CONSTRAINT. With the copy centred above the group, the group has to
   start below it. Measured on a 1440x900 window the copy block ends at
   61% of the hero height. In a 16:9 frame a group's height fraction is
   its width fraction x (16/9) / its own aspect ratio — so a squat group
   can never be wide. The 13 August plinths were 1.08:1 and that capped
   them at 16% of the frame width, which is why they read as three small
   objects in an empty field. That framing was already at its maximum.

   WHAT CHANGED. A new plate (14 August, nano_banana_pro): three tall
   fluted travertine columns at three heights, spaced well apart, stones
   large on top. The prompt lines that produced it are in
   images/README.txt. Then the canvas trick from 13 August, applied to
   both axes at once, places the group at exactly 50% down and 30.8% wide,
   centred on 50.0%. Every number below was measured on the composited
   pixels, not assumed.

     group      x 664-1255   y 539-1041
     width      30.8% of the frame        (was 16.2%)
     centre     50.0%                     (was 55.1%, never centred)
     top / bot  49.9% / 96.4%

   WHY THE HERO LEAD IS COMMENTED OUT IN index.html. With that paragraph
   the copy block ran to 78% of the hero at 1440x900 and the button row
   sat on the stones. Without it the block ends at 49% and clears the
   group at every tested size. That is the trade the client asked for:
   "dan moet de tekst maar minder of kleiner".

   Scoped to .page-home. No other page uses .hm-hero or carries
   .page-home, so none of this reaches the collection or product heroes.
   ---------------------------------------------------------------------- */
.page-home .hm-hero__copy {
  max-width: 58rem;
  margin-inline: auto;
  text-align: center;
}
.page-home .hm-hero__label { justify-content: center; }
.page-home .hm-actions { justify-content: center; }

/* The block is anchored to the top of the hero rather than centred in it:
   centred, it would drift down onto the group as the viewport gets
   shorter, which is exactly the case that has the least room. */
.page-home .hm-hero { align-items: flex-start; }

/* The base padding clamps up to 208px. The masthead is 121px, so 140 is
   already clear of it, and the 68px saved is the difference between the
   button row landing above the stones at 1440x900 and on them. */
.page-home .hm-hero { padding-block-start: 140px; }

/* The display clamp tops out at 84px site-wide. On the homepage hero those
   two lines cost 193px of vertical room and every pixel of it comes out of
   the gap above the columns, so this one instance runs smaller. Measured at
   1440x900: 193px becomes 166px and the block ends at 51% instead of 56%. */
.page-home .hm-hero .hm-title--display { font-size: clamp(38px, 5vw, 72px); }

/* Two lines of display type plus the buttons is the whole block now, so
   the gap above the buttons can close a little. */
.page-home .hm-hero .hm-actions { margin-top: var(--a-s5); }

/* Short viewports give the copy block less room and the group underneath it
   more to lose: the block is a near-constant ~450px tall, so on a 700px hero
   it eats 58% of the height while on a 1080px one it eats 42%. That worst
   case is what caps how far the plate can be zoomed in before the buttons
   land on the stones, and the client asked for the group to read larger.
   So on a short window the block is tightened: less clearance above it, and
   a display size that answers to the height as well as the width. The client
   signed off on this trade in its own words — "dan moet de tekst maar minder
   of kleiner". */
@media (max-height: 950px) {
  /* NOT below 140px. The masthead is 121px tall and this padding is what
     clears it; at 100px the eyebrow sat 21px underneath it, behind the
     scrim. The room for a larger group has to come from the type, not from
     this number. */
  .page-home .hm-hero { padding-block-start: 140px; }
  .page-home .hm-hero .hm-title--display { font-size: clamp(34px, min(5vw, 6.5vh), 72px); }
  .page-home .hm-hero .hm-actions { margin-top: var(--a-s4); }
}

/* The scrim follows the copy. A left-to-right wash would brighten the
   empty side and leave the type over the columns; top-down keeps the
   contrast under the words and lets the stones stay at full strength.
   It is fully clear by 62%, which is where the tallest stone begins. */
.page-home .hm-hero::after {
  background: linear-gradient(
    180deg,
    rgba(var(--a-ivory-rgb), 0.92) 0%,
    rgba(var(--a-ivory-rgb), 0.86) 30%,
    rgba(var(--a-ivory-rgb), 0.62) 48%,
    rgba(var(--a-ivory-rgb), 0.16) 58%,
    rgba(var(--a-ivory-rgb), 0) 66%
  );
}

/* The cue keeps the container's left edge, where the marble is empty. The
   scrim has faded out by then and the marble carries the group's shadow,
   so it takes full ink rather than the muted token. */
.page-home .hm-hero__scroll { color: var(--a-ink); }

/* The still hero is a <picture>, and <picture> is inline by default. The
   image inside resolves its height:100% against that box, so without this
   the hero art collapses to its intrinsic height and object-fit has nothing
   to cover. Only the homepage wraps its hero art this way; a <video> needs
   no wrapper, so restoring the moving hero means deleting this rule. */
.page-home .hm-hero__media picture {
  display: block;
  width: 100%;
  height: 100%;
}

/* The parallax only ever translates the media DOWNWARD: the shift is
   scrollY * 0.06 clamped to a positive range, so it is 0 at the top of the
   page and never negative. The 6% of bleed below the box is therefore never
   used for anything — but it does push the bottom of the plate past the
   bottom of the hero, and that edge is the marble floor and the feet of the
   drums. Measured on an 1800x780 window: 47px of floor lost for no reason.
   Bleed above only; the downward shift still has its headroom. */
.page-home .hm-hero__media { inset: -6% 0 0; }

/* The two .hm-origins rules that stood here are gone with the section they
   propped up: Nature's Origins was removed on 15 August. They existed only
   because dropping its four country cards had left a one-column .hm-split
   in a two-column grid. Kept as a note because the same thing will happen to
   any section that loses one half of a split — the surviving column keeps
   half the width, and .page-home .hm-actions centres inside it. */

/* The still and the moving hero are stacked, not swapped: the <picture> is
   always in the flow and carries the layout, and the video lies over it.
   That way the still stays the LCP element and there is no gap while the
   video loads. The video is absent in the two bands whose framing needs a
   different crop — a video element cannot pick a source by media query, and
   those crops are the whole reason the floor and the outer drums survive. */
.page-home .hm-hero__motion {
  position: absolute;
  inset: 0;
}
/* The video used to be switched off in these two bands because it could
   only carry one framing. It now follows the same three crops as the
   <picture>, so it plays everywhere and nothing is hidden. The still stays
   underneath as the LCP element and as the no-JavaScript fallback. */

@media (max-width: 900px) {
  /* Portrait crops this frame to roughly a third of its width. The group
     is centred and 30.8% wide, so a centred crop holds all three columns
     — no object-position override is needed here, and the base rule's
     66% bias has to be cancelled or the group slides out of shot. */
  .page-home .hm-hero__video { object-position: center; }
  /* The copy runs much further down here (the buttons stack full width),
     so the wash has to hold most of the height. */
  .page-home .hm-hero::after {
    background: linear-gradient(
      180deg,
      rgba(var(--a-ivory-rgb), 0.94) 0%,
      rgba(var(--a-ivory-rgb), 0.92) 46%,
      rgba(var(--a-ivory-rgb), 0.86) 66%,
      rgba(var(--a-ivory-rgb), 0.46) 84%,
      rgba(var(--a-ivory-rgb), 0) 100%
    );
  }
}

/* ----------------------------------------------------------------------
   Two earlier framings, kept because each is one file swap away.

   FRAMING B (14 August, free): copy left over the empty half, group large
   on the right, recut from hero-plinths-source.mp4. Group 35.8% wide —
   the biggest of the three — but off-centre by design, so it needs the
   left-aligned copy. Files: hero-plinths.mp4 / .webm / -poster.jpg.
   To restore: point index.html at those three, uncomment the hero lead,
   and replace everything above with:

.page-home .hm-hero__video { object-position: 58% center; }
.page-home .hm-hero::after {
  background: linear-gradient(
    90deg,
    rgba(var(--a-ivory-rgb), 0.94) 0%,
    rgba(var(--a-ivory-rgb), 0.82) 32%,
    rgba(var(--a-ivory-rgb), 0) 58%
  );
}
.page-home .hm-hero__scroll { color: var(--a-ink); }
@media (max-width: 900px) {
  .page-home .hm-hero__video { object-position: 80% center; }
  .page-home .hm-hero::after {
    background: linear-gradient(
      180deg,
      rgba(var(--a-ivory-rgb), 0.95) 0%,
      rgba(var(--a-ivory-rgb), 0.93) 50%,
      rgba(var(--a-ivory-rgb), 0.90) 72%,
      rgba(var(--a-ivory-rgb), 0.62) 84%,
      rgba(var(--a-ivory-rgb), 0.10) 95%,
      rgba(var(--a-ivory-rgb), 0) 100%
    );
  }
}

   FRAMING A (13 August): the centred plinths, group at 16.2%. Files:
   hero-plinths-v2-centred.mp4 / .webm / -poster.jpg. Its rules were the
   same shape as the block at the top of this section, with the wash
   holding to 88% instead of 66% and no padding override.
   ---------------------------------------------------------------------- */

/* ----------------------------------------------------------------------
   11. Split hero — 18 August
   Copy left, clip right, house mark faint behind the copy. Replaces the
   full-bleed hero above; that block is left intact because everything in
   it is still what .hm-hero means on its own, and this section only says
   where it differs.

   Written as a MODIFIER (.hm-hero--split) rather than by editing section
   6b or the .page-home block, for the reason the rest of this file is
   layered the way it is: the earlier rules are correct for a full-bleed
   hero, and a reader comparing the two should be able to see what the
   split changes. It is also the whole revert — drop the class from
   index.html and point the media back at hero-drums-*, which are still in
   images/generated/.
   ---------------------------------------------------------------------- */

.hm-hero--split .hm-hero__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(32px, 5vw, 88px);
  width: 100%;
}

/* The clip is confined to this box, so the section no longer needs the
   gradient wash that kept copy legible over footage. Killing it is what
   lets the stones read at full contrast instead of through 78% ivory. */
.page-home .hm-hero--split::after { content: none; }

/* The copy was centred and 58rem wide when it sat over the whole frame.
   In a column it is left-aligned and measured, and the two justification
   overrides have to be undone with it or the label rule and the button row
   stay centred inside their half. */
.page-home .hm-hero--split { align-items: center; }
.page-home .hm-hero--split .hm-hero__copy {
  position: relative;
  max-width: 30rem;
  margin-inline: 0;
  text-align: left;
}
.page-home .hm-hero--split .hm-hero__label { justify-content: flex-start; }
.page-home .hm-hero--split .hm-actions { justify-content: flex-start; }

/* THE MARK BEHIND THE COPY.
   Sized to the copy block and centred on it, with a breath of blur.

   OPACITY WENT FROM 0.1 TO 0.3 ON 15 OCTOBER, at the client's request to
   have the mark come forward. What that costs is measurable and it lands
   entirely on one element, so read the next block before changing it back
   or pushing it further.

   Measured at 1440x900: the mark is exactly as wide as the copy block and
   taller than it, so every line of the copy crosses a stroke at full alpha
   — eyebrow, headline and both buttons. The mark is gold rgb(205,173,112)
   on marble rgb(233,228,219), so each step of opacity darkens the ground
   under the type:

     opacity   darkest ground      eyebrow at 62% ink   full ink
     0.10      rgb(226,219,204)    4.43                 12.94
     0.20      rgb(220,209,189)    4.27                 11.84
     0.30      rgb(213,200,175)    4.11                 10.81
     0.40      rgb(207,191,160)    3.95                  9.84

   Full ink has room to spare at any of these. The eyebrow did not: at 62%
   on bare marble it measures 4.58, eight hundredths over AA, so it was
   already the binding constraint before the mark was touched at all. That
   is why the rule below exists.

   Not a negative z-index. That would put it behind the section background
   in some stacking arrangements and vanish; instead it sits at 0 and the
   copy's own children are lifted to 1.

   The plain url() comes first so a browser without image-set() still gets
   the PNG; the image-set line then overrides it with the WebP, which is a
   third of the weight. */
.page-home .hm-hero--split .hm-hero__copy::before {
  content: "";
  position: absolute;
  z-index: 0;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -52%);
  width: min(30rem, 104%);
  aspect-ratio: 560 / 627;
  background-image: url("images/generated/auremo-mark.png");
  background-image: image-set(
    url("images/generated/auremo-mark.webp") type("image/webp"),
    url("images/generated/auremo-mark.png") type("image/png")
  );
  background-size: contain;
  background-repeat: no-repeat;
  background-position: center;
  opacity: 0.3;
  filter: blur(1px);
  pointer-events: none;
}
.page-home .hm-hero--split .hm-hero__copy > * {
  position: relative;
  z-index: 1;
}

/* The eyebrow takes full ink here, and only here. Site-wide an 11px label
   runs at --a-ink-mute, which is 62% and was itself raised from 46% to
   clear AA; over this mark that leaves nothing in hand. Same call the
   scroll cue already got when it landed on a plinth instead of bare
   marble: the token is right for a label on a plain ground and wrong for
   one sitting on an ornament. Nothing moves — only the colour changes. */
.page-home .hm-hero--split .hm-hero__label { color: var(--a-ink); }

/* The panel carries the box; .hm-hero__media inside it keeps the 6% bleed
   the parallax needs, and .hm-hero__video already covers. Height rather
   than aspect-ratio so the box is predictable at every window and the
   cropping happens in object-fit, where there is cream to spare. */
.hm-hero--split .hm-hero__panel {
  position: relative;
  overflow: hidden;
  width: 100%;
  height: clamp(380px, 64svh, 660px);
  background: var(--a-marble);
}
.hm-hero--split .hm-hero__media { inset: -6% 0 0; }

/* NO EDGES ON THE SIDE THAT MEETS THE COPY — 18 August.
   Sat in its column as a hard-edged rectangle, which read as a separate
   block dropped next to the text rather than one composition. Two changes
   fix that, and they only make sense together:

   1. It runs to the edge of the viewport and the full height of the
      section, so the only boundaries left are the ones the window already
      has. Absolute rather than a grid cell, because a cell cannot leave the
      container; the grid keeps its second column and that empty column is
      what still reserves the space.
   2. The side facing the copy is masked to nothing, so the footage
      dissolves into the page instead of stopping at a line.

   The dissolve is invisible rather than merely soft because the two greys
   nearly match: the clip's own backdrop measures 226,221,210 at that edge
   against 233,228,219 for --a-marble behind it. Seven points, spread over a
   third of the panel's width. That is also why the panel's own background is
   marble — anything else would show through the ramp.

   ONE MASK LAYER, not two. Softening the top and bottom as well would want
   mask-composite: intersect, and where that is not understood the layers
   composite as a union instead, which cancels most of the left fade — the
   one edge that actually matters. Full bleed already removes the horizontal
   edges, so there is nothing left for a second layer to do. */
/* THE GRID MUST STAY UNPOSITIONED. Giving it position: relative to lift it
   above the panel made it the panel's containing block instead of the
   section, so inset-block: 0 stretched the panel to the height of the COPY —
   227px of a 720px hero, which read as a band across the middle. The copy is
   lifted on its own instead; .hm-shell is static, so the panel resolves
   against .hm-hero, which is not. */
.page-home .hm-hero--split .hm-hero__copy { z-index: 2; }

@media (min-width: 901px) {
  .hm-hero--split .hm-hero__panel {
    position: absolute;
    z-index: 0;
    inset-block: 0;
    inset-inline-end: 0;
    width: clamp(460px, 56vw, 1060px);
    height: auto;
    -webkit-mask-image: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, 0.5) 16%, #000 38%);
    mask-image: linear-gradient(90deg, transparent 0%, rgba(0, 0, 0, 0.5) 16%, #000 38%);
  }
}

@media (max-width: 900px) {
  /* Stacked: copy first, clip under it, full width and short — which is
     the framing hero-stones-wide is cut for. */
  .hm-hero--split .hm-hero__grid {
    grid-template-columns: minmax(0, 1fr);
    gap: clamp(28px, 6vw, 44px);
  }
  /* Same idea stacked, turned ninety degrees: the clip runs to both window
     edges and dissolves upward into the copy above it. A horizontal fade
     would eat the stones — they sit across the middle of this crop, not off
     to one side.

     Both ends in ONE gradient. The desktop rule needs a second mask layer to
     soften anything but its left edge, and pays for that in mask-composite
     support; here both fades run the same axis, so a gradient with stops at
     each end does the whole job. The bottom fade is short — the last tenth —
     because below the plinths is the marble floor, and dissolving that would
     leave the stones standing on nothing. */
  .hm-hero--split .hm-hero__panel {
    height: clamp(240px, 38svh, 380px);
    width: auto;
    margin-inline: calc(-1 * var(--a-gutter));
    -webkit-mask-image: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.5) 12%, #000 30%, #000 90%, transparent 100%);
    mask-image: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.5) 12%, #000 30%, #000 90%, transparent 100%);
  }
  .page-home .hm-hero--split .hm-hero__copy { max-width: none; }

  /* The base .page-home rule washes the whole frame vertically on small
     windows. Same reasoning as the desktop wash: there is no copy over the
     footage any more. */
  .page-home .hm-hero--split::after { content: none; }
}

/* The display clamp is tuned for copy that had the whole frame; in a half
   column at 1440 it rendered at 58.5px and broke "selected one at a time."
   across three lines, which put the button row 115px lower and left the
   headline reading as a paragraph. Measured on the same window after this
   rule: 48px, two lines, block 90px shorter. */
.page-home .hm-hero--split .hm-title--display { font-size: clamp(32px, 3.4vw, 50px); }

/* .hm-actions wraps by default, and at 480px the CTA and the text link do
   not fit on one line — they stacked, which reads as two buttons rather
   than one action with an aside. Below 560px of column they are allowed to
   stack again, because there they genuinely do not fit. */
@media (min-width: 901px) {
  .page-home .hm-hero--split .hm-actions {
    flex-wrap: nowrap;
    align-items: baseline;
    gap: var(--a-s4);
  }
  .page-home .hm-hero--split .hm-actions .hm-cta { white-space: nowrap; }
}

/* Stacked, the copy runs the full width but stays left-aligned, so a mark
   centred on the block sits off to the right of the text it is meant to be
   behind. Anchored left instead, and pulled in, since there is no second
   column to balance it against. */
@media (max-width: 900px) {
  .page-home .hm-hero--split .hm-hero__copy::before {
    left: 0;
    width: min(22rem, 78%);
    transform: translate(-8%, -50%);
  }
}

/* ======================================================================
   Stone cards: the photograph fills its frame, and is a link (23 October)
   ======================================================================
   createGemMarkup() wraps every stone photo in .gem-photo, and styles.css
   still gives that class its old treatment: a 160px circle with a drop
   shadow. On the collection page collection.css undid the size but not the
   shape; under "Also In The Collection" nothing undid either, so the stone
   sat as a small disc in a large square. Here it fills the square edge to
   edge, the same as the four stones on the homepage.

   .stone-media-link is the photograph as a link to the stone (see
   renderGemstones / renderRelatedProducts in script.js and the homepage
   cards). It is a block so the whole square is the target. */
.stone-media-link {
  display: block;
  color: inherit;
}
.hm-stone__figure .gem-photo,
.page-collection .collection-visual .gem-photo {
  width: 100%;
  height: auto;
  aspect-ratio: 1;
  margin: 0;
  border-radius: 0;
  box-shadow: none;
  background: var(--a-marble);
}
.hm-stone__figure .gem-photo-img,
.page-collection .collection-visual .gem-photo-img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}
/* The CSS-drawn fallback, for a photo that fails to load, keeps its size
   inside the square rather than stretching to it. */
.hm-stone__figure .stone-media-link > .gem {
  width: 46%;
  margin: 27% auto;
}
/* On the collection page the frame is a grid with a 310px min-height left in
   styles.css, which made it 261x310 at 1280 instead of the square
   collection.css asks for, and the link inside grew to its content. The
   frame is square again and the link covers it exactly. */
.page-collection .collection-visual { min-height: 0; }
.page-collection .collection-visual > .stone-media-link {
  position: absolute;
  inset: 0;
}
.page-collection .collection-visual .stone-media-link .gem-photo {
  width: 100%;
  height: 100%;
  min-height: 0; /* styles.css gives it 310px, taller than the frame on a 4-column row */
  aspect-ratio: auto;
}

/* Stone cards on a phone: two columns, as on the collection page (23 October).
   Applies to the homepage's four and to "Also In The Collection". */
@media (max-width: 560px) {
  .hm-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--a-s2);
  }
  .hm-stone__name { font-size: 20px; }
}

/* A request that could not be sent (see sendRequestMail in script.js).
   --a-alert, the one colour outside the palette: this has to read as a
   failure, and the sentence itself says what to do. */
.form-send-error {
  margin: var(--a-s3) 0 0;
  font-size: var(--a-small);
  line-height: var(--a-lh-body);
  color: var(--a-alert);
}
.form-send-error[hidden] { display: none; }

/* Saved selection (7 November): the grid of saved stones, its empty state,
   and the note that it lives in this browser. */
.hm-saved { margin-top: var(--a-s6); display: grid; gap: var(--a-s5); }
.hm-saved__empty[hidden], .hm-saved .hm-grid[hidden] { display: none; }
.hm-saved__note { margin: 0; max-width: var(--a-measure); font-size: var(--a-small); color: var(--a-ink-mute); }
.hm-stone__actions { display: flex; flex-wrap: wrap; gap: var(--a-s2) var(--a-s4); margin-top: var(--a-s3); }
.hm-stone__actions .hm-stone__link { margin-top: 0; }
button.hm-link { padding: 0; border: 0; background: none; font-family: var(--sans); cursor: pointer; }
