/* Platform cart + contact styles. Every class is 'os-'-prefixed so it can never collide with the
   page's own CSS. Generated by services/design/freeform.js — do not edit by hand. */

*,*::before,*::after{box-sizing:border-box}
/* Chrome for Android inflates text on pages it decides are not mobile-friendly. That is the most
   common cause of an unreproducible "the menu text is too big" report, so pin it. */
html{-webkit-text-size-adjust:100%; text-size-adjust:100%; scroll-behavior:smooth}
@media (prefers-reduced-motion: reduce){ html{scroll-behavior:auto} }
body{
  margin:0; background:var(--surface); color:var(--ink);
  font-family:var(--font-body); font-size:var(--text-body); line-height:var(--leading-body);
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
}
img,picture,video,svg{max-width:100%; display:block}
img{height:auto}
h1,h2,h3,h4{font-family:var(--font-display); line-height:var(--leading-heading); margin:0 0 var(--space-xs); font-weight:600; letter-spacing:-0.015em; text-wrap:balance}
h1{font-size:var(--text-h1); line-height:var(--leading-tight)}
h2{font-size:var(--text-h2)}
h3{font-size:var(--text-h3)}
p{margin:0 0 var(--space-s); max-width:var(--measure)}
ul,ol{margin:0 0 var(--space-s); padding-left:var(--space-s)}
li{margin-bottom:var(--space-3xs)}
a{color:var(--accent-ink); text-underline-offset:3px}
/* Vercel guidelines: no double-tap zoom on controls, tap highlight follows the design, and the drawer
   never lets a scroll chain to the page behind it. */
a,button,[role=button],summary,label{touch-action:manipulation; -webkit-tap-highlight-color:color-mix(in oklab, var(--accent) 22%, transparent)}
a:hover{color:var(--accent)}
:focus-visible{outline:var(--border-strong) solid var(--focus); outline-offset:3px; border-radius:var(--radius-s)}
strong,b{font-weight:600; color:var(--ink)}
hr{border:0; border-top:var(--border-hair) solid var(--border-soft); margin:var(--space-m) 0}


.os-container{width:100%; max-width:var(--container); margin-inline:auto; padding-inline:var(--gutter-wide)}
.os-container--text{max-width:var(--container-text)}
/* PROSE LISTS BREATHE, AND AN ITEM'S TITLE IS NOT BURIED IN ITS SENTENCE.
   The global li rule above is a 4px reset meant for compact UI lists. In a content page's prose it put
   86px-tall list items 4px apart — measurably one grey block (live harvest page: five items, 4px gaps)
   — and every item ran its title into its description as "Picked this morning — Our cutters are…".
   Scoped here rather than changing a rule every other list on every site depends on. */
.os-container--text li{margin-bottom:var(--space-xs)}
.os-container--text li b{display:block}
.os-container--text li .os-li__text{display:block; margin-top:4px}
.os-container--text li .os-li__price{display:block; margin-top:4px; font-weight:600}
.os-bleed{width:100%}
.os-band{padding-block:var(--space-l)}
.os-band--tight{padding-block:var(--space-m)}
.os-band--deep{padding-block:var(--space-xl)}
.os-band--sunken{background:var(--surface-sunken)}
.os-band--invert{background:var(--ink); color:var(--surface)}
.os-band--invert h2,.os-band--invert h3,.os-band--invert strong,.os-band--invert b{color:var(--surface)}
.os-band--invert a{color:var(--surface)}
.os-band--accent{background:var(--accent); color:var(--on-accent)}
/* INHERIT, DO NOT RESTATE. The band's own colour is set beside its background — by the base rule above
   and by whichever visual style overrides it — so repeating it here broke as soon as a style changed the
   background: on a dark page the editorial style paints this band with the (light) ink token, and a
   hardcoded on-accent heading left WHITE words on a near-white band. */
.os-band--accent h2,.os-band--accent h3{color:inherit}
.os-band--accent a:not(.os-btn){color:inherit}
.os-rule{border-top:var(--border-hair) solid var(--border-soft)}

/* 12-col grid with explicit occupancy — asymmetry is required, never incidental */
/* A <picture> wrapper is not a box. Without this it becomes an inline element between the container
   and the <img>, and every rule written as ".os-card__media img" or ".os-hero__panel > img" quietly stops
   matching the layout it was written for. display:contents removes it from the box tree entirely. */
picture{display:contents}
.os-grid{display:grid; gap:var(--space-m); grid-template-columns:repeat(12,1fr)}
.os-col-1-6{grid-column:1/7}.os-col-7-12{grid-column:7/13}
.os-col-1-5{grid-column:1/6}.os-col-6-12{grid-column:6/13}
.os-col-1-7{grid-column:1/8}.os-col-8-12{grid-column:8/13}
.os-col-full{grid-column:1/13}
/* A COLUMN CLASS MUST EXIST, AND MUST COME AFTER .os-col-full.
   .os-col-full was declared last, and it sets the same property at the same specificity as every other
   column class — so it won at every width, and every "stack on a phone, half-width on a desktop"
   pairing in the markup stayed full width forever. The CTA band's Find us / Reach us columns stacked
   into the left half of the band with the right half empty, on every site this engine built.
   .os-col-1-2 was also used in the markup with no rule anywhere: an invented class name matching nothing,
   which is the bug class this file keeps being bitten by (tests/design-css-classes.os-test.os-mjs now checks
   that no column class is used in markup without a rule here). "span 6" lets the grid auto-place the
   two halves side by side. */
@media (min-width:52rem){
  .os-col-1-2{grid-column:span 6}
}
.os-stack{display:flex; flex-direction:column; gap:var(--space-s)}
.os-cluster{display:flex; flex-wrap:wrap; gap:var(--space-2xs) var(--space-s); align-items:center}
.os-split{display:grid; gap:var(--space-m); grid-template-columns:1fr}
@media (min-width:52rem){
  .os-split{grid-template-columns:1fr 1fr; align-items:center}
  .os-split--7-5{grid-template-columns:7fr 5fr}
  .os-split--5-7{grid-template-columns:5fr 7fr}
}
.os-eyebrow{
  font-family:var(--font-body); font-size:var(--text-small); font-weight:600;
  text-transform:uppercase; letter-spacing:.14em; color:var(--accent-ink); margin:0 0 var(--space-2xs)
}
.os-band--accent .os-eyebrow,.os-band--invert .os-eyebrow{color:inherit; opacity:.85}
.os-lede{font-size:var(--text-lede); max-width:46ch; color:var(--ink-muted)}
.os-band--invert .os-lede,.os-band--accent .os-lede{color:inherit; opacity:.9}
.os-muted{color:var(--ink-muted)}
.os-band--invert .os-muted,.os-band--accent .os-muted,.os-hero--split .os-muted{color:color-mix(in oklab, var(--surface) 82%, transparent)}
.os-text--center{text-align:center}
.os-measure{max-width:var(--measure)}


.os-foot{padding-block:var(--space-m) var(--space-s); border-top:var(--border-hair) solid var(--border-line); background:var(--surface-sunken, var(--surface))}
.os-foot .os-container{display:grid; gap:var(--space-s)}
.os-foot__id{display:flex; align-items:center; gap:var(--space-s); flex-wrap:wrap}
.os-foot__name{font-family:var(--font-display); font-size:var(--step-1); letter-spacing:-0.01em}
.os-foot__tag{margin:0; color:var(--ink-muted); font-size:var(--step--1); flex:1 1 16rem}
.os-foot__grid{display:grid; gap:var(--space-s); grid-template-columns:1fr}
@media (min-width:40rem){.os-foot__grid{grid-template-columns:repeat(3,1fr)}}
.os-foot__col strong{display:block; margin-bottom:var(--space-3xs); font-size:var(--step--1); letter-spacing:0.04em; text-transform:uppercase; color:var(--ink-muted)}
.os-foot__col p{margin:0; font-size:var(--step--1)}
/* Standalone contact links, stacked one per line — not links inside a sentence, so WCAG 2.5.8's
   inline exception does not cover them. At 2px padding they measured 20px tall on the live site. */
.os-foot__col a{display:inline-block; padding-block:8px}
/* Same reasoning for the CTA band's contact column: one method per line, each its own tap target. */
.os-contactline{margin:0}
.os-contactline a{display:inline-block; padding-block:8px}
.os-foot__map{margin-top:var(--space-2xs)}
.os-foot__map iframe{display:block; width:100%; height:190px; border:var(--border-hair) solid var(--border-line); border-radius:var(--radius-m); filter:saturate(0.92)}
@media (min-width:48rem){.os-foot__map iframe{height:220px}}
.os-foot__legal{display:flex; justify-content:space-between; gap:var(--space-s); flex-wrap:wrap; padding-top:var(--space-2xs); border-top:var(--border-hair) solid var(--border-line); font-size:var(--step--1); color:var(--ink-muted)}

/* EVERY VISUAL LOOK PAINTS ITS OWN DARK FOOTER BACKGROUND (the style layer was deleted with the
 * templates on 25 Sep 2026, so the footer's contrast is the tokens' job): the footer must never rely
   words are pinned light. Using var(--surface) made them follow the PAGE surface instead — on a dark page
   that is near-black text on a black footer. */
.os-foot{background:var(--ink); color:#f7f6f6; padding-block:var(--space-m) var(--space-s)}
/* A column LABEL is not a heading: a bare <strong> here inherited display type, so VISIT / REACH US /
   FOLLOW rendered bigger than the site's own section headings and the footer looked like a poster. */
.os-foot h3{color:#f7f6f6; font-size:var(--text-h3)}
/* The global strong/b colour rule beat the footer's light colour, so the wordmark rendered
   dark-green on dark-green — measured 1.00:1, invisible (owner, 14 Sep 2026). No backticks in
   this file: it is a CSS template literal and a stray one breaks the whole module. */
.os-foot strong,.os-foot b,.os-foot__name{color:#f7f6f6}
.os-foot__tag{color:color-mix(in oklab, #f7f6f6 82%, transparent)}
.os-foot__col strong{color:color-mix(in oklab, #f7f6f6 62%, transparent); font-size:var(--step--1); letter-spacing:0.06em; text-transform:uppercase}
.os-foot a:not(.os-btn){color:color-mix(in oklab, #f7f6f6 88%, transparent); text-decoration:none; display:inline-flex; align-items:center; min-height:32px}
/* …but a social link is a word in a row, not a 32px tap row: ten of them stacked made the footer tall. */
.os-foot__socials{display:flex; flex-wrap:wrap; gap:var(--space-2xs) var(--space-xs); margin:0}
.os-foot__socials a{min-height:28px; padding-block:8px; display:inline-flex; align-items:center}
.os-foot a svg, .os-nav-drawer__panel a svg{width:var(--icon-s); height:var(--icon-s); flex:0 0 auto}
.os-foot a{gap:var(--space-3xs)}
.os-foot__grid{display:grid; gap:var(--space-s); grid-template-columns:1fr}
@media (min-width:52rem){.os-foot__grid{grid-template-columns:1.3fr 1fr 1.3fr}}
.os-foot__legal{display:flex; flex-wrap:wrap; gap:var(--space-s); justify-content:space-between; font-size:var(--text-small); margin-top:var(--space-s); padding-top:var(--space-2xs); border-top:var(--border-hair) solid color-mix(in oklab, #f7f6f6 22%, transparent); color:color-mix(in oklab, #f7f6f6 78%, transparent)}
/* PHOTOGRAPHY CREDITS (25 Sep 2026). A licence is a promise the site has to keep VISIBLE: the reference
   site puts its credits in the page, not in a file nobody opens. Collapsed by default so it does not
   push the contact details out of the first screen, and never rendered at all when there is nothing
   true to say (services/design/photography.os-js decides; the footer only prints it).
   COLOUR: 78% of the footer's own light ink on the dark footer measures ~10:1; the by-line at 88% is
   the same value already used for footer links, which the contrast tests cover. */
.os-credits{margin-top:var(--space-s); padding-top:var(--space-2xs); border-top:var(--border-hair) solid color-mix(in oklab, #f7f6f6 22%, transparent)}
.os-credits summary{cursor:pointer; padding-block:6px; font-size:var(--step--1); letter-spacing:0.05em; text-transform:uppercase; color:color-mix(in oklab, #f7f6f6 78%, transparent)}
.os-credits__list{margin:var(--space-2xs) 0 0; padding-left:1.1em; display:grid; gap:5px; list-style:decimal}
@media (min-width:48rem){.os-credits__list{grid-template-columns:repeat(2,minmax(0,1fr)); gap:5px var(--space-s)}}
.os-credits li{font-size:var(--text-small); line-height:1.5; color:color-mix(in oklab, #f7f6f6 78%, transparent)}
.os-foot .os-credits__by{color:color-mix(in oklab, #f7f6f6 88%, transparent)}
/* A source link inside a sentence is a WORD, not a 32px tap row: the footer's link rule would stack
   every credit line out of shape. WCAG 2.5.8 exempts links inline in a sentence. */
.os-foot .os-credits a{display:inline; min-height:0; color:inherit; text-decoration:underline}
.os-credits__note{margin:var(--space-2xs) 0 0; max-width:80ch; font-size:var(--text-small); line-height:1.55; color:color-mix(in oklab, #f7f6f6 78%, transparent)}
.os-credits__note--ai{color:color-mix(in oklab, #f7f6f6 88%, transparent)}
/* Visually hidden but available to assistive tech — used for live-region announcements. */
.os-sr-only{position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0}
/* Defensive floor: an icon is NEVER allowed to size itself from a missing token or a cached
   stylesheet. This rule is intentionally blunt. */
.os-btn svg, .os-nav-drawer summary svg, .os-tile__label svg, .os-foot svg, .os-nav-drawer__panel svg{max-width:1.5rem; max-height:1.5rem}
.os-fact svg{max-width:1.5rem; max-height:1.5rem}
.os-skip{position:absolute; left:var(--space-s); top:-100px; background:var(--accent); color:var(--on-accent); padding:var(--space-2xs) var(--space-xs); border-radius:var(--radius-s); z-index:100}
.os-skip:focus{top:var(--space-2xs)}


/* auto-fit, so a row with ONE or TWO offerings fills the width instead of leaving dead space —
   a one-card services row at 32% of a 940px row read as unfinished (owner, 14 Sep 2026). */
.os-products{display:grid; gap:var(--space-xs); grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
.os-card{display:flex; flex-direction:column; background:var(--surface-raised); border:var(--border-hair) solid var(--border-soft); border-radius:var(--radius-l); overflow:hidden; box-shadow:var(--elev-1); transition:transform var(--dur-base) var(--ease-out), box-shadow var(--dur-base) var(--ease-out)}
.os-card:hover{transform:translateY(-3px); box-shadow:var(--elev-2)}
.os-card__media{aspect-ratio:1/1; background:var(--surface-sunken)}
/* A product we know only from the shop's photos has no picture of its own. A tinted plate with the
   shop's motif fills the space honestly — it is decoration, never a borrowed product photo. */
/* The illustrated panel fills its media box: no pale box with a floating glyph. */
.os-card__media--art{aspect-ratio:4/5; overflow:hidden; background:var(--surface-sunken)}
.os-card__media--art svg{display:block; width:100%; height:100%}
.os-card__media--symbol{display:grid; place-items:center; background:var(--accent-soft); color:var(--accent-ink)}
.os-card__media--symbol svg{width:var(--icon-l, 1.5rem); height:var(--icon-l, 1.5rem); opacity:.9}
/* Alternate the plate so a row of photo-less products is a rhythm, not seven clones. */
.os-card__media--alt{background:var(--surface-sunken); color:var(--accent)}
/* A PHOTO-LESS OFFERING gets a designed monogram plate, not a pale box with a small grey glyph — that
   read as a broken image and the owner caught it on a phone. The initial is decorative (aria-hidden);
   the card title carries the real name. */
.os-card__media--plate{display:grid; place-items:center; aspect-ratio:4/5; background-color:color-mix(in oklab, var(--accent) 8%, var(--surface-raised)); background-image:var(--pattern-plate, none), radial-gradient(120% 120% at 20% 0%, color-mix(in oklab, var(--accent) 22%, transparent), transparent 70%); background-size:var(--pattern-size, 6.5rem), cover; background-repeat:repeat, no-repeat}
.os-card__initial{font-family:var(--font-display); font-size:clamp(2.5rem,7vw,3.375rem); font-weight:700; letter-spacing:-.04em; line-height:1; color:color-mix(in oklab, var(--accent) 70%, var(--ink)); text-transform:uppercase}
.os-card__media img{width:100%; height:100%; object-fit:cover}
.os-card__body{display:flex; flex-direction:column; gap:var(--space-3xs); padding:var(--space-s); flex:1}
.os-card__title{font-family:var(--font-display); font-size:var(--text-h3); margin:0}
.os-card__text{font-size:var(--text-small); color:var(--ink-muted); margin:0}
.os-card__price{font-family:var(--font-body); font-weight:600; color:var(--ink); margin:0}
.os-card__foot{margin-top:auto; padding-top:var(--space-2xs); display:flex; gap:var(--space-2xs); align-items:center}
/* A product we know only from the shop's photos has no picture of its own — it renders as a text card
   rather than borrowing another product's image. */
.os-card--text{border-left:var(--border-strong) solid var(--accent)}
.os-card--text .os-card__body{justify-content:space-between}
.os-badge{display:inline-block; padding:var(--space-3xs) var(--space-2xs); border-radius:var(--radius-pill); background:var(--accent-soft); color:var(--accent-ink); font-size:var(--text-small); font-weight:600}
.os-badge--muted{background:var(--surface-sunken); color:var(--ink-muted)}

/* cart (JS-enhanced; the plain list still reads without it) */
.os-cart-bar{position:fixed; left:var(--gutter-wide); right:var(--gutter-wide); bottom:var(--space-xs); z-index:60; display:flex; gap:var(--space-xs); align-items:center; justify-content:space-between; padding:var(--space-xs) var(--space-s); background:var(--ink); color:var(--surface); border-radius:var(--radius-pill); box-shadow:var(--elev-3)}
.os-cart-bar[hidden]{display:none}
/* SHOP TOOLS — search, category filter, detail modal, shared toast (owner, 19 Sep 2026). */
[data-shop-item][hidden]{display:none}
.os-shop-tools{display:flex; flex-wrap:wrap; gap:var(--space-2xs); align-items:center; justify-content:flex-end; margin:var(--space-2xs) 0 var(--space-s)}
.os-shop-search{flex:0 1 15rem}
.os-shop-search input{width:100%; padding:var(--space-3xs) var(--space-2xs); border:var(--border-hair) solid var(--border-line); border-radius:var(--radius-pill); background:var(--surface); color:var(--ink); font-size:var(--text-body)}
.os-shop-chips{display:flex; flex-wrap:wrap; gap:var(--space-3xs)}
.os-shop-chip{border:var(--border-hair) solid var(--border-line); background:var(--surface); color:var(--ink); border-radius:var(--radius-pill); padding:var(--space-3xs) var(--space-2xs); font:600 var(--text-small)/1.2 inherit; cursor:pointer}
.os-shop-chip.os-is-on{background:var(--accent); color:var(--on-accent); border-color:var(--accent)}
.os-shop-chip:focus-visible,.os-card__open:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
.os-shop-count{margin:0; font-size:var(--text-small)}
.os-card__open{background:none; border:0; padding:var(--space-3xs) 0; min-height:24px; font:inherit; color:inherit; text-align:left; cursor:pointer}
.os-card__open:hover{text-decoration:underline}
.os-modal{border:0; padding:0; background:transparent; width:100%; max-width:min(38rem, 92vw)}
.os-modal::backdrop{background:color-mix(in oklab, var(--ink) 62%, transparent)}
.os-modal__card{position:relative; background:var(--surface-raised); color:var(--ink); border-radius:var(--radius-l); box-shadow:var(--elev-3); padding:var(--space-s); max-height:86vh; overflow:auto}
.os-modal__x{position:absolute; top:var(--space-2xs); right:var(--space-2xs); border:0; background:var(--surface-sunken); color:var(--ink); border-radius:var(--radius-pill); width:44px; height:44px; font-size:var(--text-h3); line-height:1; cursor:pointer}
.os-modal__media{margin-bottom:var(--space-2xs)}
.os-modal__media img{display:block; width:100%; height:auto; border-radius:var(--radius-m)}
.os-modal__title{margin:0 0 var(--space-3xs)}
.os-modal__price{margin:0 0 var(--space-2xs); font-weight:700}
.os-modal__text{margin:0 0 var(--space-s); color:var(--ink-muted)}
.os-toast{position:fixed; left:50%; bottom:var(--space-s); transform:translate(-50%, 12px); z-index:90; max-width:92vw; text-align:center; padding:var(--space-2xs) var(--space-s); border-radius:var(--radius-pill); background:var(--ink); color:var(--surface); box-shadow:var(--elev-3); font-size:var(--text-small); opacity:0; pointer-events:none; transition:opacity .22s ease, transform .22s ease}
.os-toast.os-is-on{opacity:1; transform:translate(-50%, 0)}
.os-toast--bad{background:var(--danger, #b3261e); color:#fff}
.os-cart-bar strong{color:var(--surface)}
.os-cart-bar .os-btn,.os-cart-bar a,.os-cart-bar .os-btn span{color:var(--surface); border-color:currentColor}
.os-cart-panel{border:var(--border-hair) solid var(--border-soft); border-radius:var(--radius-l); padding:var(--space-s); background:var(--surface-raised); box-shadow:var(--elev-1)}
.os-cart-lines{list-style:none; margin:0; padding:0}
.os-cart-lines li{display:grid; grid-template-columns:1fr auto auto; gap:var(--space-xs); align-items:center; padding-block:var(--space-2xs); border-bottom:var(--border-hair) solid var(--border-soft)}
.os-qty{display:inline-flex; align-items:center; gap:var(--space-2xs)}
.os-qty button{min-width:32px; min-height:32px; border-radius:var(--radius-s); border:var(--border-hair) solid var(--border-line); background:var(--surface); color:var(--ink); font-size:var(--text-body); cursor:pointer}
.os-cart-total{display:flex; justify-content:space-between; gap:var(--space-s); padding-top:var(--space-xs); font-weight:600}
.os-cart-buyer{display:grid; gap:var(--space-2xs); grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); margin-top:var(--space-s)}
.os-cart-buyer label{display:block; font-size:var(--text-small); color:var(--ink-muted); font-weight:600}
.os-cart-buyer input{display:block; width:100%; margin-top:var(--space-3xs,4px); padding:12px; font:inherit; font-size:var(--text-body); color:var(--ink); background:var(--surface); border:var(--border-hair) solid var(--border-line); border-radius:var(--radius-s)}


.os-btn{
  display:inline-flex; align-items:center; justify-content:center; gap:var(--space-2xs);
  min-height:44px; min-width:44px; padding:var(--space-xs) var(--space-s);
  background:var(--accent); color:var(--on-accent); border:var(--border-hair) solid transparent;
  border-radius:var(--radius-pill); font-family:var(--font-body); font-size:var(--text-body);
  font-weight:600; text-decoration:none; cursor:pointer;
  transition:transform var(--dur-fast) var(--ease-out), box-shadow var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.os-btn:hover{color:var(--on-accent); transform:translateY(-1px); box-shadow:var(--elev-2)}
.os-btn:active{transform:translateY(0)}
.os-btn--ghost{background:transparent; color:var(--ink); border-color:var(--border-line)}
.os-btn--ghost:hover{background:var(--surface-sunken); color:var(--ink)}
/* BUTTONS ON AN INVERTED BAND ARE PAIRED, FILL AND INK TOGETHER.
   Two independent rules used to decide this: the band gave every link its own ink, and a visual style gave
   .os-btn--ghost a solid white fill with the style's ink. On the farm site the two met as a WHITE button with
   WHITE words — the ghost's colour was overridden to the band's on-accent while its fill stayed the
   style's white. Setting both here means one cannot contradict the other. */
.os-band--invert .os-btn,.os-band--accent .os-btn{background:var(--on-accent); color:var(--accent); border-color:transparent}
.os-band--invert .os-btn--ghost,.os-band--accent .os-btn--ghost{background:transparent; color:var(--on-accent); border-color:currentColor}
.os-band--invert .os-btn--ghost:hover,.os-band--accent .os-btn--ghost:hover{background:color-mix(in oklab, var(--on-accent) 14%, transparent); color:var(--on-accent)}
.os-btn--quiet{background:var(--accent-soft); color:var(--accent-ink); border-color:transparent}
/* Icons sit WITH their label, not above it: 1em, so the icon reads as part of the words. */
.os-btn svg{width:var(--icon-s, 1rem); height:var(--icon-s, 1rem); flex:0 0 auto}
/* Icon-only control: square and always paired with a descriptive aria-label — never a wordless mystery. */
.os-btn--icon{min-width:44px; min-height:44px; padding:var(--space-2xs); justify-content:center}
/* Loading keeps the label and adds a spinner (Vercel guidelines: loading buttons keep their label). */
[data-loading]{cursor:progress}
[data-loading] svg{display:none}
[data-loading]::after{content:""; width:1em; height:1em; flex:0 0 auto; border:var(--border-strong) solid currentColor; border-right-color:transparent; border-radius:var(--radius-pill)}
@media (prefers-reduced-motion: no-preference){
  [data-loading]::after{animation:spin .7s linear infinite}
  @keyframes spin{to{transform:rotate(1turn)}}
}
.os-btn--block{width:100%}
/* The house tap standard is 44px (see .os-btn). At 36px this variant quietly broke it, and the measured
   audit flagged every "Enquire" button on the live site — a thumb does not know a button is meant to
   be a quiet one. The type stays small; only the touchable area grows. */
.os-btn--sm{min-height:44px; padding:var(--space-3xs) var(--space-xs); font-size:var(--text-small)}
@media (prefers-reduced-motion: no-preference){
  .os-btn:hover{transform:translateY(-2px)}
}


.os-cform{display:grid; gap:var(--space-s); margin-top:var(--space-s)}
.os-cform__grid{display:grid; gap:var(--space-s); grid-template-columns:1fr}
@media (min-width:40rem){.os-cform__grid{grid-template-columns:repeat(2,1fr)}.os-cform__row--wide{grid-column:1/-1}}
.os-cform__row{margin:0}
.os-cform__row label{display:block; margin-bottom:var(--space-3xs); font-size:var(--step--1); color:var(--ink-muted)}
.os-cform input,.os-cform textarea{width:100%; font:inherit; padding:var(--space-2xs) var(--space-xs); color:inherit; background:var(--surface-raised); border:var(--border-hair) solid var(--border-line); border-radius:var(--radius-s)}
.os-cform input:focus,.os-cform textarea:focus{outline:2px solid var(--accent); outline-offset:2px}
.os-cform__hp{position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden}
.os-cform__actions{display:flex; align-items:center; gap:var(--space-s); margin:0; flex-wrap:wrap}
.os-cform__status,.os-cform__note{font-size:var(--step--1); margin:0}
.os-cform__check{margin:0}
.os-cform__check input{max-width:6rem}
