/** Shopify CDN: Minification failed

Line 438:0 Unexpected "0%"
Line 439:0 Unexpected "46%"
Line 440:0 Unexpected "84%"
Line 441:0 Unexpected "100%"
Line 456:0 Unexpected "0%"
Line 457:0 Unexpected "46%"
Line 458:0 Unexpected "84%"
Line 459:0 Unexpected "100%"

**/
/* ==========================================================================
   Greenleaf Diamonds — "Storefront" direction applied over Prestige v11.4.0
   --------------------------------------------------------------------------
   Loaded from layout/theme.liquid immediately AFTER theme.css, so every
   declaration here wins on load order without needing !important.

   SCOPE RULE: only things Prestige cannot express as a theme setting belong
   in this file. Anything reachable from the theme editor lives in
   config/settings_data.json instead. Keep it that way — it is what preserves
   the upgrade path (Maestrooo updates arrive as a new theme to migrate into,
   not a patch, so every core-file edit is a migration cost later).

   Source of truth for every value below:
     design-system/tokens/*.css  +  greenleaf-design-direction.md §2 (Storefront)
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. TYPE SCALE — the one genuine conflict with Prestige
   --------------------------------------------------------------------------
   Prestige's --text-h1 tops out at 32px at heading_font_size_factor 1, and
   48px even at the maximum factor of 1.5. It has no hero size variable at
   all. The veto checklist (item 11) blocks a desktop hero under 40px, so this
   override is required to pass our own review, not a stylistic preference.

   APPROACH: re-anchored fluid (Andrew, 5 Aug 2026). Prestige's clamp()
   behaviour is kept; only the min/max anchors move so the scale lands on the
   design system's desktop numbers. Anchored 375px -> 1440px viewport.
   Top end matches design-system/tokens/typography.css exactly; the low end
   matches that file's <=768px step (57/39/26/20).

   Math, so the next person can re-derive it rather than trust it:
     size(px) = a + b*vw   where  b = (max-min)/10.65,  a = min - 3.75*b
   -------------------------------------------------------------------------- */
:root {
  --text-hero: clamp(3.5625rem, 3.1444rem + 1.784vw, 4.75rem);  /*  57 -> 76px */
  --text-h1:   clamp(2.4375rem, 2.1514rem + 1.2207vw, 3.25rem); /*  39 -> 52px */
  --text-h2:   clamp(1.625rem, 1.4489rem + 0.7512vw, 2.125rem); /*  26 -> 34px */
  --text-h3:   clamp(1.25rem, 1.162rem + 0.3756vw, 1.5rem);     /*  20 -> 24px */

  /* --text-h4/h5/h6 are deliberately left on Prestige's own scale. The design
     system defines no h4-h6; those slots carry labels and captions here. */
}

/* Line heights. Veto item 10 blocks a display serif at line-height 1.5, and
   Alegreya is the display serif. Both the element and Prestige's .hN utility
   classes are targeted; whichever the section markup uses, this lands. */
h1, .h1 { line-height: 1.1;  }
h2, .h2 { line-height: 1.15; }
h3, .h3 { line-height: 1.25; }

/* Hero. Prestige has no hero slot, so this is an opt-in utility rather than a
   token override: add `gl-hero` to a section/block's custom CSS class in the
   theme editor to promote its heading to the 76px display size.
   Tracking is 0.005em here vs the global 1% (0.01em) the heading_letter_spacing
   setting applies to h1-h3 — the design scale loosens as size drops. */
.gl-hero, .gl-hero h1, .gl-hero .h1 {
  font-size: var(--text-hero);
  line-height: 1.05;
  letter-spacing: 0.005em;
}

/* --------------------------------------------------------------------------
   2. MEASURED FIGURES — tabular numerals
   --------------------------------------------------------------------------
   Prices, carat weights, facet counts and item numbers are Inter with
   tabular numerals. This is the register that replaced the dropped monospace
   (design-system/tokens/typography.css), so prices must not shift width
   between variants.

   The [class*="price"] hook is a deliberate broad match against Prestige's
   price markup. `gl-spec` is ours, for spec tables and measured copy.
   -------------------------------------------------------------------------- */
[class*="price"],
.gl-spec {
  font-variant-numeric: tabular-nums;
}

.gl-spec {
  letter-spacing: 0.04em; /* --type-spec-track */
  font-weight: 500;       /* --weight-spec */
}

/* --------------------------------------------------------------------------
   3. PAGE GUTTERS
   --------------------------------------------------------------------------
   Prestige: 1.25rem -> 2rem -> 3rem (48px desktop).
   Design system: 24px mobile, 64px desktop (--gutter-page).
   Veto item 15 only blocks under 32px, so Prestige passes the veto but misses
   the design target. Raised to the target.

   NOTE (not changed, flagged instead): design-system sets --max-page 1680px;
   Prestige's widest container is 1360px. Widening that shifts the layout of
   every section, so it is left alone in a foundations pass.
   -------------------------------------------------------------------------- */
:root { --container-gutter: 1.5rem; } /* 24px */

@media screen and (min-width: 1000px) {
  :root { --container-gutter: 4rem; } /* 64px */
}

/* --------------------------------------------------------------------------
   4. SHADOWS — veto item 18
   --------------------------------------------------------------------------
   "box-shadow on anything but a modal or dropdown." Prestige spreads three
   generic shadow tokens across cards and blocks. Remapped onto the only two
   shadows the design system declares legal (tokens/form.css), so anything
   still reaching for a shadow gets one of the two sanctioned values.
   -------------------------------------------------------------------------- */
:root {
  --shadow-sm: 0 2px 8px rgb(0 0 0 / 0.10);  /* --shadow-dropdown */
  --shadow:    0 2px 8px rgb(0 0 0 / 0.10);  /* --shadow-dropdown */
  --shadow-md: 0 8px 40px rgb(0 0 0 / 0.16); /* --shadow-modal    */
}

/* --------------------------------------------------------------------------
   5. FORM CONTROLS — the contrast floor
   --------------------------------------------------------------------------
   Prestige derives --border-color by mixing 15% of the scheme's text colour
   into its background. On the Diamond White canvas that lands at 212 216 218,
   far too light to carry a form border: WCAG 1.4.11 needs 3:1.

   #8C8B86 is the ONLY legal form-input border in this system, at 3.22:1 on
   #F5F9FC. That margin is thin on purpose — see design-system/readme.md
   item 4. Do not darken the canvas without re-checking it.
   -------------------------------------------------------------------------- */
input:not([type="checkbox"]):not([type="radio"]):not([type="submit"]),
textarea,
select {
  border-color: #8c8b86;
}

/* Focus ring: 2px solid green, 2px offset (design-direction §2, tokens/form.css). */
:focus-visible {
  outline: 2px solid #2a4641;
  outline-offset: 2px;
}

/* On the green ground the ring inverts so it stays visible. */
.color-scheme--scheme-3 :focus-visible,
.color-scheme--scheme-4 :focus-visible {
  outline-color: #ffffff;
}

/* --------------------------------------------------------------------------
   6. BUTTON TYPE SIZE
   --------------------------------------------------------------------------
   Prestige builds --button-font from --text-sm (15px at a 16px base). The
   design system's button type is 12px, caps, 0.12em tracked — the casing and
   tracking are theme settings; only the size needs code.
   -------------------------------------------------------------------------- */
.button, .btn, button[class*="button"] {
  font-size: 0.75rem; /* 12px */
}

/* --------------------------------------------------------------------------
   7. --page-background — working around a Prestige v11.4.0 bug
   --------------------------------------------------------------------------
   snippets/css-variables.liquid emits:

     --page-background: {{ settings.color_schemes[0].settings.background.rgb }};

   That renders EMPTY. `settings.color_schemes` is a color_scheme_group drop,
   which is iterable but not numerically indexable, so `[0]` returns nil. The
   proof it is the index and not the data: the `for color_scheme in
   settings.color_schemes` loop directly below it emits all five schemes
   correctly, and --modal-scheme-background (which reaches through
   settings.modal_color_scheme.settings.background.rgb) resolves fine.

   Almost certainly pre-existing in the vendor file rather than caused by our
   settings — the chain works everywhere the group is not index-accessed. Not
   A/B tested against the stock preset, so it is stated as inference.

   Set explicitly to scheme-1's canvas, which is what the vendor line was
   trying to read. Keep this in sync if scheme-1's background changes.
   -------------------------------------------------------------------------- */
:root { --page-background: 245 249 252; } /* #F5F9FC Diamond White */

/* --------------------------------------------------------------------------
   8. MOTION — P7
   --------------------------------------------------------------------------
   200-400ms, opacity and <=8px translate, at most two animated elements per
   page. The staggered-apparition and page-transition switches are settings
   and are already off. This is the reduced-motion guarantee, which is not a
   setting.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* --------------------------------------------------------------------------
   9. VIDEO HERO SCRIM — the only way to make white text legible on footage
   --------------------------------------------------------------------------
   sections/video.liquid has NO overlay setting. Its full list is autoplay,
   show_controls, video_size, video, mobile_video, external_video_url, poster,
   text_color — unlike image-with-text-overlay, which exposes overlay_color
   and overlay_opacity. So there is no theme-editor path to darken footage.

   BUT Prestige already has the mechanism, it just isn't wired for this
   section. theme.css contains:

     .content-over-media:before {
       background: var(--content-over-media-gradient-overlay,
                       rgb(var(--content-over-media-overlay)));
       ...covers the full area, z-index 1, pointer-events none
     }

   So the correct fix is to set the vendor's own variable rather than invent a
   competing ::before. Nothing is overridden; an unset variable is filled in.

   WHY 20%, MEASURED RATHER THAN EYEBALLED. Sampling the centre band of the
   chosen frame (where the centred headline lands) and computing WCAG contrast
   for white text:

     scrim    mean      95th-percentile pixel
       0%   9.12:1      4.46:1   <- fails 4.5 at the bright end
      10%  10.23:1      5.30:1
      20%  11.46:1      6.34:1   <- chosen
      30%  12.79:1      7.63:1

   20% clears 4.5:1 at the 95th percentile with real margin while leaving the
   footage essentially intact.

   HONEST LIMIT: individual blown-out highlight pixels (a white cloud edge)
   never reach 4.5:1 at any opacity that keeps the video watchable — at 20%
   the single brightest pixel is 1.65:1. That is a property of white text on
   photography, not something a scrim fixes. The 95th percentile is the right
   threshold for a text background; a handful of specular pixels is not.

   Measured on the valley clip. THIS VALUE IS FOOTAGE-SPECIFIC — the coast
   clip needed 53% for the same guarantee, which is why it was not chosen.
   Re-measure if the footage changes.
   -------------------------------------------------------------------------- */
.shopify-section--video .content-over-media {
  --content-over-media-overlay: 0 0 0 / 0.2;
}

/* --------------------------------------------------------------------------
   10. THE NARROW-VIEWPORT MENU TOGGLE — the word, not the glyph
   --------------------------------------------------------------------------
   design-system/components/navigation/SiteHeader.prompt.md specifies the word
   "Menu", not a hamburger. Prestige renders both: the word is already in the
   markup as the button's accessible label, and the glyph sits next to it.

     <button type="button" aria-controls="sidebar-menu"
             {% unless ... == 'drawer' %}class="md:hidden"{% endunless %}>
       <span class="sr-only">{{ 'header.general.menu' | t }}</span>
       {%- render 'icon' with 'hamburger', class: 'header__nav-icon' -%}
     </button>

   So this is NOT a vendor edit: reveal the label that is already there, hide
   the icon that is already there. Recorded at length in
   greenleaf-theme-changelog.md §13.3 — the short version of why this shape:

     1. No media query needed. The button carries `md:hidden`, so it does not
        exist on desktop and these rules are inert there.
     2. The label stays localised — `header.general.menu` still supplies the
        text, so a future locale keeps working. Hard-coding "Menu" in CSS
        content: would break that.
     3. Un-hiding an sr-only span means undoing every part of the sr-only
        clip, not just position — hence the full reset below.

   The button has no class of its own; it is the only direct <button> child.
   -------------------------------------------------------------------------- */
.header__primary-nav > button .header__nav-icon {
  display: none;
}

.header__primary-nav > button .sr-only {
  position: static;
  width: auto;
  height: auto;
  margin: 0;
  overflow: visible;
  clip: auto;
  clip-path: none;
  white-space: nowrap;
}

/* ============================================================
   GREENLEAF COMPONENT LAYER — ported from preview/home-about.template.html
   Generated by scripts/port-preview-css.py. Do not hand-edit; edit the preview.
   New classes only — nothing here overrides a Prestige selector.
   ============================================================ */
:root{
  /* base palette — design-system/tokens/colors.css, direction "Storefront" round 7 */
  --diamond-white:#F5F9FC;
  --white:#FFFFFF;
  --stone-50:#F5F4F1;
  --hairline:#E1E0DC;
  --border-strong:#8C8B86;
  --grey-600:#5C625F;
  --graphite:#1B1A17;
  --green-700:#2A4641;
  --green-800:#1E3531;

  --surface-canvas:var(--diamond-white);
  --surface-alt:var(--stone-50);
  --surface-chrome:var(--green-700);
  --surface-green-raised:var(--green-800);

  --text-body:var(--graphite);
  --text-secondary:var(--grey-600);
  --text-on-green:var(--white);
  --text-on-green-muted:rgba(255,255,255,.82);
  --text-label-on-green:rgba(255,255,255,.72);

  --rule-decorative:var(--hairline);
  --rule-on-green:rgba(255,255,255,.24);
  /* The maile draws at a hairline weight but it is a mark, not a divider: at
     --rule-on-green's .24 the leaves disappear and only the stem survives, which
     is the generic line it was brought in to replace. Held just under body text
     so it reads as drawn rather than as chrome. */
  --maile-on-green:rgba(255,255,255,.62);
  --maile-on-canvas:rgba(42,70,65,.5);

  --action-fill:var(--green-700);
  --action-fill-hover:var(--green-800);
  --focus-ring:var(--green-700);
  --focus-ring-on-green:var(--white);
  --focus-ring-width:2px;
  --focus-ring-offset:2px;

  --font-display:"Alegreya",Georgia,"Times New Roman",serif;
  --font-sans:"Inter",system-ui,-apple-system,"Helvetica Neue",Arial,sans-serif;

  --type-hero-size:clamp(40px,6.1vw,76px); --type-hero-lh:1.05; --type-hero-track:.005em;
  --type-h1-size:clamp(34px,4.2vw,52px);   --type-h1-lh:1.1;    --type-h1-track:.01em;
  --type-h2-size:clamp(26px,2.7vw,34px);   --type-h2-lh:1.15;   --type-h2-track:.01em;
  --type-h3-size:clamp(20px,1.9vw,24px);   --type-h3-lh:1.25;   --type-h3-track:.01em;
  --type-body-size:16px;   --type-body-lh:1.6;
  --type-lead-size:clamp(18px,1.5vw,21px); --type-lead-lh:1.5;
  --type-caption-size:13px;--type-caption-lh:1.5;--type-caption-track:.01em;
  --type-button-size:12px; --type-button-track:.12em;
  --type-spec-track:.04em;

  --weight-display:400;
  --weight-label:500;
  --weight-spec:500;
  --measure:64ch;

  --space-1:8px; --space-2:16px; --space-3:24px; --space-4:40px;
  --space-5:64px; --space-6:96px; --space-7:160px;
  --gutter-page:clamp(24px,6vw,64px);
  --section-gap:var(--space-5);
  --max-page:1680px;
  --max-text:1180px;

  --radius-max:2px;
  --control-height:52px;

  --duration-fast:200ms; --duration-base:280ms; --duration-slow:400ms;
  --ease-standard:cubic-bezier(.2,0,.2,1);
  --ease-settle:cubic-bezier(.16,1,.3,1);
  --translate-max:8px;
  --transition-interactive:background-color var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard),
    border-color var(--duration-fast) var(--ease-standard),
    opacity var(--duration-fast) var(--ease-standard);
}
*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--surface-canvas);color:var(--text-body);
  font-family:var(--font-sans);font-size:var(--type-body-size);
  line-height:var(--type-body-lh);text-wrap:pretty;
  -webkit-font-smoothing:antialiased;
}
h1,h2,h3{font-family:var(--font-display);font-weight:var(--weight-display);margin:0;text-wrap:balance}
p{margin:0}
img{display:block;max-width:100%}
:focus-visible{outline:var(--focus-ring-width) solid var(--focus-ring);outline-offset:var(--focus-ring-offset)}
.on-green :focus-visible{outline-color:var(--focus-ring-on-green)}
.skip{position:absolute;left:-9999px;top:0;background:var(--white);color:var(--graphite);padding:var(--space-2);z-index:10}
.skip:focus{left:var(--space-2);top:var(--space-2)}
/* ---------- shell ---------- */
.wrap{max-width:var(--max-page);margin:0 auto;padding-inline:var(--gutter-page)}
.band{padding-block:var(--space-5)}
@media (min-width:900px){.band{padding-block:var(--space-6)}}
.band--green{background:var(--surface-chrome);color:var(--text-on-green)}
.band--canvas{background:var(--surface-canvas)}
.band--alt{background:var(--surface-alt)}
.rule{height:1px;background:var(--rule-decorative);border:0;margin:0}
.band--green .rule,.rule--on-green{background:var(--rule-on-green)}
/* ---------- icons ----------
   One sprite, two halves. Phosphor Regular paints with fill; the four brand
   marks paint with stroke. Each <symbol> declares its own paint, so nothing is
   repainted here — these rules only size and align. Never add fill or stroke to
   a .i rule: it would blank out one half of the set. */
.icon-sprite{display:none}
/* NO `color:inherit` on .i. It is redundant — colour inherits by default — and
   as an explicit declaration it out-ranks any `.gem--x{color:…}
` tint at equal
   specificity, which silently paints every tinted stone mark in the inherited
   ink instead. It was here, and in the ported custom.css, before the tints
   existed; the contact sheet hit the identical bug and it is only ever visible
   in a render. Paint on the shared .i class is already banned for blanking out
   the Phosphor half of the set — same hazard, different clothes. */
.i{width:16px;height:16px;flex:none}
.i--lg{width:20px;height:20px}
.i--xl{width:32px;height:32px}
/* Stone marks are drawn on a 40 grid, so 26px renders the same 1px hairline the
   16px icons do. Below ~22px their table facets close into a blob — the size is
   load-bearing, see assets/icons/README.md. */
.i--shape{width:26px;height:26px}
/* ---------- marks that draw themselves ----------
   Andrew, 7 Aug 2026, on this and only this out of the whole icon round:
   "the Gently animated aspects are so clean I love those... I would love to
   have that on more things as well."

   ⚠ READ THIS BEFORE ADDING A RULE HERE, IT IS THE WHOLE TRICK ⚠
   `<use href="#i-x">` clones the symbol into a SHADOW TREE. Document CSS cannot
   select into it: `.some-class path{...}
from{stroke-dashoffset:1}
to{stroke-dashoffset:0}
/* At rest a .draw mark is an ordinary icon: dasharray on its own changes
   nothing visible. Only the keyframe's opening dashoffset hides it, and that is
   only in play while .is-drawing is set. So a mark that is never triggered — or
   a browser running no script at all — shows the icon, never a blank. */
.draw{stroke-dasharray:1}
.draw.is-drawing{animation:gl-draw var(--duration-draw,1.15s) var(--ease-settle) both}
0%{stroke-dashoffset:1}
46%{stroke-dashoffset:0}
84%{stroke-dashoffset:0;opacity:1}
100%{stroke-dashoffset:0;opacity:0}
.maile-grow path{stroke-dasharray:1;stroke-dashoffset:1;
  animation:maile-grow 2.4s cubic-bezier(.16,1,.3,1) infinite}
.maile-grow path:nth-child(n+2){animation-delay:.26s}
.maile-grow path:nth-child(n+5){animation-delay:.46s}
.maile-grow path:nth-child(n+8){animation-delay:.66s}
.boot{
  position:fixed;inset:0;z-index:20;display:grid;place-items:center;
  background:var(--surface-chrome);color:var(--text-on-green);
  transition:opacity .5s ease;
}
.boot[data-done="true"]{opacity:0;pointer-events:none}
.boot .maile-grow{width:clamp(90px,13vw,148px);height:auto;
  fill:none;stroke:currentColor;stroke-width:1.6;
  stroke-linecap:round;stroke-linejoin:round}
0%{stroke-dashoffset:1}
46%{stroke-dashoffset:0}
84%{stroke-dashoffset:0;opacity:1}
100%{stroke-dashoffset:0;opacity:0}
.btn--pending{display:inline-flex;align-items:center;gap:10px}
.btn--pending .i{width:18px;height:18px;flex:none;
  stroke-dasharray:1;animation:gl-draw-loop 1.6s var(--ease-settle) infinite}
/* ---- saving a piece ---- */
/* Deliberately NOT a third button. Saving is tertiary to "Ask about this
   piece", and boxing it made it compete with the two that matter — three
   equal-weight controls in a row and the eye picks none of them. So it borrows
   .linkbtn's idiom: a control that reads as a link, with the rule only under
   the words rather than around the whole thing.
   (First cut used var(--border-interactive), which does not exist in this file
   — only the base palette is inlined here, not the semantic aliases. An
   undefined custom property invalidates the WHOLE declaration, so the border
   computed to 0px none instead of falling back to anything.) */
.save{
  display:inline-flex;align-items:center;gap:9px;align-self:center;
  background:none;border:0;padding:var(--space-1) 0;cursor:pointer;
  font:inherit;font-size:15px;color:var(--text-body);
}
.save span{border-bottom:1px solid var(--rule-decorative);padding-bottom:2px}
.save:hover span{border-bottom-color:var(--green-700)}
.save[aria-pressed="true"] span{border-bottom-color:var(--green-700)}
/* No fill swap on the pressed state: the set is hairline throughout and a solid
   heart would be the only filled mark on the site. The word carries the state,
   the draw-on carries the moment. */
.save .i{width:19px;height:19px}
@media (prefers-reduced-motion:reduce){
  .maile-grow path,.draw.is-drawing,.btn--pending .i{
    animation:none;stroke-dashoffset:0;opacity:1}
}
/* ---------- header ---------- */
.masthead{background:var(--surface-chrome);color:var(--text-on-green);position:sticky;top:0;z-index:8}
.navbar{position:relative}
.masthead__in{
  max-width:var(--max-page);margin:0 auto;padding:var(--space-3) var(--gutter-page);
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:var(--space-3);
}
.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}
.mast__side{display:flex;align-items:center;gap:var(--space-3);min-width:0}
.mast__side--end{justify-content:flex-end;gap:20px}
.mastlink{
  display:inline-flex;align-items:center;gap:7px;
  font-family:var(--font-sans);font-size:14px;color:var(--text-on-green-muted);
  text-decoration:none;border-bottom:1px solid transparent;white-space:nowrap;
  transition:var(--transition-interactive);
}
/* Icon-only masthead links carry their name in aria-label, so they need a real
   target area rather than inheriting the text link's line box. */
.mastlink--icon{padding:4px}
@media (max-width:1460px){.mastlink__t{display:none}.mastlink{gap:0}}
.mastlink:hover{color:var(--text-on-green);border-bottom-color:var(--text-on-green)}
.mastsearch{position:relative;display:flex;align-items:center;max-width:280px;width:100%}
.mastsearch input{
  width:100%;font-family:var(--font-sans);font-size:14px;color:var(--text-on-green);
  background:transparent;border:1px solid var(--rule-on-green);border-radius:var(--radius-max);
  padding:0 38px 0 12px;height:42px;transition:var(--transition-interactive);
}
.mastsearch input::placeholder{color:var(--text-label-on-green)}
.mastsearch input:hover,.mastsearch input:focus{border-color:var(--white)}
.mastsearch .i{
  position:absolute;right:12px;width:17px;height:17px;pointer-events:none;
  color:var(--text-on-green-muted);
}
.brand{justify-self:center}
.mast__ctabar{display:none}
/* mobile only — see the 900px block */
.brand{display:inline-flex;align-items:center;border:0;background:none;padding:0;cursor:pointer}
/* 64px, not 52px. The mark occupies 1000 of the stacked lockup's 1380 units, so
   64px of lockup draws the GD at 46px — within a few pixels of the 52px mark this
   replaced — and the wordmark sits in the 18px below it. Any taller and the
   masthead row passes 124px; any shorter and the wordmark stops being readable. */
/* No `display` here. `.brand img` outranks `.brand__lockup`, so a display on this
   rule silently beat the mobile swap below and drew both logos at once. */
.brand img{height:64px;width:auto}
.brand__mark{display:none}
/* mobile only — see the 900px block */
.navtoggle{
  display:none;background:none;border:1px solid var(--rule-on-green);color:var(--text-on-green);
  border-radius:var(--radius-max);padding:10px var(--space-2);cursor:pointer;
  font-family:var(--font-sans);font-size:var(--type-button-size);
  letter-spacing:var(--type-button-track);text-transform:uppercase;font-weight:var(--weight-label);
  transition:var(--transition-interactive);
}
.navtoggle:hover{border-color:var(--white)}
.nav{
  display:flex;align-items:center;justify-content:center;gap:clamp(var(--space-3),3.2vw,var(--space-5));flex-wrap:wrap;
  max-width:var(--max-page);margin:0 auto;padding:0 var(--gutter-page);
}
.navbar{
  background:var(--surface-canvas);
  border-top:1px solid rgba(255,255,255,.10);
  border-bottom:1px solid var(--rule-decorative);
  padding-block:14px;
}
.nav a,.nav__btn{
  font-family:var(--font-sans);font-size:17px;letter-spacing:0;text-transform:none;
  font-weight:400;color:var(--text-body);text-decoration:none;
  border-bottom:1px solid transparent;padding-block:3px;white-space:nowrap;
  transition:var(--transition-interactive);
}
.nav a:hover,.nav__btn:hover{color:var(--graphite);border-bottom-color:var(--graphite)}
.nav a[aria-current="page"]{color:var(--graphite);border-bottom-color:var(--graphite)}
@media (max-width:1100px){.nav a,.nav__btn{font-size:15px}.nav{gap:var(--space-2)}}
@media (max-width:900px){
  /* The mark alone below 900px — see the note on the markup. At 360px the whole
     header is a logo, two icons and a Menu button, and the 66px mark leaves 2px
     of slack; the 90px lockup would overrun the phone icon. */
  .brand__lockup{display:none}
  .brand__mark{display:block}
  .brand img{height:40px}
  .navtoggle{display:inline-flex;justify-self:end}
  /* THE PHONE, THE BAG AND THE CTA STAY ON MOBILE.
     This block once read `.mast__side--end{display:none}`, and the document holds
     exactly one tel: link, one cart link and one "start your design" link — all
     three inside it. Below 900px the header became a logo and a Menu button, and
     someone standing in the Shops at Wailea could not call the shop.

     ⚠ THIS FIX WAS MADE ONCE BEFORE, ON 7 AUG, AND WAS LOST. It was written
     straight into theme/AP-Design/assets/custom.css — which is GENERATED from this
     file by scripts/port-preview-css.py — so the next port silently reverted it,
     and it went live in that state. Andrew found it: the CTA had no styling left
     and rendered as a stray line of text under the search box. **Never edit
     custom.css directly. It is output.** */
  .masthead__in{grid-template-columns:auto 1fr auto auto;gap:var(--space-2)}
  .mast__side--start{display:none}
  .mast__side--end{gap:2px}
  .mast__side--end .mastlink__t{display:none}   /* icons only — labels are on aria-label */
  .mast__side--end .mast__cta{display:none}     /* re-appears as the full-width bar below */
  /* Two icons, not four. Measured at 390px: with the pin and account link present
     the logo ran to x=90 while the phone began at x=77 — a 13px overlap. */
  .mast__side--end .mastlink[href*="/account"]{display:none}
  .mast__side--end .mastlink[aria-label*="Shops"],
  .mast__side--end .mastlink[aria-label*="Wailea"]{display:none}
  .mastlink{min-width:44px;min-height:44px;justify-content:center}  /* WCAG 2.5.8 */
  .brand,.navtoggle{min-height:44px;display:inline-flex;align-items:center}
  .brand{justify-self:start}
  /* The primary action, full width, directly under the masthead. A bar rather than
     a button because a bar is what a thumb finds without looking. */
  .mast__ctabar{
    display:block;background:var(--surface-green-raised);color:var(--text-on-green);
    text-align:center;text-decoration:none;padding:14px var(--space-3);
    font-family:var(--font-sans);font-size:var(--type-button-size);
    letter-spacing:var(--type-button-track);text-transform:uppercase;
    border-top:1px solid rgba(255,255,255,.14);min-height:44px;
  }
  .mast__ctabar:hover,.mast__ctabar:focus-visible{background:var(--surface-chrome)}
  .navbar{display:none;background:var(--surface-chrome);border-bottom:0;padding-bottom:var(--space-3)}
  .navbar[data-open="true"]{display:block}
  .nav{flex-direction:column;align-items:flex-start;gap:var(--space-2);justify-content:flex-start}
  .nav a,.nav__btn{font-size:16px;color:var(--text-on-green-muted)}
  .nav a:hover,.nav__btn:hover{color:var(--text-on-green);border-bottom-color:var(--text-on-green)}
  .nav a[aria-current="page"]{color:var(--text-on-green);border-bottom-color:var(--text-on-green)}
}
/* ---------- breadcrumbs ---------- */
.crumbs{background:var(--surface-canvas);border-bottom:1px solid var(--rule-decorative);padding-block:12px}
.crumbs .wrap{display:flex;gap:8px;align-items:center;flex-wrap:wrap;font-size:var(--type-caption-size);color:var(--text-secondary)}
.crumbs a{color:var(--text-secondary);text-decoration:none;border-bottom:1px solid transparent;
  display:inline-block;padding-block:7px}
.crumbs a:hover{color:var(--graphite);border-bottom-color:var(--graphite)}
.crumbs em{font-style:normal;color:var(--graphite)}
/* ---------- filter bar ---------- */
.filterbar{
  display:flex;justify-content:space-between;align-items:center;gap:var(--space-3);flex-wrap:wrap;
  padding-block:var(--space-2);border-top:1px solid var(--rule-decorative);
  border-bottom:1px solid var(--rule-decorative);margin-bottom:var(--space-4);
}
.filterbar__group{display:flex;gap:var(--space-1);flex-wrap:wrap}
.chip{
  display:inline-flex;align-items:center;gap:7px;cursor:pointer;
  background:var(--white);border:1px solid var(--border-strong);border-radius:var(--radius-max);
  padding:9px 14px;font-family:var(--font-sans);font-size:14px;color:var(--text-body);
  transition:var(--transition-interactive);
}
.chip:hover{border-color:var(--graphite)}
.chip .i{width:11px;height:11px}
.filterbar__meta{display:flex;align-items:center;gap:var(--space-3);flex-wrap:wrap}
.filterbar__count{font-size:14px;color:var(--text-secondary)}
.sortsel select{
  font-family:var(--font-sans);font-size:14px;color:var(--text-body);background:var(--white);
  border:1px solid var(--border-strong);border-radius:var(--radius-max);padding:9px 12px;cursor:pointer;
}
/* the chips used to be decoration. Each one now opens a real panel of values
   read out of the catalogue, so the filter bar cannot claim a facet the data
   does not have. */
.chip[data-on="true"]{border-color:var(--graphite);background:var(--stone-50)}
.chip__n{font-variant-numeric:tabular-nums;color:var(--text-secondary)}
.chip--clear{color:var(--text-secondary)}
.filterbar__group{position:relative}
.facet{
  position:absolute;top:calc(100% + 8px);left:0;z-index:30;min-width:210px;
  background:var(--white);border:1px solid var(--border-strong);border-radius:var(--radius-max);
  padding:var(--space-2);display:grid;gap:2px;box-shadow:0 10px 30px rgba(21,38,35,.10);
}
.facet__opt{display:flex;align-items:center;gap:10px;padding:8px 8px;cursor:pointer;
  font-family:var(--font-sans);font-size:15px;color:var(--text-body);border-radius:var(--radius-max)}
.facet__opt:hover{background:var(--stone-50)}
.facet__opt input{width:16px;height:16px;accent-color:var(--green-700);cursor:pointer;margin:0}
.noresults{margin-top:var(--space-4);color:var(--text-secondary);font-size:16px;line-height:1.7}
/* a button that has to read as a link, for the one place a link would be wrong */
.linkbtn{background:none;border:0;padding:0;cursor:pointer;font:inherit;color:var(--text-body);
  border-bottom:1px solid var(--green-700)}
.linkbtn:hover{color:var(--green-700)}
/* a field that failed its check. Colour is not the only signal — the border
   weight changes too, because roughly one man in twelve cannot use the colour. */
[data-invalid="true"]{border-color:#B4362F !important;border-width:2px !important}
/* ---------- product grid ---------- */
.grid{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4) var(--space-3)}
.grid--4{grid-template-columns:repeat(4,minmax(0,1fr))}
@media (max-width:1100px){.grid,.grid--4{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.grid,.grid--4{grid-template-columns:1fr}}
.card a{display:flex;flex-direction:column;text-decoration:none;color:inherit}
.card .slot{margin-bottom:var(--space-2)}
.card a:hover .slot{border-color:var(--graphite)}
.card__name{font-family:var(--font-display);font-size:19px;line-height:1.3}
.card__meta{font-size:var(--type-caption-size);color:var(--text-secondary);margin-top:2px}
.card__price{
  margin-top:6px;font-family:var(--font-sans);font-size:15px;
  font-variant-numeric:tabular-nums;letter-spacing:var(--type-spec-track);color:var(--text-body);
}
/* A slot with no piece behind it. Dimmed so a grid of real prices reads as a
   column of numbers with a gap, rather than as a price that says "dash". */
.card__price--empty{color:var(--text-secondary)}
/* ---------- pagination ---------- */
.pager{display:flex;gap:var(--space-1);align-items:center;justify-content:center;margin-top:var(--space-5)}
.pager a,.pager button,.pager__cur{
  min-width:42px;height:42px;display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--rule-decorative);border-radius:var(--radius-max);
  font-family:var(--font-sans);font-size:14px;color:var(--text-body);text-decoration:none;
  font-variant-numeric:tabular-nums;transition:var(--transition-interactive);
}
.pager a:hover,.pager button:hover{border-color:var(--graphite)}
.pager button{background:var(--white);cursor:pointer}
.pager__cur{background:var(--action-fill);border-color:var(--action-fill);color:var(--white)}
.pager__next{padding-inline:var(--space-2)}
/* ---------- product detail ---------- */
.pdp{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--space-5)}
@media (max-width:900px){.pdp{grid-template-columns:1fr;gap:var(--space-4)}}
.pdp__thumbs{display:grid;grid-template-columns:repeat(4,1fr);gap:var(--space-1);margin-top:var(--space-1)}
.pdp__thumbs .slot{padding:var(--space-1)}
.pdp__info h1{font-family:var(--font-display);font-size:var(--type-h2-size);line-height:1.15}
.pdp__sub{margin-top:6px;color:var(--text-secondary);font-size:15px}
.pdp__price{margin-top:var(--space-3)}
/* The one number on the page. Sized between the subtitle and the h1 — big
   enough to be the answer to the question the customer came with, not so big
   it competes with the piece. Tabular figures so it does not shift when
   openProduct() swaps a 4-digit price for a 5-digit one. */
.pdp__amount{font-family:var(--font-sans);font-size:22px;
  font-variant-numeric:tabular-nums;letter-spacing:var(--type-spec-track);color:var(--text-body)}
.pdp__desc{margin-top:var(--space-3);line-height:1.7;max-width:56ch}
.pdp__care p{max-width:56ch}
/* ---------- embedded map ----------
   The live theme drops a real Google iframe into .map__frame. The preview keeps
   a fallback underneath, because the artifact host blocks every external origin
   — an empty white box would read as a broken map rather than a blocked one, so
   it says which it is. */
.map__frame{position:relative;aspect-ratio:3/2;width:100%;overflow:hidden;
  border:1px solid var(--border-strong);border-radius:var(--radius-max);background:var(--stone-50)}
.map__frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.map__fallback{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:var(--space-3);text-align:center;padding:var(--space-4);
  background:repeating-linear-gradient(-45deg,transparent 0 14px,rgba(140,139,134,.10) 14px 15px)}
.map__note{margin-top:10px;font-size:var(--type-caption-size);color:var(--text-secondary)}
/* ---------- search suggestions ----------
   The live Horology theme has predictive search and the new design did not.
   Results are matched against the same catalogue the collection page filters,
   so the box can never suggest something the grid cannot show. */
.mastsearch{position:relative}
.sugg{position:absolute;top:calc(100% + 8px);left:0;right:0;z-index:60;margin:0;padding:6px;
  list-style:none;background:var(--white);border:1px solid var(--border-strong);
  border-radius:var(--radius-max);box-shadow:0 14px 34px rgba(21,38,35,.16);
  max-height:min(60vh,420px);overflow:auto}
.sugg li{margin:0}
.sugg button{display:flex;width:100%;gap:10px;align-items:baseline;text-align:left;
  background:none;border:0;cursor:pointer;padding:9px 10px;border-radius:var(--radius-max);
  font-family:var(--font-sans);font-size:15px;color:var(--text-body)}
.sugg button:hover,.sugg button[aria-selected="true"]{background:var(--stone-50)}
.sugg b{font-weight:var(--weight-label)}
.sugg span{color:var(--text-secondary);font-size:13px;margin-left:auto;white-space:nowrap}
.sugg mark{background:none;color:var(--green-700);font-weight:var(--weight-label)}
.sugg__all{border-top:1px solid var(--rule-decorative);margin-top:4px;padding-top:4px}
.sugg__none{padding:10px;color:var(--text-secondary);font-size:15px}
/* ---------- one featured piece ----------
   The live site leads on a single ring and it is the strongest thing on that
   page. Kept to one: a row of four is a catalogue, and this shop has one of
   each. */
.feature{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:var(--space-5);align-items:center}
@media (max-width:820px){.feature{grid-template-columns:1fr;gap:var(--space-3)}}
.feature__media{display:block;text-decoration:none}
.feature__media .slot{width:100%}
.feature__media:hover .slot{border-color:var(--graphite)}
.feature__eyebrow{font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:.12em;text-transform:uppercase;font-weight:var(--weight-label);
  color:var(--text-secondary);margin-bottom:var(--space-2)}
.feature__body h2{font-size:var(--type-h2-size);line-height:var(--type-h2-lh)}
.feature__body p{margin-top:var(--space-2);max-width:52ch;line-height:var(--type-body-lh)}
.feature__price{font-family:var(--font-sans);font-size:16px;
  letter-spacing:var(--type-spec-track);color:var(--text-body)}
.feature__actions{display:flex;gap:var(--space-2);flex-wrap:wrap;margin-top:var(--space-4)}
/* ---------- customer words ----------
   Deliberately no stars, no counts, no "4.9 out of 5" badge. Tiffany, David
   Yurman and Helzberg show no reviews at all; Blue Nile covers every surface
   in them. The split is not about size — a catalogue retailer selling the same
   ring as everyone else has nothing but the review to differentiate on, and a
   house selling its own name does not need one. Greenleaf is the second kind
   without the name yet, so it gets the customers' language and none of the
   arithmetic. See REVIEWS.md for the measurements. */
.quotes{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4) var(--space-3)}
@media (max-width:980px){.quotes{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:640px){.quotes{grid-template-columns:1fr}}
.quotes > li{border-top:1px solid var(--rule-decorative);padding-top:var(--space-3)}
.band--green .quotes > li,.on-green .quotes > li{border-top-color:var(--rule-on-green)}
.quotes blockquote{margin:0;font-family:var(--font-display);font-size:20px;line-height:1.5}
@media (min-width:1100px){.quotes blockquote{font-size:21px}}
.quotes figcaption{margin-top:var(--space-2)}
/* The source is a labelled placeholder on purpose, and looks like every other
   placeholder on this site. These four quotes are already public on the live
   site, but it does not say who wrote them or which platform they came from —
   and an unsourced testimonial is indistinguishable from an invented one. It
   cannot ship looking like finished copy. */
.quotes__src{font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:var(--type-spec-track);color:var(--text-secondary);
  border:1px dashed var(--border-strong);border-radius:var(--radius-max);
  padding:5px 10px;display:inline-block}
.band--green .quotes__src,.on-green .quotes__src{color:var(--text-label-on-green);border-color:var(--rule-on-green)}
/* REVIEWS THAT NAME THEIR SOURCE — sections/gl-reviews.liquid.
   Solid caption, not the dashed .quotes__src above: the dash means "we do not
   know who wrote this yet", so a review carrying a real name and platform must
   not wear it. gl-reviews refuses to render a review without both. */
.gl-reviews__src{display:block;margin-top:2px;font-family:var(--font-sans);
  font-size:var(--type-caption-size);letter-spacing:var(--type-spec-track);
  color:var(--text-secondary)}
.band--green .gl-reviews__src,.on-green .gl-reviews__src{color:var(--text-label-on-green)}
.gl-reviews__stars{margin:0 0 var(--space-2);font-size:15px;line-height:1;
  letter-spacing:2px;color:var(--text-primary)}
.band--green .gl-reviews__stars,.on-green .gl-reviews__stars{color:var(--text-on-green)}
.gl-reviews__more{margin:var(--space-4) 0 0}
/* The TestimonialHub container. The app ships its own stylesheet, so this only
   bounds the box it injects into — it does not try to restyle their markup. */
.gl-reviews__widget{margin-top:var(--space-3);max-width:var(--measure-wide,72ch)}
.gl-reviews__widget:empty{display:none}
.pdp__actions{display:flex;gap:var(--space-2);flex-wrap:wrap;margin-top:var(--space-4)}
.spec{margin:var(--space-4) 0 0;display:flex;flex-direction:column}
.spec > div{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.4fr);gap:var(--space-2);
  padding-block:11px;border-top:1px solid var(--rule-decorative)}
.spec > div:last-child{border-bottom:1px solid var(--rule-decorative)}
.spec dt{font-size:14px;color:var(--text-secondary)}
.spec dd{margin:0;font-size:15px;font-variant-numeric:tabular-nums;letter-spacing:var(--type-spec-track)}
.pdp__care{margin-top:var(--space-4);padding-top:var(--space-3);border-top:1px solid var(--rule-decorative);
  font-size:var(--type-caption-size);line-height:1.65;color:var(--text-secondary)}
/* ---------- the assurance row (.pledge) ----------
   Four marks and four short labels: what a customer gets, said once. Replaces
   the stock black "trust badge" strip Andrew liked the idea of — see the markup
   comment on the product page for why only these four claims are made.

   NOT .promise and NOT .assure, and both were tried. .promise is already the
   hero HEADLINE on 26 elements, so naming this one .promise applied list-style,
   a grid and a border to every page's h1. .assure is already the homepage's
   three-up icon row, so the second name collided with that instead. Both
   rendered wrong and check-preview.py passed cleanly through both, because a
   headline turning into a grid is not a console error or a dead link. Only
   looking at the pixels caught it. Grep a class name before minting it here —
   this file is 4,900 lines and its stylesheet is one flat namespace.

   Two up on the product page, where it sits in the narrow info column and a
   4-up wraps to an orphaned 3+1. Four up under the bag, which has the width. */
.pledge{
  list-style:none;margin:var(--space-4) 0 0;padding:var(--space-4) 0 0;
  border-top:1px solid var(--rule-decorative);
  display:grid;grid-template-columns:repeat(2,1fr);gap:var(--space-4) var(--space-2);
}
.pledge--wide{margin-top:var(--space-5);padding-top:var(--space-5);
  grid-template-columns:repeat(auto-fit,minmax(190px,1fr))}
.pledge li{display:flex;flex-direction:column;align-items:center;text-align:center;gap:7px}
.pledge .i{width:26px;height:26px;color:var(--text-body)}
.pledge b{font-family:var(--font-display);font-weight:500;font-size:16px;line-height:1.25}
.pledge em{font-style:normal;font-size:13px;line-height:1.45;color:var(--text-secondary);max-width:22ch}
/* ---------- full-width mega menus ---------- */
.mega{
  position:absolute;left:0;right:0;top:100%;z-index:9;
  background:var(--white);border-top:1px solid var(--hairline);
  border-bottom:1px solid var(--hairline);
}
.mega[hidden]{display:none}
.mega__head{max-width:var(--max-page);margin:0 auto;padding:var(--space-4) var(--gutter-page) 0}
.mega__head a{font-family:var(--font-sans);font-size:17px;color:var(--graphite);text-decoration:underline;text-underline-offset:4px}
.mega__in{
  max-width:var(--max-page);margin:0 auto;padding:var(--space-3) var(--gutter-page) var(--space-4);
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-4) var(--space-5);
}
/* A menu with fewer real destinations than columns leaves a quarter of the
   panel empty and reads as a rendering fault rather than as restraint. The
   Jewelry menu lost a column when the links that led nowhere came out of it. */
.mega__in--3{grid-template-columns:repeat(3,minmax(0,1fr))}
.mega__col{display:flex;flex-direction:column;gap:10px;align-items:flex-start}
/* A shape/stone row runs ACROSS the foot of the panel rather than inside one
   column. Eleven stones in a single column would be taller than the four link
   columns beside it and would set the height of the whole panel; across the
   foot it stays a row, which is also how a shopper reads it — a rack of stones,
   not another list of links. */
.mega__strip{
  max-width:var(--max-page);margin:0 auto;
  padding:0 var(--gutter-page) var(--space-4);
}
.mega__strip h3{
  font-family:var(--font-sans);font-size:var(--type-label-size);
  letter-spacing:var(--type-label-tracking);text-transform:uppercase;
  color:var(--text-secondary);margin:0 0 var(--space-2);font-weight:600;
}
.mega__strip .mega__stripIn{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(132px,1fr));
  gap:12px var(--space-3);border-top:1px solid var(--hairline);
  padding-top:var(--space-3);
}
.mega__strip a{
  display:flex;align-items:center;gap:10px;font-family:var(--font-sans);
  font-size:15px;color:var(--graphite);text-decoration:none;border:0;
}
.mega__strip a span{border-bottom:1px solid transparent}
.mega__strip a:hover span{border-bottom-color:var(--graphite)}
@media (max-width:900px){
  .mega__strip{padding:0 0 var(--space-3) var(--space-2)}
  .mega__strip h3{color:var(--text-label-on-green)}
  .mega__strip .mega__stripIn{border-top-color:var(--rule-on-green)}
  .mega__strip a{color:var(--text-on-green-muted)}
  .mega__strip a:hover span{border-bottom-color:var(--text-on-green)}
}
.mega__col h3{
  font-family:var(--font-sans);font-size:var(--type-caption-size);letter-spacing:.1em;
  text-transform:uppercase;font-weight:var(--weight-label);color:var(--text-secondary);
  margin:0 0 4px;
}
.mega__col a{
  font-family:var(--font-sans);font-size:16px;color:var(--text-body);text-decoration:none;
  border-bottom:1px solid transparent;transition:var(--transition-interactive);
}
.mega__col a:hover{color:var(--graphite);border-bottom-color:var(--graphite)}
.mega__feature{display:flex;flex-direction:column;gap:var(--space-2)}
.mega__featureNote{font-size:var(--type-caption-size);line-height:1.6;color:var(--text-secondary)}
@media (max-width:1100px){.mega__in{grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4)}.mega__feature{display:none}}
@media (max-width:900px){
  .mega{position:static;background:none;border:0}
  .mega__in{grid-template-columns:1fr;gap:var(--space-3);padding:var(--space-2) 0 var(--space-3) var(--space-2)}
  .mega__col h3{color:var(--text-label-on-green)}
  .mega__col a{color:var(--text-on-green-muted);font-size:15px}
  .mega__col a:hover{color:var(--text-on-green);border-bottom-color:var(--text-on-green)}
  .mega__feature{display:none}
}
/* ---------- primary nav with a Shop tree ---------- */
.nav__item{position:relative}
.nav__btn{background:none;border:0;cursor:pointer;display:inline-flex;align-items:center;gap:7px}
.nav__btn[aria-expanded="true"]{color:var(--graphite);border-bottom-color:var(--graphite);border-bottom-width:2px}
.nav__btn .i{width:10px;height:10px;transition:var(--transition-interactive)}
.nav__btn[aria-expanded="true"] .i{transform:rotate(180deg)}
.nav__panel{
  position:absolute;top:calc(100% + 14px);left:0;min-width:238px;z-index:9;
  background:var(--white);border:1px solid var(--hairline);
  border-radius:var(--radius-max);padding:var(--space-2);
  display:none;flex-direction:column;gap:2px;
}
.nav__panel[data-open="true"]{display:flex}
.nav__panel a{
  font-size:15px;padding:8px 10px;border-bottom:0;border-radius:var(--radius-max);color:var(--text-body);
}
.nav__panel a:hover{background:var(--stone-50);color:var(--graphite);border-bottom:0}
@media (max-width:900px){
  .nav__panel{position:static;display:flex;background:none;border:0;padding:0 0 0 var(--space-2);margin-top:var(--space-1)}
  .nav__panel a{font-size:15px;color:var(--text-on-green-muted)}
  .nav__panel a:hover{background:none;color:var(--text-on-green)}
}
/* ---------- buttons ---------- */
.btn{
  display:inline-flex;align-items:center;justify-content:center;
  min-height:var(--control-height);padding-inline:var(--space-3);
  font-family:var(--font-sans);font-size:var(--type-button-size);
  letter-spacing:var(--type-button-track);text-transform:uppercase;
  font-weight:var(--weight-label);text-decoration:none;border-radius:var(--radius-max);
  border:1px solid transparent;cursor:pointer;transition:var(--transition-interactive);
}
/* .btn--ghost lived up beside .pdp__actions, which put it BEFORE .btn — and
   .btn sets `border:1px solid transparent`, so at equal specificity the later
   rule won and every ghost button on the site drew a transparent border. It
   looked like bare uppercase text. Variants belong next to each other, after
   the base. */
.btn--ghost{background:transparent;color:var(--graphite);border-color:var(--border-strong)}
.btn--ghost:hover{border-color:var(--graphite)}
.btn--light{background:var(--white);color:var(--green-700);border-color:var(--white)}
.btn--light:hover{background:var(--diamond-white);border-color:var(--diamond-white)}
.btn--ghost-green{background:transparent;color:var(--text-on-green);border-color:var(--rule-on-green)}
.btn--ghost-green:hover{border-color:var(--white)}
.btn--dark{background:var(--action-fill);color:var(--white);border-color:var(--action-fill)}
.btn--dark:hover{background:var(--action-fill-hover);border-color:var(--action-fill-hover)}
.textlink{
  color:inherit;text-decoration:none;border-bottom:1px solid currentColor;
  transition:var(--transition-interactive);
  /* The hit area, not the look. On an inline element vertical padding grows the
     clickable box WITHOUT changing the line box, so prose stays exactly where it
     was while a 22px link becomes ~32px to a thumb. This matters most where a
     textlink is the only thing in its paragraph and is really a button. */
  padding-block:5px;padding-bottom:6px;
}
.textlink:hover{opacity:.72}
/* ---------- hero: the single sentence ---------- */
.hero{
  position:relative;isolation:isolate;overflow:hidden;
  background:var(--surface-chrome);color:var(--text-on-green);
  min-height:min(78vh,780px);display:flex;align-items:center;
  padding-block:var(--space-6);
}
.hero__media{position:absolute;inset:0;z-index:-2}
.hero__media .slot{height:100%;border:0;border-radius:0}
/* A full-bleed hero placeholder centres its caption, which lands directly on
   top of the headline. Pin it to the far corner instead — on the bench page the
   words "Hero image" were sitting inside the h1. Hidden on phones, where the
   copy fills the whole field. */
.hero__media .slot__label{
  position:absolute;right:var(--gutter-page);bottom:var(--space-4);
  text-align:right;max-width:30ch;
}
@media (max-width:900px){.hero__media .slot__label{display:none}}
.hero__media video,.hero__media img{width:100%;height:100%;object-fit:cover}
/* scrim: strongest on the left where the copy sits, so a bright frame cannot
   swallow the headline. This is the fix for the weak 20% overlay in the theme.

   REBALANCED 10 Aug 2026, when the promise went from four lines to two. The old
   ramp was built to cover a paragraph: it held .80 out to 42% of the width and
   was still at .24 at the right-hand edge, which is most of a beautiful piece of
   footage under a wash. Two lines need a smaller dark field, so the strong part
   is tighter to the copy and the right two-thirds of the frame open up.

   Not eyeballed — scripts/check-hero-contrast.py hides the type, screenshots the
   real composite at six points through the loop, and measures white against the
   95th-percentile background at desktop and phone widths. This ramp holds
   5.26:1 at its worst, against a 4.5:1 threshold. Re-run it if either the
   footage or the copy changes; both have now changed once. */
.hero--media::after{
  content:"";position:absolute;inset:0;z-index:-1;
  background:linear-gradient(100deg,rgba(21,38,35,.88) 0%,rgba(21,38,35,.80) 38%,rgba(21,38,35,.52) 60%,rgba(21,38,35,.12) 100%);
}
.hero__in{max-width:var(--max-page);margin:0 auto;padding-inline:var(--gutter-page);width:100%;position:relative}
/* 860, not 760. The homepage promise is authored to break in exactly two places
   and at 760 the first line ran two characters long, breaking after "Designed
   or" and leaving an orphan. Widening the column rather than shrinking the type
   keeps the headline at hero scale; the scrim was re-measured afterwards,
   because moving type rightwards moves it into a lighter part of the ramp. */
.hero__copy{max-width:min(100%,860px)}
@media (max-width:820px){
  .hero--media::after{background:linear-gradient(180deg,rgba(21,38,35,.62) 0%,rgba(21,38,35,.84) 100%)}
}
.promise{
  font-family:var(--font-display);font-weight:var(--weight-display);
  font-size:clamp(36px,4.9vw,62px);line-height:var(--type-hero-lh);
  letter-spacing:var(--type-hero-track);
  /* 30ch, not 24. The homepage promise is the only headline that uses this
     measure — every other .promise on the site carries its own inline
     max-width — and at 24ch its first line broke after "Designed or", which
     turned a deliberate two-line sentence into three with an orphan. The
     authored line breaks are the point of this element; the measure has to fit
     them rather than the other way round. */
  margin:0;max-width:30ch;
}
.promise span{display:block}
.promise em{font-style:italic}
@media (max-width:820px){
  .promise{max-width:none}
  .promise span{display:inline}
}
/* Closer to the headline than it was: with two lines instead of four the buttons
   are what fills the space the paragraph used to, and 64px of air under a
   two-line promise left them stranded halfway down the frame. */
.hero__foot{
  margin-top:var(--space-4);display:flex;flex-direction:column;gap:var(--space-3);
  align-items:flex-start;
}
.hero__actions{display:flex;flex-wrap:wrap;gap:var(--space-2);align-items:center}
.hero__note{
  font-size:var(--type-caption-size);line-height:var(--type-caption-lh);
  letter-spacing:var(--type-caption-track);color:var(--text-label-on-green);
  max-width:52ch;
}
@media (prefers-reduced-motion:no-preference){
  .promise span,.hero__foot{animation:settle var(--duration-slow) var(--ease-settle) both}
  .promise span:nth-child(1){animation-delay:0ms}
  .promise span:nth-child(2){animation-delay:70ms}
  .promise span:nth-child(3){animation-delay:140ms}
  .promise span:nth-child(4){animation-delay:210ms}
  .hero__foot{animation-delay:320ms}
}
from{transform:translateY(var(--translate-max));opacity:.001}
to{transform:none;opacity:1}
/* ---------- editorial spread ---------- */
.spread{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:var(--space-4) var(--space-5);align-items:start}
@media (max-width:900px){.spread{grid-template-columns:1fr;gap:var(--space-4)}}
.spread__lede h2{font-size:var(--type-h2-size);line-height:var(--type-h2-lh);letter-spacing:var(--type-h2-track)}
.spread__lede p{
  margin-top:var(--space-2);font-size:var(--type-body-size);line-height:1.65;
  color:var(--text-secondary);max-width:44ch;
}
.band--green .spread__lede p{color:var(--text-on-green-muted)}
/* ---------- ruled definition list (pillars) ---------- */
.deflist{margin:0;display:flex;flex-direction:column}
.deflist__row{
  display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);
  gap:var(--space-2) var(--space-4);
  padding-block:var(--space-3);border-top:1px solid var(--rule-decorative);
}
.deflist__row:last-child{border-bottom:1px solid var(--rule-decorative)}
.band--green .deflist__row{border-color:var(--rule-on-green)}
@media (max-width:700px){.deflist__row{grid-template-columns:1fr;gap:var(--space-1)}}
.deflist dt{
  font-family:var(--font-display);font-size:var(--type-h3-size);
  line-height:var(--type-h3-lh);letter-spacing:var(--type-h3-track);margin:0;
}
.deflist dd{margin:0;color:var(--text-secondary);line-height:1.65;max-width:var(--measure)}
.band--green .deflist dd{color:var(--text-on-green-muted)}
/* ---------- the sequence (measured register) ---------- */
.steps{margin:0;padding:0;list-style:none;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-3)}
@media (max-width:900px){.steps{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:520px){.steps{grid-template-columns:1fr}}
.steps li{border-top:1px solid var(--rule-on-green);padding-top:var(--space-2)}
.steps__ord{
  display:block;font-family:var(--font-sans);font-variant-numeric:tabular-nums;
  font-size:var(--type-caption-size);letter-spacing:var(--type-spec-track);
  font-weight:var(--weight-spec);color:var(--text-label-on-green);
}
.steps__name{
  display:block;margin-top:var(--space-1);font-family:var(--font-display);
  font-size:var(--type-h3-size);line-height:var(--type-h3-lh);
}
.steps__note{display:block;margin-top:var(--space-1);font-size:var(--type-caption-size);line-height:1.5;color:var(--text-on-green-muted)}
/* the sequence is four-up by default; three steps in a four-column grid leaves a
   hole, so the count drives the columns rather than the other way round */
.steps--3{grid-template-columns:repeat(3,minmax(0,1fr))}
@media (max-width:900px){.steps--3{grid-template-columns:1fr}}
/* ---------- the sequence, grown on a maile vine ----------
   The four steps are the site's central claim — sit down, design it, made,
   fitted before you fly — and they appear on three pages. They were three
   identical four-column lists under three identical hairlines, which is the
   same treatment every jeweller's site gives its process.

   The vine IS that hairline. One drawing spans the sequence with a whorl of
   maile at each step, so the rule that was separating the steps is now the thing
   joining them, and the plant that means weddings and blessings on this island
   is doing structural work rather than sitting in a corner as a logo.
   assets/icons/brand/maile-vine.svg, one node per column, largest at step four.

   The viewBox is repeated in the markup because the outer <svg> needs an aspect
   ratio of its own to scale on `height:auto` — a <use> alone gives the element
   no intrinsic size. scripts/check-preview.py asserts it still matches the
   generated drawing, so a redrawn vine cannot silently letterbox itself. */
.sequence__vine{
  display:block;width:100%;height:auto;
  color:var(--maile-on-green);margin-bottom:var(--space-2);
}
.band--canvas .sequence__vine{color:var(--maile-on-canvas)}
/* the vine has taken the rule's job, so the per-step rules come off */
.sequence .steps li{border-top:0;padding-top:0}
.steps__node{display:none}
@media (max-width:900px){
  .sequence__vine{display:none}
  .sequence .steps li{border-top:1px solid var(--rule-on-green);padding-top:var(--space-2)}
  .band--canvas .sequence .steps li{border-top-color:var(--rule-decorative)}
  .steps__node{
    display:block;width:18px;height:18px;
    margin-bottom:var(--space-1);color:var(--maile-on-green);
  }
  .band--canvas .steps__node{color:var(--maile-on-canvas)}
}
@media (max-width:520px){
  .sequence .steps{gap:0}
  .sequence .steps li{
    border-top:0;padding:0 0 var(--space-3) 38px;position:relative;
  }
  .sequence .steps li:last-child{padding-bottom:0}
  .sequence .steps__node{
    position:absolute;left:0;top:1px;width:20px;height:20px;margin-bottom:0;
  }
  .sequence .steps li:not(:last-child)::before{
    content:'';position:absolute;left:9px;top:26px;bottom:0;
    width:1px;background:var(--rule-on-green);
  }
  .band--canvas .sequence .steps li:not(:last-child)::before{
    background:var(--rule-decorative);
  }
}
/* a sold piece has to say so before the price area, not after the description */
.pdp__flag{
  display:inline-block;margin-bottom:var(--space-2);
  font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:.1em;text-transform:uppercase;font-weight:var(--weight-label);
  color:var(--graphite);border:1px solid var(--border-strong);
  border-radius:var(--radius-max);padding:6px 12px;
}
/* the sequence and the detail list are green-ground by default; these are the
   canvas variants, so the same component works on either band */
.band--canvas .steps li{border-top-color:var(--rule-decorative)}
.band--canvas .steps__ord,
.band--canvas .steps__note{color:var(--text-secondary)}
.band--canvas .visit dt{color:var(--text-secondary)}
.band--canvas .visit dd a{border-bottom-color:var(--border-strong)}
.band--canvas .visit dd a:hover{border-bottom-color:var(--graphite)}
.band--canvas .visit__pending{color:var(--text-secondary)}
/* ---------- assurance strip ----------
   Three verified facts about how the shop works, sitting between the promise
   and the first browse band. Deliberately quieter than the sequence: an icon
   and one sentence, no heading, no box. A heading here would compete with the
   promise it sits directly beneath, and boxes would turn the page's first move
   after the hero into three cards. */
.assure-band{padding-block:var(--space-4);border-bottom:1px solid var(--rule-decorative)}
.assure{
  margin:0;padding:0;list-style:none;
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4);
}
.assure li{
  display:flex;align-items:flex-start;gap:var(--space-2);
  border-top:1px solid var(--rule-decorative);padding-top:var(--space-2);
}
.assure .i{color:var(--graphite);margin-top:2px}
.assure p{
  font-size:var(--type-caption-size);line-height:1.55;
  color:var(--text-secondary);max-width:32ch;
}
@media (max-width:820px){
  .assure{grid-template-columns:1fr;gap:0}
  .assure li{padding-block:var(--space-2)}
}
/* ---------- image placeholder slots ----------
   Deliberate, labelled slots so it is obvious what photography is needed and at
   what crop. Andrew, 6 Aug 2026: "you should be building placeholder spots for
   where imagery should go." Each slot names its subject and ratio. */
.slot{
  position:relative;display:flex;align-items:center;justify-content:center;
  background:var(--stone-50);border:1px solid var(--border-strong);
  border-radius:var(--radius-max);overflow:hidden;text-align:center;
  padding:var(--space-3);
  /* width must be explicit: with aspect-ratio, a min-height would otherwise
     drive the WIDTH up and overflow the viewport on small screens. */
  width:100%;
}
.slot::before{
  content:"";position:absolute;inset:0;
  background:repeating-linear-gradient(-45deg,transparent 0 14px,rgba(140,139,134,.10) 14px 15px);
}
.slot__label{
  position:relative;font-family:var(--font-sans);font-size:var(--type-caption-size);
  line-height:1.5;letter-spacing:var(--type-spec-track);color:var(--grey-600);
  max-width:32ch;
}
.slot__label b{display:block;font-weight:var(--weight-label);color:var(--graphite);text-transform:uppercase;letter-spacing:.1em;margin-bottom:4px}
.slot--onGreen{background:var(--green-800);border-color:var(--rule-on-green)}
.slot--onGreen::before{background:repeating-linear-gradient(-45deg,transparent 0 14px,rgba(255,255,255,.07) 14px 15px)}
.slot--onGreen .slot__label{color:var(--text-on-green-muted)}
.slot--onGreen .slot__label b{color:var(--text-on-green)}
.slot--wide{aspect-ratio:3/2}
.slot--portrait{aspect-ratio:4/5}
.slot--square{aspect-ratio:1/1}
.slot--hero{aspect-ratio:16/9}
/* ---------- real photography ----------
   Real photography, graded by scripts/grade-photos.py. Was two assets: the Andre portrait and the coast footage. Everything
   else is still a labelled .slot. So this is deliberately minimal — square
   corners, no border, no shadow — and the caption uses the same register as
   .slot__label, so a filled slot and an empty one read as the same object in
   the layout rather than as two different components. */
.shot{margin:0}
.shot img{display:block;width:100%;height:auto;border-radius:var(--radius-max)}
.shot--wide img{aspect-ratio:3/2;object-fit:cover}
.shot--portrait img{aspect-ratio:4/5;object-fit:cover}
.shot--band img{aspect-ratio:16/9;object-fit:cover}
.shot--square img{aspect-ratio:1/1;object-fit:cover}
/* a 4:5 or 1:1 frame at full band width is a wall, not a photograph */
.shot--inset{max-width:460px;margin-inline:auto}
.shot--inset figcaption{max-width:38ch;text-align:center;margin-inline:auto}
.shot figcaption{margin-top:10px;font-family:var(--font-sans);font-size:var(--type-caption-size);
  line-height:1.5;letter-spacing:var(--type-spec-track);color:var(--text-secondary)}
.on-green .shot figcaption{color:var(--text-label-on-green)}
/* the founder pairing: portrait beside the quote, not above it */
.founder{display:grid;grid-template-columns:minmax(0,240px) minmax(0,1fr);gap:var(--space-4);align-items:start}
@media (max-width:820px){.founder{grid-template-columns:1fr;gap:var(--space-3)}}
@media (max-width:820px){.founder .shot{max-width:260px}}
@media (min-width:900px){.slot--hero{min-height:320px}}
.pair{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-2)}
@media (max-width:640px){.pair{grid-template-columns:1fr}}
/* ---------- ported from the "single sentence" build, 7 Aug 2026 ----------
   The testimonial quotes, the featured-piece band, the embedded-map fallback and
   the assurance strip. Same design system and same token names, so these drop in
   without overriding anything — 33 of that build's 41 pages were already
   byte-identical to this file. ------------------------------------------------ */
.i--lg{width:20px;height:20px}
/* ---------- embedded map ----------
   The live theme drops a real Google iframe into .map__frame. The preview keeps
   a fallback underneath, because the artifact host blocks every external origin
   — an empty white box would read as a broken map rather than a blocked one, so
   it says which it is. */
.map__frame{position:relative;aspect-ratio:3/2;width:100%;overflow:hidden;
  border:1px solid var(--border-strong);border-radius:var(--radius-max);background:var(--stone-50)}
.map__frame iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
.map__fallback{position:absolute;inset:0;display:flex;flex-direction:column;
  align-items:center;justify-content:center;gap:var(--space-3);text-align:center;padding:var(--space-4);
  background:repeating-linear-gradient(-45deg,transparent 0 14px,rgba(140,139,134,.10) 14px 15px)}
.map__note{margin-top:10px;font-size:var(--type-caption-size);color:var(--text-secondary)}
/* ---------- one featured piece ----------
   The live site leads on a single ring and it is the strongest thing on that
   page. Kept to one: a row of four is a catalogue, and this shop has one of
   each. */
.feature{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:var(--space-5);align-items:center}
@media (max-width:820px){.feature{grid-template-columns:1fr;gap:var(--space-3)}}
.feature__media{display:block;text-decoration:none}
.feature__media .slot{width:100%}
.feature__media:hover .slot{border-color:var(--graphite)}
.feature__eyebrow{font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:.12em;text-transform:uppercase;font-weight:var(--weight-label);
  color:var(--text-secondary);margin-bottom:var(--space-2)}
.feature__body h2{font-size:var(--type-h2-size);line-height:var(--type-h2-lh)}
.feature__body p{margin-top:var(--space-2);max-width:52ch;line-height:var(--type-body-lh)}
.feature__price{font-family:var(--font-sans);font-size:16px;
  letter-spacing:var(--type-spec-track);color:var(--text-body)}
.feature__actions{display:flex;gap:var(--space-2);flex-wrap:wrap;margin-top:var(--space-4)}
/* ---------- customer words ----------
   Deliberately no stars, no counts, no "4.9 out of 5" badge. Tiffany, David
   Yurman and Helzberg show no reviews at all; Blue Nile covers every surface
   in them. The split is not about size — a catalogue retailer selling the same
   ring as everyone else has nothing but the review to differentiate on, and a
   house selling its own name does not need one. Greenleaf is the second kind
   without the name yet, so it gets the customers' language and none of the
   arithmetic. See REVIEWS.md for the measurements. */
.quotes{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4) var(--space-3)}
@media (max-width:980px){.quotes{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:640px){.quotes{grid-template-columns:1fr}}
.quotes > li{border-top:1px solid var(--rule-decorative);padding-top:var(--space-3)}
.band--green .quotes > li,.on-green .quotes > li{border-top-color:var(--rule-on-green)}
.quotes blockquote{margin:0;font-family:var(--font-display);font-size:20px;line-height:1.5}
@media (min-width:1100px){.quotes blockquote{font-size:21px}}
.quotes figcaption{margin-top:var(--space-2)}
/* The source is a labelled placeholder on purpose, and looks like every other
   placeholder on this site. These four quotes are already public on the live
   site, but it does not say who wrote them or which platform they came from —
   and an unsourced testimonial is indistinguishable from an invented one. It
   cannot ship looking like finished copy. */
.quotes__src{font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:var(--type-spec-track);color:var(--text-secondary);
  border:1px dashed var(--border-strong);border-radius:var(--radius-max);
  padding:5px 10px;display:inline-block}
.band--green .quotes__src,.on-green .quotes__src{color:var(--text-label-on-green);border-color:var(--rule-on-green)}
/* ---------- assurance strip ----------
   Three verified facts about how the shop works, sitting between the promise
   and the first browse band. Deliberately quieter than the sequence: an icon
   and one sentence, no heading, no box. A heading here would compete with the
   promise it sits directly beneath, and boxes would turn the page's first move
   after the hero into three cards. */
.assure-band{padding-block:var(--space-4);border-bottom:1px solid var(--rule-decorative)}
.assure{
  margin:0;padding:0;list-style:none;
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4);
}
.assure li{
  display:flex;align-items:flex-start;gap:var(--space-2);
  border-top:1px solid var(--rule-decorative);padding-top:var(--space-2);
}
.assure .i{color:var(--graphite);margin-top:2px}
.assure p{
  font-size:var(--type-caption-size);line-height:1.55;
  color:var(--text-secondary);max-width:32ch;
}
@media (max-width:820px){
  .assure{grid-template-columns:1fr;gap:0}
  .assure li{padding-block:var(--space-2)}
}
/* ---------- utilities used by the Shopify customer pages ----------
   The sign-in and account pages are Liquid-only — Shopify owns their markup, so
   they are not rendered in this preview. Their type utilities live here anyway,
   because this stylesheet is the single source and port-preview-css.py is what
   carries it across. Editing the theme CSS by hand would be overwritten on the
   next port. -------------------------------------------------------------- */
.h2{font-family:var(--font-display);font-size:var(--type-h2-size);line-height:var(--type-h2-lh);font-weight:500;margin:0}
.h3{font-family:var(--font-display);font-size:var(--type-h3-size);line-height:1.2;font-weight:500;margin:0}
.lede-sm{margin-top:var(--space-2);color:var(--text-secondary);max-width:52ch}
.line__link{display:block;margin-top:8px}
/* Scoped to the account page rather than changed on .line__name / .line__meta
   themselves. Those two are the cart's, where they sit inline by design; the
   account page stacks them. Widening the shared class to display:block would
   silently relayout the cart. */
.acct .line__name,.acct .line__meta,.acct .line__link{display:block}
/* .line__meta is NOT redefined here — the cart already owns it (see below), and a
   second definition earlier in the file is simply dead weight the later rule wins over. */
.line__date{font-family:var(--font-sans);font-size:15px;letter-spacing:var(--type-spec-track);color:var(--text-secondary);white-space:nowrap}
.form__errors{border-left:2px solid var(--surface-chrome);padding:var(--space-2) 0 var(--space-2) var(--space-3);margin-bottom:var(--space-3)}
.form__errors p{margin:0;color:var(--text-body)}
/* ---------- 360 drag-to-turn viewer (product page main image) ----------
   The spin is the primary media slot, not a thumbnail: a customer can take
   hold of the piece before reading a word. Frames are generated by
   scripts/build-spin-frames.py — 72 per piece, one constant crop across the
   whole rotation so nothing jitters as it turns. ---------------------- */
/* The viewer takes the SHAPE OF THE PIECE, not a square.
   These rings were photographed lying flat: a piece fills 73% of a frame's width
   and 35% of its height, so a square viewer is mostly empty sweep with a sliver
   floating in it. scripts/fit-spin-crop.py fits each piece a frame of its own —
   full width, and the height the piece and its reflection actually need — which
   takes height fill 35% -> 61% with no zoom and no resampling. The viewer is
   told that shape by the script once the first sheet has loaded; 1/1 is what it
   uses until then, because the honest default before anything is known is the
   square the frames are stored in. */
.spin{
  position:relative; aspect-ratio:var(--spin-aspect, 1/1); background:var(--white);
  border:1px solid var(--border); overflow:hidden;
  touch-action:none; cursor:grab; user-select:none;
}
.spin.is-dragging{cursor:grabbing}
.spin canvas{display:block;width:100%;height:100%}
.spin:focus-visible{outline:2px solid var(--focus-ring);outline-offset:3px}
.spin__badge{
  position:absolute;top:12px;left:12px;z-index:2;background:rgba(255,255,255,.92);
  border:1px solid var(--border);color:var(--text-body);
  font-size:11px;letter-spacing:.12em;text-transform:uppercase;font-weight:600;padding:5px 9px;
}
.spin__hint{
  position:absolute;left:50%;bottom:14px;transform:translateX(-50%);z-index:2;margin:0;
  background:rgba(27,26,23,.82);color:var(--white);font-size:13px;padding:7px 14px;
  white-space:nowrap;transition:opacity .4s ease;pointer-events:none;
}
.spin__hint[hidden]{display:block;opacity:0}
.spin__load{position:absolute;left:0;right:0;bottom:0;height:2px;background:transparent;z-index:3}
.spin__load i{display:block;height:100%;width:0;background:var(--surface-chrome);transition:width .12s linear}
.spin__load[hidden]{display:none}
.card__shot{display:block;aspect-ratio:1/1;background:var(--white);border:1px solid var(--border);overflow:hidden}
.card__shot img{display:block;width:100%;height:100%;object-fit:contain}
.pdp__thumbs .thumb{
  padding:0;border:1px solid var(--border);background:var(--white);
  aspect-ratio:1/1;overflow:hidden;cursor:pointer;
}
.pdp__thumbs .thumb img{display:block;width:100%;height:100%;object-fit:contain}
.pdp__thumbs .thumb[aria-pressed="true"]{border-color:var(--surface-chrome)}
.pdp__thumbs .thumb:focus-visible{outline:2px solid var(--focus-ring);outline-offset:2px}
@media (prefers-reduced-motion:reduce){.spin__hint{transition:none}}
/* ---------- category tiles (Helzberg-style flow) ---------- */
.tiles{
  display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-2);
  margin:0;padding:0;list-style:none;
}
@media (max-width:820px){.tiles{grid-template-columns:1fr}}
.tile{
  display:flex;flex-direction:column;text-decoration:none;color:inherit;
  transition:var(--transition-interactive);
}
.tile .slot{width:100%}
.tile:hover .slot{border-color:var(--graphite)}
.tile h3{font-size:var(--type-h3-size);line-height:1.2;margin:var(--space-2) 0 0}
.tile p{margin-top:var(--space-1);font-size:var(--type-caption-size);line-height:1.5;color:var(--text-secondary);max-width:34ch}
.tile__go{
  margin-top:var(--space-2);font-family:var(--font-sans);font-size:var(--type-button-size);
  letter-spacing:var(--type-button-track);text-transform:uppercase;font-weight:var(--weight-label);
  border-bottom:1px solid currentColor;padding-bottom:2px;align-self:flex-start;
}
.section-head{margin-bottom:var(--space-3)}
.section-head h2{font-size:var(--type-h2-size);line-height:var(--type-h2-lh)}
.section-head p{margin-top:var(--space-1);color:var(--text-secondary);max-width:56ch}
.band--green .section-head p{color:var(--text-on-green-muted)}
/* ---------- form ---------- */
.form{display:grid;gap:var(--space-3);max-width:560px}
.field{display:flex;flex-direction:column;gap:6px}
.field label{
  font-family:var(--font-sans);font-size:var(--type-caption-size);letter-spacing:.1em;
  text-transform:uppercase;font-weight:var(--weight-label);color:var(--text-secondary);
}
.field input,.field select,.field textarea{
  font-family:var(--font-sans);font-size:16px;color:var(--text-body);
  background:var(--white);border:1px solid var(--border-strong);border-radius:var(--radius-max);
  padding:0 var(--space-2);min-height:var(--control-height);
  transition:var(--transition-interactive);
}
.field textarea{padding-block:12px;min-height:132px;resize:vertical;line-height:1.6}
.field input:hover,.field select:hover,.field textarea:hover{border-color:var(--graphite)}
.field input::placeholder,.field textarea::placeholder{color:var(--grey-600)}
.field__hint{font-size:var(--type-caption-size);line-height:1.5;color:var(--text-secondary)}
.form__note{font-size:var(--type-caption-size);line-height:1.6;color:var(--text-secondary);max-width:52ch}
/* ---------- review slots ---------- */
.reviews{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-3);margin:0;padding:0;list-style:none}
@media (max-width:980px){.reviews{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:640px){.reviews{grid-template-columns:1fr}}
.review{border-top:1px solid var(--rule-decorative);padding-top:var(--space-3);display:flex;flex-direction:column;gap:var(--space-2)}
.band--green .review{border-top-color:var(--rule-on-green)}
/* ---------- form ---------- */


/* ---------- guided design intake (Tier A) ---------- */
.flow{max-width:920px}
.flow__bar{display:flex;gap:var(--space-1);margin-bottom:var(--space-4)}
.flow__bar span{flex:1;height:2px;background:var(--rule-on-green);position:relative}
.flow__bar span[data-done="true"]{background:var(--white)}
.flow__count{font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:var(--type-spec-track);color:var(--text-label-on-green);margin-bottom:var(--space-2)}
.step{display:none}
.step[data-active="true"]{display:block}
.step h2{font-size:var(--type-h2-size);line-height:var(--type-h2-lh);max-width:26ch}
.step__note{margin-top:var(--space-2);color:var(--text-on-green-muted);max-width:52ch}
.opts{list-style:none;margin:var(--space-4) 0 0;padding:0;
  display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:var(--space-2)}
@media (max-width:820px){.opts{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:480px){.opts{grid-template-columns:1fr}}
.opts--wide{grid-template-columns:repeat(3,minmax(0,1fr))}
@media (max-width:820px){.opts--wide{grid-template-columns:1fr}}
.opt{width:100%;text-align:left;cursor:pointer;background:transparent;
  border:1px solid var(--rule-on-green);border-radius:var(--radius-max);
  padding:var(--space-2);color:var(--text-on-green);
  display:flex;flex-direction:column;gap:6px;transition:var(--transition-interactive)}
.opt:hover{border-color:var(--white)}
.opt[aria-pressed="true"]{background:var(--white);color:var(--green-700);border-color:var(--white)}
.opt strong{font-family:var(--font-display);font-size:19px;font-weight:400;line-height:1.25}
.opt span{font-size:var(--type-caption-size);line-height:1.5;color:var(--text-on-green-muted)}
.opt[aria-pressed="true"] span{color:var(--grey-600)}
.flow__nav{display:flex;gap:var(--space-2);align-items:center;margin-top:var(--space-5);flex-wrap:wrap}
.flow__back{background:none;border:0;padding:0;cursor:pointer;color:var(--text-on-green-muted);
  font-family:var(--font-sans);font-size:14px;border-bottom:1px solid currentColor}
.flow__back:hover{color:var(--text-on-green)}
.flow .field label{color:var(--text-label-on-green)}
.flow .field input,.flow .field textarea,.flow .field select{background:transparent;color:var(--text-on-green);border-color:var(--rule-on-green)}
.flow .field input::placeholder{color:var(--text-label-on-green)}
.flow .field input:hover,.flow .field input:focus,.flow .field textarea:hover{border-color:var(--white)}
.flow .field__hint{color:var(--text-label-on-green)}
.flow__brief{border:1px solid var(--rule-on-green);border-radius:var(--radius-max);
  padding:var(--space-3);margin-top:var(--space-4)}
.flow__brief dt{font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:.1em;text-transform:uppercase;color:var(--text-label-on-green)}
.flow__brief dd{margin:2px 0 var(--space-2);font-family:var(--font-display);font-size:18px}
/* ---------- email capture ---------- */
.signup{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--space-5);align-items:start}
@media (max-width:820px){.signup{grid-template-columns:1fr;gap:var(--space-3)}}
.signup__form{display:flex;gap:var(--space-2);flex-wrap:wrap;margin-top:var(--space-3)}
.signup__form input{flex:1 1 220px;min-height:var(--control-height);padding:0 var(--space-2);
  font-family:var(--font-sans);font-size:16px;color:var(--text-on-green);background:transparent;
  border:1px solid var(--rule-on-green);border-radius:var(--radius-max);transition:var(--transition-interactive)}
.signup__form input::placeholder{color:var(--text-label-on-green)}
.signup__form input:hover,.signup__form input:focus{border-color:var(--white)}
/* ---------- header CTA ---------- */
.mast__cta{
  display:inline-flex;align-items:center;justify-content:center;min-height:42px;
  padding-inline:var(--space-2);background:var(--white);color:var(--green-700);
  border:1px solid var(--white);border-radius:var(--radius-max);text-decoration:none;
  font-family:var(--font-sans);font-size:var(--type-button-size);
  letter-spacing:var(--type-button-track);text-transform:uppercase;font-weight:var(--weight-label);
  white-space:nowrap;transition:var(--transition-interactive)}
.mast__cta:hover{background:var(--diamond-white);border-color:var(--diamond-white)}
@media (max-width:1100px){.mast__cta{display:none}}
@media (min-width:1101px){.nav__cta-fallback{display:none}}
/* ---------- cart line items ---------- */
.bag{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,360px);gap:var(--space-5);align-items:start}
@media (max-width:900px){.bag{grid-template-columns:1fr;gap:var(--space-4)}}
.line{display:grid;grid-template-columns:96px minmax(0,1fr) auto;gap:var(--space-3);
  padding-block:var(--space-3);border-top:1px solid var(--rule-decorative);align-items:start}
.line:last-of-type{border-bottom:1px solid var(--rule-decorative)}
@media (max-width:560px){.line{grid-template-columns:72px minmax(0,1fr);gap:var(--space-2)}}
.line .slot{width:96px;padding:var(--space-1)}
@media (max-width:560px){.line .slot{width:72px}}
.line__name{font-family:var(--font-display);font-size:19px;line-height:1.3}
.line__meta{font-size:var(--type-caption-size);color:var(--text-secondary);margin-top:2px}
.line__remove{margin-top:4px;font-size:var(--type-caption-size);color:var(--text-secondary);
  background:none;border:0;cursor:pointer;
  /* 16px tall before this. Padding rather than a bigger font, so the control
     stays visually secondary to the piece it belongs to while still being
     hittable with a thumb. The rule sits on a span so the underline hugs the
     word instead of the padding box. */
  padding:8px 0;display:inline-flex;align-items:center;min-height:32px}
.line__remove span{border-bottom:1px solid currentColor}
.line__remove:hover{color:var(--graphite)}
.line__price{font-family:var(--font-sans);font-size:15px;font-variant-numeric:tabular-nums;
  letter-spacing:var(--type-spec-track);white-space:nowrap}
.summary{border:1px solid var(--rule-decorative);border-radius:var(--radius-max);padding:var(--space-3)}
.summary__row{display:flex;justify-content:space-between;gap:var(--space-2);padding-block:8px;
  font-size:15px;font-variant-numeric:tabular-nums}
.summary__row--total{border-top:1px solid var(--rule-decorative);margin-top:var(--space-1);
  padding-top:var(--space-2);font-family:var(--font-display);font-size:19px}
.summary .btn{width:100%;margin-top:var(--space-3)}
.summary__note{margin-top:var(--space-2);font-size:var(--type-caption-size);line-height:1.6;color:var(--text-secondary)}
/* ---------- gallery pairs ---------- */
.pairs{list-style:none;margin:0;padding:0;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:var(--space-5) var(--space-4)}
@media (max-width:820px){.pairs{grid-template-columns:1fr}}
.pairs > li{display:flex;flex-direction:column;gap:var(--space-2)}
.pairs__imgs{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-1)}
.pairs__cap{font-size:var(--type-caption-size);line-height:1.6;color:var(--text-secondary)}
.pairs__cap strong{font-family:var(--font-display);font-size:17px;color:var(--text-body);display:block;margin-bottom:2px}
/* ---------- site index / grouped link columns ----------
   Three columns of grouped links. Also what the footer's Greenleaf column
   should eventually become — it is fifteen links deep and still growing. */
.sitelist{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-5) var(--space-4)}
@media (max-width:900px){.sitelist{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width:560px){.sitelist{grid-template-columns:1fr}}
.sitelist > li{border-top:1px solid var(--rule-decorative);padding-top:var(--space-2)}
.sitelist h2{font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:.12em;text-transform:uppercase;font-weight:var(--weight-label);
  color:var(--text-secondary)}
.sitelist ul{list-style:none;margin:var(--space-2) 0 0;padding:0;display:grid;gap:2px}
.sitelist ul a{font-family:var(--font-display);font-size:19px;color:var(--text-body);
  text-decoration:none;border-bottom:1px solid transparent;transition:var(--transition-interactive);
  display:inline-block;padding-block:7px}
.sitelist ul a:hover{border-bottom-color:var(--green-700)}
/* ---------- long-form document (legal, terms, privacy) ----------
   A different problem from a marketing band: one narrow column of continuous
   prose that has to stay readable for a thousand words, with an index beside
   it. Measure is capped at 68ch — legal text is the one place on the site
   where nobody will re-read a line they lost. */
.doc{display:grid;grid-template-columns:minmax(0,210px) minmax(0,68ch);gap:var(--space-5);
  align-items:start;justify-content:center}
@media (max-width:900px){.doc{grid-template-columns:1fr;gap:var(--space-3)}}
.doc__toc{position:sticky;top:calc(var(--space-5) + 96px)}
@media (max-width:900px){.doc__toc{position:static}}
.doc__toc h2{font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:.12em;text-transform:uppercase;font-weight:var(--weight-label);
  color:var(--text-secondary);margin-bottom:var(--space-2)}
.doc__toc ol{list-style:none;margin:0;padding:0;display:grid;gap:0;counter-reset:toc}
.doc__toc a{font-size:15px;color:var(--text-secondary);text-decoration:none;
  border-bottom:1px solid transparent;transition:var(--transition-interactive);
  display:inline-block;padding-block:6px}
.doc__toc a:hover{color:var(--text-body);border-bottom-color:var(--green-700)}
.doc__body{max-width:68ch}
.doc__body section + section{margin-top:var(--space-5);border-top:1px solid var(--rule-decorative);padding-top:var(--space-4)}
.doc__body h2{font-size:var(--type-h3-size);line-height:1.2}
.doc__body p{margin-top:var(--space-2);line-height:var(--type-body-lh);color:var(--text-body)}
.doc__body ul{margin:var(--space-2) 0 0;padding-left:1.1em;display:grid;gap:8px;color:var(--text-body)}
.doc__meta{font-family:var(--font-sans);font-size:var(--type-caption-size);letter-spacing:var(--type-spec-track);
  color:var(--text-secondary);margin-bottom:var(--space-3)}
/* A draft notice has to be impossible to mistake for body copy: the policies
   on this site are not lawyer-reviewed yet, and shipping unmarked boilerplate
   is exactly how the current live site ended up carrying a competitor's terms. */
.doc__draft{border:1px solid var(--border-strong);border-left:3px solid var(--green-700);
  padding:var(--space-3);margin-bottom:var(--space-4);background:var(--stone-50)}
.doc__draft b{display:block;font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:.1em;text-transform:uppercase;font-weight:var(--weight-label);
  color:var(--graphite);margin-bottom:6px}
.doc__draft p{margin:0;font-size:15px;line-height:1.65;color:var(--text-secondary)}
/* ---------- account ---------- */
.acct{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,320px);gap:var(--space-5);align-items:start}
@media (max-width:900px){.acct{grid-template-columns:1fr;gap:var(--space-4)}}
@media (max-width:560px){.acct .line > span:last-child{grid-column:2}}
.acct .line a.textlink{display:inline-block;padding-block:5px}
.acct__empty{border:1px solid var(--rule-decorative);border-radius:var(--radius-max);
  padding:var(--space-4);text-align:center;color:var(--text-secondary)}
.signin{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:var(--space-5);align-items:start}
@media (max-width:820px){.signin{grid-template-columns:1fr;gap:var(--space-4)}}
.signin__aside{border-top:1px solid var(--rule-decorative);padding-top:var(--space-3)}
/* ---------- cart line items ---------- */
/* ---------- pull quote ---------- */
.quote{margin:0}
.quote blockquote{
  margin:0;font-family:var(--font-display);font-style:italic;
  font-size:var(--type-h1-size);line-height:1.15;letter-spacing:var(--type-h1-track);
  max-width:30ch;
}
.quote figcaption{
  margin-top:var(--space-3);font-size:var(--type-caption-size);
  letter-spacing:var(--type-spec-track);font-weight:var(--weight-spec);
  color:var(--text-secondary);text-transform:uppercase;
}
/* ---------- timeline (about) ---------- */
.timeline{margin:0;display:flex;flex-direction:column}
.timeline__row{
  display:grid;grid-template-columns:78px minmax(0,1fr);gap:var(--space-3);
  padding-block:var(--space-4);border-top:1px solid var(--rule-decorative);
}
.timeline__row:last-child{border-bottom:1px solid var(--rule-decorative)}
@media (max-width:640px){.timeline__row{grid-template-columns:1fr;gap:var(--space-1);padding-block:var(--space-3)}}
.timeline__year{
  font-family:var(--font-sans);font-variant-numeric:tabular-nums;
  font-size:var(--type-caption-size);letter-spacing:var(--type-spec-track);
  font-weight:var(--weight-spec);color:var(--text-secondary);padding-top:.5em;
}
.timeline__what{font-family:var(--font-display);font-size:var(--type-h3-size);line-height:1.3;margin:0}
.timeline__note{margin-top:var(--space-1);color:var(--text-secondary);line-height:1.65;max-width:var(--measure)}
/* ---------- visit ---------- */
.visit{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4)}
@media (max-width:820px){.visit{grid-template-columns:1fr;gap:var(--space-3)}}
.visit dt{
  display:flex;align-items:center;gap:7px;
  font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:.1em;text-transform:uppercase;font-weight:var(--weight-label);
  color:var(--text-label-on-green);
}
/* the label is 12px caps, so the mark beside it has to come down to match —
   a 16px icon next to it reads as the larger of the two and takes the lead */
.visit dt .i{width:14px;height:14px}
.visit dd{margin:var(--space-1) 0 0;font-family:var(--font-display);font-size:var(--type-h3-size);line-height:1.35}
.visit dd a{color:inherit;text-decoration:none;border-bottom:1px solid var(--rule-on-green)}
.visit dd a:hover{border-bottom-color:var(--white)}
.visit__pending{
  display:block;margin-top:var(--space-1);font-family:var(--font-sans);
  font-size:var(--type-caption-size);letter-spacing:var(--type-spec-track);
  color:var(--text-label-on-green);
}
/* ---------- footer ---------- */
.foot{background:var(--surface-green-raised);color:var(--text-on-green)}
.foot__in{max-width:var(--max-page);margin:0 auto;padding:var(--space-6) var(--gutter-page) var(--space-4)}
.foot__top{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:var(--space-5)}
@media (max-width:900px){.foot__top{grid-template-columns:1fr;gap:var(--space-4)}}
.foot__mark img{height:72px;width:auto}
.foot__addr{margin-top:var(--space-3);font-style:normal;color:var(--text-on-green-muted);line-height:1.7}
.foot__cols{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:var(--space-4)}
@media (max-width:640px){.foot__cols{grid-template-columns:1fr;gap:var(--space-3)}}
.foot h3{
  font-family:var(--font-sans);font-size:var(--type-caption-size);
  letter-spacing:.1em;text-transform:uppercase;font-weight:var(--weight-label);
  color:var(--text-label-on-green);margin:0 0 var(--space-2);
}
.foot ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:var(--space-1)}
/* declared AFTER .foot ul on purpose — .foot ul sets margin:0 at equal
   specificity, and without the gap the phone number's underline reads as
   belonging to the link on the line below it. */
.foot .foot__direct{margin-top:var(--space-3)}
/* `display:inline-block` with vertical padding, not a bare inline link.
   MEASURED ON A PHONE, 10 Aug 2026: every footer link was 21px tall at 390px —
   24 standalone navigation links on every one of 49 pages, all of them FAILING
   WCAG 2.5.8 AA, which floors a target at 24x24. Inline links inherit no height,
   so line-height was the only thing giving them any. The nav is where a thumb
   goes when the header is scrolled away, so this is the worst place to have it.
   9px of vertical padding takes them to ~44px without moving the rows, because
   `.foot ul` already has an 11px gap absorbing it. */
.foot ul a{color:var(--text-on-green-muted);text-decoration:none;border-bottom:1px solid transparent;transition:var(--transition-interactive);
  display:inline-block;padding-block:9px}
/* The gap was 11px between 21px links; with 18px of padding now inside each one,
   the same visual rhythm needs less gap or the column grows by 24 lines. */
.foot ul{gap:0}
.foot ul a:hover{color:var(--text-on-green);border-bottom-color:var(--text-on-green)}
.foot__care{
  display:flex;align-items:flex-start;gap:10px;
  margin-top:var(--space-2);color:var(--text-on-green-muted);line-height:1.65;max-width:46ch;
}
.foot__care .i{margin-top:4px}
/* Helzberg's footer breathes because the rows are further apart than the type
   needs. Andrew flagged it as clean; this is the part worth taking.
   The 11px gap now lives INSIDE each link as padding — see `.foot ul a` above —
   so the rhythm is unchanged and every link is a 44px target instead of 21px. */
.foot ul{gap:0}
/* Follow us — two accounts only. See §28 of the theme changelog for why the
   other five that exist are not linked. */

.foot__social{display:flex;gap:var(--space-2);margin-top:var(--space-2)}
.foot__social a{
  display:inline-flex;align-items:center;justify-content:center;width:40px;height:40px;
  border:1px solid var(--rule-on-green);border-radius:var(--radius-max);
  color:var(--text-on-green-muted);transition:var(--transition-interactive);
}
.foot__social a:hover{color:var(--text-on-green);border-color:var(--text-on-green)}
.foot__social svg{width:18px;height:18px;fill:currentColor}
/* The house mark, closing every page.
   The maile was fully drawn, wired into the sprite and used on nothing — a
   brand element that exists only in the asset folder is not a brand element.
   The vine carries it on three pages; this carries it on all of them.
   No caption. The mark is a Hawaiian plant with a specific meaning, and a line
   of type explaining that meaning under our own logo would be both weak design
   and us narrating a culture at the reader. It is left to be recognised by the
   people who already know it. */
.foot__maile{
  margin:var(--space-5) 0 0;display:flex;justify-content:center;
  color:var(--maile-on-green);
}
/* 60, not 38. The display drawing is nine leaves on a curved stem and it starts
   smudging under about 28px; 38 rendered as a grey speck rather than a plant,
   which is the same as not having wired it in at all. */
.foot__maile svg{width:60px;height:60px}
.foot__maile + .foot__legal{margin-top:var(--space-3)}
/* the legal row: separate from the navigation columns, because these are the
   links people look for deliberately rather than browse into */
.foot__legal{
  margin-top:var(--space-5);padding-top:var(--space-3);border-top:1px solid var(--rule-on-green);
  display:flex;flex-wrap:wrap;align-items:center;justify-content:center;
  gap:10px var(--space-3);font-size:var(--type-caption-size);
}
.foot__legal a{color:var(--text-label-on-green);text-decoration:none;
  border-bottom:1px solid transparent;padding-block:5px;transition:var(--transition-interactive)}
.foot__legal a:hover{color:var(--text-on-green);border-bottom-color:var(--text-on-green)}
.foot__legal span{color:var(--rule-on-green);user-select:none}
@media (max-width:640px){.foot__legal span{display:none}
  .foot__legal{flex-direction:column;align-items:flex-start;gap:2px}}
.foot__base{
  margin-top:var(--space-3);
  display:flex;flex-wrap:wrap;gap:var(--space-2) var(--space-4);justify-content:space-between;
  font-size:var(--type-caption-size);color:var(--text-label-on-green);
}
/* ---------- page routing ---------- */
[hidden]{display:none !important}
/* ---- design-system/tokens/gems.css — inlined verbatim, single source ---- */
/* Greenleaf Diamonds — gemstone tints. Added 11 Aug 2026 on Andrew's call:
   *"if we are trying to describe colors of stones, dont you think it should look
   like this?"*, against a reference nav that tints one glyph per stone.

   THE STONE LIST COMES FROM THE STORE, NOT FROM THE REFERENCE.
   Andrew, same day: *"we only need what Greenleaf is actually planning to sell,
   and obv we are still loading in thousands of products."* So every stone below
   is one the catalogue actually holds, counted by `scripts/stone-census.py`
   against the live Admin API. The first cut of this file was drawn from a
   competitor's nav and carried **alexandrite and moissanite, of which Greenleaf
   has zero** — while missing tanzanite, the single largest coloured stone in the
   catalogue at 63 pieces. Re-run the census before editing this list; the
   catalogue was mid-load when it was written and the counts will move.

   Counts at 11 Aug 2026, 6,276 products loaded:
     tanzanite 63 · pearl 57 · sapphire 49 · morganite 23 · ruby 15 · zircon 15
     topaz 14 · yellow diamond 12 · emerald 11 · aquamarine 9 · larimar 9
     quartz 7 · opal 3 · amethyst 3 · turquoise 2 · tourmaline 2 · black diamond 1

   THIS IS NOT AN ACCENT PALETTE, AND THE ONE-GREEN RULE STILL STANDS.
   `colors.css` round 6 is unchanged: green is the ground, there is no accent,
   and there is no gold. These are *descriptors* — a sapphire mark is blue for
   the same reason the word "sapphire" is next to it — and they are legal in
   exactly one place: the mark in a stone row. They never paint a button, a link,
   a rule, a heading or a surface. `--gem-yellow` is a citrine standing in for a
   yellow diamond; it is NOT the withdrawn gold token returning by a side door.

   ONLY THE MARK IS TINTED — the label beside it stays `--text-body` (or
   `--text-on-green`). Tinting the text too turns a wayfinding row into a paint
   chart and puts type below its contrast floor.

   THE TINTS ARE DEEPER THAN REAL STONES, ON PURPOSE.
   A true aquamarine or morganite is a pastel, and a pastel hairline at 26px on
   `#F5F9FC` is invisible — the reference block has exactly this problem, its own
   moissanite and aquamarine barely surviving on white. Every tint is measured;
   `scripts/check-gem-contrast.py` re-derives the numbers from this file and
   fails if an edit drops one below 3:1.

   ELEVEN HUES FOR SEVENTEEN STONES, AND **FORM RESOLVES WHAT HUE CANNOT.**
   Five of these stones live in the blue-to-teal band and no hairline at 26px
   separates five blues. It does not have to, because the catalogue splits by
   *shape of stone* as well as colour, and the icon set now has three bodies:

     faceted    `start/gemstone`  transparent, cut — sapphire, tanzanite, ruby…
     cabochon   `start/cabochon`  opaque, domed, no facets — larimar, turquoise, opal
     pearl      `start/pearl`     a bead, not a cut stone at all — 57 pieces

   So larimar and topaz share `--gem-sky` but never look alike; turquoise and
   aquamarine share `--gem-teal` and never look alike; pearl and quartz share
   `--gem-grey` and never look alike. Where two stones share both hue and form —
   emerald and green tourmaline, zircon and aquamarine — the label does the
   precision work, which is the same division of labour as the rest of this file:
   the tint gives the family, the word gives the stone. */

:root{
  /* on --diamond-white #F5F9FC */
  --gem-blue:#1F4E8C;      /* sapphire */
  --gem-sky:#1D6FA8;       /* blue topaz, larimar */
  --gem-teal:#136F7C;      /* aquamarine, zircon, turquoise, opal */
  --gem-green:#1B6B4A;     /* emerald, green tourmaline */
  --gem-violet:#6B4EA8;    /* tanzanite */
  --gem-purple:#7040A0;    /* amethyst */
  --gem-red:#B01E36;       /* ruby */
  --gem-rose:#A8586A;      /* morganite, and opal's second flash */
  --gem-yellow:#8A6A0A;    /* yellow diamond */
  --gem-grey:#68757C;      /* pearl, clear and gold quartz */
  --gem-ink:#3C4448;       /* black diamond */
}

/* On --green-700 #2A4641 the same stone needs the light cut of its hue. The
   token NAMES do not change — only their values, rescoped — so every `.gem--x`
   rule below works on both grounds with no variant class of its own. */
.gems-on-green{
  --gem-blue:#93B9EC;
  --gem-sky:#8CC6EC;
  --gem-teal:#7ED2D8;
  --gem-green:#7FCBA4;
  --gem-violet:#BCA9EF;
  --gem-purple:#C4A6F0;
  --gem-red:#F2949C;
  --gem-rose:#F3B0B8;
  --gem-yellow:#EBD07E;
  --gem-grey:#C6D0D6;
  --gem-ink:#AEB8BD;
}

/* The stones the catalogue holds. A stone names a hue; it never invents one. */
.gem--tanzanite     {color:var(--gem-violet)}
.gem--pearl         {color:var(--gem-grey)}
.gem--sapphire      {color:var(--gem-blue)}
.gem--morganite     {color:var(--gem-rose)}
.gem--ruby          {color:var(--gem-red)}
.gem--zircon        {color:var(--gem-teal)}
.gem--topaz         {color:var(--gem-sky)}
.gem--yellow-diamond{color:var(--gem-yellow)}
.gem--emerald       {color:var(--gem-green)}
.gem--aquamarine    {color:var(--gem-teal)}
.gem--larimar       {color:var(--gem-sky)}
.gem--quartz        {color:var(--gem-grey)}
.gem--amethyst      {color:var(--gem-purple)}
.gem--turquoise     {color:var(--gem-teal)}
.gem--tourmaline    {color:var(--gem-green)}
.gem--black-diamond {color:var(--gem-ink)}

/* Opal is the one stone whose whole point is that it is two colours at once, so
   it is the one that uses the bi-colour body: teal through the dome, a rose
   flash beneath. `--gem-alt` paints the second half.

   A custom property is the only thing that can paint half a mark once these
   live in the sprite — a CSS *selector* cannot reach a path inside the shadow
   tree a `<use>` clone creates, but an inherited custom property crosses it.
   Same mechanism that makes the draw-on work, used the other way round. */
.gem--opal{color:var(--gem-teal); --gem-alt:var(--gem-rose)}

/* ---------------------------------------------------------------------------
   METALS — added 11 Aug 2026, same day and the same argument as the stones.

   ⚠ READ THIS BEFORE ASSUMING GOLD IS BACK. `colors.css` round 6 withdrew gold
   FULLY — "not adjusted", no gold token, and that still stands. `--metal-yellow`
   and `--metal-rose` are not it. They are descriptors: a yellow-gold swatch is
   yellow for the same reason the words "14k Yellow Gold" sit next to it, and
   they are legal ONLY on a metal swatch. They never paint a button, a link, a
   rule, a heading, a border or a surface, and there is no --metal-* value
   anywhere near the brand palette.

   The store spells nine metals across 14k and 18k — white, yellow, rose and two
   tone. Karat is not a colour, so it is not a token; the label carries it.
   --------------------------------------------------------------------------- */
:root{
  --metal-white:#5F6B72;   /* white gold / platinum */
  --metal-yellow:#8A6A0A;
  --metal-rose:#A4635B;
}
.gems-on-green{
  --metal-white:#C6D0D6;
  --metal-yellow:#EBD07E;
  --metal-rose:#F0B3A8;
}
.metal--white  {color:var(--metal-white)}
.metal--yellow {color:var(--metal-yellow)}
.metal--rose   {color:var(--metal-rose)}
/* Two-tone is white and yellow, which is what the store's "Two Tone Gold" tag
   means on every piece carrying it. */
.metal--two-tone{color:var(--metal-white); --metal-alt:var(--metal-yellow)}
