/* =============================================================
   HEWN — Refined direction (v2)
   Photography-led · fluid dark→light gradation · monochrome chrome
   Type: Bricolage Grotesque (light) + Instrument Serif (accents)
   One board, offered in a few timbers. Simplify, simplify.
   ============================================================= */

/* ---- self-hosted webfont (woff2, latin) — no third-party round-trip to Google ----
   files live in assets/fonts/; paths are relative to this stylesheet (assets/). */
@font-face {
  font-family: "Schibsted Grotesk"; font-style: normal; font-weight: 400; font-display: swap;
  src: url("fonts/schibsted-grotesk-v7-latin-regular.woff2") format("woff2");
}
@font-face {
  font-family: "Schibsted Grotesk"; font-style: normal; font-weight: 500; font-display: swap;
  src: url("fonts/schibsted-grotesk-v7-latin-500.woff2") format("woff2");
}
@font-face {
  font-family: "Schibsted Grotesk"; font-style: normal; font-weight: 600; font-display: swap;
  src: url("fonts/schibsted-grotesk-v7-latin-600.woff2") format("woff2");
}
@font-face {
  font-family: "Schibsted Grotesk"; font-style: normal; font-weight: 700; font-display: swap;
  src: url("fonts/schibsted-grotesk-v7-latin-700.woff2") format("woff2");
}

@property --w1 { syntax: "<color>"; inherits: false; initial-value: #6b4a31; }
@property --w2 { syntax: "<color>"; inherits: false; initial-value: #38230f; }

:root {
  /* Schibsted Grotesk — a more refined grotesque than Inter, still clean/editorial */
  --sans: "Schibsted Grotesk", system-ui, -apple-system, "Segoe UI", sans-serif;
  --serif: "Instrument Serif", Georgia, serif;
  --geo: var(--sans);

  /* monochrome palette — warm-neutral stone & warm ink, no brass in the UI */
  --ink: #15110c;
  --ink-soft: #1c1711;
  --paper: #e9e4d8;
  --paper-2: #f2ede3;

  --fs-eyebrow: 0.74rem;
  --fs-body: clamp(1rem, 0.97rem + 0.15vw, 1.08rem);
  --fs-lead: clamp(1.1rem, 1rem + 0.5vw, 1.35rem);
  --fs-h2: clamp(1.9rem, 1.3rem + 2.6vw, 3.6rem);
  --fs-display: clamp(2.8rem, 1.3rem + 5vw, 5.6rem);

  --maxw: 1280px;
  --gutter: clamp(1.25rem, 0.6rem + 3vw, 4rem);
  --section: clamp(6.5rem, 4rem + 9vw, 13rem);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --ease-slow: cubic-bezier(0.16, 1, 0.3, 1);   /* slow, dramatic ease-out for hero/header entrances */

  /* default surface = light (the page is light plaster); .on-dark overrides for the hero/footer.
     this guarantees --fg/--fg-soft/--hair are always defined, even on sections tagged only data-theme. */
  --fg: #1b1712; --fg-soft: #5f5849; --hair: rgba(27, 23, 18, 0.16); --surface: var(--paper);
}

/* ---- context surfaces: set text colours, never a hard background ---- */
.on-dark  { --fg: #ece6da; --fg-soft: #a39a8b; --hair: rgba(236,230,218,0.16); --surface: var(--ink); color: var(--fg); }
.on-light { --fg: #1b1712; --fg-soft: #5f5849; --hair: rgba(27,23,18,0.16); --surface: var(--paper); color: var(--fg); }

*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; overflow-x: clip; }
/* hide the scrollbars entirely (the page still scrolls via wheel / trackpad / touch / keys) —
   removes the duplicate/overlapping scrollbar artefact some browsers show with the pinned layout */
html, body { scrollbar-width: none; -ms-overflow-style: none; }
html::-webkit-scrollbar, body::-webkit-scrollbar { width: 0; height: 0; display: none; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
body {
  font-family: var(--sans); font-weight: 400; font-size: var(--fs-body); line-height: 1.6;
  color: var(--fg); -webkit-font-smoothing: antialiased; overflow-x: clip;
  background: var(--ink);
}
/* overflow-x: clip (not hidden) avoids turning the body into a 2nd scroll container,
   which produced the overlapping/duplicate scrollbars with the pinned-hero layout */

/* =============================================================
   HBA-style scroll: a pinned hero + pinned footer, with the page
   content rising up over the hero (clean line at the top) and
   lifting away to reveal the footer (fading to its colour).
   Swap .hero-scene's photo for a <video> later — same mechanic.
   ============================================================= */
/* z-index 2 by default so the pinned hero covers the pinned footer at the top;
   JS drops it to 1 once you've scrolled past the hero, so the footer can reveal at the end */
.hero-scene { position: fixed; inset: 0; height: 100svh; z-index: 2; overflow: hidden; display: flex; align-items: flex-end; }
.reveal { position: relative; z-index: 10; background-color: var(--paper); }   /* opaque; hard top edge = the clean line over the hero */
.has-hero .reveal { margin-top: 100svh; }                                /* only the home pins a hero */
.site-footer { position: fixed; left: 0; right: 0; bottom: 0; z-index: 1; background: var(--ink); }
/* JS sets .reveal margin-bottom = footer height so the content lifts to reveal it */
@media (prefers-reduced-motion: reduce) {
  .hero-scene, .site-footer { position: relative; height: auto; }
  .has-hero .reveal { margin-top: 0; }
  .reveal { margin-bottom: 0 !important; }
}
img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
ul { list-style: none; padding: 0; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }
:focus-visible { outline: 1.5px solid currentColor; outline-offset: 4px; }

.container { width: 100%; max-width: var(--maxw); margin-inline: auto; padding-inline: var(--gutter); }
.section { padding-block: var(--section); position: relative; }

/* ---- type ---- */
.eyebrow {
  font-family: var(--sans); font-weight: 500; font-size: var(--fs-eyebrow);
  letter-spacing: 0.28em; text-transform: uppercase; color: var(--fg-soft);
  display: inline-flex; align-items: center; gap: 0.8rem;
}
/* (the eyebrow's leading dash was removed — the .inlay-rule motif is the single line language now) */
/* all-caps, lighter weight + open tracking reads more 'expensive' (fashion-house register) */
.display { font-family: var(--sans); font-weight: 400; text-transform: uppercase; letter-spacing: 0.01em; line-height: 1.07; font-size: var(--fs-display); }
.h2 { font-family: var(--sans); font-weight: 400; text-transform: uppercase; letter-spacing: 0.035em; line-height: 1.12; font-size: var(--fs-h2); }
/* accent words — no styling now (italics removed); kept as hooks for future use */
.accent { font-style: normal; }
.lead { font-size: var(--fs-lead); line-height: 1.5; color: var(--fg-soft); font-weight: 350; max-width: 44ch; }
.muted { color: var(--fg-soft); }

/* ---- brass-line signature: a monochrome hairline 'inlay' that opens sections (echoes the
   inlaid brass ribbon + the Option-B underline). Never gold — it borrows the surface ink. ---- */
.inlay-rule { display: block; position: relative; overflow: hidden; height: 1px; width: clamp(64px, 6vw, 96px);
  background: currentColor; opacity: 0.6; transform-origin: left; margin-bottom: clamp(1.5rem, 3vh, 2.4rem);
  /* a 1px highlight just beneath the groove makes the line read as INLAID into the surface (no gold) */
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); }
/* a slow glint travels along the line every few seconds — brass catching the light, monochrome */
.inlay-rule::after { content: ""; position: absolute; top: 0; left: 0; height: 100%; width: 45%;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.95), transparent);
  transform: translateX(-180%); animation: inlay-sheen 7.5s ease-in-out 1.8s infinite; }
@keyframes inlay-sheen { 0% { transform: translateX(-180%); } 26%, 100% { transform: translateX(320%); } }
.on-dark .inlay-rule, .hero-scene .inlay-rule { box-shadow: 0 1px 0 rgba(0, 0, 0, 0.32); }
.inlay-rule--center { margin-inline: auto; transform-origin: center; }
[data-r] .inlay-rule { transform: scaleX(0); transition: transform 1.1s var(--ease) 0.12s; }
[data-r].in .inlay-rule { transform: scaleX(1); }
@media (prefers-reduced-motion: reduce) { .inlay-rule { transform: none !important; } .inlay-rule::after { animation: none; opacity: 0; } }

/* ---- buttons — Option B: text + a drawing underline + a reaching arrow (no box) ---- */
.btn {
  display: inline-flex; align-items: center; gap: 0.7rem; position: relative;
  padding: 0.5rem 0; font-weight: 500; font-size: 0.95rem; letter-spacing: 0.02em;
  color: var(--fg); background: none; border: 0; cursor: pointer;
}
.btn::after { content: ""; position: absolute; left: 0; bottom: 0; height: 1px; width: 100%;
  background: currentColor; transform: scaleX(var(--u, 0)); transform-origin: left; transition: transform 0.55s var(--ease); }
.btn:hover::after, .btn:focus-visible::after { transform: scaleX(1); }
.btn .arr { width: 40px; height: 12px; flex: none; color: currentColor; transition: transform 0.5s var(--ease); }
.btn:hover .arr, .btn:focus-visible .arr { transform: translateX(7px); }
.btn .arr path { stroke: currentColor; stroke-width: 1.2; }
.btn:focus-visible { outline: none; }
/* lead = primary action: larger/heavier; underline now draws in on hover like every other button */
.btn--lead { font-size: 1.05rem; font-weight: 600; }
.btn--lead:hover .arr, .btn--lead:focus-visible .arr { transform: translateX(9px); }
.btn--block { width: 100%; }
.link { display: inline-flex; align-items: center; gap: 0.5rem; font-weight: 500; letter-spacing: 0.01em; padding-bottom: 4px; border-bottom: 1px solid var(--hair); transition: gap 0.4s var(--ease), border-color 0.4s; }
.link:hover { gap: 0.85rem; border-color: currentColor; }
.link svg { width: 15px; height: 15px; }

/* ---- nav (fixed/persistent; JS swaps on-dark/on-light by what's behind it) ---- */
.nav { position: fixed; inset: 0 0 auto 0; z-index: 60; padding-block: clamp(1rem, 0.6rem + 1vw, 1.6rem); transition: background-color 0.4s var(--ease), padding 0.4s var(--ease); }
.nav.is-scrolled { background: color-mix(in srgb, var(--surface) 48%, transparent); -webkit-backdrop-filter: blur(16.2px) saturate(120%); backdrop-filter: blur(16.2px) saturate(120%); }
.nav__inner { display: flex; align-items: center; gap: clamp(0.8rem, 2vw, 2rem); }

/* cart — far left, easy to reach; badge appears once something is reserved */
.cart-btn { position: relative; display: grid; place-items: center; width: 44px; height: 44px; border-radius: 50%; color: var(--fg); border: 1px solid transparent; transition: border-color 0.3s, color 0.3s; flex: none; }
.cart-btn:hover { border-color: var(--hair); }
.cart-btn svg { width: 22px; height: 22px; }
.cart-count { position: absolute; top: 1px; right: 1px; min-width: 17px; height: 17px; padding: 0 4px; border-radius: 9px; background: var(--fg); color: var(--surface); font-size: 0.62rem; font-weight: 600; display: grid; place-items: center; transform: scale(0); transition: transform 0.3s var(--ease); }
.cart-count.is-active { transform: scale(1); }

/* logo — real artwork inlined as SVG; recolours via currentColor (bone on dark, ink on light) */
.brand { display: inline-flex; align-items: center; color: var(--fg); flex: none; }
.logo { display: block; color: inherit; }
.logo--word { height: 33px; width: auto; }
.logo--mark { height: 130px; width: auto; }
@media (max-width: 600px) { .logo--mark { height: 94px; } }

/* links spread evenly across the central width */
.nav__links { flex: 1; display: none; justify-content: space-evenly; align-items: center; gap: 1rem; }
.nav__links a { font-size: 0.9rem; font-weight: 400; letter-spacing: 0.02em; color: var(--fg-soft); transition: color 0.3s; }
.nav__links a:hover { color: var(--fg); }
.nav-enquire { flex: none; }
@media (min-width: 820px) { .nav__links { display: flex; } }

/* cart + hamburger sit together at the right; margin-left:auto keeps them right even when links are hidden */
.nav__right { display: flex; align-items: center; gap: 0.3rem; flex: none; margin-left: auto; }

/* ---- mobile menu: hamburger toggle + full-screen overlay (links live here, cart stays in the bar) ---- */
.nav__toggle { display: inline-flex; flex-direction: column; justify-content: center; gap: 5px; width: 44px; height: 44px; flex: none; color: var(--fg); }
.nav__toggle span { display: block; width: 22px; height: 1.5px; background: currentColor; margin-inline: auto; transition: transform 0.4s var(--ease), opacity 0.25s; }
@media (min-width: 820px) { .nav__toggle { display: none; } }
body.menu-open .nav__toggle { color: #ece6da; }   /* hamburger sits above the dark overlay */
body.menu-open .nav__toggle span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
body.menu-open .nav__toggle span:nth-child(2) { opacity: 0; }
body.menu-open .nav__toggle span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

.nav__menu { position: fixed; inset: 0; z-index: 55; color: #ece6da;
  /* same texture treatment as the footer: ink + soft-light grain under a faint top glow */
  background-color: var(--ink);
  background-image:
    radial-gradient(130% 90% at 50% -8%, rgba(236, 230, 218, 0.045), rgba(236, 230, 218, 0) 60%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1100' height='1100'%3E%3Cfilter id='fg' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.016' numOctaves='3' seed='11' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.4'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23fg)'/%3E%3C/svg%3E");
  background-size: auto, 1100px 1100px;
  background-repeat: no-repeat, repeat;
  background-blend-mode: normal, soft-light;
  display: flex; flex-direction: column; justify-content: center; gap: clamp(1.4rem, 4vh, 2.6rem);
  padding: 18vh var(--gutter) var(--gutter); opacity: 0; visibility: hidden;
  /* visibility flips instantly (so it's focusable the moment it opens), but on CLOSE it's delayed
     0.5s so the menu stays visible through the opacity fade-out. The open rule below zeroes the delay. */
  transition: opacity 0.5s var(--ease), visibility 0s linear 0.5s; }
body.menu-open .nav__menu { opacity: 1; visibility: visible;
  transition: opacity 0.5s var(--ease), visibility 0s linear 0s; }
.nav__menu a { font-family: var(--sans); font-weight: 400; text-transform: uppercase; letter-spacing: 0.04em;
  font-size: clamp(1.9rem, 1.2rem + 4vw, 3rem); line-height: 1.1; color: #ece6da;
  opacity: 0; transform: translateY(14px); transition: opacity 0.5s var(--ease), transform 0.5s var(--ease); }
body.menu-open .nav__menu a { opacity: 1; transform: none; }
body.menu-open .nav__menu a:nth-child(1) { transition-delay: 0.12s; }
body.menu-open .nav__menu a:nth-child(2) { transition-delay: 0.18s; }
body.menu-open .nav__menu a:nth-child(3) { transition-delay: 0.24s; }
.nav__menu .nav__menu-meta { margin-top: clamp(2rem, 6vh, 4rem); display: flex; gap: 1.6rem; font-size: 0.82rem; letter-spacing: 0.16em; text-transform: uppercase; color: #a39a8b; }
.nav__menu .nav__menu-meta a { font-size: 0.82rem; letter-spacing: 0.16em; opacity: 1; transform: none; color: #a39a8b; }
@media (min-width: 820px) { .nav__menu { display: none; } }
@media (prefers-reduced-motion: reduce) { .nav__menu, .nav__menu a { transition-duration: 0.001ms; } }

/* ---- hero: full-bleed photography (lives inside .hero-scene, pinned) ---- */
/* full-bleed photograph (interim image; swap for your own / a <video>). */
/* image "settles" into place on arrival: starts slightly enlarged, eases to 1 (JS adds .is-ready). */
/* HERO IMAGE — the photo is assets/hero.jpg. Its FRAMING (which part of the photo stays in view
   when it's cropped to fill the screen) is set with --hero-focus in a small, clearly-commented
   <style> block in index.html's <head> — edit it there (separate values for desktop and phones),
   no other file needed. See SELF-EDIT-GUIDE.pdf ("The homepage photo & its framing").
   The 62% 40% below is only a fallback used if that block is ever removed. */
.hero__photo { position: absolute; inset: 0; z-index: 0;
  background: #100d0a url("hero.jpg?v=3") var(--hero-focus, 62% 40%) / cover no-repeat;
  transform: scale(1.055); transform-origin: 58% 45%; transition: transform 2s var(--ease-slow); will-change: transform; }
/* Modern formats for the hero. The plain .jpg above stays as the fallback for older browsers;
   image-set() lets anything newer take the much lighter AVIF/WebP, at a size that suits the
   screen. All of these are generated from assets/hero.jpg by the photo tool (npm run photos). */
.hero__photo { background-image: image-set(url("hero-1440.avif") type("image/avif"), url("hero-1440.webp") type("image/webp"), url("hero.jpg?v=3") type("image/jpeg")); }
@media (min-width: 900px)  { .hero__photo { background-image: image-set(url("hero-2000.avif") type("image/avif"), url("hero-2000.webp") type("image/webp"), url("hero.jpg?v=3") type("image/jpeg")); } }
@media (min-width: 1600px) { .hero__photo { background-image: image-set(url("hero-2600.avif") type("image/avif"), url("hero-2600.webp") type("image/webp"), url("hero.jpg?v=3") type("image/jpeg")); } }
.is-ready .hero__photo { transform: scale(1); }
@media (prefers-reduced-motion: reduce) { .hero__photo { transform: none; transition: none; } }
.hero__photo::before { /* warm dark grade — left + foot for the copy. (Foot darkening is covered as content rises, so the clean line is unaffected.) */
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(90deg, rgba(16,13,10,0.84) 0%, rgba(16,13,10,0.46) 34%, rgba(16,13,10,0.08) 66%, transparent 86%),
    linear-gradient(180deg, rgba(16,13,10,0.40) 0%, transparent 26%, transparent 46%, rgba(16,13,10,0.66) 100%);
}
/* NOTE: a 10% noise overlay used to sit here (.hero__photo::after) to give the placeholder
   hero some texture against the plaster page. Removed once real photography landed — the
   photograph carries its own grain, and the overlay only softened genuine detail. */
.hero__content { position: relative; z-index: 3; width: 100%; padding-bottom: clamp(3.5rem, 9vh, 7rem); }
.hero__title { margin: 1.3rem 0 1.4rem; max-width: 17ch; font-size: clamp(1.85rem, 1.1rem + 3.1vw, 3.85rem); }
.hero__sub { max-width: 40ch; font-size: clamp(0.9rem, 0.87rem + 0.18vw, 1rem); }
.photo-credit { position: absolute; right: var(--gutter); bottom: 1.4rem; z-index: 3; font-size: 0.7rem; letter-spacing: 0.18em; text-transform: uppercase; color: var(--fg-soft); }

/* ---- the board / timber selector (landscape-first) ---- */
/* clean plaster panel — its hard top edge IS the clean line that rises over the hero */
#board-section {
  position: relative;
  padding-top: clamp(6.5rem, 12vh, 10rem);
  padding-bottom: clamp(4rem, 8vh, 6rem);
  background: transparent;   /* plaster comes from .reveal.plaster */
}
#board-section .container { position: relative; z-index: 1; }

.board-intro { margin-bottom: clamp(4rem, 9vh, 7rem); }
.board-intro .board-head { margin-bottom: 0; }

.board-cta { margin-top: clamp(4rem, 9vh, 7rem); display: grid; gap: 1.8rem; max-width: 640px; margin-inline: auto; text-align: center; }
.board-cta .lead { margin-inline: auto; }
.board-cta .btns { display: flex; gap: 2.2rem; flex-wrap: wrap; justify-content: center; }

/* ---- editorial gallery: the one board shown in its four timbers, asymmetric grid (home) ---- */
.gallery { --stagger: 54px; --gap-out: clamp(3rem, 5vw, 4.5rem);
  margin-top: var(--gap-out); display: grid;
  column-gap: 40px; row-gap: 28px; grid-template-columns: 1fr; }
@media (min-width: 720px) {
  .gallery { grid-template-columns: repeat(12, 1fr);
    /* the stagger lifts the odd tiles half its value ABOVE the grid line and
       drops the even ones the same below it, which eats into the space at both
       ends of the section. Add it back so the gap you see either side of the
       grid is the one the design asks for, whatever the stagger is set to. */
    margin-top: calc(var(--gap-out) + var(--stagger) / 2);
    margin-bottom: calc(var(--stagger) / 2); }
}
.gallery__item { position: relative; display: block; cursor: pointer; }
.gallery__item:focus-visible { outline: 2px solid var(--fg); outline-offset: 6px; border-radius: 6px; }
/* MOBILE ORDER ONLY — desktop/tablet keep the document order untouched.
   Stacked in one column the source order reads dark, pale, pale, dark, so the
   two pale-backdrop shots clump in the middle. Swapping the last two alternates
   the backdrops down the page instead. `order` is visual only, so the link and
   reading order in the markup is unchanged. */
@media (max-width: 719px) {
  .gallery__item:nth-child(3) { order: 4; }   /* Tasmanian Oak   -> last  */
  .gallery__item:nth-child(4) { order: 3; }   /* Tas. Blackwood  -> third */
}
/* Four EQUAL tiles. The old 7+5 / 5+7 spans made two tiles twice the area of
   the others, implying a hierarchy that doesn't exist — one board, four timbers,
   one price — and making the timbers harder to compare, which is this section's
   whole job. The asymmetry was designed when these were flat placeholder
   graphics that needed the help; the photography supplies its own interest. */
@media (min-width: 720px) {
  .gallery__item { grid-column: span 6; }
  /* Vertical stagger, split EVENLY about the grid line rather than hung off the
     right column. Two reasons: `top` (not margin) means it can't inflate the row
     height, and splitting it stops the left column sitting exactly where the
     heading and body text sit — which made it read as the page's anchor, so all
     the scroll motion appeared to belong to the right column alone. */
  .gallery__item:nth-child(odd)  { top: calc(var(--stagger) / -2); }
  .gallery__item:nth-child(even) { top: calc(var(--stagger) / 2); }
}
.gallery__item .board-stage { aspect-ratio: 16 / 11; will-change: transform;
  /* a resting shadow: the parallax gives the motion cue for the tiles sitting
     above the plaster, this gives the physical one. Hover deepens it. */
  box-shadow: 0 30px 54px -34px rgba(20, 16, 10, 0.5);
  /* a softer, longer ease so the lift glides rather than snaps */
  transition: transform 0.85s cubic-bezier(0.22, 0.8, 0.24, 1), box-shadow 0.85s cubic-bezier(0.22, 0.8, 0.24, 1); }
/* hover: the board PEEKS UP (no border) */
.gallery__item:hover .board-stage, .gallery__item:focus-visible .board-stage { transform: translateY(-10px); box-shadow: 0 38px 64px -36px rgba(20,16,10,0.5); }
/* species name appears underneath on hover (always shown where there's no hover, e.g. touch) */
.gallery__cap { margin-top: 0.9rem; min-height: 1.25em; }
.gallery__name { font-size: 0.86rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--fg);
  opacity: 0; transform: translateY(5px); transition: opacity 0.4s var(--ease), transform 0.4s var(--ease); }
.gallery__item:hover .gallery__name, .gallery__item:focus-visible .gallery__name { opacity: 1; transform: none; }
@media (hover: none) { .gallery__name { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .gallery__item .board-stage { transition: none; } }

/* ---- soft warm glow trailing the cursor (lag + slight bounce) ---- */
/* Option A — a neutral blob: darkens light/plaster, lifts dark areas (normal-blend mid grey) */
.cursor-glow { position: fixed; top: 0; left: 0; width: 360px; height: 360px; margin: -180px 0 0 -180px; border-radius: 50%;
  pointer-events: none; z-index: 40; opacity: 0; transition: opacity 0.6s ease;
  background: radial-gradient(circle, rgba(128,120,106,0.17) 0%, rgba(128,120,106,0.07) 38%, rgba(128,120,106,0) 70%); }
.cursor-glow.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce), (hover: none) { .cursor-glow { display: none; } }

.board-head { max-width: 760px; margin-bottom: clamp(2rem, 4vw, 3.5rem); }
.board-figure { margin: 0; }
.board-bar { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 1rem 1.5rem; margin-top: 1.4rem; }
.buy .btns { display: flex; gap: 0.8rem; flex-wrap: wrap; }
.btn--hair { border-color: var(--hair); }

.board-stage { position: relative; aspect-ratio: 16 / 10; background-color: #e4ded1; border-radius: 4px; }
/* photography swap: add <img class="shot" src="…" alt="…"> as the first child of any .board-stage
   and it covers the gradient placeholder (which stays as the fallback until real shots land). */
.board-stage picture { display: contents; }   /* <picture> is only a format chooser — it must not box the photo */
.board-stage .shot { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: 4px; z-index: 2;
  opacity: 0; transition: opacity 0.5s ease; }
.board-stage .shot.is-ready { opacity: 1; }   /* JS adds .is-ready on load → the photo eases in over the neutral base */
@media (prefers-reduced-motion: reduce) { .board-stage .shot { transition: none; } }
/* When a photo is expected (.has-photo, set in HTML/JS while it loads), hide the drawn placeholder so
   only the calm neutral base above shows during the download — no placeholder "flash" before the photo.
   The drawn placeholder is kept for stages WITHOUT .has-photo (i.e. genuinely no photo yet). */
.board-stage.has-photo .board2 { display: none; }
.board2 {
  --w1: #6b4a31; --w2: #38230f;
  position: absolute; inset: 0; border-radius: 4px; overflow: hidden;
  background: linear-gradient(120deg, var(--w1), var(--w2) 78%);
  box-shadow: 0 40px 80px -40px rgba(0,0,0,0.5);
  transition: --w1 0.6s var(--ease), --w2 0.6s var(--ease);
}
.board2::before { /* grain / grain-of-timber */
  content: ""; position: absolute; inset: 0; mix-blend-mode: soft-light; opacity: 0.5;
  background-image:
    repeating-linear-gradient(97deg, rgba(0,0,0,0.16) 0 2px, transparent 2px 8px),
    repeating-linear-gradient(95deg, rgba(255,240,210,0.06) 0 1px, transparent 1px 12px);
}
.board2__inlay { position: absolute; inset: 0; }
.board2__inlay svg { width: 100%; height: 100%; }
.board2__inlay path, .board2__inlay circle { fill: none; stroke: #d7b576; opacity: 0.85; filter: drop-shadow(0 1px 1px rgba(0,0,0,0.5)); }
.board-cap { font-size: 0.82rem; color: var(--fg-soft); letter-spacing: 0.02em; }

.timbers { display: flex; gap: 0.8rem; margin: 0; }
.timber { display: flex; flex-direction: column; align-items: center; gap: 0.6rem; cursor: pointer; }
.timber__chip { width: 34px; height: 34px; flex: none; border-radius: 50%; border: 1px solid var(--hair); position: relative; transition: transform 0.3s var(--ease); }
.timber__chip::after { content: ""; position: absolute; inset: -5px; border-radius: 50%; border: 1px solid transparent; transition: border-color 0.3s; }
.timber[aria-pressed="true"] .timber__chip::after { border-color: var(--fg); }
.timber:hover .timber__chip { transform: scale(1.06); }
.timber__name { font-size: 0.72rem; letter-spacing: 0.04em; color: var(--fg-soft); }
.timber[aria-pressed="true"] .timber__name { color: var(--fg); }

.spec { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; margin: 0; border: 1px solid var(--hair); }
.spec > div { padding: 1rem 1.1rem; border: 0.5px solid var(--hair); }
.spec dt { font-size: 0.68rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--fg-soft); }
.spec dd { font-size: 1.05rem; font-weight: 500; margin-top: 5px; letter-spacing: -0.01em; }
.price-row { display: flex; align-items: baseline; gap: 1rem; margin-bottom: 1.6rem; }
.price { font-size: 1.7rem; font-weight: 500; letter-spacing: -0.02em; }

/* ---- closing CTA: clean plaster; the fixed footer is revealed on a clean line beneath (the 'riser') ---- */
.fade-end { position: relative; text-align: center; padding-top: clamp(3rem, 6vh, 4.5rem); padding-bottom: clamp(5.5rem, 13vh, 10rem); background: transparent; }
.fade-end__inner { position: relative; max-width: 720px; margin-inline: auto; }
/* closing block: two re-firing reveals. JS toggles .in (the name note + its bracketing rules) and
   .in-statement (the closing lines) on #closing-inner independently, each keyed to its OWN element
   entering view — they sit ~190px apart, so a single trigger left whichever one led it blank for a
   long stretch of scrolling before anything appeared. */
#closing-inner .inlay-rule { transform: scaleX(0); transition: transform 1.1s var(--ease) 0.1s; }
#closing-inner.in .inlay-rule { transform: scaleX(1); }
.cl-line { display: block; opacity: 0; transform: translateY(34px); filter: blur(6px);
  /* gentle ease-in-out so the opacity ramps up gradually — a soft fade, not a front-loaded snap */
  transition: opacity 1.5s cubic-bezier(0.42, 0, 0.58, 1), transform 1.5s cubic-bezier(0.42, 0, 0.58, 1), filter 1.5s cubic-bezier(0.42, 0, 0.58, 1);
  transition-delay: var(--d, 0s); }
#closing-inner.in-statement .cl-line { opacity: 1; transform: none; filter: blur(0); }
/* the brand-name note above the statement — same .in gate, leads the stagger. Mirrors .cl-line but
   gentler (shorter travel, less blur) so the small type doesn't out-perform the display beneath it. */
.cl-note { max-width: 46ch; margin-inline: auto;
  font-size: clamp(0.95rem, 0.9rem + 0.2vw, 1.08rem); line-height: 1.55; color: var(--fg-soft);
  opacity: 0; transform: translateY(20px); filter: blur(4px);
  transition: opacity 1.3s cubic-bezier(0.42, 0, 0.58, 1), transform 1.3s cubic-bezier(0.42, 0, 0.58, 1), filter 1.3s cubic-bezier(0.42, 0, 0.58, 1);
  transition-delay: var(--d, 0s); }
#closing-inner.in .cl-note { opacity: 1; transform: none; filter: blur(0); }
/* The lower of the two bracketing rules. Its top margin keeps the pair tight around the note
   (.inlay-rule only carries margin-bottom); its bottom margin is deliberately much larger so the
   statement below reads as the next beat, not as part of the bracket — matches the ~114px of air
   above the block, so the whole thing sits evenly between the board section and the statement. */
.inlay-rule--gap { margin-top: clamp(1.5rem, 3vh, 2.4rem); margin-bottom: clamp(4rem, 13vh, 7rem); }
/* each closing line carries its own title sheen (per-line clip renders reliably; a parent-level
   background-clip:text leaves child <span> glyphs with no background to clip → invisible) */
.cl-line { --sheen-a: #1b1712; --sheen-b: #948b7b; }
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .cl-line {
    background-image: linear-gradient(105deg, var(--sheen-a) 0%, var(--sheen-a) 36%, var(--sheen-b) 50%, var(--sheen-a) 64%, var(--sheen-a) 100%);
    background-size: 220% 100%; background-position: 0 0; background-repeat: repeat-x;
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
    animation: sheen-sweep 7s ease-in-out 0.3s infinite;
  }
}
@media (prefers-reduced-motion: reduce) { .cl-line, .cl-note { opacity: 1 !important; transform: none !important; filter: none !important; animation: none !important; } #closing-inner .inlay-rule { transform: none !important; } }
.fade-end .lead { margin-inline: auto; }
.fade-end .btns { display: flex; gap: 1.6rem; justify-content: center; flex-wrap: wrap; margin-top: 2rem; }

/* ---- footer ---- */
/* footer: lifted off flat black with a soft top-glow + a faint dark grain (page material, but dark),
   and a hairline 'inlay' seam at the very top where the riser meets the footer */
.foot { padding-block: clamp(3rem, 5vw, 5rem); background-color: var(--ink);
  background-image:
    radial-gradient(130% 90% at 50% -8%, rgba(236, 230, 218, 0.045), rgba(236, 230, 218, 0) 60%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1100' height='1100'%3E%3Cfilter id='fg' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.016' numOctaves='3' seed='11' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.4'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23fg)'/%3E%3C/svg%3E");
  background-size: auto, 1100px 1100px;
  background-repeat: no-repeat, repeat;
  background-blend-mode: normal, soft-light;
  box-shadow: inset 0 1px 0 rgba(236, 230, 218, 0.16); }
.foot__row { display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1.5rem; align-items: end; }
.foot__links { display: flex; gap: 1.6rem; flex-wrap: wrap; }
.foot__links a { color: var(--fg-soft); font-size: 0.9rem; display: inline-flex; align-items: center; gap: 0.45rem; }
.foot__links a:hover { color: var(--fg); }
/* outline-only social glyphs (stroke = current text colour, no fill) */
.foot__links .soc-ico { width: 15px; height: 15px; flex: none; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
/* The five utility links need 378px but only get 333px on a phone, so they wrap 4+1 and strand
   "Privacy" on its own. A greedy flex wrap can only ever fill the first row, so the split is forced
   here: order groups put the first two links, then a zero-height full-width ::after break, then the
   rest — giving a balanced 2 + 3 (184px / 169px). DOM order is untouched, so reading order is too.
   row-gap is zeroed (the break sits on its own row and would otherwise double the space) and the
   links carry their own vertical padding instead, which also makes them a decent tap target. */
@media (max-width: 479px) {
  .foot__bar .foot__links { row-gap: 0; }
  .foot__bar .foot__links a { padding-block: 0.6rem; }
  .foot__bar .foot__links a:nth-child(-n + 2) { order: -1; }
  .foot__bar .foot__links::after { content: ""; flex-basis: 100%; height: 0; order: 0; }
  .foot__bar .foot__links a:nth-child(n + 3) { order: 1; }
}
.foot__bar { margin-top: 2.5rem; padding-top: 1.5rem; border-top: 1px solid var(--hair); display: flex; flex-wrap: wrap; justify-content: space-between; gap: 1rem; font-size: 0.8rem; color: var(--fg-soft); }

/* ---- plaster surface — subtle, even, soft-light mottle over the warm paper ---- */
/* tile is seamless: filter region pinned to the tile (x=0..100%) so stitchTiles wraps at the tile edge,
   baseFrequency*size is an integer (0.015*1200=18), and the larger 1200px tile makes any repeat hard to spot */
.plaster { background-color: var(--paper);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1200' height='1200'%3E%3Cfilter id='p' x='0' y='0' width='100%25' height='100%25'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.015' numOctaves='3' seed='8' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3CfeComponentTransfer%3E%3CfeFuncA type='linear' slope='0.6'/%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)'/%3E%3C/svg%3E");
  background-repeat: repeat; background-size: 1200px 1200px; background-blend-mode: soft-light; }
.ph-header { padding-top: clamp(7rem, 13vh, 11rem); }
.breadcrumb { display: flex; gap: 0.6rem; font-size: 0.8rem; color: var(--fg-soft); letter-spacing: 0.03em; margin-bottom: 1.6rem; }
.breadcrumb a { color: var(--fg-soft); } .breadcrumb a:hover { color: var(--fg); }
/* image now lives in the left column (aligned with the prose, smaller) + buy panel right */
/* figure, buy panel and prose are direct grid children. Mobile: single column, stacking in DOM
   order (image → buy panel → description) so purchase actions aren't buried below the copy.
   Desktop: grid-template-areas restores the original 2-col layout (figure+prose left, buy right). */
.product-grid { display: grid; gap: clamp(2.5rem, 5vw, 5rem); align-items: start; margin-top: clamp(2.5rem, 5vw, 4rem); }
@media (min-width: 940px) {
  .product-grid {
    grid-template-columns: 1fr minmax(320px, 380px);
    grid-template-areas: "figure buy" "prose buy";
    column-gap: clamp(2.5rem, 5vw, 5rem); row-gap: clamp(2.5rem, 4vw, 3.5rem);
  }
  .product-grid .product-figure { grid-area: figure; }
  .product-grid .prose { grid-area: prose; }
  .product-grid .buy-panel { grid-area: buy; position: sticky; top: 92px; }
}
.product-figure { margin: 0; }   /* spacing handled by grid gap (mobile) / row-gap (desktop) */
.product-figure .board-stage { aspect-ratio: 3 / 2; }

/* ---- product image gallery (per-species; cycle through angles/uses; placeholders now, real photos later) ---- */
.pgallery { position: relative; }
.pgallery__viewport { overflow: hidden; border-radius: 4px; touch-action: pan-y; cursor: grab; }
.pgallery__viewport:active { cursor: grabbing; }
.pgallery__track { display: flex; transition: transform 0.7s var(--ease); will-change: transform; }
.pgallery__slide { flex: 0 0 100%; }
.pgallery__nav { display: flex; align-items: center; justify-content: center; gap: 1.6rem; margin-top: 1.1rem; }
.pgallery__btn { display: inline-flex; align-items: center; color: var(--fg); background: none; border: 0; padding: 0.4rem; cursor: pointer; }
.pgallery__btn svg { width: 72px; height: 13px; transition: transform 0.45s var(--ease); }
.pgallery__btn svg path { stroke: currentColor; stroke-width: 1.2; }
.pgallery__btn:focus-visible { outline: 2px solid var(--fg); outline-offset: 4px; border-radius: 3px; }
.pgallery__btn--prev:hover svg, .pgallery__btn--prev:focus-visible svg { transform: translateX(-5px); }
.pgallery__btn--next:hover svg, .pgallery__btn--next:focus-visible svg { transform: translateX(5px); }
.pgallery__count { font-size: 0.8rem; letter-spacing: 0.14em; color: var(--fg-soft); font-variant-numeric: tabular-nums; min-width: 7ch; text-align: center; }
@media (prefers-reduced-motion: reduce) { .pgallery__track { transition: none; } }
.prose p { color: var(--fg-soft); max-width: 60ch; }
.prose p + p { margin-top: 1.1rem; }
.prose h3 { font-family: var(--sans); font-weight: 400; text-transform: uppercase; letter-spacing: 0.06em; font-size: 1.05rem; margin: clamp(2rem, 4vw, 3rem) 0 0.9rem; }
/* sticky is applied only in the 2-col desktop layout (see the min-width:940px block above);
   in the single-column layout pinning it would let the description scroll up underneath → overlap */
.buy-panel { border: 1px solid var(--hair); padding: clamp(1.5rem, 3vw, 2.2rem); background: rgba(255,255,255,0.22); }
.buy-panel .price { display: block; margin: 0.4rem 0 0; }
.buy-panel .timbers { display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem; margin: 1.5rem 0; }
.buy-panel .timber { flex-direction: row; justify-content: flex-start; text-align: left; }  /* left-align so a wrapping name (Tas. Blackwood) matches the others */
.buy-panel .btn { width: 100%; justify-content: space-between; margin-top: 1.2rem; }
/* separate the two paths: Acquire (add to cart) vs Commission, with breathing room + a hairline */
.buy-panel .btn + .btn { margin-top: 1.9rem; padding-top: 1.7rem; border-top: 1px solid var(--hair); }
/* notes below the buttons: a hairline under "Commission your own size" that mirrors the one
   above it (.btn + .btn), with room to breathe before the notes */
.buy-foot { margin-top: 1.9rem; padding-top: 1.7rem; border-top: 1px solid var(--hair); }
.buy-note { font-size: 0.82rem; color: var(--fg-soft); margin: 0; text-align: left; }
.buy-fn { margin-top: 1rem; }
.buy-panel .fn, .spec .fn { color: var(--fg-soft); }   /* footnote asterisk (lead time -> "Need it sooner?") */
sup.fn { font-size: 0.7em; margin-left: 0.15em; }

/* ---- commission enquiry form ---- */
.cform { max-width: 660px; margin-top: clamp(2.5rem, 5vw, 4rem); }
.cform__fields { display: grid; gap: 1.5rem; }
.cform__row { display: grid; gap: 1.4rem; }
@media (min-width: 620px) { .cform__row--2 { grid-template-columns: 1fr 1fr; } .cform__row--3 { grid-template-columns: repeat(3, 1fr); } }
.field { display: flex; flex-direction: column; gap: 0.5rem; }
.field > .field__label, .field > legend { font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--fg-soft); padding: 0; }
.field input, .field textarea, .field select {
  font: inherit; font-size: 0.98rem; color: var(--fg); background: rgba(255, 255, 255, 0.16);
  border: 1px solid var(--hair); border-radius: 2px; padding: 0.75rem 0.85rem; width: 100%;
  transition: border-color 0.3s var(--ease), background 0.3s var(--ease); }
.field input:focus, .field textarea:focus, .field select:focus { outline: none; border-color: var(--fg); background: rgba(255, 255, 255, 0.34); }
.field textarea { resize: vertical; min-height: 130px; }
.field input::placeholder, .field textarea::placeholder { color: var(--fg-soft); opacity: 0.7; }
.field--error input, .field--error textarea { border-color: #9b3b2f; }
.field__err { font-size: 0.72rem; color: #9b3b2f; min-height: 1em; display: none; }
.field--error .field__err { display: block; }
.field--check { border: 0; margin: 0; padding: 0; min-width: 0; }  /* drop the default <fieldset> padding so it aligns with the other fields */

/* timber as a chip radio group */
/* even grid so the 5 species sit 3+2 (2+2+1 on narrow) instead of an awkward 4+1 */
.timber-choice { display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.6rem; margin-top: 0.2rem; }
@media (min-width: 560px) { .timber-choice { grid-template-columns: repeat(3, 1fr); } }
.timber-choice label { justify-content: flex-start; }
.timber-choice input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.timber-choice label { display: inline-flex; align-items: center; gap: 0.55rem; padding: 0.55rem 0.95rem; border: 1px solid var(--hair); border-radius: 100px; font-size: 0.85rem; cursor: pointer; transition: border-color 0.3s, background 0.3s; }
.timber-choice label:hover { border-color: var(--fg-soft); }
.timber-choice input:checked + label { border-color: var(--fg); background: rgba(255, 255, 255, 0.32); }
.timber-choice input:focus-visible + label { outline: 1.5px solid currentColor; outline-offset: 2px; }
.timber-choice .swatch { width: 15px; height: 15px; border-radius: 50%; flex: none; }

.cform__actions { margin-top: 2.2rem; display: flex; align-items: center; gap: 1.6rem; flex-wrap: wrap; }
.cform__success { display: none; }
.cform.is-sent .cform__fields, .cform.is-sent .cform__actions { display: none; }
.cform.is-sent .cform__success { display: block; }
.cform__success .h2 { margin-bottom: 1rem; }
.hp { position: absolute !important; left: -9999px; width: 1px; height: 1px; overflow: hidden; }  /* spam honeypot */

/* dimension inputs: strip the number spinner arrows */
.cform input[type="number"] { -moz-appearance: textfield; appearance: textfield; }
.cform input[type="number"]::-webkit-outer-spin-button, .cform input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
/* suggested standard sizes — chips that fill the dimension boxes */
.size-presets { margin: 0.4rem 0 1.1rem; }
.size-presets__label { display: block; font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--fg-soft); margin-bottom: 0.7rem; }
/* two-up grid → tidy 2×2 (stacks to one column on narrow screens) */
.size-presets__chips { display: grid; grid-template-columns: 1fr; gap: 0.6rem; }
@media (min-width: 560px) { .size-presets__chips { grid-template-columns: 1fr 1fr; } }
.size-preset { display: flex; justify-content: space-between; align-items: baseline; gap: 0.8rem; text-align: left;
  font: inherit; font-size: 0.82rem; padding: 0.5rem 0.95rem; border: 1px solid var(--hair); border-radius: 100px;
  background: none; color: var(--fg-soft); cursor: pointer; transition: border-color 0.3s var(--ease), color 0.3s var(--ease); }
.size-preset:hover, .size-preset:focus-visible { border-color: var(--fg); color: var(--fg); outline: none; }
.size-preset b { font-weight: 600; color: var(--fg); font-variant-numeric: tabular-nums; }

/* ---- cart drawer (front-end mock) ---- */
.cart-drawer { position: fixed; inset: 0; z-index: 70; visibility: hidden; pointer-events: none; }
.cart-drawer.is-open { visibility: visible; pointer-events: auto; }
.cart-drawer__scrim { position: absolute; inset: 0; background: rgba(12, 9, 6, 0.42); opacity: 0; transition: opacity 0.45s var(--ease); }
.cart-drawer.is-open .cart-drawer__scrim { opacity: 1; }
.cart-drawer__panel { position: absolute; top: 0; right: 0; height: 100%; width: min(420px, 92vw);
  --fg: #1b1712; --fg-soft: #5f5849; --hair: rgba(27, 23, 18, 0.16); color: var(--fg);
  display: flex; flex-direction: column; padding: clamp(1.4rem, 3vw, 2rem);  /* background comes from .plaster */
  transform: translateX(100%); transition: transform 0.5s var(--ease); box-shadow: -30px 0 60px -30px rgba(0, 0, 0, 0.4); }
.cart-drawer.is-open .cart-drawer__panel { transform: none; }
.cart-drawer__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1.6rem; }
.cart-drawer__close { background: none; border: 0; cursor: pointer; color: var(--fg); width: 40px; height: 40px; display: inline-flex; align-items: center; justify-content: center; margin-right: -8px; }
.cart-drawer__close svg { width: 16px; height: 16px; }
.cart-drawer__items { list-style: none; flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 1.1rem; margin: 0; padding: 0; }
.cart-line { display: grid; grid-template-columns: 34px 1fr auto; gap: 0.4rem 0.9rem; align-items: center; padding-bottom: 1.1rem; border-bottom: 1px solid var(--hair); }
.cart-line__chip { width: 34px; height: 34px; border-radius: 4px; grid-row: span 2; }
.cart-line__info { display: flex; flex-direction: column; gap: 0.15rem; }
.cart-line__name { font-size: 0.95rem; }
.cart-line__meta { font-size: 0.76rem; color: var(--fg-soft); }
.cart-line__price { font-size: 0.9rem; font-variant-numeric: tabular-nums; }
.cart-line__rm { grid-column: 2 / 4; justify-self: start; background: none; border: 0; padding: 0 0 1px; font: inherit; font-size: 0.7rem; letter-spacing: 0.12em; text-transform: uppercase; color: var(--fg-soft); cursor: pointer; border-bottom: 1px solid var(--hair); }
.cart-line__rm:hover { color: var(--fg); border-color: currentColor; }
.cart-drawer__empty { color: var(--fg-soft); padding: 0.6rem 0; }
.cart-drawer__foot { margin-top: 1.4rem; padding-top: 1.4rem; border-top: 1px solid var(--hair); }
.cart-drawer__subtotal { display: flex; justify-content: space-between; align-items: baseline; font-size: 1.05rem; margin-bottom: 1.2rem; }
.cart-drawer__subtotal span:last-child { font-variant-numeric: tabular-nums; }
.cart-drawer__foot .btn { width: 100%; justify-content: center; }
.cart-drawer__foot .muted { font-size: 0.74rem; margin-top: 0.85rem; text-align: center; }
@media (prefers-reduced-motion: reduce) { .cart-drawer__panel, .cart-drawer__scrim { transition-duration: 0.001ms; } }

/* ---- reveal (quiet) ---- */
[data-r] { opacity: 0; transform: translateY(20px); transition: opacity 1s var(--ease), transform 1s var(--ease); transition-delay: var(--d, 0s); }
[data-r].in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  [data-r] { opacity: 1 !important; transform: none !important; }
  * { transition-duration: 0.001ms !important; }
}

/* ---- slow, dramatic entrance for the hero text + page headers (rides the same [data-r] .in toggle) ---- */
.enter { transform: translateY(28px); filter: blur(7px);
  transition: opacity 1.35s var(--ease-slow), transform 1.35s var(--ease-slow), filter 1.35s var(--ease-slow);
  transition-delay: var(--d, 0s); }
.enter.in { filter: blur(0); }
@media (prefers-reduced-motion: reduce) { .enter { filter: none !important; } }

/* hero content drifts + fades on scroll (JS sets transform/opacity each frame → reverses when scrolling back up) */
.hero__content { will-change: transform, opacity; }

/* ---- monochrome title sheen: a defined bright band sweeps across the text — a 'lit' glint (strictly no gold) ----
   explicit per-surface colours (NO color-mix); base + a clearly brighter highlight so the streak actually reads. */
.sheen { color: inherit; --sheen-a: #1b1712; --sheen-b: #948b7b; }              /* default / on-light: ink base, lighter streak */
.on-dark .sheen { --sheen-a: #d6ccb6; --sheen-b: #fffefb; }                       /* on-dark: bone base, bright streak */
/* hero sits over the dark photo: opaque warm bone text, and the sheen band is the *transparent* part —
   the same tone at 30% opacity, so as it sweeps the photo reads through the glyphs (a glassy ripple). */
.hero-scene .sheen { --sheen-a: #efe4cd; --sheen-b: rgba(239, 228, 205, 0.3); }
@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  .sheen {
    background-image: linear-gradient(105deg, var(--sheen-a) 0%, var(--sheen-a) 36%, var(--sheen-b) 50%, var(--sheen-a) 64%, var(--sheen-a) 100%);
    background-size: 220% 100%; background-position: 0 0; background-repeat: repeat-x;
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
    animation: sheen-sweep 7s ease-in-out 0.3s infinite;
  }
  /* hero: a soft, slightly wider band so the translucent sweep reads as a gentle ripple */
  .hero-scene .sheen {
    background-image: linear-gradient(105deg, var(--sheen-a) 0%, var(--sheen-a) 34%, var(--sheen-b) 50%, var(--sheen-a) 66%, var(--sheen-a) 100%);
  }
}
@keyframes sheen-sweep {
  /* band starts fully off the right, sweeps right→left clean across the text and fully off the
     left, then rests OFF-SCREEN until the next cycle — so neither end is ever parked on the text
     (no visible stop, no jump). -30%/130% over-clear the band past both edges of the 220% image. */
  0%   { background-position: -30% 0; animation-timing-function: linear; }
  60%  { background-position: 130% 0; }
  100% { background-position: 130% 0; }
}
/* reduced motion: park the band off-screen so the title sits fully opaque / uniform (no streak) */
@media (prefers-reduced-motion: reduce) { .sheen { animation: none; background-position: -30% 0; } }

/* ============================================================================
   MOBILE-ONLY refinements (≤600px) — touch tuning that must not touch desktop.
   Placed last so equal-specificity rules win on source order. Desktop (>600px)
   is entirely unaffected.
   ============================================================================ */
@media (max-width: 600px) {
  /* 16px inputs stop iOS Safari auto-zooming the page when a field is focused */
  .field input, .field textarea, .field select { font-size: 16px; }

  /* ~44px minimum touch targets (these elements are flex, so min-height applies cleanly) */
  .btn { min-height: 44px; }
  .timber-choice label, .size-preset, .buy-panel .timber { min-height: 44px; }
  .pgallery__btn { padding-block: 1rem; }             /* taller hit area around the carousel arrows (~45px) */
  .cart-drawer__close { width: 44px; height: 44px; }
}

/* ---- the plaster surface stays put while the page slides over it ----
   Driving background-position from a scroll handler can never be smooth: scroll
   is composited off the main thread, so a JS-positioned background always trails
   the content by a frame and repaints the visible texture on every move. That
   was the lag.
   background-attachment: fixed hands the job to the browser — the texture is
   positioned against the viewport instead of the element, so it simply doesn't
   move, and the content reads as floating above it. It stays CLIPPED to .reveal,
   which a position:fixed layer would not: that would paint over the pinned hero.
   Scoped to .reveal so the cart drawer's .plaster is unaffected.
   iOS Safari ignores fixed attachment and falls back to a normal scrolling
   background — the effect is lost there, nothing breaks. */
.reveal.plaster { background-attachment: fixed; }
@media (prefers-reduced-motion: reduce) {
  .reveal.plaster { background-attachment: scroll; }
}
