/* Farm Meat Market — site styles
 *
 * Ported from the confirmed Round 1 artboards. Three things differ from the
 * mockups, all deliberate:
 *
 *   1. Type and gutters are fluid rather than fixed at the 1440px artboard size,
 *      so there is no dead zone between breakpoints.
 *   2. Colour pairings are encoded as ground classes instead of per-element
 *      inline styles. Put .on-wine on a section and everything inside it picks
 *      the pairing that passes contrast on wine. That is what stops the 158
 *      failures from creeping back the first time someone adds a heading.
 *   3. Every clamp() keeps its maximum under 2.5x its minimum, so the page still
 *      reflows properly for anyone who has raised their browser's text size.
 */

/* ---------------------------------------------------------------- fonts */
/* Headline Gothic is licensed from ATF — the webfont here is Farm Meat Market's
   own licensed copy. Familjen Grotesk is OFL. Both are subset to Latin. */
@font-face {
  font-family: 'Headline Gothic';
  src: url('/fonts/hg.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Familjen Grotesk';
  src: url('/fonts/fg-400.woff2') format('woff2');
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: 'Familjen Grotesk';
  src: url('/fonts/fg-600.woff2') format('woff2');
  font-weight: 600;
  font-display: swap;
}
@font-face {
  font-family: 'Familjen Grotesk';
  src: url('/fonts/fg-700.woff2') format('woff2');
  font-weight: 700;
  font-display: swap;
}

/* --------------------------------------------------------------- tokens */
:root {
  --wine: #4E1325;
  --crimson: #CC1517;
  --bone: #DED6AD;
  --tallow: #F5F3E2;
  --flesh: #D99695;
  --paper: #DBDDE2;          /* the studio ground the product shots sit on */

  /* frozen chip — the one sanctioned departure from the palette */
  --ice: #DCE9F0;
  --ice-line: #BCD6E4;
  --ice-ink: #24536B;

  --gut: clamp(1.25rem, 6vw, 6.25rem);
  --max: 77.5rem;

  /* Display sizes. The artboards were drawn at the maximum; the minimum is what
     a 360px phone gets. */
  --d1: clamp(2.75rem, 1.4rem + 6vw, 5.5rem);
  --d2: clamp(2rem, 1.1rem + 4vw, 3.875rem);
  --d3: clamp(1.625rem, 1.1rem + 2.2vw, 2.625rem);
  --d4: clamp(1.25rem, 1rem + 1vw, 1.6875rem);

  --band: clamp(3.5rem, 8vw, 6.875rem);
  --band-tight: clamp(2.75rem, 6vw, 4.75rem);
}

/* ----------------------------------------------------------- foundation */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--tallow);
  color: var(--wine);
  font-family: 'Familjen Grotesk', system-ui, -apple-system, 'Segoe UI', Arial, sans-serif;
  font-size: 1.0625rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

img { display: block; max-width: 100%; height: auto; }

/* The browser's own [hidden] rule is a bare element selector, so any class that
   sets display — .btn, .row, .grid — silently beats it and the element stays on
   screen. This is the fix every reset carries, and it is the difference between
   the map button being hidden without JavaScript and being offered as a control
   that cannot work. */
[hidden] { display: none !important; }

p { margin: 0; text-wrap: pretty; }

h1, h2, h3, h4 { text-wrap: balance; }

a { color: var(--crimson); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { color: var(--wine); }

/* Visible focus everywhere. Keyboard users get a ring that clears the text. */
:focus-visible {
  outline: 3px solid currentColor;
  outline-offset: 3px;
}

/* Honour a reduced-motion preference for every transition on the site. */
@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;
  }
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--wine);
  color: var(--tallow);
  padding: 0.75rem 1.25rem;
  z-index: 100;
}
.skip:focus { left: 0; }

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ------------------------------------------------------------- typography */
.hg {
  font-family: 'Headline Gothic', 'Arial Narrow', Arial, sans-serif;
  text-transform: uppercase;
  font-weight: 400;
  letter-spacing: 0;
}

.d1 { font-size: var(--d1); line-height: 0.92; margin: 0; }
.d2 { font-size: var(--d2); line-height: 0.96; margin: 0; }
.d3 { font-size: var(--d3); line-height: 1; margin: 0; }
.d4 { font-size: var(--d4); line-height: 1.08; margin: 0; }

.lede { font-size: clamp(1.125rem, 1rem + 0.6vw, 1.3125rem); line-height: 1.55; margin: 0; }
.small { font-size: 0.90625rem; line-height: 1.58; }

/* Secondary text — dates, captions. Set here rather than inline because the
   value is a contrast threshold, not a taste decision: below about .7 the wine
   lifts off the tallow and small text drops under 4.5:1. */
.meta { opacity: 0.72; }

.tiny {
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
}

/* --------------------------------------------------------------- grounds */
/* Set the ground once on a section; text inside picks a pairing that passes.
   These values come from measuring the palette against itself:
     on wine     bone 9.9   crimson 2.6 (display only)
     on crimson  tallow 5.1  bone 3.9 (display only)
     on bone     wine 9.9   crimson 3.9 (display only)
     on tallow   wine 13.0  crimson 5.1
   Small text never gets a pairing below 4.5. */
/* Each ground sets its own colours as custom properties rather than as
   descendant rules. It matters when grounds nest — a bone card inside a tallow
   section, say: descendant selectors of equal specificity are settled by their
   order in this file, so the outer section would win and the card's small text
   would be styled for the wrong background. Custom properties inherit, so the
   nearest ground always wins, which is the behaviour you would expect. */
.on-wine    { background: var(--wine);    color: var(--bone);   --accent: var(--bone);    --link: var(--bone); }
.on-crimson { background: var(--crimson); color: var(--tallow); --accent: var(--tallow);  --link: var(--tallow); }
.on-bone    { background: var(--bone);    color: var(--wine);   --accent: var(--wine);    --link: var(--wine); }
.on-tallow  { background: var(--tallow);  color: var(--wine);   --accent: var(--crimson); --link: var(--crimson); }
.on-flesh   { background: var(--flesh);   color: var(--wine);   --accent: var(--wine);    --link: var(--wine); }

.eyebrow, .cue { color: var(--accent, var(--crimson)); }
a { color: var(--link, var(--crimson)); }

/* Display type takes the ground's own text colour. */
.d1, .d2, .d3, .d4 { color: inherit; }

.eyebrow {
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  margin: 0;
}

/* ---------------------------------------------------------------- layout */
.wrap { max-width: var(--max); margin-inline: auto; padding-inline: var(--gut); }

.band { padding-block: var(--band); }
.band-tight { padding-block: var(--band-tight); }

.stack { display: flex; flex-direction: column; }
.row { display: flex; align-items: center; flex-wrap: wrap; }

/* Grids collapse by available width rather than by device, so a two-column
   block inside a narrow container behaves the same as on a small screen. */
.grid { display: grid; gap: clamp(1.5rem, 4vw, 4rem); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 22rem), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 13rem), 1fr)); }
.grid-5 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 11rem), 1fr)); }

.rule { height: 3px; background: var(--crimson); border: 0; margin: 0; }
.hair { height: 1px; background: currentColor; opacity: 0.3; border: 0; margin: 0; }

/* --------------------------------------------------------------- header */
.topstrip {
  background: var(--wine);
  color: var(--bone);
  padding: 0.5625rem var(--gut);
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
}
.topstrip .second { display: none; }
@media (min-width: 60rem) { .topstrip .second { display: inline-flex; align-items: center; } }

.nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 2.5rem;
  padding: 1.25rem var(--gut);
  background: var(--crimson);
}
.nav__logo { width: clamp(7.5rem, 14vw, 10.25rem); }

.nav__links { display: flex; gap: 0.5rem; align-items: center; list-style: none; margin: 0; padding: 0; }
.nav__links a {
  font-size: 0.8125rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--tallow);              /* 5.1 on crimson; bone would be 3.9 */
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  min-height: 44px;                   /* a real tap target */
  padding-inline: 0.75rem;
}
/* Hover keeps tallow. Switching to bone here (as this rule used to) drops the
   link from 5.1:1 to 3.9:1 on crimson — the hover state was the only place on
   the site that failed AA, and it failed silently because a static audit never
   sees it. The hover feedback is the darkened ground below, not a colour change. */
/* The current page is marked by an underline as well as colour, so it doesn't
   depend on colour vision to read. */
.nav__links a[aria-current="page"] {
  color: var(--tallow);
  box-shadow: inset 0 -3px 0 0 var(--wine);
}

/* Below the nav breakpoint the links become a disclosure, driven by <details>
   so it works with JavaScript switched off. */
.nav .nav__toggle { display: none; }   /* beats .btn's inline-flex on specificity */
@media (max-width: 63.99rem) {
  .nav { flex-wrap: wrap; }
  .nav__links { display: none; }
  .nav .nav__toggle { display: inline-flex; }
  .nav[data-open] .nav__links {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    width: 100%;
    gap: 0;
    border-top: 1px solid rgba(245, 243, 226, 0.25);
    margin-top: 0.75rem;
    padding-top: 0.5rem;
  }
  /* The enquiry button would otherwise wrap onto a row of its own and push the
     photograph down the screen, so below the breakpoint it lives inside the
     menu rather than in the bar. */
  .nav .nav__cta { display: none; }
  .nav[data-open] .nav__cta {
    display: inline-flex;
    justify-content: center;
    width: 100%;
    margin-top: 0.875rem;
  }
}

/* -------------------------------------------------------------- controls */
.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.625rem;
  padding: 1.0625rem 1.875rem;
  font-size: 0.8125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  min-height: 48px;
  text-decoration: none;
  border: 0;
  cursor: pointer;
}
.btn--crimson { background: var(--crimson); color: var(--tallow); }
.btn--bone { background: var(--bone); color: var(--wine); }
.btn--wine { background: var(--wine); color: var(--bone); }
.btn--outline { border: 2px solid currentColor; background: transparent; color: inherit; }
.btn:hover { filter: brightness(1.08); }

.cue {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  text-decoration: none;
}
.cue:hover { text-decoration: underline; }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  padding: 0.4375rem 0.8125rem;
  font-size: 0.71875rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}
.chip--fresh { background: var(--crimson); color: var(--tallow); }
.chip--frozen { background: var(--ice); color: var(--ice-ink); border: 1px solid var(--ice-line); }

/* A value the business hasn't supplied yet. Loud on purpose — it should be
   impossible to ship a page with one of these still on it. */
/* Solid rather than tinted. A translucent fill takes its contrast from whatever
   happens to be behind it, and these markers sit on all five grounds. */
.tbc {
  background: var(--crimson);
  color: var(--tallow);
  padding: 1px 6px;
  font-weight: 700;
}
.on-wine .tbc, .on-crimson .tbc, .footer .tbc {
  background: var(--bone);
  color: var(--wine);
}

/* ----------------------------------------------------------- range cards */
.rcard {
  display: flex;
  flex-direction: column;
  background: #fff;
  outline: 1px solid rgba(78, 19, 37, 0.1);
  container-type: inline-size;        /* the card scales by its own width */
}
.rcard__img { position: relative; aspect-ratio: 1; overflow: hidden; background: var(--paper); }
.rcard__img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.rcard__swatch { height: 9px; outline: 1px solid rgba(78, 19, 37, 0.45); outline-offset: -1px; }
.rcard__body { padding: 1.25rem 1.25rem 1.5rem; display: flex; flex-direction: column; gap: 0.5rem; }
.rcard__title { font-size: clamp(1.125rem, 7cqi, 1.5rem); line-height: 1.05; color: var(--wine); margin: 0; }

/* ---------------------------------------------------------- deck nine-up */
.nineup { position: relative; width: 100%; max-width: 56.25rem; }
.nineup__grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.625rem; }
.nineup__badge {
  position: absolute;
  transform: translate(-50%, -50%);
  pointer-events: none;
}

/* ---------------------------------------------------------------- footer */
.footer { background: var(--wine); color: var(--bone); }
.footer a { color: var(--bone); text-decoration: none; }
.footer a:hover { text-decoration: underline; }
.foot-col .eyebrow { color: var(--bone); margin-bottom: 1rem; display: block; }
.foot-col p { margin: 0 0 0.5625rem; font-size: 0.9375rem; opacity: 0.9; }
.foot-col ul { list-style: none; margin: 0; padding: 0; }
.foot-col li { margin: 0 0 0.5625rem; font-size: 0.9375rem; }

/* ------------------------------------------------------------- utilities */
.media { background: var(--paper); }
.ratio-32 { aspect-ratio: 3 / 2; object-fit: cover; width: 100%; }
.ratio-11 { aspect-ratio: 1; object-fit: cover; width: 100%; }
.ratio-45 { aspect-ratio: 4 / 5; object-fit: cover; width: 100%; }

.slot {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.625rem;
  border: 2px dashed rgba(204, 21, 23, 0.55);
  background: rgba(204, 21, 23, 0.05);
  color: var(--crimson);
  text-align: center;
  padding: 1.5rem;
}

/* ------------------------------------------------------------------ hero */
.hero {
  position: relative;
  min-height: clamp(30rem, 62vw, 51.25rem);
  display: grid;
  align-items: end;
  overflow: hidden;
  background: var(--paper);
}
.hero__slides { position: absolute; inset: 0; }
.hero__slide {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity 0.85s ease-in-out;
}
.hero__slide.is-on { opacity: 1; }

.hero__copy {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1.375rem;
  padding-block: clamp(2.5rem, 6vw, 4.625rem);
  max-width: var(--max);
  width: 100%;
}

.hero__dots {
  position: absolute;
  right: var(--gut);
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 0.6875rem;
}
.hero__dots button {
  width: 44px;                     /* a real tap target, with a small visible mark */
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.hero__dots button::before {
  content: "";
  width: 9px;
  height: 9px;
  border: 1.5px solid var(--wine);
  background: transparent;
  transition: background 0.3s;
}
.hero__dots button[aria-selected="true"]::before { background: var(--wine); }

/* On a phone the frame is portrait while the photograph is landscape. Overlaying
   the copy means either the words sit on the meat or a scrim washes the meat
   out, and neither is worth having — so the hero unstacks: the photograph keeps
   close to its own proportions in a band of its own, and the copy sits below it
   on solid paper. The crossfade and the alt text are unchanged. */
@media (max-width: 47.99rem) {
  .hero { display: block; min-height: 0; }
  .hero__slides { position: relative; inset: auto; aspect-ratio: 16 / 10; }
  .hero__slide { object-position: 62% 50%; }
  .hero__copy { padding-top: clamp(1.75rem, 7vw, 2.5rem); }
  .hero__dots { display: none; }
}

/* -------------------------------------------------------------- masthead */
.masthead { padding-block: clamp(3.25rem, 7vw, 5.5rem); }
.masthead__eyebrow { display: block; margin-bottom: 1.375rem; }
.masthead .d2 { max-width: 52rem; }
.masthead__lede { max-width: 41rem; margin-top: 1.5rem; opacity: 0.86; }

/* ----------------------------------------------------- range page blocks */
/* Two columns where there is room for both, one where there isn't. The photo
   grid keeps its own maximum so it never outgrows the words beside it. */
.rangehead {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
  margin-bottom: clamp(2.5rem, 5vw, 4rem);
}
@media (min-width: 60rem) {
  .rangehead { grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr); }
}

/* Cut and specification. A description list because that is what it is: each
   cut is a term and each spec defines it, which is also how a screen reader
   will read it out. */
.cuts { margin: 0; }
.cuts__row {
  display: grid;
  gap: 0.25rem 1.5rem;
  padding: 0.875rem 0;
  border-top: 1px solid rgba(78, 19, 37, 0.18);
}
.cuts__row:last-child { border-bottom: 1px solid rgba(78, 19, 37, 0.18); }
@media (min-width: 40rem) {
  .cuts { columns: 2; column-gap: clamp(2rem, 5vw, 3.5rem); }
  .cuts__row { grid-template-columns: minmax(0, 11rem) minmax(0, 1fr); break-inside: avoid; }
  /* A column break can leave the first row of the second column without its
     rule, so the rule is restated at the top of each column. */
  .cuts__row:nth-child(1) { break-before: auto; }
}
.cuts__cut { font-size: 1.1875rem; line-height: 1.25; color: var(--wine); margin: 0; }
.cuts__spec { margin: 0; opacity: 0.82; }

.chip--quiet { background: rgba(78, 19, 37, 0.1); color: var(--wine); }
.on-wine .chip--quiet { background: rgba(222, 214, 173, 0.18); color: var(--bone); }

/* Three photographs edge to edge, or a single strip of one on a phone. The
   aspect ratio sits on the cell rather than the image, because each image is
   wrapped in a parallax container that needs a definite height of its own. */
.photostrip { display: grid; grid-template-columns: repeat(3, 1fr); }
.photostrip > * { aspect-ratio: 4 / 3; background: var(--paper); }
.photostrip img { width: 100%; height: 100%; object-fit: cover; }
@media (max-width: 40rem) {
  .photostrip { grid-template-columns: 1fr; }
  .photostrip > * { aspect-ratio: 16 / 9; }
}

/* A placeholder slot on a dark ground. Same loudness, legible colours. */
.slot--dark {
  border-color: rgba(222, 214, 173, 0.55);
  background: rgba(222, 214, 173, 0.08);
  color: var(--bone);
}

/* ------------------------------------------------------------------ form */
.form-card { padding: clamp(1.75rem, 4vw, 2.75rem); }

.form { display: flex; flex-direction: column; gap: 1.125rem; }

.field { display: flex; flex-direction: column; gap: 0.4375rem; border: 0; padding: 0; margin: 0; }
.field-row { display: grid; gap: 1.125rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 10rem), 1fr)); }

.field label, .field legend {
  font-size: 0.78125rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--wine);
  padding: 0;
}

.field input[type="text"],
.field input[type="email"],
.field input[type="tel"],
.field textarea {
  font: inherit;
  color: var(--wine);
  background: var(--tallow);
  border: 1px solid rgba(78, 19, 37, 0.35);
  border-radius: 0;
  padding: 0.75rem 0.875rem;
  min-height: 3rem;
  width: 100%;
}
.field textarea { resize: vertical; min-height: 6.5rem; }
.field input:focus-visible, .field textarea:focus-visible { outline-offset: 1px; }

/* :user-invalid marks a field only once the person has actually engaged with it,
   so an untouched required field is never shown as an error. */
.field input:user-invalid, .field textarea:user-invalid { border-color: var(--crimson); border-width: 2px; }

.checks__row { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.check {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  background: var(--tallow);
  border: 1px solid rgba(78, 19, 37, 0.35);
  padding: 0.5625rem 0.875rem;
  min-height: 44px;
  cursor: pointer;
  font-size: 0.875rem;
  text-transform: none;
  letter-spacing: 0;
  font-weight: 400;
  color: var(--wine);
}
.check input { accent-color: var(--crimson); width: 1.0625rem; height: 1.0625rem; }
.check:has(input:checked) { border-color: var(--crimson); border-width: 2px; padding: calc(0.5625rem - 1px) calc(0.875rem - 1px); }

/* Off-screen rather than display:none — a hidden field some bots still fill in,
   and one a real person never sees or tabs to. */
.form__trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.form__submit { align-self: flex-start; margin-top: 0.5rem; }

.form__status { margin: 0; font-weight: 600; }
.form__status:empty { display: none; }
.form__status[data-kind="ok"] { color: var(--wine); }
.form__status[data-kind="bad"] { color: var(--crimson); }

/* --------------------------------------------------------- detail blocks */
.deets { margin: 0; display: flex; flex-direction: column; gap: 1.125rem; }
.deets > div { display: grid; gap: 0.25rem 1.5rem; }
@media (min-width: 30rem) { .deets > div { grid-template-columns: minmax(0, 8.5rem) minmax(0, 1fr); } }
.deets dt {
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--crimson);
  padding-top: 0.1875rem;
}
.deets dd { margin: 0; }
.deets__line { display: block; }

/* ----------------------------------------------------------------- prose */
/* Long-form text — legal notices, journal bodies. One measure, set in ch so it
   stays around 70 characters whatever the reader's text size. */
.prose { max-width: 68ch; }
.prose h2 {
  font-family: 'Headline Gothic', 'Arial Narrow', Arial, sans-serif;
  text-transform: uppercase;
  font-weight: 400;
  font-size: var(--d4);
  line-height: 1.1;
  margin: 2.5rem 0 0.875rem;
}
.prose h2:first-child { margin-top: 0; }
.prose h3 { font-size: 1.125rem; margin: 2rem 0 0.625rem; }
.prose p { margin: 0 0 1.125rem; }
.prose ul, .prose ol { margin: 0 0 1.125rem; padding-left: 1.25rem; }
.prose li { margin-bottom: 0.4375rem; }
.prose > :last-child { margin-bottom: 0; }

/* Footer navigation is a list of links, not links inside a sentence, so each one
   needs a target of at least 24px in its own right (WCAG 2.2, 2.5.8). */
.foot-col li a, .foot-col p a {
  display: inline-block;
  padding-block: 0.1875rem;
  min-height: 24px;
}

/* ==========================================================================
   MOTION
   Ported from the approved sales deck so the two move the same way: the same
   easing curve, the same 80ms stagger, the same 1.035 image zoom.

   Every rule that moves something is prefixed .js-motion, a class added to
   <html> by motion.js only when the visitor has not asked for reduced motion.
   With JavaScript off, or with reduced motion on, none of these rules match and
   the page renders in its finished state. Nothing is hidden waiting for a script
   that might not run.
   ========================================================================== */

/* The one easing curve on the site. Quick out of the gate, long settle. */
:root {
  --ease: cubic-bezier(0.2, 0.7, 0.3, 1);
  /* The header's height. motion.js overwrites this with the measured value;
     the default here is the uncondensed height, which is what a visitor
     without JavaScript actually gets. */
  --nav-h: 7.875rem;
}

html {
  scroll-behavior: smooth;
  /* So an anchor link doesn't park its target underneath the sticky header. */
  scroll-padding-top: calc(var(--nav-h) + 1rem);
}
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* ------------------------------------------------------------ sticky header */
/* The strip above scrolls away; the navigation stays. Sticky rather than fixed,
   so it occupies real space at the top of the document and nothing has to be
   padded down to compensate. */
.nav {
  position: sticky;
  top: 0;
  z-index: 40;
  transition: padding 0.28s var(--ease), box-shadow 0.28s var(--ease);
}
.nav__logo { transition: width 0.28s var(--ease); }

/* Set once the page has scrolled past the top. Applied on <html> so anything
   else that needs to know can read it too. */
.is-scrolled .nav {
  padding-block: 0.625rem;
  box-shadow: 0 2px 20px rgba(78, 19, 37, 0.28);
}
.is-scrolled .nav__logo { width: clamp(6.25rem, 11vw, 8rem); }

/* No resting opacity here. The deck fades its nav links to .82, which is fine
   against wine; against crimson it takes tallow from 5.1:1 to 4.0:1. Full
   opacity throughout, and the hover state is carried by the ground alone —
   one thing changing rather than three. */
.nav__links a { transition: background-color 0.2s, box-shadow 0.2s; }
/* Tinted towards wine, not towards bone. The deck's nav sits on wine, where a
   pale tint lightens the ground and lifts the contrast; this one sits on
   crimson, where the same tint lightens the ground *under* pale text and drops
   it to 4.3:1. Darkening instead takes it to 6.3:1 and still reads as a
   pressed-in state. */
.nav__links a:hover { background: rgba(78, 19, 37, 0.22); }

/* --------------------------------------------------------------- reveals */
.js-motion [data-rise] {
  opacity: 0;
  transform: translateY(18px);
}
.js-motion [data-rise].in {
  opacity: 1;
  transform: none;
  transition: opacity 0.6s ease-out, transform 0.6s var(--ease);
  transition-delay: calc(var(--d, 0) * 80ms);
}

/* Children of a revealed group stagger rather than arriving together. Set on the
   container so the markup stays clean: one attribute, not one per child. */
.js-motion [data-stagger] > * { opacity: 0; transform: translateY(16px); }
.js-motion [data-stagger].in > * {
  opacity: 1;
  transform: none;
  transition: opacity 0.5s ease-out, transform 0.5s var(--ease);
}
.js-motion [data-stagger].in > *:nth-child(2) { transition-delay: 80ms; }
.js-motion [data-stagger].in > *:nth-child(3) { transition-delay: 160ms; }
.js-motion [data-stagger].in > *:nth-child(4) { transition-delay: 240ms; }
.js-motion [data-stagger].in > *:nth-child(5) { transition-delay: 320ms; }
.js-motion [data-stagger].in > *:nth-child(6) { transition-delay: 400ms; }
.js-motion [data-stagger].in > *:nth-child(7) { transition-delay: 480ms; }
.js-motion [data-stagger].in > *:nth-child(8) { transition-delay: 560ms; }
.js-motion [data-stagger].in > *:nth-child(9) { transition-delay: 640ms; }

/* A three-column grid staggered a column at a time reads as a diagonal wave
   rather than a queue, and it has to be quick: nine cells at 80ms leaves a hole
   in the layout for over a second while someone is still scrolling. These are
   the deck's own delays — three rows, each starting a beat after the last,
   everything landing inside 220ms. */
.js-motion [data-stagger="wave"].in > *:nth-child(1) { transition-delay: 0ms; }
.js-motion [data-stagger="wave"].in > *:nth-child(2) { transition-delay: 50ms; }
.js-motion [data-stagger="wave"].in > *:nth-child(3) { transition-delay: 100ms; }
.js-motion [data-stagger="wave"].in > *:nth-child(4) { transition-delay: 60ms; }
.js-motion [data-stagger="wave"].in > *:nth-child(5) { transition-delay: 110ms; }
.js-motion [data-stagger="wave"].in > *:nth-child(6) { transition-delay: 160ms; }
.js-motion [data-stagger="wave"].in > *:nth-child(7) { transition-delay: 120ms; }
.js-motion [data-stagger="wave"].in > *:nth-child(8) { transition-delay: 170ms; }
.js-motion [data-stagger="wave"].in > *:nth-child(9) { transition-delay: 220ms; }
.js-motion [data-stagger="wave"].in > * { transition-duration: 0.45s; }

/* An element whose finished state is not full opacity has to say so, or the
   reveal would brighten it past where the design puts it. */
.js-motion .meta[data-rise].in { opacity: 0.72; }

/* -------------------------------------------------------------- parallax */
/* The container clips; the image inside is taller than it and gets nudged. */
[data-plx] { overflow: hidden; }
[data-plx] > img {
  width: 100%;
  height: 118%;
  object-fit: cover;
  margin-block: -9%;
  will-change: transform;
}

/* ----------------------------------------------------------- image zoom */
/* A slow, small scale on hover. The wrapper clips, so the frame stays put and
   only the picture inside it moves. */
.zoom { overflow: hidden; }
.zoom > img { transition: transform 0.5s var(--ease); }
a:hover .zoom > img,
.zoom:hover > img,
article:hover .zoom > img { transform: scale(1.035); }

/* ------------------------------------------------------------- hovers */
/* Buttons get a defined hover pairing per variant rather than a blanket
   brightness filter, which lightens the crimson ones towards failing contrast. */
.btn {
  transition: background-color 0.22s var(--ease), color 0.22s var(--ease),
              border-color 0.22s var(--ease), transform 0.22s var(--ease);
}
.btn:hover { filter: none; }
.btn--crimson:hover { background: var(--wine); color: var(--bone); }
.btn--wine:hover    { background: var(--crimson); color: var(--tallow); }
.btn--bone:hover    { background: var(--tallow); color: var(--wine); }
.btn--outline:hover { background: currentColor; }
.btn--outline:hover { color: var(--wine); background: var(--bone); border-color: var(--bone); }
.on-crimson .btn--outline:hover { color: var(--crimson); background: var(--tallow); border-color: var(--tallow); }
.btn:active { transform: translateY(1px); }

/* The arrow on a cue walks forward. margin rather than transform on the text so
   the link's hit area doesn't shift under the pointer. */
.cue { transition: color 0.2s var(--ease); }
.cue__arrow { display: inline-block; transition: transform 0.24s var(--ease); }
.cue:hover .cue__arrow { transform: translateX(0.3125rem); }

/* Range cards lift a little and firm up their outline. */
.rcard {
  transition: transform 0.3s var(--ease), box-shadow 0.3s var(--ease),
              outline-color 0.3s var(--ease);
}
.rcard:hover {
  transform: translateY(-4px);
  box-shadow: 0 12px 28px rgba(78, 19, 37, 0.14);
  outline-color: rgba(78, 19, 37, 0.3);
}
@media (prefers-reduced-motion: reduce) {
  .rcard:hover { transform: none; }
}

/* A cut and its specification highlight together, so it is obvious which
   specification belongs to which cut on a long list. */
.cuts__row { transition: background-color 0.2s; }
.cuts__row:hover { background: rgba(78, 19, 37, 0.05); }

/* Journal headline links underline on hover rather than being underlined always. */
.jcard__title a { text-decoration: none; transition: color 0.2s; }
article:hover .jcard__title a { text-decoration: underline; text-underline-offset: 3px; }

/* Form fields and the range chips. */
.field input, .field textarea { transition: border-color 0.2s, box-shadow 0.2s; }
.field input:hover, .field textarea:hover { border-color: rgba(78, 19, 37, 0.6); }
.check { transition: border-color 0.2s, background-color 0.2s; }
.check:hover { border-color: var(--crimson); }
.chip { transition: background-color 0.2s, color 0.2s; }

/* --------------------------------------------------------- brand marks */
/* The two marks that are drawn to move: a round seal turns, a hung tag swings.
   Both are slow enough to read as ambient rather than as something demanding
   attention, and both stop entirely under reduced motion. */
@keyframes fmm-spin { to { transform: rotate(360deg); } }
@keyframes fmm-swing { from { transform: rotate(-4.5deg); } to { transform: rotate(4.5deg); } }
.js-motion .spin { animation: fmm-spin 46s linear infinite; }
.js-motion .swing {
  transform-origin: 50% 7%;
  animation: fmm-swing 5.5s ease-in-out infinite alternate;
}
/* Two seals on one line turning in lockstep look mechanical. The second goes
   the other way. */
.js-motion .spin--rev { animation-direction: reverse; }

/* ------------------------------------------------- sticky range switcher */
/* Our range is five long sections. This keeps the jump list to hand rather than
   making someone scroll back to the top to reach the fourth one. */
.rangenav {
  position: sticky;
  top: var(--nav-h);
  z-index: 20;
  background: var(--wine);
  border-block: 1px solid rgba(222, 214, 173, 0.22);
  padding-block: 0.625rem;
}
.rangenav ul {
  display: flex;
  gap: 0.25rem;
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.rangenav ul::-webkit-scrollbar { display: none; }
.rangenav a {
  display: inline-flex;
  align-items: center;
  min-height: 40px;
  padding-inline: 0.75rem;
  white-space: nowrap;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--bone);
  text-decoration: none;
  opacity: 0.82;
  transition: opacity 0.2s, background-color 0.2s;
}
.rangenav a:hover { opacity: 1; background: rgba(222, 214, 173, 0.16); }
/* The section being read is marked as the page scrolls. */
.rangenav a[aria-current="true"] {
  opacity: 1;
  background: var(--crimson);
  color: var(--tallow);
}

/* The phone number and email address are the two things a visitor on a phone is
   most likely to tap, so they get a target of their own rather than inheriting
   the line height of the text around them (WCAG 2.2, 2.5.8). */
.deets dd a {
  display: inline-block;
  padding-block: 0.1875rem;
  min-height: 24px;
}

/* ------------------------------------------------------------------ map */
/* The panel that stands in for the map until someone asks for it. It has the
   shape the map will have, so nothing jumps when it loads. */
.mapbox {
  aspect-ratio: 16 / 10;
  background: var(--bone);
  color: var(--wine);
  --link: var(--wine);
  display: grid;
  overflow: hidden;
}
.mapbox__plate {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 0.875rem;
  padding: clamp(1.25rem, 4vw, 2.25rem);
  /* A hint of the map to come, rather than a flat slab. */
  background-image:
    linear-gradient(rgba(78, 19, 37, 0.07) 1px, transparent 1px),
    linear-gradient(90deg, rgba(78, 19, 37, 0.07) 1px, transparent 1px);
  background-size: 2.5rem 2.5rem;
}
.mapbox__pin { color: var(--crimson); flex: none; }
.mapbox__where { font-size: var(--d4); line-height: 1; margin: 0; }
.mapbox__note { opacity: 0.8; max-width: 30rem; }

/* Once loaded the iframe is the whole panel. */
.mapbox iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}

/* ------------------------------------------------------- product tiles */
/* Each frame in a nine-up. The wrapper clips so the photograph can grow inside
   a frame that stays exactly where it is — the grid never reflows on hover. */
.tile {
  display: block;
  overflow: hidden;
  background: var(--paper);
  cursor: zoom-in;
  transition: box-shadow 0.3s var(--ease), outline-color 0.3s var(--ease);
  outline: 1px solid transparent;
  outline-offset: -1px;
}
.tile img {
  display: block;
  width: 100%;
  height: auto;
  transition: transform 0.5s var(--ease);
}
.tile:hover img, .tile:focus-visible img { transform: scale(1.06); }
.tile:hover {
  box-shadow: 0 8px 22px rgba(78, 19, 37, 0.16);
  outline-color: rgba(78, 19, 37, 0.25);
}
/* The focus ring has to clear the clipped edge, so it goes outside the box. */
.tile:focus-visible { outline: 3px solid var(--crimson); outline-offset: 2px; }

/* ----------------------------------------------------------- lightbox */
.lightbox {
  padding: 0;
  border: 0;
  background: transparent;
  max-width: min(64rem, 92vw);
  max-height: 92vh;
  overflow: visible;
}
.lightbox img {
  display: block;
  width: auto;
  height: auto;
  max-width: min(64rem, 92vw);
  max-height: 92vh;
  background: var(--paper);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45);
}
.lightbox::backdrop { background: rgba(78, 19, 37, 0.94); }

.lightbox__close {
  position: absolute;
  top: 0;
  right: 0;
  transform: translate(35%, -35%);
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: var(--crimson);
  color: var(--tallow);
  cursor: pointer;
  transition: background-color 0.2s var(--ease);
}
.lightbox__close:hover { background: var(--tallow); color: var(--wine); }

/* The dialog lives in the top layer, where display toggles between none and
   block — so the transition needs allow-discrete to survive, and a starting
   style to have something to come from. Where this is unsupported the dialog
   simply appears, which is a perfectly good outcome. */
@media (prefers-reduced-motion: no-preference) {
  .lightbox, .lightbox::backdrop {
    transition: opacity 0.25s var(--ease), display 0.25s allow-discrete, overlay 0.25s allow-discrete;
    opacity: 0;
  }
  .lightbox { transition: opacity 0.25s var(--ease), transform 0.25s var(--ease), display 0.25s allow-discrete, overlay 0.25s allow-discrete; }
  .lightbox[open] { opacity: 1; transform: none; }
  .lightbox[open]::backdrop { opacity: 1; }
  @starting-style {
    .lightbox[open] { opacity: 0; transform: scale(0.97); }
    .lightbox[open]::backdrop { opacity: 0; }
  }
}

/* --------------------------------------------------- About endplate */
.endplate {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: clamp(1.5rem, 4vw, 3rem);
}
.endplate img { width: clamp(5.5rem, 9vw, 8.25rem); }
.endplate__line {
  font-size: var(--d3);
  line-height: 1.05;
  margin: 0;
  text-align: center;
}
/* Below the point where three across stops fitting, the seals sit either side of
   a line that has dropped under them. */
@media (max-width: 48rem) {
  .endplate { grid-template-columns: auto auto; justify-content: center; gap: 1.5rem 2rem; }
  .endplate__line { grid-column: 1 / -1; order: -1; }
}
