/* GRAPHIX page layouts — the grids that are specific to one page.

   components.css owns every reusable block. This file owns only composition:
   where the blocks sit relative to each other on a given page. If a rule here
   would be useful on a second page, it belongs in components.css instead.

   Depends on tokens.css. Loads last. */

/* ----------------------------------------------------------------- shell */
/* The search field is the widest thing in the header on desktop, because
   finding a design is the main job here. Below 860 px it is hidden and the
   magnifier in the actions row reveals it as a second row, so the brand, the
   cart and the burger never get squeezed. */
.nav { flex-wrap: wrap; }
/* 360, not 440. Search is the main job on this site and the field should look
   it, but at 440 it was taking the room the language control needs and the
   header read as one long box with three icons crowded after it. */
.nav__search { display: none; flex: 1; min-width: 0; max-width: 360px; margin-left: var(--s-5); }
.nav__search.is-open { display: block; order: 10; flex-basis: 100%; max-width: none; margin: var(--s-3) 0 0; }
.nav__search-toggle { display: grid; }
.nav__account { display: none; }

@media (min-width: 860px) {
  .nav__search { display: block; }
  .nav__search.is-open { order: 0; flex-basis: auto; margin: 0 0 0 var(--s-5); }
  .nav__search-toggle { display: none; }
  .nav__account { display: grid; }
}

/* The filter sheet's backdrop. One step under the sheet, one step over
   everything else. Hidden entirely while closed, so it is never in the way. */
.sheet__backdrop {
  position: fixed; inset: 0; z-index: calc(var(--z-modal) - 1);
  background: var(--c-scrim);
}
@media (min-width: 860px) { .sheet__backdrop { display: none; } }

/* The drawer's backdrop. Carries the click-to-close target and dims the page. */
.drawer__backdrop {
  position: fixed; inset: 0; z-index: calc(var(--z-drawer) - 1);
  background: var(--c-scrim);
}

/* ------------------------------------------------------- product detail */
/* Two columns from 860 px. The gallery does NOT travel with the scroll: a
   sticky image beside a shorter buy column slides down over the page as you
   read, which is what the owner saw and disliked. The single rule this layout
   exists to enforce: one photograph must never take more than one screen
   (§17 #47). */
/* One column until this layout's breakpoint, and that column must be allowed to
   shrink: a grid item defaults to min-width:auto, so without minmax(0, …) one
   long word — an email address, a product name — sizes the whole column and the
   page scrolls sideways. Declared per layout rather than in one shared rule,
   because a shared rule placed after these breakpoints silently beat them and
   left the product page and the shop with no second column on any screen. */
.pdp { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-5); }

.pdp__gallery { min-width: 0; }
.pdp__buy { min-width: 0; display: flex; flex-direction: column; gap: var(--s-5); }

.pdp__title {
  font-family: var(--f-display);
  font-size: clamp(var(--fs-2xl), 7vw, var(--fs-4xl));
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  text-wrap: balance;
}
.pdp__price {
  font-size: var(--fs-2xl);
  font-weight: var(--fw-semibold);
  color: var(--c-brand);
  font-variant-numeric: tabular-nums;
}
.pdp__actions { display: flex; gap: var(--s-2); }

/* Mobile keeps the buy bar fixed to the bottom, so the page needs room under it. */
.pdp__pad { height: 84px; }

@media (min-width: 860px) {
  .pdp {
    grid-template-columns: minmax(0, 1fr) var(--pdp-buy-col);
    gap: var(--s-7);
    align-items: start;
  }
  .pdp__pad { display: none; }
}

/* The gallery: main image capped in height, thumbnails below on mobile and in a
   vertical rail beside the image on desktop. */
/* The frame is sized by the layout, never by the photograph. Its width is
   derived from the height cap and the catalogue ratio, and the image inside is
   taken out of flow entirely — so a 3000x500 source and a 500x3000 source
   produce exactly the same box, which is the whole point of a catalogue that
   will be filled from mixed sources. */
.pdp__frame {
  position: relative;
  aspect-ratio: var(--ar-product);
  width: min(100%, calc(var(--pdp-image-max) * 4 / 5));
  max-height: var(--pdp-image-max);
  margin-inline: auto;
  overflow: hidden;
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
}
.pdp__frame img { position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; }

.pdp__rail { display: flex; gap: var(--s-2); margin-top: var(--s-2); }
.pdp__rail button {
  position: relative;
  flex: 0 0 64px;
  aspect-ratio: var(--ar-product);
  overflow: hidden;
  border: 1px solid var(--c-line);
  border-radius: var(--r-sm);
  opacity: .55;
  transition: opacity var(--t-fast), border-color var(--t-fast);
}
.pdp__rail button:hover { opacity: .85; }
.pdp__rail button[aria-current="true"] { opacity: 1; border-color: var(--c-brand); }
.pdp__rail img { position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; }

@media (min-width: 1024px) {
  /* Vertical thumbnail rail to the left of the image (§17 #47).
     Scoped to the modifier, not to `.pdp__gallery`, because the rail is
     CONDITIONAL: item.html omits it for a product with one photograph. Applied
     to the bare block, the two columns were still declared, the frame became
     the first grid item, and `width: min(100%, …)` resolved 100% against the
     64 px thumbnail column — a 760 px photograph rendered 64 px wide with the
     rest of the column empty. A layout must never assume a child the template
     can decide not to render (§17 #109). */
  .pdp__gallery--rail { display: grid; grid-template-columns: 64px minmax(0, 1fr); gap: var(--s-3); }
  .pdp__rail { flex-direction: column; margin-top: 0; order: -1; }
  /* Beside its thumbnails, not floating in the middle of the column: centred,
     the frame sat 60 px away from the rail and the two stopped reading as one
     gallery. The slack falls on the right, where it becomes the gutter before
     the buy column. */
  .pdp__frame { margin-inline: 0; }
}

/* The reviews section. The cards themselves are `.review` in components.css —
   only the summary above them is page-specific. */
.pdp__reviews:empty { display: none; }
.reviews { margin-top: var(--s-7); }
.reviews__summary { display: grid; gap: var(--s-5); align-items: center;
  padding: var(--s-5); background: var(--c-surface); border-radius: var(--r-md);
  border: 1px solid var(--c-line); }
.reviews__score { text-align: center; }
.reviews__avg { font-family: var(--f-display); font-size: var(--fs-3xl);
  font-weight: var(--fw-bold); line-height: 1; color: var(--c-brand);
  font-variant-numeric: tabular-nums; }
/* The distribution reads as a chart, so the numbers are the narrow columns and
   the bars take whatever is left — rather than five bars of arbitrary width. */
.reviews__dist { width: 100%; border-collapse: collapse;
  font-size: var(--fs-sm); color: var(--c-fg-muted); }
.reviews__dist th { font-weight: var(--fw-regular); text-align: left;
  white-space: nowrap; padding-right: var(--s-3); }
.reviews__dist td { padding: 2px 0; }
.reviews__dist td:last-child { width: 3ch; text-align: right;
  padding-left: var(--s-3); font-variant-numeric: tabular-nums; }
/* The bar cell holds one empty span, so its intrinsic width is zero — and an
   auto table hands its leftover width out in proportion to content, which left
   the bars in a column 0 px wide: five rows of nothing, on every breakpoint.
   Claiming 100% makes this the column that absorbs whatever the label and the
   count do not need. */
.reviews__dist td:first-of-type { width: 100%; }
.reviews__bar { display: block; height: 6px; border-radius: var(--r-full);
  background: var(--c-surface-3); overflow: hidden; }
.reviews__bar-fill { display: block; height: 100%; background: var(--c-brand); }
.reviews__list { list-style: none; }
/* The first card sits under the summary, so its top rule would double as the
   summary's bottom edge. */
.reviews__list .review:first-child { border-top: 0; }
.stars--sm .i { width: 14px; height: 14px; }
.pager { display: flex; gap: var(--s-3); align-items: center; justify-content: center;
  margin-top: var(--s-4); }

@media (min-width: 640px) {
  .reviews__summary { grid-template-columns: auto minmax(0, 1fr); gap: var(--s-7); }
  .reviews__score { padding-right: var(--s-5); border-right: 1px solid var(--c-line); }
}

/* ------------------------------------------------------------------ shop */
/* Filters are a sidebar on desktop and a sheet on mobile — the same markup
   either way, so there is only one set of controls to keep in step. */
/* Cleared by the sticky header plus a little air. */
.shop { --shop-sticky-top: calc(var(--s-8) + var(--s-2));
  display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-5); }
/* The bar belongs above the results, not above the whole layout: sitting in
   the head it stretched the full width and left the sort control stranded a
   thousand pixels from the count it belongs to. */
.shop__main { display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--s-5); align-content: start; }
/* On mobile the sidebar is a fixed sheet and its wrapper has no job, so it is
   taken out of the grid entirely rather than left as an empty row. */
.shop__aside { display: contents; }
.shop__bar { display: flex; flex-wrap: wrap; gap: var(--s-3); align-items: center; }
.shop__bar .shop__count { color: var(--c-fg-muted); font-size: var(--fs-sm); }
.shop__sort { margin-left: auto; }
/* Once the bar wraps, `margin-left: auto` stranded the sort control against the
   right edge of its own line with nothing beside it, which read as a stray
   dropdown rather than a deliberate pairing. On its own row it takes the row. */
@media (max-width: 560px) {
  .shop__sort { margin-left: 0; flex: 1 0 100%; }
}

.shop__filters { display: none; }
/* Down from the top, over the header. Rising from the bottom put the filters
   below the control that opened them, in the part of the screen the visitor had
   just looked away from. */
.shop__filters.is-open {
  display: block;
  position: fixed; inset: 0 0 auto 0; z-index: var(--z-modal);
  max-height: 86vh; overflow-y: auto; overscroll-behavior: contain;
  padding: var(--s-5) var(--pad-x) var(--s-6);
  background: var(--c-surface);
  border-bottom: 1px solid var(--c-line-strong);
  border-radius: 0 0 var(--r-lg) var(--r-lg);
  box-shadow: var(--sh-lg);
}

@media (min-width: 860px) {
  /* No `align-items: start` here: a sticky sidebar can only travel inside
     its own grid area, and a start-aligned item's area is just its content,
     so it scrolled away with the page. Stretched, it has the whole row. */
  .shop { grid-template-columns: 248px minmax(0, 1fr); gap: var(--s-7); }
  .shop__head { grid-column: 1 / -1; align-self: start; }
  /* The wrapper is the grid item and stretches to the full row; the sidebar is
     sticky inside it. Without the wrapper the sidebar was the stretched item
     itself, and a sticky box that already fills its containing block has
     nowhere to travel, so it scrolled away like any other element. */
  .shop__aside { display: block; }
  /* The sidebar holds its place while the grid scrolls past it, and scrolls on
     its own once the filter list is longer than the screen — so a long list can
     never make the page taller than the results it is filtering. */
  .shop__filters, .shop__filters.is-open {
    /* `inset: auto` first, then `top`. The other way round the shorthand reset
       the offset it had just been given and the sidebar never stuck at all -
       which is how it shipped, because nothing was measuring it. */
    display: block; inset: auto; position: sticky; top: var(--shop-sticky-top);
    padding: 0 var(--s-2) 0 0;
    max-height: calc(100vh - var(--shop-sticky-top) - var(--s-5));
    overflow-y: auto; overscroll-behavior: contain; scrollbar-gutter: stable;
    background: none; border: 0; border-radius: 0; box-shadow: none;
  }
  .shop__filter-toggle { display: none; }
  /* Stays visible while the filter list scrolls under it. */
  .shop__filters-hd { position: sticky; top: 0; z-index: 1;
    padding-block: var(--s-2); background: var(--c-bg); }
}

/* --------------------------------------------- page-level helper classes */
/* These exist so no template carries a style attribute. A value used on two
   pages belongs in components.css instead; these are each used once. */

.msgs { padding-top: var(--s-4); }

.drawer__hd { margin-bottom: var(--s-5); }
.drawer__lang { margin-top: var(--s-5); }
.drawer__lang .label { display: block; margin-bottom: var(--s-2); }

.foot__link { display: inline-flex; align-items: center; gap: var(--s-2); }

.crumbs--page { margin-bottom: var(--s-5); }

/* Quantity stepper beside the heart and share buttons. The stack has to stop
   stretching or the stepper grows to fill the row and pushes the actions off the
   side of a 390 px screen — which is exactly what it did the first time. */
.pdp__qty { display: flex; flex-wrap: wrap; gap: var(--s-4); align-items: flex-end; }
.pdp__qty > .stack { align-items: flex-start; flex: 0 0 auto; }
.pdp__qty > .pdp__actions { margin-left: auto; }

/* The rating links down to the reviews section; keep it clear of the sticky nav. */
/* Clear of the sticky header, plus air. This was `var(--s-8)` — 64 px against
   a 69 px header — so tapping the rating at the top of the page scrolled the
   reviews heading five pixels *underneath* the nav, on every width and in
   every language. A spacing token is a guess about the header; `--nav-h` is
   the header. */
.pdp__reviews { scroll-margin-top: calc(var(--nav-h) + var(--s-4)); }

.spec dt .i { vertical-align: -3px; margin-right: var(--s-1); }

.shop__clear { border-bottom: 0 !important; }
.shop__filters-hd { margin-bottom: var(--s-3); }

/* The pager's "previous" chevron is the "next" symbol turned around, so the
   sprite only has to carry one. */
.i--flip { transform: rotate(180deg); }

/* ------------------------------------------------------------------ home */
/* The hero is height-capped so a product row is always partly visible at
   390 x 844 without scrolling (§17 #29). It leads with a real design rather
   than a fixed marketing image, so the first thing on the page is stock. */
/* ------------------------------------------------------------ home slides */
/* A scroll-snap track. The browser does the swiping, the momentum and the
   snapping; the script only drives the arrows, the dots and the advancing.
   That is why the carousel still works with the script blocked, and why
   there is no transform-based slider here to keep in sync with anything.

   The track sits inside `.wrap`, so the gutter either side is the page's own
   `--pad-x` and the card lines up with every other band on the page rather
   than being full-bleed. That is the owner's ask on both widths: a little
   space to the border on a phone, the same space on a laptop. */
.slides { padding-block: var(--s-4) 0; }
.slides__frame { position: relative; }

.slides__track {
  display: flex;
  gap: var(--s-3);
  margin: 0; padding: 0;
  list-style: none;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.slides__track::-webkit-scrollbar { display: none; }

.slides__item {
  flex: 0 0 100%;
  scroll-snap-align: center;
  scroll-snap-stop: always;
  aspect-ratio: var(--ar-slide);
  overflow: hidden;
  border-radius: var(--r-md);
  background: var(--c-surface);
}
.slides__link { display: block; width: 100%; height: 100%; }
/* Centred, and said rather than left to the default: below 860 px the
   16:5 picture is wider than its 16:9 card, and the middle is the part
   that is kept (§17 #300). */
.slides__item img {
  width: 100%; height: 100%; display: block;
  object-fit: cover; object-position: center;
}

/* Over the picture on a laptop, where there is room beside the card and a
   pointer to reach them with. Below 860 px they are hidden: a thumb swipes,
   and two 44 px targets sitting on top of the image would cover the part of
   it the owner cares about. */
.slides__arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  display: none;
  align-items: center; justify-content: center;
  width: 44px; height: 44px;
  color: var(--c-fg);
  background: var(--c-surface);
  border: 1px solid var(--c-line);
  border-radius: 50%;
  cursor: pointer;
  transition: border-color var(--t-fast), background var(--t-fast);
}
.slides__arrow:hover { border-color: var(--c-fg-subtle); }
.slides__arrow[disabled] { opacity: .4; cursor: default; }
.slides__arrow--prev { left: var(--s-4); }
.slides__arrow--next { right: var(--s-4); }

/* The dots and the pause button do nothing without the script, so a browser
   that will not run one must not be shown them - and a browser that will must
   be shown them *from the first paint*, not when the script gets round to it.
   The row used to ship with `hidden` and `slides.js` removed it, which added
   its height to the page after paint and pushed every band below the carousel
   down: a measured CLS of 0.053 on the home page against a budget of zero,
   while the shop measured 0.000 (§17 #276).

   `html.js` is set by one line in the head, so the row's height is decided
   before anything is painted. Nothing here is revealed by script. */
.slides__controls { display: none; }
html.js .slides__controls {
  display: flex; align-items: center; justify-content: center;
  gap: var(--s-4);
  padding-block: var(--s-3);
}

.slides__dots { display: flex; align-items: center; gap: var(--s-2); }
/* The dot is 8 px and the button around it is 44: the mark is small because
   it is a position indicator, the target is not because it is a target. */
.slides__dot {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  background: none; border: 0; cursor: pointer;
}
.slides__dot::before {
  content: ""; width: 8px; height: 8px; border-radius: 50%;
  background: var(--c-line-control);
  transition: background var(--t-fast), transform var(--t-fast);
}
.slides__dot[aria-selected="true"]::before {
  background: var(--c-brand); transform: scale(1.35);
}

.slides__pause {
  display: flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  color: var(--c-fg-muted);
  background: none; border: 0; cursor: pointer;
}
.slides__pause:hover { color: var(--c-fg); }

@media (min-width: 860px) {
  .slides { padding-block: var(--s-6) 0; }
  /* Long on a laptop: the whole 16:5 picture, where the 16:9 card below
     shows its middle at full height (§17 #300). A token, like the phone's
     ratio above; it used to be written here as 1120 / 300 (§17 #299). */
  .slides__item { aspect-ratio: var(--ar-slide-wide); }
  .slides__arrow { display: flex; }
}

/* Nothing moves on its own for somebody who asked for that. The script reads
   the same query and never starts the timer; this is the belt to its braces,
   and it also stops the snap-scroll animating. */
@media (prefers-reduced-motion: reduce) {
  .slides__track { scroll-behavior: auto; }
}

.home__statement { max-width: 62ch; display: flex; flex-direction: column;
  gap: var(--s-4); align-items: flex-start; }



/* ------------------------------------------------------------------ cart */
.cartpage { display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--s-6); align-items: start; }
.cartline { display: grid; grid-template-columns: 84px minmax(0, 1fr);
  gap: var(--s-3) var(--s-4); padding-block: var(--s-4);
  border-bottom: 1px solid var(--c-line); }
.cartline__thumb { position: relative; grid-row: span 2;
  aspect-ratio: var(--ar-product); overflow: hidden;
  background: var(--c-surface); border: 1px solid var(--c-line); border-radius: var(--r-sm); }
.cartline__thumb img { position: absolute; inset: 0;
  width: 100%; height: 100%; object-fit: cover; }
.cartline__actions { grid-column: 2; display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--s-3); }
.cartline__total { margin-left: auto; font-weight: var(--fw-semibold); }
/* A line that went off sale: the badge sits at its own width in the column. */
.cartline__off { align-self: flex-start; }
.cartpage__summary { position: sticky; top: calc(var(--s-8) + var(--s-2)); }
.cartpage__total dt, .cartpage__total dd { font-size: var(--fs-lg);
  font-weight: var(--fw-semibold); color: var(--c-fg); }
.cartpage__total dd { color: var(--c-brand); }

@media (min-width: 860px) {
  .cartpage { grid-template-columns: minmax(0, 1fr) 340px; gap: var(--s-7); }
  .cartline { grid-template-columns: 96px minmax(0, 1fr) auto; }
  .cartline__actions { grid-column: auto; }
  .cartline__total { margin-left: 0; }
}

/* ------------------------------------------------------------------ auth */
/* One centred column, at every width, on every screen in the flow: sign in,
   sign up, the OTP step, the three reset steps and the payment result. Sign-in
   and sign-up used to be the exception — two columns from 1024 px with the
   brand mark parked in the right-hand half — which pushed the form off to the
   side of a wide screen and made a decoration the largest thing on a page with
   exactly one job (§17 #177). The mark is in the header already. */
.auth { display: grid; gap: var(--s-7); max-width: 460px; margin-inline: auto; }
.auth--narrow { max-width: 420px; }

/* --------------------------------------------------------------- account */
/* Tabs across the top on mobile, a sidebar from 860 px. */
.account { display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--s-6); align-items: start; }
.account__nav { display: flex; gap: var(--s-1); overflow-x: auto;
  border-bottom: 1px solid var(--c-line); }
.account__nav a { padding: var(--s-3) var(--s-4); white-space: nowrap; color: var(--c-fg-muted);
  border-bottom: 2px solid transparent; }
.account__nav a:hover { color: var(--c-fg); }
.account__nav a.is-active { color: var(--c-fg); border-bottom-color: var(--c-brand); }
.account__logout { display: none; }
/* Three links wrap rather than scroll. The strip was scrollable, but with no
   affordance it simply looked like a word cut in half at the screen edge. */
@media (max-width: 560px) {
  .account__nav { flex-wrap: wrap; overflow-x: visible; }
}

@media (min-width: 860px) {
  .account { grid-template-columns: 220px minmax(0, 1fr); gap: var(--s-7); }
  .account__nav { flex-direction: column; overflow: visible; border-bottom: 0;
    position: sticky; top: calc(var(--s-8) + var(--s-2)); }
  .account__nav a { padding: var(--s-3) 0; border-bottom: 0;
    border-left: 2px solid transparent; padding-left: var(--s-3); }
  .account__nav a.is-active { border-left-color: var(--c-brand); }
  .account__logout { display: block; margin-top: var(--s-4); }
}

/* ---------------------------------------------------- checkout / summary */
/* Same two-column shape as the cart: the form leads, the summary sits beside
   it on desktop and underneath on mobile. */
.checkout { display: grid; grid-template-columns: minmax(0, 1fr);
  gap: var(--s-6); align-items: start; }
.checkout__summary { position: sticky; top: calc(var(--s-8) + var(--s-2)); }

@media (min-width: 860px) {
  .checkout { grid-template-columns: minmax(0, 1fr) 340px; gap: var(--s-7); }
}

/* ------------------------------------------------------------ order steps */
/* Five states, each either done, current, or still ahead. Colour is never the
   only signal: the dot fills and the label changes weight too. */
.steps { display: grid; gap: 0; }
.steps__step { display: grid; grid-template-columns: 24px minmax(0, 1fr);
  gap: var(--s-3); align-items: center; padding-block: var(--s-3);
  color: var(--c-fg-subtle); }
.steps__dot { width: 12px; height: 12px; margin-inline: auto; border-radius: var(--r-full);
  border: 2px solid var(--c-line-strong); background: var(--c-bg); }
.steps__step.is-done { color: var(--c-fg-muted); }
.steps__step.is-done .steps__dot { background: var(--c-fg-muted); border-color: var(--c-fg-muted); }
.steps__step.is-current { color: var(--c-fg); font-weight: var(--fw-semibold); }
.steps__step.is-current .steps__dot { background: var(--c-brand); border-color: var(--c-brand);
  box-shadow: 0 0 0 4px var(--c-brand-bg); }
/* The connecting line, drawn between the dots rather than under the labels. */
.steps__step + .steps__step { position: relative; }
.steps__step + .steps__step::before { content: ""; position: absolute;
  left: 11px; top: calc(-50% + 6px); width: 2px; height: 100%; background: var(--c-line); }

/* ---------------------------------------------------------------- panels */
.panel { padding: var(--s-5); background: var(--c-surface);
  border: 1px solid var(--c-line); border-radius: var(--r-md); }

/* Long-form copy: legal pages, about, delivery, the size guide. */
.prose { max-width: 68ch; }
.prose img { border: 1px solid var(--c-line); border-radius: var(--r-md); }

/* The size guide is one photograph or diagram the owner uploads. It is the
   content of the page, so it gets the full column and is never cropped: an
   intrinsic ratio is unknown here, so `height: auto` with the `.prose` width
   cap is the honest constraint. */
.sizeguide__img { display: block; width: 100%; height: auto; }

/* ------------------------------------------------------- legal documents */
/* The terms, the privacy policy and the delivery page share one shape: the
   title with the version in force, a contents list, the text in a readable
   measure, and the history of versions under it (§17 #182). On a phone the
   contents fold away above the text; from 1024 px they are a rail that stays
   beside it. The two copies are never on screen together (§17 #192). */
.legal { display: grid; gap: var(--s-6); }
.legal__head { display: grid; gap: var(--s-2); }
/* A document's title can be one long word: «конфиденциальности» is 422 px at
   the display size, and a 390 px phone has a 351 px column. The title steps
   down on a phone instead of pushing the page sideways, and below 320 px it may
   break rather than overflow (§17 #65, #202). */
.legal__head .h1 { font-size: clamp(var(--fs-2xl), 7.5vw, var(--fs-5xl));
  overflow-wrap: anywhere; }
.legal__meta { color: var(--c-fg-hint); font-size: var(--fs-sm); }
.legal__layout { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-6); }
.legal__text { display: grid; gap: var(--s-7); }

/* A translation says, once and at the top, which text is the original. */
.legal__lang { padding: var(--s-3) var(--s-4); border-left: 2px solid var(--c-brand);
  background: var(--c-surface); color: var(--c-fg-muted); font-size: var(--fs-sm); }

/* The header is sticky, so a section a contents link jumps to has to land
   below it rather than under it (§17 #140). */
.legal__sec { display: grid; gap: var(--s-3);
  scroll-margin-top: calc(var(--nav-h) + var(--s-4)); }
.legal__sec h2 { font-family: var(--f-display); font-size: var(--fs-2xl);
  font-weight: var(--fw-semibold); line-height: var(--lh-snug); color: var(--c-fg); }
.legal__sec h3 { margin-top: var(--s-2); font-size: var(--fs-lg);
  font-weight: var(--fw-semibold); color: var(--c-fg); }
.legal__sec p, .legal__sec li { color: var(--c-fg-muted); line-height: var(--lh-normal); }
.legal__sec strong { color: var(--c-fg); font-weight: var(--fw-semibold); }
.legal__sec ul { display: grid; gap: var(--s-2); padding-left: var(--s-5); list-style: disc; }
.legal__sec li::marker { color: var(--c-fg-subtle); }
.legal__sec code { font-family: var(--f-mono); font-size: var(--fs-sm); color: var(--c-fg); }

/* The seller's details and the delivery prices: the label over its value on a
   phone, beside it from 720 px. A long email address breaks where it must
   rather than pushing the page sideways (§17 #65). */
.legal__facts { display: grid; }
.legal__facts > div { display: grid; gap: var(--s-1); padding-block: var(--s-3);
  border-bottom: 1px solid var(--c-line); }
.legal__facts dt { color: var(--c-fg-hint); font-size: var(--fs-sm); }
.legal__facts dd { color: var(--c-fg); overflow-wrap: anywhere; }

.legal__history { display: grid; gap: var(--s-4); padding-top: var(--s-6);
  border-top: 1px solid var(--c-line); }

/* The contents. Every entry is a full-height row on a phone, where it is a
   tap target (§9); on the rail, where a pointer does the aiming, it is text. */
.legal__toc a { display: flex; align-items: center; min-height: 44px;
  color: var(--c-fg-muted); font-size: var(--fs-sm); line-height: var(--lh-snug);
  transition: color var(--t-fast); }
.legal__toc a:hover { color: var(--c-fg); }
.legal__toc--fold { border: 1px solid var(--c-line); border-radius: var(--r-md);
  background: var(--c-surface); }
.legal__toc--fold summary { display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-3); min-height: 44px; padding-inline: var(--s-4); cursor: pointer;
  font-weight: var(--fw-semibold); list-style: none; }
.legal__toc--fold summary::-webkit-details-marker { display: none; }
.legal__toc--fold summary .i { color: var(--c-fg-muted); transition: transform var(--t-base); }
.legal__toc--fold[open] summary .i { transform: rotate(180deg); }
.legal__toc--fold ol { padding: 0 var(--s-4) var(--s-3); }
.legal__toc--rail { display: none; }

@media (min-width: 720px) {
  .legal__facts > div { grid-template-columns: var(--legal-label) minmax(0, 1fr);
    gap: var(--s-4); }
}

@media (min-width: 1024px) {
  .legal__layout { grid-template-columns: var(--legal-rail) minmax(0, 1fr); gap: var(--s-8); }
  .legal__toc--fold { display: none; }
  .legal__toc--rail { display: block; position: sticky; align-self: start;
    top: calc(var(--nav-h) + var(--s-5));
    max-height: calc(100vh - var(--nav-h) - var(--s-7)); overflow-y: auto;
    padding-right: var(--s-4); border-right: 1px solid var(--c-line); }
  .legal__toc--rail a { min-height: 0; padding-block: var(--s-2); }
}
