/* ==========================================================================
   Weka Roastery — components
   Buttons, forms, cards, chips, timeline, empty states, messages.
   ========================================================================== */

/* ----------------------------------------------------------- buttons ---- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-base);
  min-block-size: var(--tap-target);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius);
  font-size: var(--text-body-md);
  font-weight: var(--weight-medium);
  text-align: center;
  transition: background var(--transition), color var(--transition), border-color var(--transition);
}
.btn:active { transform: scale(0.98); }
.btn[disabled], .btn[aria-disabled="true"] { opacity: 0.5; cursor: not-allowed; }

.btn--primary { background: var(--color-accent); color: var(--color-on-accent); }
.btn--primary:hover { background: var(--color-primary-container); }

.btn--secondary {
  background: transparent;
  color: var(--color-primary);
  border: 1px solid var(--color-primary);
}
.btn--secondary:hover { background: var(--color-surface-container); }

.btn--ghost { color: var(--color-on-surface-variant); }
.btn--ghost:hover { background: var(--color-surface-container); color: var(--color-primary); }

.btn--danger { color: var(--color-error); }
.btn--danger:hover { background: var(--color-error-container); }

.btn--block { inline-size: 100%; }

/* ------------------------------------------------------------- forms ---- */

.field { margin-block-end: var(--space-md); }

.field__label {
  display: block;
  margin-block-end: var(--space-xs);
  font-size: var(--text-label-md);
  font-weight: var(--weight-medium);
  color: var(--color-on-surface);
}
.field__label .required { color: var(--color-error); }

.field__input {
  inline-size: 100%;
  min-block-size: var(--tap-target);
  padding: var(--space-sm);
  background: var(--color-surface-container-lowest);
  color: var(--color-on-surface);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius);
  transition: border-color var(--transition), box-shadow var(--transition);
}
.field__input::placeholder { color: var(--color-outline); }
.field__input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgb(37 22 14 / 10%);
}
.field__input[aria-invalid="true"] { border-color: var(--color-error); }

.field__help { margin-block-start: var(--space-xs); font-size: var(--text-label-sm); color: var(--color-on-surface-variant); }

/* Errors carry an icon as well as colour — never colour alone. */
.field__error {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin-block-start: var(--space-xs);
  font-size: var(--text-label-md);
  color: var(--color-error);
}

textarea.field__input { min-block-size: 96px; resize: vertical; }

/* --------------------------------------------------------- select ------ */
/* A native <select> ignores most of .field__input: the browser draws its own
   arrow and its own padding, so the control ends up looking foreign next to
   our inputs. Strip the native appearance and draw the chevron ourselves,
   positioned with a LOGICAL property so it sits on the correct side in RTL. */

select.field__input {
  appearance: none;
  -webkit-appearance: none;
  padding-block: var(--space-sm);
  /* Room for the chevron on the inline-end side, normal padding on the other. */
  padding-inline: var(--space-sm) calc(var(--space-sm) + 20px);
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%234f4540' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-size: 16px 16px;
  background-position: left var(--space-sm) center;
  line-height: var(--leading-body-md);
  cursor: pointer;
  text-overflow: ellipsis;
}
/* RTL puts the chevron on the left; LTR on the right. */
[dir="ltr"] select.field__input {
  padding-inline: calc(var(--space-sm) + 20px) var(--space-sm);
  background-position: right var(--space-sm) center;
}
select.field__input:hover { border-color: var(--color-outline); }
select.field__input:focus {
  outline: none;
  border-color: var(--color-primary);
  box-shadow: 0 0 0 3px rgb(37 22 14 / 10%);
}
/* Native option lists cannot be fully styled; at least keep them legible. */
select.field__input option { background: var(--color-surface-container-lowest); color: var(--color-on-surface); }

/* Number inputs: the native spinners are tiny, sit on the wrong side in RTL,
   and are inconsistent between browsers. Quantity fields use a plain box. */
input[type="number"].field__input {
  appearance: textfield;
  -moz-appearance: textfield;
  text-align: center;
}
input[type="number"].field__input::-webkit-outer-spin-button,
input[type="number"].field__input::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}

/* Search inputs: hide the browser's own clear button, which is unstyleable
   and lands on the wrong side in RTL. */
input[type="search"].field__input::-webkit-search-cancel-button {
  -webkit-appearance: none;
}


/* --------------------------------------------------------- OTP input ---- */

.otp-input {
  inline-size: 100%;
  padding: var(--space-md);
  font-family: var(--font-headline);
  font-size: 2rem;
  letter-spacing: 0.5em;
  text-align: center;
  background: var(--color-surface-container-lowest);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-lg);
}
.otp-input:focus { outline: none; border-color: var(--color-primary); }

.otp-timer {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  margin-block-start: var(--space-sm);
  font-size: var(--text-label-md);
  color: var(--color-on-surface-variant);
}

/* ------------------------------------------------------------- cards ---- */

.card {
  background: var(--color-surface-container-lowest);
  border: var(--border-hairline);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.card__body { padding: var(--space-md); }

.product-card { display: flex; flex-direction: column; block-size: 100%; }
.product-card__media { position: relative; aspect-ratio: 4 / 3; background: var(--color-surface-container-low); }
.product-card__media img { inline-size: 100%; block-size: 100%; object-fit: cover; }
.product-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  flex: 1;
  padding: var(--space-md);
}

/* Flavour-note chips: ONE line that drifts itself.
   This previously reserved two lines so every card matched height, which
   left short-note cards carrying dead space. A single non-wrapping line is
   both shorter and uniform by construction, and notes that do not fit are
   revealed by notes-marquee.js drifting the row end to end.

   The row is NOT hand-scrollable — see the overflow note below. Motion is
   therefore the only route to an overflowing note, so the reduced-motion
   rule at the end must keep every note visible without it.

   Note: this rule existed in TWO places in this file and the later copy won
   silently. Keep exactly one. */
.product-card__notes {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--space-sm);
  /* hidden, not auto: the row drives itself, so there is no manual scrolling
     to afford. This also stops a trackpad swipe from fighting the animation,
     and keeps the card's tap target whole — a finger dragging sideways here
     scrolls nothing and the tap still opens the product. */
  overflow: hidden;
}
/* The chips are wrapped in this track by notes-marquee.js so one transform
   moves them together. Declared here rather than set from script so the row
   is laid out correctly on the very first paint, before the JS runs. */
.product-card__notes-track {
  display: flex;
  flex-wrap: nowrap;
  gap: var(--space-sm);
  will-change: transform;
}
.product-card__notes .chip { flex: 0 0 auto; }

/* Reduced motion: no drift. The row cannot be scrolled by hand either, so
   the notes must all be visible at rest — wrap them onto as many lines as
   they need rather than hiding any. Card heights go uneven, which is the
   correct trade: never hide content behind motion someone has opted out of. */
@media (prefers-reduced-motion: reduce) {
  .product-card__notes { flex-wrap: wrap; overflow: visible; }
  /* The track must wrap too: the chips live inside it, so wrapping only the
     outer row would leave them on one clipped line. */
  .product-card__notes-track { flex-wrap: wrap; transform: none !important; }
}

/* Name and origin are clamped and given a reserved height, so every card in
   a row is the same height whatever its text does. At two-up on a phone the
   column is narrow enough that one product's name wraps to two lines and the
   next fits on one — without this the row is visibly ragged. */
.product-card__name {
  font-family: var(--font-headline);
  /* 18px, down from the 24px headline size: at two cards per row on a phone
     that was close to the page heading, and a catalogue reads as a list of
     products rather than a stack of headlines. Stays on the type scale. */
  font-size: var(--text-body-lg);
  line-height: 1.45;
  color: var(--color-on-surface);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* Two lines reserved, so a one-line name does not shorten its card. */
  min-block-size: calc(2 * 1.45em);
}
.product-card__meta {
  font-size: var(--text-label-md);
  line-height: 1.5;
  color: var(--color-on-surface-variant);
  display: -webkit-box;
  -webkit-line-clamp: 1;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-block-size: 1.5em;
}
.product-card__price {
  margin-block-start: auto;
  font-weight: var(--weight-bold);
  color: var(--color-primary);
}
.product-grid {
  display: grid;
  /* Two per row on a phone. auto-fill with a 240px minimum gave ONE column
     below 500px, so a catalogue was one tall stack — two cards let a shopper
     compare without scrolling between them. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-sm);
}
@media (min-width: 640px) {
  .product-grid {
    grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
    gap: var(--space-md);
  }
}

/* ------------------------------------------------------------- chips ---- */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 4px var(--space-sm);
  background: var(--color-tertiary-fixed);
  color: var(--color-on-tertiary-fixed);
  border-radius: var(--radius-full);
  font-size: var(--text-label-sm);
}
.chip--outline { background: transparent; border: 1px solid var(--color-outline-variant); color: var(--color-on-surface-variant); }

/* Badge for order status. */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 4px var(--space-sm);
  border-radius: var(--radius-full);
  font-size: var(--text-label-sm);
  font-weight: var(--weight-medium);
}
.badge--pending   { background: var(--color-surface-container-high); color: var(--color-on-surface-variant); }
.badge--confirmed,
.badge--roasting  { background: var(--color-primary-fixed); color: var(--color-on-primary-fixed); }
.badge--dispatched{ background: var(--color-secondary-container); color: var(--color-on-secondary-fixed); }
.badge--delivered { background: var(--color-success-container); color: var(--color-on-success-container); }
.badge--cancelled { background: var(--color-error-container); color: var(--color-on-error-container); }

/* ---------------------------------------------------- flavour profile --- */

.profile-bar__row {
  display: grid;
  grid-template-columns: 5rem 1fr;
  align-items: center;
  gap: var(--space-sm);
}
.profile-bar__row + .profile-bar__row { margin-block-start: var(--space-sm); }

/* Both of these are <span>s in the markup, so they MUST be told to be
   blocks: an inline element ignores width and height, which rendered every
   bar at zero. */
.profile-bar__track {
  display: block;
  block-size: 8px;
  background: var(--color-surface-container-high);
  border-radius: var(--radius-full);
  overflow: hidden;
}
.profile-bar__fill {
  display: block;
  block-size: 100%;
  background: var(--color-primary-container);
  border-radius: var(--radius-full);
  transition: inline-size var(--transition-slow);
}

/* ---------------------------------------------------------- timeline ---- */

.timeline { display: grid; gap: 0; }
.timeline__step { display: grid; grid-template-columns: 32px 1fr; gap: var(--space-sm); }
.timeline__marker { display: grid; justify-items: center; gap: 2px; }
.timeline__dot {
  inline-size: 16px; block-size: 16px;
  border-radius: var(--radius-full);
  background: var(--color-surface-container-high);
  border: 2px solid var(--color-outline-variant);
}
.timeline__line { inline-size: 2px; flex: 1; min-block-size: 32px; background: var(--color-outline-variant); }
.timeline__step:last-child .timeline__line { display: none; }
.timeline__body { padding-block-end: var(--space-md); }

.timeline__step[data-state="done"] .timeline__dot,
.timeline__step[data-state="current"] .timeline__dot {
  background: var(--color-primary); border-color: var(--color-primary);
}
.timeline__step[data-state="done"] .timeline__line { background: var(--color-primary); }
.timeline__step[data-state="current"] .timeline__body { font-weight: var(--weight-bold); color: var(--color-primary); }
.timeline__step[data-state="upcoming"] .timeline__body { color: var(--color-on-surface-variant); }
.timeline__step[data-state="cancelled"] .timeline__dot { background: var(--color-error); border-color: var(--color-error); }

/* ------------------------------------------------------ empty states ---- */
/* Every list needs one: it is where new users land most often. */

.empty-state {
  display: grid;
  justify-items: center;
  gap: var(--space-sm);
  padding: var(--space-xl) var(--space-md);
  text-align: center;
}
.empty-state__icon { font-size: 48px; color: var(--color-outline); }
.empty-state__title { font-family: var(--font-headline); font-size: var(--text-headline-sm); }
.empty-state__text { max-inline-size: 44ch; color: var(--color-on-surface-variant); }

/* ---------------------------------------------------------- messages ---- */

.messages { display: grid; gap: var(--space-sm); margin-block-end: var(--space-md); }
.message {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius);
  border-inline-start: 3px solid currentcolor;
}
.message--success { background: var(--color-success-container); color: var(--color-on-success-container); }
.message--error   { background: var(--color-error-container); color: var(--color-on-error-container); }
.message--warning { background: var(--color-primary-fixed); color: var(--color-on-primary-fixed); }
.message--info    { background: var(--color-surface-container-high); color: var(--color-on-surface); }

/* -------------------------------------------------------------- misc ---- */

.section { padding-block: var(--space-lg); }
.section__title { margin-block-end: var(--space-md); font-size: var(--text-headline-md); font-family: var(--font-headline); }

.divider { block-size: 1px; background: var(--color-outline-variant); border: 0; margin-block: var(--space-md); }
/* A break between two groups of fields rather than between two rows. Matched
   to the card's own 24px padding: at --space-lg (48px) the rule claimed twice
   the breathing room of the card containing it, which read as a gap rather
   than a division. Spacing scale only — CONTRACT §2.1. */
.divider--section { margin-block: var(--space-md); }

.breadcrumb { display: flex; flex-wrap: wrap; gap: var(--space-xs); font-size: var(--text-label-md); color: var(--color-on-surface-variant); }
.breadcrumb a:hover { color: var(--color-primary); text-decoration: underline; }

/* ============================================================== HERO =====
   Three slides stacked in the same box, crossfading. Stacking rather than
   sliding keeps the layout stable in RTL and avoids a horizontal scroller.
   ======================================================================== */

.hero { position: relative; min-block-size: 68vh; overflow: hidden; }

.hero__slides { position: relative; min-block-size: inherit; display: grid; }

/* Every slide occupies the same grid cell; only opacity separates them. */
.hero__slide {
  grid-area: 1 / 1;
  position: relative;
  display: grid;
  align-items: center;
  min-block-size: 68vh;
  opacity: 0;
  visibility: hidden;
  transition: opacity 900ms ease, visibility 900ms;
}
.hero__slide.is-active { opacity: 1; visibility: visible; }

.hero__media { position: absolute; inset: 0; }
.hero__media img { inline-size: 100%; block-size: 100%; object-fit: cover; }
.hero__media::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgb(37 22 14 / 25%), rgb(37 22 14 / 72%));
}

.hero__inner { position: relative; z-index: 1; padding-block: var(--space-xl); color: #fff; }
/* A scrim behind the copy. The bag photography is bright and busy on the
   reading side, and white text sat straight on it was hard to read — the
   gradient is anchored to the RTL start so it darkens only behind the words,
   leaving the product itself clear. */
.hero__slide::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  background: linear-gradient(
    to left,
    rgb(26 26 26 / 72%) 0%,
    rgb(26 26 26 / 45%) 42%,
    rgb(26 26 26 / 8%) 72%,
    transparent 100%);
  pointer-events: none;
}
.hero__eyebrow { color: var(--color-primary-fixed); margin-block-end: var(--space-sm); }
.hero__title { color: #fff; max-inline-size: 18ch; margin-block-end: 0; }
.hero__sub { margin-block-start: var(--space-md); max-inline-size: 52ch; color: rgb(255 255 255 / 88%); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-sm); margin-block-start: var(--space-lg); }
.hero .btn--secondary { color: #fff; border-color: rgb(255 255 255 / 60%); }
.hero .btn--secondary:hover { background: rgb(255 255 255 / 12%); }

/* ----------------------------------------------------------- controls -- */

.hero__arrow {
  position: absolute;
  inset-block-start: 50%;
  transform: translateY(-50%);
  z-index: 2;
  display: grid;
  place-items: center;
  inline-size: var(--tap-target);
  block-size: var(--tap-target);
  border-radius: var(--radius-full);
  background: rgb(37 22 14 / 35%);
  color: #fff;
  backdrop-filter: blur(4px);
  transition: background var(--transition);
}
.hero__arrow:hover { background: rgb(37 22 14 / 60%); }
/* PHYSICAL left/right, deliberately not logical.

   The dots row above is forced to direction: ltr so slide 1 sits leftmost and
   the sequence reads left-to-right. The arrows must obey that same spatial
   model, or they contradict it: with logical properties the "next" arrow
   landed on the physical LEFT in RTL, so clicking the left chevron advanced
   the carousel rightward — pointing one way and moving the other. */
.hero__arrow--prev { left: var(--space-sm); right: auto; }
.hero__arrow--next { right: var(--space-sm); left: auto; }

.hero__dots {
  position: absolute;
  inset-block-end: var(--space-md);
  inset-inline: 0;
  z-index: 2;
  display: flex;
  justify-content: center;
  gap: var(--space-base);
  /* A progress row reads left-to-right even in an RTL page: dot 1 is the
     first slide. Without this the row mirrors and dot 1 sits on the right,
     which made the active dot look like it was on the wrong slide. */
  direction: ltr;
}
.hero__dot {
  inline-size: 8px;
  block-size: 8px;
  padding: 0;
  border-radius: var(--radius-full);
  background: rgb(255 255 255 / 45%);
  transition: inline-size var(--transition), background var(--transition);
}
/* The active dot differs by WIDTH as well as colour, so the state is not
   carried by colour alone. */
.hero__dot.is-active { inline-size: 24px; background: #fff; }

/* A dot is only 8px tall but needs a 44px touch target. */
.hero__dot::before {
  content: "";
  position: absolute;
  inline-size: var(--tap-target);
  block-size: var(--tap-target);
  transform: translate(-50%, -50%);
}
.hero__dots { position: absolute; }
.hero__dot { position: relative; }

@media (prefers-reduced-motion: reduce) {
  .hero__slide { transition: none; }
}

/* ============================================================= SECTIONS == */

.section__head { display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: var(--space-sm); margin-block-end: var(--space-md); }
.section__head .section__title { margin-block-end: 0; }

.story { display: grid; gap: var(--space-lg); align-items: center; }
.story__text { display: grid; gap: var(--space-md); justify-items: start; }
.story__media img { inline-size: 100%; border-radius: var(--radius-lg); aspect-ratio: 4 / 3; object-fit: cover; }
@media (min-width: 768px) { .story { grid-template-columns: 1fr 1fr; } }

/* ---------------------------------------------------------- categories -- */

.category-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: var(--space-md); }
.category-card { display: grid; gap: var(--space-sm); text-align: center; }
.category-card__media { display: block; border-radius: var(--radius-lg); overflow: hidden; aspect-ratio: 1; background: var(--color-surface-container); }
.category-card__media img { inline-size: 100%; block-size: 100%; object-fit: cover; transition: transform var(--transition-slow); }
.category-card:hover .category-card__media img { transform: scale(1.04); }
.category-card__name { font-family: var(--font-headline); font-size: var(--text-headline-sm); color: var(--color-on-surface); }

/* -------------------------------------------------------- product card -- */

.product-card__link { display: flex; flex-direction: column; block-size: 100%; }
.product-card__media { position: relative; display: block; aspect-ratio: 4 / 3; background: var(--color-surface-container-low); overflow: hidden; }
.product-card__media img { inline-size: 100%; block-size: 100%; object-fit: cover; transition: transform var(--transition-slow); }
.product-card:hover .product-card__media img { transform: scale(1.04); }
.product-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  flex: 1;
  padding: var(--space-md);
}

.product-card__flag {
  position: absolute;
  inset-block-start: var(--space-base);
  inset-inline-start: var(--space-base);
  padding: 2px var(--space-base);
  background: var(--color-inverse-surface);
  color: var(--color-inverse-on-surface);
  border-radius: var(--radius-full);
  font-size: var(--text-label-sm);
}
.product-card__flag--featured { background: var(--color-accent); color: var(--color-on-accent); }

/* ------------------------------------------------------------- promise -- */

.promise-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: var(--space-md); }
.promise { display: grid; gap: var(--space-xs); justify-items: start; padding: var(--space-md); background: var(--color-surface-container-low); border-radius: var(--radius-lg); }
.promise__icon { font-size: 32px; color: var(--color-primary-container); }
.promise__title { font-family: var(--font-headline); font-size: var(--text-headline-sm); }

/* ============================================================== AUTH ===== */

.auth-layout { display: grid; place-items: center; padding-block: var(--space-xl); }
.auth-card { inline-size: min(440px, 100%); }
/* Heading rhythm follows the scale: a 32px headline gets --space-sm (12px)
   beneath it, and its lead paragraph gets --space-md (24px) before the form.
   4px under a 32px heading reads as a collision. */
.auth-card__title {
  font-family: var(--font-headline);
  font-size: var(--text-headline-md);
  line-height: var(--leading-headline-md);
  margin-block-end: var(--space-sm);
}
.auth-card__lead { margin-block-end: var(--space-md); }
.auth-card__resend { margin-block-start: var(--space-sm); }
.auth-card__legal { margin-block-start: var(--space-md); font-size: var(--text-label-sm); text-align: center; }
.auth-card__legal a { color: var(--color-primary); text-decoration: underline; }

/* ============================================================ ACCOUNT ==== */

.account-layout { display: grid; gap: var(--space-lg); padding-block: var(--space-md); }
@media (min-width: 900px) { .account-layout { grid-template-columns: 260px 1fr; align-items: start; } }

.account-nav { padding: var(--space-md); background: var(--color-surface-container-low); border-radius: var(--radius-lg); }
.account-nav__user {
  display: flex; align-items: center; gap: var(--space-base);
  padding-block-end: var(--space-sm); margin-block-end: var(--space-sm);
  border-block-end: var(--border-hairline);
  font-weight: var(--weight-medium);
}
.account-main { min-inline-size: 0; }

.address-list, .order-list { display: grid; gap: var(--space-md); }

/* ------------------------------------------------------------- orders --- */

.order-items { display: grid; gap: var(--space-sm); }
.order-item {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: var(--space-xs) var(--space-sm);
  padding-block: var(--space-sm);
  border-block-end: var(--border-hairline);
}
.order-item__name { font-weight: var(--weight-medium); }
.order-item__price { grid-row: span 2; align-self: center; font-weight: var(--weight-bold); color: var(--color-primary); }

.order-totals { display: grid; gap: var(--space-xs); margin-block-start: var(--space-md); }
.order-totals > div { display: flex; justify-content: space-between; gap: var(--space-md); }
.order-totals dt, .order-totals dd { margin: 0; }
.order-totals dd { font-variant-numeric: proportional-nums; }
.order-totals__grand {
  padding-block-start: var(--space-sm);
  border-block-start: var(--border-hairline);
  font-weight: var(--weight-bold);
  font-size: var(--text-body-lg);
  color: var(--color-primary);
}

/* =============================================================== PROSE === */

.prose { max-inline-size: 72ch; padding-block: var(--space-lg); }
.prose__title { margin-block: var(--space-sm) var(--space-md); }
.prose h2 { margin-block: var(--space-lg) var(--space-sm); font-size: var(--text-headline-sm); }
.prose p { margin-block-end: var(--space-sm); }

/* ==========================================================================
   Rhythm utilities — CONTRACT §2.1

   These exist so no template ever needs an inline style for spacing. If you
   reach for `style="margin-…"`, use one of these instead, or add a proper
   component class.
   ========================================================================== */

.mbs-sm { margin-block-start: var(--space-sm); }
.mbs-md { margin-block-start: var(--space-md); }
.mbs-lg { margin-block-start: var(--space-lg); }
.mbe-base { margin-block-end: var(--space-base); }
.mbe-md { margin-block-end: var(--space-md); }

/* A stack of sections inside one page column, evenly spaced. */
.flow > * + * { margin-block-start: var(--space-lg); }
.flow-tight > * + * { margin-block-start: var(--space-md); }

/* Full-width control, e.g. a submit button inside a menu row. */
.w-full { inline-size: 100%; }

/* Section titles own their rhythm: the first in a flow needs no top margin. */
.section__title + * { margin-block-start: var(--space-md); }

/* ============================================================== SHOP ===== */

/* Title block at the reading start, category tabs at the far end — one row
   on desktop, stacked on a phone where they cannot share the width. */
.shop-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-md);
  /* --space-lg below was a section-sized gap between the tabs and the filter
     row that follows them — they are one control group and belong closer. */
  margin-block: var(--space-md);
}
.shop-head__intro { min-inline-size: 0; }
.shop-head__title {
  font-family: var(--font-headline);
  font-size: var(--text-display-lg-mobile);
  font-weight: var(--weight-bold);
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--color-on-surface);
  margin-block: var(--space-xs) 0;
}
.shop-head__lead {
  font-size: var(--text-body-md);
  margin-block-start: var(--space-xs);
}
@media (min-width: 900px) {
  .shop-head__title { font-size: var(--text-display-lg); }
}
.shop-layout { display: grid; gap: var(--space-lg); align-items: start; }
@media (min-width: 900px) { .shop-layout { grid-template-columns: 260px 1fr; } }

.shop-results { min-inline-size: 0; }

/* The toggle and the applied chips share a row on a wide screen. On a phone
   they stack: side by side, the chip panel is squeezed into whatever the
   button leaves, which is too narrow to read even one chip. */
.filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  margin-block-end: var(--space-md);
  min-inline-size: 0;
}
@media (max-width: 639px) {
  /* Full width forces the panel onto its own line under the button. */
  .filter-row > .active-filters-panel { flex-basis: 100%; }
}
.shop-filter-toggle {
  flex: 0 0 auto;
  margin-block-end: 0;
}
/* A count on the toggle, so a phone user can see something is applied
   without opening the drawer to find out. */
.shop-filter-toggle__count {
  display: inline-grid;
  place-items: center;
  inline-size: 18px;
  block-size: 18px;
  margin-inline-start: var(--space-xs);
  border-radius: var(--radius-full);
  background: var(--color-primary);
  color: var(--color-on-primary);
  font-size: 11px;
  font-weight: var(--weight-bold);
  line-height: 1;
}

/* The panel wraps the label, the chips and the clear link. Only the CHIPS
   scroll; the two anchors sit outside the scroller so they stay put. */
.active-filters-panel {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  flex: 1 1 auto;
  min-inline-size: 0;
  padding: var(--space-xs) var(--space-sm);
  background: var(--color-surface-container-lowest);
  border: var(--border-hairline);
  border-radius: var(--radius-lg);
}

.active-filters {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  /* Allowed to shrink below its content width, so the chips scroll instead
     of pushing the anchors off the row. */
  flex: 1 1 auto;
  min-inline-size: 0;

  /* More chips than fit scroll sideways rather than wrapping into a block
     that grows downward and pushes the products off the screen. */
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.active-filters::-webkit-scrollbar { display: none; }
/* Nothing in the row may wrap or shrink, or the scroll never engages. */
.active-filters > * { flex: 0 0 auto; }
/* A sibling of the scroller, like the clear link at the other end — both
   stay put while only the chips move. */
.active-filters__label {
  flex: 0 0 auto;
  font-size: var(--text-label-sm);
  color: var(--color-on-surface-variant);
  white-space: nowrap;
}
/* A sibling of the chip scroller, not a child of it — so it stays pinned at
   the end of the row while the chips scroll beneath. flex:0 0 auto keeps it
   at full width however many chips there are. */
.active-filters__clear {
  flex: 0 0 auto;
  font-size: var(--text-label-sm);
  font-weight: var(--weight-medium);
  color: var(--color-error);
  white-space: nowrap;
}
.active-filters__clear:hover { text-decoration: underline; }

/* A chip carrying its own remove control. */
.filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px var(--space-sm);
  background: var(--color-surface-container);
  border-radius: var(--radius-full);
  font-size: var(--text-label-sm);
  color: var(--color-on-surface);
  white-space: nowrap;
}
.filter-chip__remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 24px visible, but the tap area is padded out to the 44px floor below. */
  inline-size: 18px;
  block-size: 18px;
  border-radius: var(--radius-full);
  color: var(--color-on-surface-variant);
  transition: background var(--transition), color var(--transition);
}
.filter-chip__remove:hover {
  background: var(--color-surface-container-high);
  color: var(--color-on-surface);
}
.filter-chip__remove .material-symbols-outlined { font-size: 14px; }
/* The visible control is small so the chip stays compact; the touch target
   is extended past it, which keeps the 44px floor without the bulk. */
.filter-chip__remove::before {
  content: "";
  position: absolute;
  inline-size: 44px;
  block-size: 44px;
}
.filter-chip { position: relative; }
.filter-chip__remove { position: relative; }

/* ---------------------------------------------------------- filters ----- */

.shop-filter-toggle { margin-block-end: var(--space-md); }

.shop-filters {
  padding: var(--space-md);
  /* White with a hairline, matching the page ground: the old cream tint was
     tuned to sit ON cream and vanishes against white. */
  background: var(--color-surface-container-lowest);
  border: var(--border-hairline);
  border-radius: var(--radius-lg);
}
.shop-filters__head { display: flex; align-items: center; justify-content: space-between; margin-block-end: var(--space-md); }
.shop-filters__close { display: inline-flex; }
.shop-filters__actions { display: grid; gap: var(--space-base); margin-block-start: var(--space-md); }
.shop-filters__scroll { display: contents; }

.filter-group { margin-block-end: var(--space-md); padding: 0; border: 0; }
.filter-group legend { padding: 0; margin-block-end: var(--space-base); }

.filter-option {
  display: flex;
  align-items: center;
  gap: var(--space-base);
  min-block-size: var(--tap-target);
  padding: var(--space-xs) var(--space-base);
  border-radius: var(--radius);
  cursor: pointer;
  transition: background var(--transition);
}
.filter-option:hover { background: var(--color-surface-container); }
.filter-option--link { justify-content: space-between; }

/* A square control with a check mark, rather than the browser's radio dot.
   These behave as radios — one roast level, one origin — but a filter reads
   as "on or off", and a tick says that more plainly than a filled circle.

   The real <input> is kept and only visually replaced, so keyboard focus,
   arrow-key navigation between radios, and screen-reader semantics all still
   work; it is the ::before on the label that is painted. */
.filter-option input[type="radio"],
.filter-option input[type="checkbox"] {
  /* Not display:none — that removes it from the tab order and from the
     accessibility tree. Kept in place, sized to the box, and made
     transparent so the drawn control shows through. */
  appearance: none;
  -webkit-appearance: none;
  inline-size: 18px;
  block-size: 18px;
  flex: 0 0 auto;
  margin: 0;
  border: 1.5px solid var(--color-outline);
  border-radius: var(--radius-sm);
  background: var(--color-surface-container-lowest);
  cursor: pointer;
  transition: background var(--transition), border-color var(--transition);
  display: grid;
  place-content: center;
}
.filter-option input[type="radio"]::before,
.filter-option input[type="checkbox"]::before {
  content: "";
  inline-size: 10px;
  block-size: 10px;
  transform: scale(0);
  transition: transform 120ms ease-in-out;
  /* A tick, drawn as a clip-path so it needs no icon font and stays crisp
     at any zoom. */
  clip-path: polygon(14% 44%, 0 60%, 39% 100%, 100% 16%, 84% 0, 39% 68%);
  background: var(--color-on-primary);
}
.filter-option input:checked {
  background: var(--color-primary);
  border-color: var(--color-primary);
}
.filter-option input:checked::before { transform: scale(1); }
.filter-option input:focus-visible {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.filter-option:hover input:not(:checked) { border-color: var(--color-on-surface-variant); }

/* Mobile: the filter panel becomes a drawer so the grid stays behind it.

   The panel is a flex column with THREE bands: a fixed header, a scrolling
   middle, and actions pinned to the bottom. Previously the whole panel
   scrolled as one, which pushed "اعمال فیلترها" 134px below the fold on a
   844px screen — the button was there, but invisible until you scrolled,
   so it read as missing. */
@media (max-width: 899px) {
  .shop-filters {
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;
    /* Above the scrim — see .drawer-scrim in base.css. */
    z-index: var(--z-drawer, 60);
    inline-size: min(340px, 88vw);
    border-radius: 0;
    transform: translateX(-100%);
    transition: transform var(--transition-slow);

    display: flex;
    flex-direction: column;
    overflow: hidden;          /* the middle band scrolls, not the panel */
    padding: 0;                /* each band owns its own padding */
  }
  [dir="rtl"] .shop-filters { transform: translateX(100%); }
  .shop-filters[data-open="true"] { transform: translateX(0); }

  .shop-filters__head {
    flex: 0 0 auto;
    margin-block-end: 0;
    padding: var(--space-md);
    border-block-end: var(--border-hairline);
  }

  /* The form is the flex child that grows; its own middle band scrolls. */
  .shop-filters > form {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-block-size: 0;         /* lets the scroll area actually shrink */
  }
  /* A hidden input is still a flex item: as a stretched child it claimed a
     ~96px band of empty space between the header and the filters. */
  .shop-filters > form > input[type="hidden"] { display: none; }

  .shop-filters__scroll {
    display: block;
    flex: 1 1 auto;
    min-block-size: 0;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: var(--space-md);
    padding-block-end: var(--space-base);
  }

  .shop-filters__actions {
    flex: 0 0 auto;
    margin-block-start: 0;
    padding: var(--space-md);
    /* Clear the iOS home indicator. */
    padding-block-end: max(var(--space-md), env(safe-area-inset-bottom));
    background: var(--color-surface);
    border-block-start: var(--border-hairline);
  }
}
@media (min-width: 900px) {
  .shop-filter-toggle { display: none; }
  .shop-filters__close { display: none; }
  .shop-filters { position: sticky; inset-block-start: calc(var(--header-height-desktop) + var(--space-md)); }
}

/* ======================================================= PRODUCT DETAIL == */

.product-detail { display: grid; gap: var(--space-lg); margin-block: var(--space-md) var(--space-lg); }
@media (min-width: 900px) { .product-detail { grid-template-columns: 1fr 1fr; align-items: start; } }

.product-gallery__main { border-radius: var(--radius-lg); overflow: hidden; background: var(--color-surface-container); }
.product-gallery__main img { inline-size: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.product-gallery__placeholder {
  display: grid; place-items: center;
  aspect-ratio: 4 / 3;
  color: var(--color-outline);
}
.product-gallery__placeholder span { font-size: 64px; }

.product-gallery__thumbs { display: flex; gap: var(--space-base); margin-block-start: var(--space-base); flex-wrap: wrap; }
.product-gallery__thumb {
  inline-size: 72px; block-size: 72px;
  border-radius: var(--radius);
  overflow: hidden;
  border: 2px solid transparent;
  transition: border-color var(--transition);
}
.product-gallery__thumb img { inline-size: 100%; block-size: 100%; object-fit: cover; }
.product-gallery__thumb[aria-current="true"] { border-color: var(--color-primary); }
.product-gallery__thumb:hover { border-color: var(--color-outline-variant); }

.product-info { display: grid; gap: var(--space-sm); align-content: start; }

.buy-form { display: grid; gap: var(--space-md); margin-block-start: var(--space-sm); }
.buy-form__group { padding: 0; border: 0; margin: 0; }
.buy-form__group legend { padding: 0; margin-block-end: var(--space-base); }
/* Quantity on the reading-start edge, running total opposite it. In RTL
   that puts the stepper right and the total on the physical LEFT, which is
   where the total was asked to sit. space-between rather than a fixed width
   so the pair stays apart at any card width. */
.buy-form__qty-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
}
.buy-form__total {
  display: grid;
  gap: 2px;
  justify-items: end;      /* physical left edge in RTL */
  text-align: end;
}
.buy-form__total-value {
  font-size: var(--text-headline-sm);
  font-weight: var(--weight-bold);
  color: var(--color-primary);
}

.option-row { display: flex; flex-wrap: wrap; gap: var(--space-base); }
.option-pill { position: relative; cursor: pointer; }
.option-pill input {
  position: absolute; opacity: 0;
  inline-size: 100%; block-size: 100%;
  margin: 0; cursor: pointer;
}
.option-pill__body {
  display: grid;
  gap: 2px;
  min-block-size: var(--tap-target);
  padding: var(--space-base) var(--space-sm);
  background: var(--color-surface-container-lowest);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius);
  text-align: center;
  transition: border-color var(--transition), background var(--transition);
}
.option-pill input:checked + .option-pill__body {
  border-color: var(--color-primary);
  background: var(--color-primary-fixed);
}
.option-pill input:focus-visible + .option-pill__body {
  outline: 2px solid var(--color-primary);
  outline-offset: 2px;
}
.option-pill__label { font-weight: var(--weight-medium); }
.option-pill__price { font-size: var(--text-label-sm); color: var(--color-on-surface-variant); }

.spec-list { display: grid; gap: var(--space-base); margin-block-start: var(--space-md); }
.spec-list > div { display: flex; gap: var(--space-sm); justify-content: space-between; padding-block: var(--space-xs); border-block-end: var(--border-hairline); }
.spec-list dt { color: var(--color-on-surface-variant); }
.spec-list dd { margin: 0; font-weight: var(--weight-medium); }

/* -------------------------------------------------------- pagination ---- */

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  margin-block-start: var(--space-lg);
  padding-block-start: var(--space-md);
  border-block-start: var(--border-hairline);
}
.pagination__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-block-size: var(--tap-target);
  padding-inline: var(--space-sm);
  border-radius: var(--radius);
  color: var(--color-primary);
  transition: background var(--transition);
}
.pagination__link:hover { background: var(--color-surface-container); }
.pagination__link--disabled { color: var(--color-outline); pointer-events: none; }
.pagination__info { font-size: var(--text-label-md); color: var(--color-on-surface-variant); }

/* ======================================================= CATEGORY BAR ==== */
/* Categories lead the browsing flow, so they sit above the grid rather than
   inside the filter panel. One markup, two behaviours: a horizontally
   scrolling row on mobile, a wrapping row on desktop. */

.category-bar {
  /* No bottom margin: .shop-head already spaces itself from what follows,
     and the two stacked into a visible gap between the tabs and the filter
     row. */
  margin-block-end: 0;
  /* Inside .shop-head this is a flex item, and a flex item refuses to shrink
     below its content width — without this the tabs push the row wider
     instead of scrolling within it. */
  min-inline-size: 0;
  max-inline-size: 100%;
}

.category-bar__list {
  display: flex;
  gap: var(--space-base);
  overflow-x: auto;
  /* No negative-margin bleed: the tabs sit inside .shop-head now, and a
     bleed started them a gutter further out than the heading above and the
     filter row below, so the first tab hung past both. */
  padding-block-end: var(--space-xs);
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  scroll-snap-type: x proximity;
}
.category-bar__list::-webkit-scrollbar { display: none; }
.category-bar__list > li { scroll-snap-align: start; flex: 0 0 auto; }

.category-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  min-block-size: var(--tap-target);
  padding-inline: var(--space-md);
  border: 1px solid var(--color-outline-variant);
  /* Softened corners, not a pill: --radius-full made these read as tags
     rather than as the tabs they are. */
  border-radius: var(--radius-md);
  color: var(--color-on-surface-variant);
  white-space: nowrap;
  transition: background var(--transition), border-color var(--transition), color var(--transition);
}
.category-chip:hover { border-color: var(--color-outline); color: var(--color-primary); }

/* The active chip differs by fill AND weight, never colour alone. */
.category-chip--active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--color-on-primary);
  font-weight: var(--weight-bold);
}

@media (min-width: 900px) {
  .category-bar__list {
    flex-wrap: wrap;
    overflow-x: visible;
    margin-inline: 0;
    padding-inline: 0;
  }
}

/* ======================================================== PRICE RANGE ====
   Layout adopted from the Stitch design (56ee0f53): readout centred above a
   single track, floor/ceiling labels beneath the ends, a 44px touch target
   around a 24px handle.

   Built on two native range inputs rather than Stitch's plain <div>s, so the
   control keeps keyboard support and screen-reader semantics. Positioning
   uses logical properties so the fill grows from the correct side in RTL.
   ======================================================================== */

.price-range { display: grid; gap: var(--space-sm); }

.price-range__readout {
  display: block;
  text-align: center;
  font-size: var(--text-body-md);
  font-weight: var(--weight-medium);
  color: var(--color-primary);
  font-variant-numeric: proportional-nums;
}

/* 44px tall so the thumbs have a full touch target (Stitch's h-11). */
.price-range__control {
  position: relative;
  block-size: 44px;
  display: flex;
  align-items: center;
}

.price-range__track {
  position: absolute;
  inset-inline: 0;
  block-size: 4px;
  background: var(--color-surface-container-high);
  border-radius: var(--radius-full);
}
.price-range__fill {
  position: absolute;
  inset-block: 0;
  background: var(--color-primary);
  border-radius: var(--radius-full);
}

.price-range__slider {
  position: absolute;
  inset-inline: 0;
  inline-size: 100%;
  block-size: 44px;
  margin: 0;
  background: none;
  appearance: none;
  -webkit-appearance: none;
  /* Only the thumbs take pointer events, so the lower slider stays reachable. */
  pointer-events: none;
}
.price-range__slider:focus { outline: none; }

.price-range__slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  pointer-events: auto;
  inline-size: 24px;
  block-size: 24px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  border: 2px solid var(--color-surface-container-lowest);
  box-shadow: 0 1px 3px rgb(60 42 33 / 20%);
  cursor: grab;
  transition: transform var(--transition), box-shadow var(--transition);
}
.price-range__slider::-moz-range-thumb {
  pointer-events: auto;
  inline-size: 24px;
  block-size: 24px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  border: 2px solid var(--color-surface-container-lowest);
  box-shadow: 0 1px 3px rgb(60 42 33 / 20%);
  cursor: grab;
}
.price-range__slider::-webkit-slider-thumb:active { cursor: grabbing; transform: scale(1.08); }
.price-range__slider:focus-visible::-webkit-slider-thumb { box-shadow: 0 0 0 4px rgb(37 22 14 / 25%); }
.price-range__slider:focus-visible::-moz-range-thumb { box-shadow: 0 0 0 4px rgb(37 22 14 / 25%); }

/* Floor and ceiling. In RTL the inline-start edge is the LOW end — the
   Stitch export had these reversed. */
.price-range__bounds {
  display: flex;
  justify-content: space-between;
  font-size: var(--text-label-sm);
  color: var(--color-outline);
  font-variant-numeric: proportional-nums;
}

.price-range__fields { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-base); }
.price-range__field { display: grid; gap: var(--space-xs); }
.price-range__field .field__input { min-block-size: 40px; padding: var(--space-base); }

/* With JS the sliders are the control and the number fields are the hidden
   source of truth; without JS the fields are the whole control. */
.js .price-range__fields { display: none; }
.no-js .price-range__control,
.no-js .price-range__bounds { display: none; }

/* =============================================================== CART ==== */

.cart-layout { display: grid; gap: var(--space-lg); align-items: start; margin-block: var(--space-md); }
@media (min-width: 900px) { .cart-layout { grid-template-columns: 1fr 340px; } }

.cart-items { display: grid; gap: var(--space-md); }

.cart-item {
  display: grid;
  grid-template-columns: 88px 1fr auto;
  gap: var(--space-md);
  align-items: start;
  padding: var(--space-md);
}
.cart-item__media {
  inline-size: 88px; block-size: 88px;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--color-surface-container);
}
.cart-item__media img { inline-size: 100%; block-size: 100%; object-fit: cover; }
.cart-item__placeholder { display: grid; place-items: center; block-size: 100%; color: var(--color-outline); }

.cart-item__body { display: grid; gap: var(--space-xs); min-inline-size: 0; }
.cart-item__name { font-family: var(--font-headline); font-size: var(--text-headline-sm); color: var(--color-on-surface); }
.cart-item__name:hover { color: var(--color-primary); }
.cart-item__meta { font-size: var(--text-label-md); }
.cart-item__controls { display: flex; align-items: center; gap: var(--space-sm); margin-block-start: var(--space-xs); }
.cart-item__remove { color: var(--color-on-surface-variant); }
.cart-item__remove:hover { color: var(--color-error); background: var(--color-error-container); }
.cart-item__total { font-weight: var(--weight-bold); color: var(--color-primary); white-space: nowrap; font-variant-numeric: proportional-nums; }

/* On a narrow screen the price moves under the details rather than
   squeezing the middle column to nothing. */
@media (max-width: 560px) {
  .cart-item { grid-template-columns: 72px 1fr; }
  .cart-item__media { inline-size: 72px; block-size: 72px; }
  .cart-item__total { grid-column: 2; justify-self: start; }
}

/* --------------------------------------------------------- quantity ----- */

.qty-stepper {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius);
  overflow: hidden;
}
.qty-stepper__btn {
  display: inline-flex; align-items: center; justify-content: center;
  inline-size: 40px; block-size: 40px;
  color: var(--color-on-surface-variant);
  transition: background var(--transition), color var(--transition);
}
.qty-stepper__btn:hover:not([disabled]) { background: var(--color-surface-container); color: var(--color-primary); }
.qty-stepper__btn[disabled] { opacity: 0.35; cursor: not-allowed; }
.qty-stepper__value {
  inline-size: 48px;
  min-block-size: 40px;
  border: 0;
  border-inline: 1px solid var(--color-outline-variant);
  background: var(--color-surface-container-lowest);
  text-align: center;
  font-variant-numeric: proportional-nums;
  appearance: textfield;
  -moz-appearance: textfield;
}
.qty-stepper__value::-webkit-outer-spin-button,
.qty-stepper__value::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.qty-stepper__value:focus { outline: none; box-shadow: inset 0 0 0 2px var(--color-primary); }

.cart-summary { position: sticky; inset-block-start: calc(var(--header-height-desktop) + var(--space-md)); }
@media (max-width: 899px) { .cart-summary { position: static; } }

/* =========================================================== CHECKOUT ==== */

.checkout-steps {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-block: var(--space-md);
  padding: 0;
  counter-reset: step;
  font-size: var(--text-label-md);
}
.checkout-steps__step {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--color-on-surface-variant);
}
.checkout-steps__step::before {
  counter-increment: step;
  content: counter(step, persian);
  display: grid;
  place-items: center;
  inline-size: 24px; block-size: 24px;
  border-radius: var(--radius-full);
  background: var(--color-surface-container-high);
  font-size: var(--text-label-sm);
}
.checkout-steps__step[data-state="done"]::before {
  background: var(--color-primary); color: var(--color-on-primary);
}
.checkout-steps__step[data-state="current"] { color: var(--color-primary); font-weight: var(--weight-bold); }
.checkout-steps__step[data-state="current"]::before {
  background: var(--color-primary); color: var(--color-on-primary);
}
.checkout-steps__step + .checkout-steps__step { padding-inline-start: var(--space-sm); }

.checkout-layout { display: grid; gap: var(--space-lg); align-items: start; }
@media (min-width: 900px) { .checkout-layout { grid-template-columns: 1fr 340px; } }
.checkout-main { min-inline-size: 0; }
.checkout-summary { position: sticky; inset-block-start: calc(var(--header-height-desktop) + var(--space-md)); }
@media (max-width: 899px) { .checkout-summary { position: static; } }

.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md); }
@media (max-width: 560px) { .field-row { grid-template-columns: 1fr; } }

.checkout-lines { display: grid; gap: var(--space-sm); margin-block-end: var(--space-md); }
.checkout-line { display: flex; justify-content: space-between; gap: var(--space-sm); align-items: baseline; }
.checkout-line__name { display: grid; gap: 2px; }
.checkout-line__price { font-weight: var(--weight-medium); white-space: nowrap; font-variant-numeric: proportional-nums; }

/* ============================================================ PAYMENT ==== */

.payment-page { max-inline-size: 640px; }

.payment-amount { margin-block: var(--space-md); text-align: center; }
.payment-amount__value {
  font-family: var(--font-headline);
  font-size: var(--text-display-lg-mobile);
  font-weight: var(--weight-bold);
  color: var(--color-primary);
  font-variant-numeric: proportional-nums;
}

.payment-steps { display: grid; gap: var(--space-md); margin-block: var(--space-lg); }
.payment-step { display: grid; grid-template-columns: 32px 1fr; gap: var(--space-sm); align-items: start; }
.payment-step__num {
  display: grid; place-items: center;
  inline-size: 32px; block-size: 32px;
  border-radius: var(--radius-full);
  background: var(--color-primary-fixed);
  color: var(--color-on-primary-fixed);
  font-weight: var(--weight-bold);
}
.payment-step__body { display: grid; gap: 2px; }

.payment-card { background: var(--color-surface-container-low); }
.payment-card__number {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-block: var(--space-xs) var(--space-md);
  padding: var(--space-sm);
  background: var(--color-surface-container-lowest);
  border: 1px dashed var(--color-outline-variant);
  border-radius: var(--radius);
}
.payment-card__digits {
  font-size: var(--text-body-lg);
  font-weight: var(--weight-bold);
  letter-spacing: 0.08em;
  font-variant-numeric: proportional-nums;
  color: var(--color-on-surface);
}
.payment-telegram-handle { text-align: center; margin-block-start: var(--space-xs); font-size: var(--text-label-md); }

.order-success { display: grid; justify-items: center; gap: var(--space-sm); text-align: center; padding-block: var(--space-lg); }
.order-success__icon { font-size: 56px; color: var(--color-success); }

/* Copy confirmation: swap the glyph so the feedback is not colour-only. */
.icon-btn.is-copied { color: var(--color-success); }
.icon-btn.is-copied .material-symbols-outlined { font-variation-settings: "FILL" 1; }

/* ========================================================= MAP PICKER ==== */
/* A centre-pin picker: the pin is fixed to the middle and the map moves
   under it. Easier one-handed than dragging a marker, and it never leaves
   the pin off-screen. */

.map-picker {
  position: relative;
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-surface-container);
}
.map-picker__canvas { block-size: 260px; inline-size: 100%; }

.map-picker__pin {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  /* Offset up by half the glyph so the point sits on the centre, not the body. */
  transform: translate(-50%, -100%);
  pointer-events: none;
  color: var(--color-accent);
  filter: drop-shadow(0 2px 4px rgb(60 42 33 / 35%));
}
.map-picker__pin .material-symbols-outlined {
  font-size: 44px;
  font-variation-settings: "FILL" 1;
}

.map-picker__hint {
  position: absolute;
  inset-block-end: var(--space-base);
  inset-inline: var(--space-base);
  padding: var(--space-xs) var(--space-base);
  background: rgb(252 249 248 / 92%);
  border-radius: var(--radius);
  font-size: var(--text-label-sm);
  color: var(--color-on-surface-variant);
  text-align: center;
  pointer-events: none;
}
.map-picker__locate {
  position: absolute;
  inset-block-start: var(--space-base);
  inset-inline-start: var(--space-base);
  min-block-size: 36px;
  padding-inline: var(--space-sm);
  background: var(--color-surface-container-lowest);
  font-size: var(--text-label-sm);
}

/* Saved-address chooser on checkout. */
.address-choice { display: grid; gap: var(--space-base); }
.address-option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-sm);
  padding: var(--space-sm);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius);
  cursor: pointer;
  transition: border-color var(--transition), background var(--transition);
}
.address-option:hover { border-color: var(--color-outline); }
.address-option:has(input:checked) {
  border-color: var(--color-primary);
  background: var(--color-surface-container-low);
}
.address-option input { margin-block-start: 3px; accent-color: var(--color-primary); }
.address-option__body { display: grid; gap: 2px; }
.address-option__label { font-weight: var(--weight-medium); display: flex; align-items: center; gap: var(--space-xs); }

/* ======================================================= ORDER TRACKER ===
   A green progress bar with point markers. The fill grows to the marker the
   order has reached, so advancing a stage visibly extends the bar.

   MOBILE  — vertical: the bar runs top to bottom with points down the
             inline-start edge. Five Persian labels do not fit across a
             phone; stacked, each gets a full line.
   DESKTOP — horizontal, right to left, where the width exists.

   The rail is one element behind the markers rather than per-step
   connectors, so the fill is a single continuous length.
   ======================================================================== */

.order-track {
  position: relative;
  margin-block: var(--space-md);
}

.order-track__steps { display: grid; gap: 0; padding: 0; }

/* ------------------------------------------------------------ mobile --- */

.order-track__rail {
  position: absolute;
  inset-block: 20px;                /* centre of first badge → centre of last */
  inset-inline-start: 19px;         /* badge centre: 40/2 - 1 */
  inline-size: 2px;
  background: var(--color-surface-container-high);
  border-radius: var(--radius-full);
  overflow: hidden;
}
.order-track__fill {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: var(--progress, 0%);
  background: var(--color-success);
  border-radius: var(--radius-full);
  transition: block-size var(--transition-slow);
}

.order-track__step {
  position: relative;
  display: grid;
  grid-template-columns: 40px 1fr;
  gap: var(--space-sm);
  align-items: start;
  padding-block-end: var(--space-md);
}
.order-track__step:last-child { padding-block-end: 0; }

.order-track__badge {
  display: grid;
  place-items: center;
  inline-size: 40px;
  block-size: 40px;
  border-radius: var(--radius-full);
  background: var(--color-surface-container-lowest);
  color: var(--color-outline);
  border: 2px solid var(--color-surface-container-high);
  position: relative;
  z-index: 1;
  transition: background var(--transition), border-color var(--transition);
}

.order-track__label {
  padding-block-start: 9px;
  font-size: var(--text-label-md);
  line-height: 1.4;
  color: var(--color-on-surface-variant);
}

/* ----------------------------------------------------------- desktop --- */

@media (min-width: 720px) {
  .order-track__rail {
    inset-block: 19px auto;
    inset-inline: 10%;              /* stop at the outer badge centres */
    inline-size: auto;
    block-size: 2px;
  }
  .order-track__fill {
    inset-block: 0;
    /* RTL: the fill grows from the inline start, so pin it there. */
    inset-inline-start: 0;
    inset-inline-end: auto;
    inline-size: var(--progress, 0%);
    block-size: 100%;
    transition: inline-size var(--transition-slow);
  }

  .order-track__steps { grid-auto-flow: column; grid-auto-columns: 1fr; }
  .order-track__step {
    grid-template-columns: none;
    justify-items: center;
    gap: var(--space-xs);
    text-align: center;
    padding-block-end: 0;
    padding-inline: var(--space-xs);
  }
  .order-track__label { padding-block-start: 0; max-inline-size: 12ch; }
}

/* -------------------------------------------------------------- state -- */

.order-track__step[data-state="done"] .order-track__badge {
  background: var(--color-success);
  border-color: var(--color-success);
  color: var(--color-on-success);
}

/* Current: filled plus a soft green ring, and a bold label. Three signals. */
.order-track__step[data-state="current"] .order-track__badge {
  background: var(--color-success);
  border-color: var(--color-success);
  color: var(--color-on-success);
  box-shadow: 0 0 0 4px var(--color-success-container);
}
.order-track__step[data-state="current"] .order-track__label {
  color: var(--color-on-success-container);
  font-weight: var(--weight-bold);
}

.order-track__step[data-state="upcoming"] .order-track__badge .icon { opacity: 0.55; }

.order-track--cancelled {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-base);
  margin-block: var(--space-md);
  padding: var(--space-md);
  background: var(--color-error-container);
  color: var(--color-on-error-container);
  border-radius: var(--radius-lg);
  font-weight: var(--weight-medium);
}

/* Origin list on the product detail page. Each origin links to its country
   filter, so a customer who likes an Ethiopian coffee can find the others. */
.origin-list { display: grid; gap: var(--space-sm); margin-block: var(--space-sm) var(--space-md); }
.origin-item { display: flex; align-items: flex-start; gap: var(--space-base); }
.origin-item .icon { color: var(--color-tertiary-container); margin-block-start: 2px; }
.origin-item__body { display: grid; gap: 1px; }
.origin-item__country { font-weight: var(--weight-medium); color: var(--color-on-surface); }
.origin-item__country:hover { color: var(--color-primary); text-decoration: underline; }

/* ========================================================== PAGE HERO ==== */

.page-hero { position: relative; min-block-size: 32vh; display: grid; align-items: end; overflow: hidden; }
.page-hero__media { position: absolute; inset: 0; }
.page-hero__media img { inline-size: 100%; block-size: 100%; object-fit: cover; }
.page-hero__media::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to bottom, rgb(37 22 14 / 20%), rgb(37 22 14 / 78%));
}
.page-hero__inner { position: relative; z-index: 1; padding-block: var(--space-lg); color: #fff; }
.page-hero__eyebrow { color: var(--color-primary-fixed); }
.page-hero__title { color: #fff; }

/* ------------------------------------------------------- milestones ----- */

.milestones { display: grid; gap: var(--space-md); }
.milestone { display: grid; grid-template-columns: 4.5rem 1fr; gap: var(--space-md); align-items: baseline; }
.milestone__year {
  font-family: var(--font-headline);
  font-size: var(--text-headline-sm);
  color: var(--color-primary-container);
  font-variant-numeric: proportional-nums;
}
.milestone__body { display: grid; gap: 2px; padding-block-end: var(--space-md); border-block-end: var(--border-hairline); }
.milestone:last-child .milestone__body { border-block-end: 0; padding-block-end: 0; }

/* --------------------------------------------------------- CTA band ----- */

.cta-band {
  display: grid;
  justify-items: center;
  gap: var(--space-sm);
  text-align: center;
  padding: var(--space-xl) var(--space-md);
  background: var(--color-surface-container-low);
  border-radius: var(--radius-lg);
}

/* ==================================================== SOCIAL ACTIONS ===== */
/* Instagram and Telegram lead the contact page: for an Iranian roastery they
   are the channels customers actually use. */

.social-actions { display: grid; gap: var(--space-sm); margin-block: var(--space-md) var(--space-lg); }
@media (min-width: 560px) { .social-actions { grid-template-columns: 1fr 1fr; } }

.social-action {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  min-block-size: 64px;
  padding: var(--space-sm) var(--space-md);
  border-radius: var(--radius-lg);
  color: var(--color-on-primary);
  transition: transform var(--transition), filter var(--transition);
}
.social-action:hover { filter: brightness(1.08); }
.social-action:active { transform: scale(0.99); }
.social-action--instagram { background: var(--color-accent); }
.social-action--telegram { background: var(--color-primary-container); }

.social-action__body { display: grid; gap: 1px; flex: 1; min-inline-size: 0; }
.social-action__label { font-weight: var(--weight-bold); }
.social-action__handle { font-size: var(--text-label-md); opacity: 0.85; }
.social-action__go { opacity: 0.7; }

/* MapLibre paints into a canvas that must fill the container, and its own
   controls default to LTR corners. Keep the pin and the locate button above
   the map's own layers. */
.map-picker__canvas .maplibregl-map,
.map-picker__canvas canvas { inline-size: 100%; block-size: 100%; }
.map-picker__pin { z-index: 3; }
.map-picker__locate { z-index: 3; }
.map-picker__hint { z-index: 2; }
/* Neshan's logo/attribution stays visible — it is a licence requirement —
   but should not collide with our hint bar. */
.map-picker .maplibregl-ctrl-bottom-right,
.map-picker .maplibregl-ctrl-bottom-left { margin-block-end: 34px; }

/* Delivery-point map on order pages. A static image rather than the
   interactive SDK: this is a record to look at, not a control to adjust,
   so it renders instantly and needs no JavaScript. */
.order-map {
  margin-block: var(--space-md) var(--space-sm);
  border: 1px solid var(--color-outline-variant);
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--color-surface-container);
}
.order-map img { inline-size: 100%; block-size: auto; display: block; }

/* Read-only map canvas on order pages. */
.order-map { position: relative; }
.order-map__canvas { block-size: 220px; inline-size: 100%; }
.order-map__canvas .maplibregl-map,
.order-map__canvas canvas { inline-size: 100%; block-size: 100%; }
.order-map__pin {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -100%);
  pointer-events: none;
  z-index: 2;
  color: var(--color-accent);
  filter: drop-shadow(0 2px 4px rgb(60 42 33 / 35%));
}
.order-map__pin .material-symbols-outlined {
  font-size: 40px;
  font-variation-settings: "FILL" 1;
}

/* ------------------------------------------------------------- price ---- */
/* Amount plus the Toman currency mark. The glyph sits on the text baseline
   and inherits the surrounding colour, so it works on cards, buttons and
   the dark hero alike. */
.price {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}
.price__amount {
  /* Proportional, NOT tabular: an inline price reads as a word, and tabular
     figures pad narrow Persian digits (۱، ۲) out to the widest glyph, which
     made ۲۶۰٬۰۰۰ about 30% wider than it needed to be. Tabular is kept for
     the totals columns, where digits genuinely need to line up. */
  font-variant-numeric: proportional-nums;
}

/* …except where prices stack in a column and must align on the decimal:
   order totals, cart line totals and the checkout summary. */
.order-totals .price__amount,
.cart-item__total .price__amount,
.checkout-line__price .price__amount,
.order-item__price .price__amount,
.payment-amount__value .price__amount {
  font-variant-numeric: proportional-nums;
}
.price__unit {
  inline-size: 1em;
  block-size: 1em;
  fill: currentColor;
  flex: 0 0 auto;
  /* Optical alignment: the mark reads slightly high against Persian digits. */
  transform: translateY(0.06em);
}

/* -------------------------------------------------- quick add on card --- */
.product-card { position: relative; }

/* ============================================================== TOAST ====
   Slides up from below the viewport edge and settles, then slides back down
   on the way out. Confirms an add without leaving the page, so a shopper can
   keep browsing.
   ======================================================================== */

.toast-stack {
  position: fixed;
  /* Anchored to the bottom edge; each toast starts below it and rises. */
  inset-block-end: 0;
  inset-inline: 0;
  z-index: var(--z-toast);
  display: grid;
  justify-items: center;
  padding: var(--space-md);
  /* Clip the entry so a toast is never seen sitting off the page. */
  overflow: hidden;
  pointer-events: none;
}
/* Clear the mobile bottom nav, or a confirmation slides up behind it and the
   "view cart" link inside it is unclickable. Removed with the bar at 768px. */
.toast-stack { inset-block-end: calc(56px + env(safe-area-inset-bottom, 0px)); }

@media (min-width: 640px) {
  .toast-stack { justify-items: start; }
  .toast-stack > * { margin-inline-start: var(--space-md); }
}
@media (min-width: 768px) {
  .toast-stack { inset-block-end: 0; }
}

.toast {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-xs) var(--space-sm);
  align-items: center;
  inline-size: 100%;
  max-inline-size: 400px;
  padding: var(--space-md);
  /* Translucent over a blur, so the page reads through the toast without
     the text losing contrast. The espresso tint keeps it on-brand rather
     than a neutral grey. */
  background: rgb(37 22 14 / 82%);
  -webkit-backdrop-filter: blur(12px) saturate(1.1);
  backdrop-filter: blur(12px) saturate(1.1);
  border: 1px solid rgb(255 255 255 / 10%);
  color: var(--color-inverse-on-surface);
  border-radius: var(--radius-lg);
  box-shadow: 0 8px 32px rgb(37 22 14 / 28%);
  pointer-events: auto;

  /* Start fully below the stack's padded edge, then ease up into place.
     The overshoot in the easing gives it a soft, settled landing rather
     than a mechanical stop. */
  transform: translateY(calc(100% + var(--space-md)));
  opacity: 0;
  transition:
    transform 420ms cubic-bezier(0.22, 1, 0.36, 1),
    opacity 260ms ease;
}
.toast.is-visible { transform: translateY(0); opacity: 1; }

/* Level accents. The toast body stays the same espresso panel — only the
   icon changes colour — so an error is unmistakable without turning the
   whole surface into an alarm. */
.toast--success .toast__icon { color: #7ddc9a; }
.toast--info    .toast__icon { color: #8fc8ee; }
.toast--warning .toast__icon { color: #f2c66b; }
.toast--error   .toast__icon { color: #ff9b8f; }

/* Without JavaScript the flash messages cannot become toasts, so they stay
   visible as ordinary blocks. With JS they are removed from the DOM by
   flash-messages.js, so this rule only ever applies in the no-js case. */
.no-js .messages { display: block; }

@supports not (backdrop-filter: blur(1px)) {
  .toast { background: rgb(37 22 14 / 96%); }
}

/* Leaving: drop back down, slightly faster than it arrived. */
.toast.is-leaving {
  transform: translateY(calc(100% + var(--space-md)));
  opacity: 0;
  transition:
    transform 280ms cubic-bezier(0.4, 0, 1, 1),
    opacity 200ms ease;
}

.toast__icon { color: var(--color-tertiary-fixed-dim); font-size: 28px; line-height: 1; }
/* Every Material Symbol inside the toast: size it so it never lays out as
   the literal glyph name while the icon font is still loading. */
.toast .material-symbols-outlined { line-height: 1; }
.toast__body { display: grid; gap: 2px; min-inline-size: 0; }
.toast__title { font-weight: var(--weight-medium); }
.toast__meta { font-size: var(--text-label-sm); opacity: 0.8; }

.toast__close {
  display: grid;
  place-items: center;
  inline-size: 32px;
  block-size: 32px;
  border-radius: var(--radius-full);
  color: var(--color-inverse-on-surface);
  opacity: 0.6;
  transition: opacity var(--transition), background var(--transition);
}
.toast__close:hover { opacity: 1; background: rgb(255 255 255 / 12%); }

.toast__actions {
  grid-column: 2 / -1;
  display: flex;
  gap: var(--space-sm);
  align-items: center;
  margin-block-start: var(--space-xs);
}
.toast__link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: 6px var(--space-sm);
  background: var(--color-primary-fixed);
  /* Explicit dark ink: --color-on-primary-fixed resolves to the same value
     as the background here, which rendered the label invisible. */
  color: var(--color-primary);
  border-radius: var(--radius);
  font-size: var(--text-label-md);
  font-weight: var(--weight-medium);
  white-space: nowrap;
}
.toast__link:hover { background: #fff; color: var(--color-primary); }

/* A progress line showing how long the toast will stay. */
.toast__timer {
  grid-column: 1 / -1;
  block-size: 2px;
  margin-block-start: var(--space-sm);
  background: rgb(255 255 255 / 20%);
  border-radius: var(--radius-full);
  overflow: hidden;
}
.toast__timer span {
  display: block;
  block-size: 100%;
  inline-size: 100%;
  background: var(--color-tertiary-fixed-dim);
  transform-origin: right center;   /* RTL: drains toward the left */
  animation: toast-drain var(--toast-life, 6000ms) linear forwards;
}
@keyframes toast-drain { to { transform: scaleX(0); } }

@media (prefers-reduced-motion: reduce) {
  .toast, .toast.is-leaving { transition: opacity 200ms ease; transform: none; }
  .toast__timer span { animation: none; }
}

/* ------------------------------------------------------------- price ---- */
/* Amount plus the Toman currency mark. The glyph sits on the text baseline
   and inherits the surrounding colour, so it works on cards, buttons and
   the dark hero alike. */
.price {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}
.price__amount {
  /* Proportional, NOT tabular: an inline price reads as a word, and tabular
     figures pad narrow Persian digits (۱، ۲) out to the widest glyph, which
     made ۲۶۰٬۰۰۰ about 30% wider than it needed to be. Tabular is kept for
     the totals columns, where digits genuinely need to line up. */
  font-variant-numeric: proportional-nums;
}

/* …except where prices stack in a column and must align on the decimal:
   order totals, cart line totals and the checkout summary. */
.order-totals .price__amount,
.cart-item__total .price__amount,
.checkout-line__price .price__amount,
.order-item__price .price__amount,
.payment-amount__value .price__amount {
  font-variant-numeric: proportional-nums;
}
.price__unit {
  inline-size: 1em;
  block-size: 1em;
  fill: currentColor;
  flex: 0 0 auto;
  /* Optical alignment: the mark reads slightly high against Persian digits. */
  transform: translateY(0.06em);
}

/* ============================================================== TOAST ==== */

/* ============================================================= REVEAL ====
   Scroll-in animation, driven by reveal.js.

   Every rule is scoped to .reveal-ready on <html>, a class the script adds
   only once it is certain it can reveal again. Without the script the class
   never appears and none of this applies, so content is never left hidden by
   a stylesheet that loaded when its JavaScript did not.
   ======================================================================== */

.reveal-ready .reveal {
  opacity: 0;
  /* Small rise only. A large travel reads as a slideshow and, on a long
     page, as the layout still settling. */
  transform: translateY(26px);
  transition:
    opacity 1100ms cubic-bezier(0.22, 1, 0.36, 1),
    transform 1100ms cubic-bezier(0.22, 1, 0.36, 1);
  /* Hint the compositor for the two properties actually animating. */
  will-change: opacity, transform;
}

.reveal-ready .reveal.is-revealed {
  opacity: 1;
  transform: none;
  /* Drop the hint once it has landed: a permanent will-change keeps a layer
     alive for every revealed element on the page. */
  will-change: auto;
}

/* Reduced motion: the script bails out before adding .reveal-ready, so this
   is belt-and-braces for anything that slipped through. */
@media (prefers-reduced-motion: reduce) {
  .reveal-ready .reveal {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* ------------------------------------------------ address chooser ------- */
/* Each saved address sits in a row with its own ویرایش link. The link is a
   sibling of the <label>, never inside it — nested, a click would both
   follow the link and toggle the radio. */
.address-option-row {
  display: flex;
  align-items: stretch;
  gap: var(--space-xs);
}
.address-option-row .address-option { flex: 1 1 auto; }

.address-option__edit {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex: 0 0 auto;
  padding-inline: var(--space-sm);
  border-radius: var(--radius);
  color: var(--color-on-surface-variant);
  font-size: var(--text-label-sm);
  font-weight: var(--weight-medium);
  white-space: nowrap;
  transition: background var(--transition), color var(--transition);
}
.address-option__edit:hover {
  background: var(--color-surface-container);
  color: var(--color-primary);
}

/* Collapsed by [hidden] from site.js. Declared so the block cannot be left
   visible by a stylesheet that disagrees with the script. */
.address-fields[hidden] { display: none; }

/* ======================================================= SECTION BAND ====
   A full-bleed band of colour behind a section, used to separate one group
   from the next without a card. The inner container keeps the content on the
   same measure as every other section, so only the ground bleeds.
   ======================================================================== */

.section-band {
  padding-block: var(--space-xl);
  /* No block margin. A band already pads itself, and its neighbours pad
     themselves too — an 80px margin here stacked with the next section's
     80px padding into 160px of dead space between them. The band's own
     padding is the separation. */
  margin-block: 0;
}
/* A band opening the page sits flush against the hero: .main-content has
   already applied its own top padding, which would otherwise show as a strip
   of cream above the colour. */
.main-content > .section-band:first-child {
  margin-block-start: calc(var(--space-md) * -1);
}
.section-band--secondary {
  background: var(--color-secondary-container);
  color: var(--color-on-secondary-container);
}
/* Muted text inside the band takes the band's own on-colour: the default
   muted grey is tuned for the cream ground and goes flat on blue-grey. */
.section-band--secondary .t-muted { color: var(--color-on-secondary-container); }
.section-band--secondary .promise__icon { color: var(--color-primary-deep); }
.section-band--secondary .section__title { color: var(--color-on-surface); }
/* The section link keeps its green on the band; only its hover needs
   changing, since the default hover colour is tuned against cream. */
.section-band--secondary .section-head__link:hover {
  color: var(--color-on-secondary-container);
}

/* ==================================================== HOME — STITCH =====
   Components for the home page as designed in Stitch (project
   15183896161304262237). The shared vocabulary is: an eyebrow above every
   heading, cards raised in WHITE off the cream ground, and horizontal rows
   with an icon tile rather than columns of equal blocks.
   ======================================================================== */

/* ------------------------------------------------------ section shell -- */

.section-block { padding-block: var(--space-xl); }
/* The first block sits under the header, where .main-content has already
   applied its own top padding — without this the two stack into a gap. */
.main-content > .section-block:first-child { padding-block-start: 0; }
/* Tinted and raised bands separate sections without rules between them. */
.section-block--tinted { background: var(--color-secondary-container); }
.section-block--raised { background: var(--color-surface-container-low); }

/* --------------------------------------------------------- eyebrow ----- */
/* A small label above a heading. Three variants, matching the design: bare,
   preceded by a short rule, or by a dot. */

.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  margin: 0 0 var(--space-xs);
  font-size: var(--text-label-sm);
  font-weight: var(--weight-medium);
  letter-spacing: 0.08em;
  color: var(--color-primary-deep);
}
.eyebrow--rule::before {
  content: "";
  inline-size: 16px;
  block-size: 1px;
  background: currentColor;
  flex: 0 0 auto;
}
.eyebrow--dot::before {
  content: "";
  inline-size: 6px;
  block-size: 6px;
  border-radius: var(--radius-full);
  background: var(--color-primary);
  flex: 0 0 auto;
}
.eyebrow--icon { color: var(--color-on-surface-variant); }

/* ------------------------------------------------------ section head --- */

.section-head { margin-block-end: var(--space-md); }
.section-head__title {
  font-family: var(--font-headline);
  font-size: var(--text-headline-md);
  font-weight: var(--weight-bold);
  color: var(--color-on-surface);
}
/* Heading at the reading start, a link opposite it. */
.section-head--split {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
}
.section-head__link {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  min-block-size: var(--tap-target);
  font-size: var(--text-label-md);
  font-weight: var(--weight-medium);
  color: var(--color-primary-deep);
  white-space: nowrap;
}
.section-head__link:hover { color: var(--color-on-surface); }

/* -------------------------------------------------------- row cards ---- */
/* A horizontal row: icon tile, text, optional trailing arrow. Used for the
   promises and the categories — the design uses rows, not a column grid. */

.row-list { display: grid; gap: var(--space-sm); }

.row-card {
  display: flex;
  align-items: flex-start;
  gap: var(--space-md);
  padding: var(--space-md);
  background: var(--color-surface-container-lowest);
  border-radius: var(--radius);
  box-shadow: var(--elevation-1, 0 1px 3px rgb(26 26 26 / 8%));
}
.row-card--link { transition: background var(--transition); }
.row-card--link:hover { background: var(--color-surface-container-low); }

.row-card__tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 48px;
  block-size: 48px;
  flex: 0 0 auto;
  border-radius: var(--radius);
  background: var(--color-primary-fixed);
  color: var(--color-primary-deep);
  transition: transform var(--transition);
}
.row-card__tile--secondary {
  background: var(--color-secondary-fixed);
  color: var(--color-on-secondary-container);
}
.row-card--link:hover .row-card__tile { transform: scale(1.05); }

/* A photograph in place of the icon tile. Same footprint as .row-card__tile
   so the rows line up whichever a category uses, but larger — a picture needs
   room to read as one, where a glyph does not. */
.row-card__thumb {
  display: block;
  /* Larger than the 48px icon tile it replaced: a photograph needs room to
     be read as one, where a glyph reads at any size. */
  inline-size: 96px;
  block-size: 96px;
  flex: 0 0 auto;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--color-surface-container);
}
.row-card__thumb img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform var(--transition);
}
.row-card--link:hover .row-card__thumb img { transform: scale(1.06); }

.row-card__body { display: flex; flex-direction: column; gap: 2px; min-inline-size: 0; }
.row-card__title {
  display: flex;
  align-items: baseline;
  gap: var(--space-xs);
  flex-wrap: wrap;
  font-size: var(--text-label-lg, 1rem);
  font-weight: var(--weight-bold);
  color: var(--color-on-surface);
}
/* The Latin name sits beside the Persian one, quieter and monospaced —
   it is a reference, not a second title. */
.row-card__latin {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: var(--text-label-sm);
  font-weight: var(--weight-regular);
  color: var(--color-on-surface-variant);
}
.row-card__text {
  font-size: var(--text-body-sm, 0.875rem);
  line-height: 1.6;
  color: var(--color-on-surface-variant);
}
.row-card__arrow {
  margin-inline-start: auto;
  align-self: center;
  color: var(--color-on-surface-variant);
  transition: transform var(--transition), color var(--transition);
}
.row-card--link:hover .row-card__arrow {
  color: var(--color-primary-deep);
  /* Toward the reading end, which is LEFT in RTL. */
  transform: translateX(-4px);
}

/* Two columns on phones, matching the design's 2x2 featured grid. */
.product-grid--home { grid-template-columns: repeat(2, minmax(0, 1fr)); }

/* ----------------------------------------------------------- desktop --- */

@media (min-width: 768px) {
  .section-block { padding-block: var(--space-2xl, 64px); }

  /* Promises and categories go three-up on a wide screen. */
  .row-list { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* …but a category row keeps its trailing arrow, so it stays a row. */
  .row-card { block-size: 100%; }

  .product-grid--home { grid-template-columns: repeat(4, minmax(0, 1fr)); }


}

/* ========================================================== JOURNAL =====
   مجله قهوه — article cards, the index grid, and the article page.
   ======================================================================== */

.article-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
  gap: var(--space-md);
}

.article-card {
  background: var(--color-surface-container-lowest);
  border-radius: var(--radius-lg);
  overflow: hidden;
  box-shadow: var(--elevation-1, 0 1px 3px rgb(26 26 26 / 8%));
  transition: box-shadow var(--transition), transform var(--transition);
}
.article-card:hover {
  box-shadow: 0 6px 18px rgb(26 26 26 / 12%);
  transform: translateY(-2px);
}
/* The whole card is the link, so a reader never has to find a small target. */
.article-card__link {
  display: grid;
  block-size: 100%;
  align-content: start;
  color: inherit;
}

.article-card__media {
  position: relative;
  display: block;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  background: var(--color-surface-container);
}
.article-card__media img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  transition: transform var(--transition-slow);
}
.article-card:hover .article-card__media img { transform: scale(1.03); }

/* The category sits on the image at the RTL start, matching the badge
   placement used on product cards. */
.article-card__tag {
  position: absolute;
  inset-block-start: var(--space-sm);
  inset-inline-start: var(--space-sm);
  padding: 2px var(--space-xs);
  background: rgb(255 255 255 / 92%);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  border-radius: var(--radius-sm);
  font-size: var(--text-label-sm);
  font-weight: var(--weight-medium);
  color: var(--color-primary-deep);
}

.article-card__body {
  display: grid;
  gap: var(--space-xs);
  padding: var(--space-md);
}
.article-card__title {
  font-family: var(--font-headline);
  font-size: var(--text-headline-sm);
  font-weight: var(--weight-bold);
  line-height: 1.5;
  color: var(--color-on-surface);
  /* Two lines, so a long title cannot make one card taller than its row. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.article-card__excerpt {
  font-size: var(--text-body-sm, 0.875rem);
  line-height: 1.8;
  color: var(--color-on-surface-variant);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.article-card__meta {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-label-sm);
  color: var(--color-on-surface-variant);
}
.article-card__dot { opacity: 0.6; }

/* ------------------------------------------------------ article page --- */

.article-head {
  display: grid;
  gap: var(--space-xs);
  max-inline-size: 70ch;
  margin-block: var(--space-md) var(--space-lg);
}
.article-head__meta {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-label-md);
}

.article-hero {
  margin: 0 0 var(--space-lg);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.article-hero img {
  inline-size: 100%;
  max-block-size: 480px;
  object-fit: cover;
}

/* A comfortable measure for running text — roughly 70 characters. */
.article-body {
  max-inline-size: 70ch;
  font-size: var(--text-body-lg);
  line-height: 2;
}
.article-body p { margin-block-end: var(--space-md); }

/* ------------------------------------------------------- pagination ---- */

.pagination {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-md);
  margin-block-start: var(--space-xl);
}

/* ================================================= MOBILE SCROLL ROWS ===
   On a phone the home page's featured products and journal posts scroll
   sideways instead of stacking. Four cards stacked is four screens of
   scrolling before the next section; a row is one gesture and shows at a
   glance that there is more than one.

   Desktop is untouched — the media query below returns both to a grid.
   ======================================================================== */

@media (max-width: 767px) {
  .product-grid--home,
  .article-grid--row {
    display: grid;
    /* Columns rather than flex, so grid-auto-flow governs the order and the
       gap stays the same token used everywhere else. */
    grid-auto-flow: column;
    grid-auto-columns: 78%;
    grid-template-columns: none;
    overflow-x: auto;
    scroll-behavior: smooth;
    scroll-snap-type: x mandatory;
    /* Contain the scroll so flicking a row never drags the page with it. */
    overscroll-behavior-x: contain;

    /* Bleed to the screen edges, then pad back in. Without this the row stops
       at the container's margin and the next card is clipped flush, which
       reads as broken rather than as "there is more". */
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    /* Room for the scrollbar so it never sits on top of a card. */
    padding-block-end: var(--space-sm);

    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .product-grid--home::-webkit-scrollbar,
  .article-grid--row::-webkit-scrollbar { display: none; }

  .product-grid--home > *,
  .article-grid--row > * {
    scroll-snap-align: start;
  }

  /* A card in a scroller sizes itself; the equal-height rule that keeps a
     grid tidy would otherwise stretch every card to the tallest. */
  .article-grid--row > .article-card { block-size: auto; }
}

/* ================================================== PAGE BACKGROUNDS =====
   White is the default: content pages are for reading and comparing, and
   photographs and cards read more clearly against a neutral ground.

   The CREAM is reserved for the home page, where it is atmosphere rather
   than a working surface. Inverted this way round — white by default, cream
   opted into — so a new page gets the right ground without remembering to
   ask for it.

   The token itself stays cream: the footer, the درباره ما band and the
   header all take their colour from it, and they keep it on every page.
   ======================================================================== */

body {
  background: var(--color-surface-container-lowest);
}
.page-home {
  background: var(--color-background);
}


/* ==================================================== PRODUCT DETAIL ====
   The four panels below the buy box sit in a 2x2 grid on desktop:

     پروفایل طعمی  |  خاستگاه
     درباره این قهوه |  راهنمای دم‌آوری

   Source order places them — in RTL the first cell is the RIGHT one — so no
   explicit column assignment is needed and the reading order matches the
   visual order for a screen reader too.

   One column below 900px: two columns of Persian prose on a phone is
   unreadable, and the panels are better stacked than squeezed.
   ======================================================================== */

.detail-grid {
  display: grid;
  gap: 0 var(--space-lg);
}
/* Each cell owns its own vertical rhythm, so a short panel beside a long one
   does not inherit the taller one's spacing. */
.detail-grid__cell { margin-block: 0; }

@media (min-width: 900px) {
  .detail-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Each card stops at its own content height rather than stretching to
     match its neighbour: three flavour bars stretched to the height of a
     full origin table left a card that was mostly empty space. align-start
     lets the shorter one end where its content does. */
  .detail-grid__cell { align-self: start; }
}

/* ==================================================== CONTACT DETAILS ===
   The contact page borrowed .site-footer__contact-item and
   .site-footer__link. Those were recoloured to cream for the green footer,
   so on the white contact page the address, phone, email and hours were
   cream on white — present but effectively invisible.

   Its own classes now, taking their colour from the page rather than from
   wherever they were first written.
   ======================================================================== */

.contact-item {
  display: flex;
  align-items: flex-start;
  gap: var(--space-base);
  color: var(--color-on-surface-variant);
}
.contact-item + .contact-item { margin-block-start: var(--space-sm); }
.contact-item__link {
  color: var(--color-on-surface);
  transition: color var(--transition);
}
.contact-item__link:hover {
  color: var(--color-primary-deep);
  text-decoration: underline;
}
