/* ==========================================================================
   SEZAR Studio — rendered page styles
   --------------------------------------------------------------------------
   THIS FILE IS THE ALIAS LAYER between the editor's vocabulary and the frozen
   design tokens, and it is the only place the two meet.

   `design-system/tokens/sezar-tokens.css` is the visual contract (D-004) and is
   not edited here. Its spacing scale is numeric — `--space-1` … `--space-32` —
   because that is what a design system uses. An editor choosing "large padding"
   should not have to know that large is 12, so Studio speaks in names and this
   block binds each name to a step of the frozen scale.

   Widening or re-tuning the scale means changing an alias here or a token
   there, once, and every Studio page that referenced it moves. That is the
   global design system the brief asks for, and it is why a Studio page cannot
   drift away from the rest of the shop.

   NO RAW VALUES BELOW THIS BLOCK. Every length, colour, radius and shadow in
   this file resolves to a token. The renderer test greps the rendered HTML for
   pixels and hex; this file is the other half of that promise.
   WHERE THIS FILE LOADS FROM. `public/css/`, beside `storefront.css` and
   `sezar-tokens.css`, and linked by the layout only on a page Studio renders.
   The storefront does not go through Vite — it links plain stylesheets — and
   putting Studio's through a different pipeline would mean the token file and
   the file that references it could be built at different times.
   ========================================================================== */

:root {
  /* -------------------------------------------------------------- SPACING -- */
  --studio-space-none: 0;
  --studio-space-xs: var(--space-2);
  --studio-space-s: var(--space-4);
  --studio-space-m: var(--space-8);
  --studio-space-l: var(--space-12);
  --studio-space-xl: var(--space-20);
  --studio-space-xxl: var(--space-32);

  /* --------------------------------------------------------------- RADIUS -- */
  --studio-radius-none: 0;
  --studio-radius-s: var(--radius-sm);
  --studio-radius-m: var(--radius-md);
  --studio-radius-l: var(--radius-lg);
  --studio-radius-pill: var(--radius-pill);
  --studio-radius-circle: var(--radius-circle);

  /* --------------------------------------------------------------- SHADOW -- */
  --studio-shadow-none: none;
  --studio-shadow-s: var(--shadow-sm);
  --studio-shadow-m: var(--shadow-md);
  --studio-shadow-l: var(--shadow-lg);

  /* ----------------------------------------------------------------- TEXT --
     NAMES OF COLOURS, NOT COLOURS. `StyleSchema::COLOUR` is this list; a value
     outside it has no alias and resolves to nothing, so an unexpected value
     shows as inherited text rather than as a colour nobody approved.          */
  --studio-text-inherit: inherit;
  --studio-text-primary: var(--text-primary);
  --studio-text-secondary: var(--text-secondary);
  --studio-text-muted: var(--text-muted);
  --studio-text-on-inverse: var(--text-on-inverse);
  --studio-text-on-gold: var(--text-on-gold);
  --studio-text-gold: var(--accent-gold);
  --studio-text-olive: var(--accent-olive);
  --studio-text-blush: var(--accent-blush);
  --studio-text-terracotta: var(--color-terracotta-500);
  --studio-text-white: var(--color-white);

  /* -------------------------------------------------------------- SURFACE -- */
  --studio-surface-none: transparent;
  --studio-surface-page: var(--surface-page);
  --studio-surface-page-alt: var(--surface-page-alt);
  --studio-surface-card: var(--surface-card);
  --studio-surface-sunken: var(--surface-sunken);
  --studio-surface-inverse: var(--surface-inverse);
  --studio-surface-gold: var(--surface-gold);
}

/* ==========================================================================
   THE NODE
   --------------------------------------------------------------------------
   Every widget's outer element reads the same custom properties. A node that
   set none of them inherits `initial` for each and lays out as an ordinary
   block, which is why an unstyled widget still looks like part of the page.
   ========================================================================== */

[class*="s-"] {
  /* EACH CHAIN ENDS ON THE ELEMENT, NOT AT AN ANCESTOR, AND THE
     NARROWEST BREAKPOINT IS READ FIRST. The old chain ended
     `var(--s-pad-block)`, which inside a media query resolves to the value
     INHERITED from the parent — so every child of a padded container took
     the container's padding as its own, and a hero headline sat in 160px
     of padding nobody asked for. Ending at a literal is what stops a style
     leaking down the tree.

     The desktop value is `--s-*-desktop` rather than the bare property
     because the bare property is emitted INLINE, and inline beats a media
     query — so a phone override on any node that also had a desktop value
     lost to it and did nothing. */
  /* RESET FIRST, THEN READ. A custom property INHERITS, so
     `--s-pad-block-desktop` set inline on a container reaches every
     descendant — and a descendant reading `var(--s-pad-block-desktop, 0px)`
     would find the ancestor's value rather than the fallback. That is the
     same leak as before, one level along: children of a padded container
     taking its padding as their own.

     `initial` on a custom property is the guaranteed-invalid value, which
     makes `var()` take its fallback — so this line says "unless this
     element was given one". An inline declaration on the element that WAS
     given one outranks this rule, so the value lands where it was set and
     nowhere else. */
  --s-gap-desktop: initial;
  --s-gap-tablet: initial;
  --s-gap-mobile: initial;
  --s-pad-block-desktop: initial;
  --s-pad-block-tablet: initial;
  --s-pad-block-mobile: initial;
  --s-pad-inline-desktop: initial;
  --s-pad-inline-tablet: initial;
  --s-pad-inline-mobile: initial;
  --s-mt-desktop: initial;
  --s-mt-tablet: initial;
  --s-mt-mobile: initial;
  --s-mb-desktop: initial;
  --s-mb-tablet: initial;
  --s-mb-mobile: initial;
  --s-radius-desktop: initial;
  --s-radius-tablet: initial;
  --s-radius-mobile: initial;
  --s-shadow-desktop: initial;
  --s-shadow-tablet: initial;
  --s-shadow-mobile: initial;
  --s-text-desktop: initial;
  --s-text-tablet: initial;
  --s-text-mobile: initial;
  --s-surface-desktop: initial;
  --s-surface-tablet: initial;
  --s-surface-mobile: initial;

  --s-gap: var(--s-gap-desktop, var(--studio-space-m));
  --s-pad-block: var(--s-pad-block-desktop, 0px);
  --s-pad-inline: var(--s-pad-inline-desktop, 0px);
  --s-mt: var(--s-mt-desktop, 0px);
  --s-mb: var(--s-mb-desktop, 0px);
  --s-radius: var(--s-radius-desktop, 0px);
  --s-shadow: var(--s-shadow-desktop, none);
  --s-text: var(--s-text-desktop, inherit);
  --s-surface: var(--s-surface-desktop, transparent);
}

/* A CONTAINER GIVEN A COLUMN COUNT IS A GRID. Flex row was the first attempt
   and it sized the hero's two halves 72/28, because flex items take their
   content's width and a photograph is wider than a paragraph. The approved hero
   is `1fr 1fr`, and equal halves are what a builder's column control has to
   mean — `minmax(0, 1fr)` so one long product name cannot blow a track out. */
.s-container[class*="s-cols-"] {
  display: grid;
  grid-template-columns: repeat(var(--s-cols, 1), minmax(0, 1fr));
}

/* AN IMAGE IN A STRETCHED CELL FILLS IT — but only once the row has a height
   to fill. `block-size: 100%` on a grid item whose row is content-sized is
   circular: the row is as tall as the image, so 100% resolves to the image's
   own height and the hero came out 1191px tall instead of 512. The container's
   `min-block-size` is the definite height, and `1fr` rows are what hand it to
   the cell. `min-block-size: 0` lets the image shrink below its intrinsic
   height so `object-fit: cover` can crop, which is the whole point.

   This is what `.hero-media { min-height: 560px }` plus `img { height: 100% }`
   does for the approved hero, expressed without a wrapper element. */
.s-container[class*="s-min-height-"][class*="s-cols-"] {
  grid-auto-rows: minmax(0, 1fr);
}

/* A RESPONSIVE IMAGE IS WRAPPED IN `<picture>`, AND THAT WRAPPER BROKE THIS.
   Setting a phone-specific crop on the hero image makes `image.blade.php` emit
   `<picture><source><img class="s-image"></picture>`, and from that moment the
   img is no longer a direct child of the container: the grid laid out the
   PICTURE, the img kept its intrinsic height, and the hero's photograph sat
   481x321 in a 629px cell with the rest of the column empty. Found by setting a
   mobile crop on a real hero, not by review — an image with no mobile override
   was fine, so every existing page and every test stayed green.

   TWO THINGS ARE NEEDED AND NEITHER IS SUFFICIENT ALONE. `display: contents`
   removes the picture's box so the img becomes the grid item; but selectors
   match the DOM, not the box tree, so `> .s-image` still would not reach it.
   Hence the second selector. Scoped to `.s-container` so the storefront's
   `.overlay-band > picture`, which needs a real box to sit in a grid area,
   is untouched. */
.s-container > picture {
  display: contents;
}

/* AND ITS `<source>` HAS TO BE TOLD IT IS NOT A BOX. `display: contents` on the
   picture promotes EVERY child to a grid item, and `<source>` is a child. The
   hero came back as a 2x2: copy top-right, an empty cell where the source
   landed, and the photograph pushed onto a second row. Chrome's UA stylesheet
   does not hide a bare `<source>`, so nothing was hiding it — the element
   renders nothing and still takes a cell.

   Found by applying the fix above and looking at the canvas, which is the only
   place this is visible: the markup is identical either way. */
.s-container > picture > source {
  display: none;
}

.s-container[class*="s-cols-"] > .s-image,
.s-container[class*="s-cols-"] > picture > .s-image {
  block-size: 100%;
  min-block-size: 0;
}

/* AN EXPLICIT CROP OUTRANKS FILLING THE CELL, and without this it did not.
   `block-size: 100%` beats `aspect-ratio`, so an editor who chose Square got
   whatever the row happened to be — and on a phone the row is not definite,
   because a stacked hero has no `min-block-size` to hand it one. `100%` of an
   indefinite row resolves against content that is itself the image, and the
   browser settles the circle at a number nobody asked for: the hero came out
   1916px tall on a 390px phone, and the Sarab band 1632.
   Found by measuring the rendered page at 390, not by looking at it — the
   screenshot just showed a lot of empty sand.

   `aspect: auto` is the one that means "fill the cell", and it keeps doing so.
   Every other value is a decision and now wins. */
.s-container[class*="s-cols-"] > .s-image:not(.s-aspect-auto),
.s-container[class*="s-cols-"] > picture > .s-image:not(.s-aspect-auto) {
  block-size: auto;
}

.s-container {
  display: flex;
  flex-direction: column;
  gap: var(--s-gap);
  padding-block: var(--s-pad-block);
  padding-inline: var(--s-pad-inline);
  margin-block-start: var(--s-mt);
  margin-block-end: var(--s-mb);
  border-radius: var(--s-radius);
  box-shadow: var(--s-shadow);
  background: var(--s-surface, transparent);
}

/* Everything that is not a container still honours spacing and shape. */
/* `.s-button` IS ABSENT FROM THIS LIST DELIBERATELY. It would set
   `padding-block: var(--s-pad-block)` — zero by default — and studio.css loads
   after storefront.css, so it would flatten the `.btn` padding and render every
   Studio button as bare text. It takes margins and nothing else, below. */
.s-heading,
.s-text,
.s-image,
.s-icon,
.s-divider,
.s-spacer,
.s-video,
.s-gallery,
.s-carousel,
.s-product-grid,
.s-collection-grid,
.s-collection-banner,
.s-category-grid,
.s-featured-product,
.s-new-arrivals,
.s-sale-products,
.s-sale-banner {
  padding-block: var(--s-pad-block);
  padding-inline: var(--s-pad-inline);
  margin-block-start: var(--s-mt);
  margin-block-end: var(--s-mb);
  border-radius: var(--s-radius);
  box-shadow: var(--s-shadow);
  color: var(--s-text, inherit);
}

/* ==========================================================================
   LAYOUT MODIFIERS
   --------------------------------------------------------------------------
   A class per named choice, matching `StyleSchema::GROUPS`. There is no class
   here that a node could be given without the validator having permitted the
   value first — these are the vocabulary, not an escape from it.
   ========================================================================== */

.s-direction-row      { flex-direction: row; }
.s-direction-column   { flex-direction: column; }
.s-wrap-wrap    { flex-wrap: wrap; }
.s-wrap-nowrap  { flex-wrap: nowrap; }

.s-align-start   { align-items: flex-start; }
.s-align-center  { align-items: center; }
.s-align-end     { align-items: flex-end; }
.s-align-stretch { align-items: stretch; }

.s-justify-start   { justify-content: flex-start; }
.s-justify-center  { justify-content: center; }
.s-justify-end     { justify-content: flex-end; }
.s-justify-between { justify-content: space-between; }
.s-justify-around  { justify-content: space-around; }
.s-justify-evenly  { justify-content: space-evenly; }

.s-width-auto   { inline-size: auto; }
.s-width-full   { inline-size: 100%; }
.s-width-boxed  { inline-size: 100%; max-inline-size: var(--container-max); margin-inline: auto; }
.s-width-narrow { inline-size: 100%; max-inline-size: 65ch; margin-inline: auto; }

.s-min-height-auto   { min-block-size: auto; }
.s-min-height-s      { min-block-size: var(--studio-space-xxl); }
.s-min-height-m      { min-block-size: calc(var(--studio-space-xxl) * 2); }
.s-min-height-l      { min-block-size: calc(var(--studio-space-xxl) * 3); }
.s-min-height-xl     { min-block-size: calc(var(--studio-space-xxl) * 4); }
.s-min-height-screen { min-block-size: 100svh; }

/* LOGICAL PROPERTIES THROUGHOUT — `start` and `end`, never `left` and `right`.
   Arabic is not a stylesheet flip (CLAUDE.md); a rule written with `left` is a
   rule that is wrong in one of the two languages this shop is read in. */
.s-text-align-start  { text-align: start; }
.s-text-align-center { text-align: center; }
.s-text-align-end    { text-align: end; }

.s-border-none   { border: none; }
.s-border-subtle { border: 1px solid var(--border-subtle); }
.s-border-strong { border: 1px solid var(--border-strong); }

.s-hidden { display: none !important; }

/* ==========================================================================
   TYPOGRAPHY ROLES
   --------------------------------------------------------------------------
   The role is the SIZE; the tag is the OUTLINE. They are separate so a band
   that must read as an h2 can be an h3 in the document, and a page keeps a
   correct heading structure without every visual choice renumbering it.
   ========================================================================== */

/* THE FROZEN FILE HAS NO COMPOSITE TYPE TOKEN. It has the parts —
   `--text-3xl`, `--leading-tight`, `--weight-regular`, `--font-display` — and a
   role is a named combination of them, declared here rather than invented per
   widget. A widget that set its own size would be exactly the drift the token
   file exists to prevent. */

.s-type-role-h1,
.s-type-role-h2,
.s-type-role-h3,
.s-type-role-h4 {
  font-family: var(--font-display);
  font-weight: var(--weight-regular);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}

.s-type-role-h1 { font-size: var(--text-4xl); }
.s-type-role-h2 { font-size: var(--text-3xl); }
.s-type-role-h3 { font-size: var(--text-2xl); }
.s-type-role-h4 { font-size: var(--text-xl); }

.s-type-role-body,
.s-type-role-body-large,
.s-type-role-small,
.s-type-role-caption {
  font-family: var(--font-body);
  font-weight: var(--weight-regular);
  line-height: var(--leading-normal);
  letter-spacing: var(--tracking-normal);
}

.s-type-role-body       { font-size: var(--text-base); }
.s-type-role-body-large { font-size: var(--text-md); }
.s-type-role-small      { font-size: var(--text-sm); }
.s-type-role-caption    { font-size: var(--text-xs); letter-spacing: var(--tracking-wide); }

.s-type-role-button {
  font-family: var(--font-body);
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  line-height: var(--leading-snug);
  letter-spacing: var(--tracking-wide);
}

/* ARABIC IS A DIFFERENT TYPEFACE, not the Latin one with the text reversed.
   `--font-arabic` is Tajawal; Cormorant Garamond carries no Arabic and would
   fall through to a system serif — the moment the page stops looking like SEZAR
   for half its traffic. Scoped on `dir`, so a mixed page is still right. */
[dir="rtl"] .s-type-role-h1,
[dir="rtl"] .s-type-role-h2,
[dir="rtl"] .s-type-role-h3,
[dir="rtl"] .s-type-role-h4,
[dir="rtl"] .s-type-role-body,
[dir="rtl"] .s-type-role-body-large,
[dir="rtl"] .s-type-role-small,
[dir="rtl"] .s-type-role-caption,
[dir="rtl"] .s-type-role-button {
  font-family: var(--font-arabic);
  letter-spacing: var(--tracking-normal);
}

/* Headings step down on a phone. A 3.75rem headline at 390px is four words a
   line and a band nobody reads. */
@media (max-width: 767px) {
  .s-type-role-h1 { font-size: var(--text-3xl); }
  .s-type-role-h2 { font-size: var(--text-2xl); }
  .s-type-role-h3 { font-size: var(--text-xl); }
  .s-type-role-h4 { font-size: var(--text-lg); }
}

/* ==========================================================================
   GRIDS
   --------------------------------------------------------------------------
   `minmax(0, 1fr)` rather than `1fr`: a grid track's default minimum is
   `auto`, so one long product name or an image with no intrinsic size blows
   the column out and the whole row stops being equal. This is the difference
   between a grid that survives real content and one that survives the demo.
   ========================================================================== */

.s-product-grid,
.s-product-carousel,
.s-collection-grid,
.s-category-grid,
.s-new-arrivals,
.s-sale-products,
.s-gallery {
  display: grid;
  gap: var(--s-gap);
  grid-template-columns: repeat(var(--s-cols, 1), minmax(0, 1fr));
}

.s-cols-1 { --s-cols: 1; }
.s-cols-2 { --s-cols: 2; }
.s-cols-3 { --s-cols: 3; }
.s-cols-4 { --s-cols: 4; }
.s-cols-5 { --s-cols: 5; }
.s-cols-6 { --s-cols: 6; }

.s-gallery { list-style: none; padding-inline-start: 0; }

/* A CAROUSEL SCROLLS. `.s-product-carousel` was in the grid list above, so nine
   products at four columns laid out as three rows — a grid wearing the word
   carousel, 1548px tall, found by measuring the band rather than reading it.
   Auto-flow column with scroll snap is the same treatment `.s-carousel-track`
   gets, and it works with no JavaScript at all: every card is present,
   reachable by keyboard and swipeable. */
.s-product-carousel {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--s-cols, 1) - 1) * var(--s-gap)) / var(--s-cols, 1));
  gap: var(--s-gap);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-padding-inline-start: var(--s-pad-inline);
  scrollbar-width: thin;
  /* Room for the card's hover lift, which an overflow container would clip. */
  padding-block-end: var(--studio-space-xs);
}

.s-product-carousel > * { scroll-snap-align: start; }

/* ==========================================================================
   MEDIA
   ========================================================================== */

.s-image,
.s-gallery img,
.s-collection-grid img,
.s-category-grid img {
  display: block;
  inline-size: 100%;
  block-size: auto;
  object-fit: var(--s-fit, cover);
}

/* ASPECT RESERVES THE BOX BEFORE THE BYTES ARRIVE. With `width` and `height`
   on the element as well, nothing below the image moves when it loads —
   layout shift is the one performance fault a visitor actually feels. */
.s-aspect-auto      { aspect-ratio: auto; }
.s-aspect-square    { aspect-ratio: 1 / 1; }
.s-aspect-portrait  { aspect-ratio: 3 / 4; }
.s-aspect-landscape { aspect-ratio: 4 / 3; }
.s-aspect-wide      { aspect-ratio: 16 / 9; }
/* For a band that runs edge to edge. 16:9 at 1905px is 1071px tall, which is
   not a banner, it is a page. */
.s-aspect-banner    { aspect-ratio: 3 / 1; }

.s-fit-cover   { --s-fit: cover; }
.s-fit-contain { --s-fit: contain; }

.s-video video,
.s-video img { inline-size: 100%; block-size: auto; display: block; }

.s-video-play {
  display: block;
  inline-size: 100%;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

/* THE CAROUSEL IS A SCROLL CONTAINER FIRST. Every slide is present, reachable
   by keyboard and swipeable, before any JavaScript arrives. The script adds
   arrows and dots; it is not what makes the widget work. */
.s-carousel-track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--s-cols, 1) - 1) * var(--s-gap)) / var(--s-cols, 1));
  gap: var(--s-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: thin;
}

.s-carousel-track > * { scroll-snap-align: start; }

/* ==========================================================================
   BANNERS AND CARDS
   ========================================================================== */

.s-collection-banner,
.s-sale-banner {
  position: relative;
  display: block;
  overflow: hidden;
  border-radius: var(--s-radius);
}

.s-collection-banner img,
.s-sale-banner img { inline-size: 100%; block-size: auto; display: block; }

.s-banner-title {
  position: absolute;
  inset-block-end: var(--studio-space-m);
  inset-inline-start: var(--studio-space-m);
  color: var(--text-on-inverse);
  font-family: var(--font-display);
  font-size: var(--text-2xl);
  line-height: var(--leading-tight);
}

.s-overlay-none::after   { content: none; }
.s-overlay-soft::after,
.s-overlay-strong::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
}
.s-overlay-soft::after   { background: linear-gradient(transparent, rgb(0 0 0 / 35%)); }
.s-overlay-strong::after { background: linear-gradient(transparent, rgb(0 0 0 / 60%)); }

/* THE CARD HAS TO STAND ON ITS OWN WHEN THERE IS NO PHOTOGRAPH. Every category
   in this catalogue is currently without artwork, so a collection or category
   grid falls back to a name — and a name with no surface under it reads as a
   list of links, not a grid. The surface, the border and the hover are what
   make it a card either way. */
.s-card {
  display: flex;
  flex-direction: column;
  gap: var(--studio-space-xs);
  padding: var(--studio-space-s);
  border: 1px solid var(--border-subtle);
  border-radius: var(--studio-radius-m);
  background: var(--surface-card);
  text-decoration: none;
  color: inherit;
  transition: border-color 150ms ease, transform 150ms ease;
}

.s-card:hover {
  border-color: var(--border-strong);
  transform: translateY(-2px);
}

.s-card img { border-radius: var(--studio-radius-s); }

.s-card-name {
  display: block;
  font-family: var(--font-body);
  font-size: var(--text-sm);
  text-align: center;
}

[dir="rtl"] .s-card-name { font-family: var(--font-arabic); }

/* A card with no photograph should not collapse to one line of text. */
.s-card:not(:has(img)) { min-block-size: var(--studio-space-xxl); justify-content: center; }

@media (prefers-reduced-motion: reduce) {
  .s-card { transition: none; }
  .s-card:hover { transform: none; }
}
.s-caption {
  display: block;
  margin-block-start: var(--studio-space-xs);
  font-family: var(--font-body);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  color: var(--text-secondary);
}

/* Spacing only — the shop's `.btn` owns everything inside the button. */
.s-button {
  margin-block-start: var(--s-mt);
  margin-block-end: var(--s-mb);
  align-self: flex-start;
}

/* A centred band centres its button too. */
.s-align-center > .s-button,
.s-text-align-center.s-button { align-self: center; }

.s-divider { border: none; border-block-start: 1px solid var(--border-subtle); }
.s-divider-hairline { border-block-start-width: 1px; }
.s-divider-thick    { border-block-start-width: 2px; }

.s-spacer { block-size: var(--s-min-height, var(--studio-space-m)); }

.s-visually-hidden {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ==========================================================================
   EDITOR-ONLY
   --------------------------------------------------------------------------
   These classes are emitted ONLY when `RenderContext::$editing` is true, so
   none of this reaches a published page. Kept in this file rather than the
   editor's own so that an empty widget occupies the same box in the canvas
   that it will occupy once it is filled in.
   ========================================================================== */

.s-empty,
.s-node-hidden {
  padding: var(--studio-space-m);
  border: 1px dashed var(--border-subtle);
  border-radius: var(--studio-radius-m);
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  text-align: center;
}

.s-incomplete { opacity: 0.55; }

/* AN EMPTY ELEMENT HAS TO BE VISIBLE TO BE EDITED. These are editor-only —
   nothing on the shop ever carries them, because the shop renders an empty
   heading as nothing at all. */
.s-placeholder { opacity: 0.4; font-style: italic; }

.s-empty-container {
  min-block-size: var(--studio-space-xxl);
  border: 1px dashed var(--border-strong);
  border-radius: var(--studio-radius-m);
  align-items: center;
  justify-content: center;
}

.s-drop-hint {
  display: block;
  padding: var(--studio-space-m);
  color: var(--text-muted);
  font-family: var(--font-body);
  font-size: var(--text-sm);
  text-align: center;
}

/* ==========================================================================
   RESPONSIVE
   --------------------------------------------------------------------------
   THE CASCADE DOES THE INHERITING, not PHP. A node emits a value for a device
   only when the editor set one there; a device with no value keeps whatever
   the wider one resolved to. That is why changing a tablet gap moves the phone
   with it, and why nothing is ever stranded on a value nobody remembers.

   Mobile first in effect: the desktop declarations above are the base, and
   each narrower block below overrides only what was explicitly set.
   ========================================================================== */

@media (max-width: 1023px) {
  [class*="s-"] {
    --s-gap: var(--s-gap-tablet, var(--s-gap-desktop, var(--studio-space-m)));
    --s-pad-block: var(--s-pad-block-tablet, var(--s-pad-block-desktop, 0px));
    --s-pad-inline: var(--s-pad-inline-tablet, var(--s-pad-inline-desktop, 0px));
    --s-mt: var(--s-mt-tablet, var(--s-mt-desktop, 0px));
    --s-mb: var(--s-mb-tablet, var(--s-mb-desktop, 0px));
    --s-radius: var(--s-radius-tablet, var(--s-radius-desktop, 0px));
    --s-shadow: var(--s-shadow-tablet, var(--s-shadow-desktop, none));
    --s-text: var(--s-text-tablet, var(--s-text-desktop, inherit));
    --s-surface: var(--s-surface-tablet, var(--s-surface-desktop, transparent));
  }

  /* The colon is escaped because it is part of the class NAME, not a
     pseudo-class. `tablet:s-cols-2` is one identifier. */
  .tablet\:s-hidden { display: none !important; }

  .tablet\:s-cols-1 { --s-cols: 1; }
  .tablet\:s-cols-2 { --s-cols: 2; }
  .tablet\:s-cols-3 { --s-cols: 3; }
  .tablet\:s-cols-4 { --s-cols: 4; }
  .tablet\:s-cols-5 { --s-cols: 5; }
  .tablet\:s-cols-6 { --s-cols: 6; }

  .tablet\:s-direction-row    { flex-direction: row; }
  .tablet\:s-direction-column { flex-direction: column; }

  .tablet\:s-text-align-start  { text-align: start; }
  .tablet\:s-text-align-center { text-align: center; }
  .tablet\:s-text-align-end    { text-align: end; }

  .tablet\:s-width-full  { inline-size: 100%; max-inline-size: none; }
  .tablet\:s-width-boxed { inline-size: 100%; }

  .tablet\:s-align-start   { align-items: flex-start; }
  .tablet\:s-align-center  { align-items: center; }
  .tablet\:s-align-stretch { align-items: stretch; }

  /* ASPECT AND MIN-HEIGHT WERE MISSING FROM THE RESPONSIVE LAYER ENTIRELY.
     The inspector offers both per device and the compiler emitted
     `mobile:s-aspect-portrait` and `mobile:s-min-height-auto` correctly — and
     no rule anywhere matched either, so setting a phone crop did nothing at
     all. Silent: the class is in the markup, the control remembers the value,
     and the page ignores it. Found by measuring a mobile hero that stayed
     1916px tall after being told to be portrait.

     THE `grid-auto-rows` OVERRIDE IS THE OTHER HALF. A split band stacks to one
     column on a phone and its min-height goes to `auto`, which leaves the row
     indefinite — and `minmax(0, 1fr)` on an indefinite row makes both rows as
     tall as the tallest content, which for an image told to be `block-size:
     100%` is a circle the browser resolves at whatever it likes. A row with no
     definite height sizes to its content, and an image in it keeps its own
     height. */
  .tablet\:s-aspect-auto      { aspect-ratio: auto; }
  .tablet\:s-aspect-square    { aspect-ratio: 1 / 1; }
  .tablet\:s-aspect-portrait  { aspect-ratio: 3 / 4; }
  .tablet\:s-aspect-landscape { aspect-ratio: 4 / 3; }
  .tablet\:s-aspect-wide      { aspect-ratio: 16 / 9; }
  .tablet\:s-aspect-banner    { aspect-ratio: 4 / 3; }

  .tablet\:s-min-height-auto   { min-block-size: auto; }
  .tablet\:s-min-height-s      { min-block-size: var(--studio-space-xxl); }
  .tablet\:s-min-height-m      { min-block-size: calc(var(--studio-space-xxl) * 2); }
  .tablet\:s-min-height-l      { min-block-size: calc(var(--studio-space-xxl) * 3); }
  .tablet\:s-min-height-xl     { min-block-size: calc(var(--studio-space-xxl) * 4); }
  .tablet\:s-min-height-screen { min-block-size: 100svh; }

  /* THE REST OF THE RESPONSIVE SURFACE. Fifty-six classes the compiler emits
     per device were matched by no rule at either breakpoint — wrap, justify,
     align-end, width auto/narrow, every type role, fit, border and overlay.
     Setting any of them for a phone stored the value, rendered the class, and
     changed nothing. `StudioDesignTokensTest` had checked that every class the
     compiler can emit has a rule, but only ever asked about desktop.

     Mirrored from the desktop declarations above rather than rewritten, so the
     two cannot say different things. */
  .tablet\:s-wrap-wrap   { flex-wrap: wrap; }
  .tablet\:s-wrap-nowrap { flex-wrap: nowrap; }

  .tablet\:s-align-end { align-items: flex-end; }

  .tablet\:s-justify-start   { justify-content: flex-start; }
  .tablet\:s-justify-center  { justify-content: center; }
  .tablet\:s-justify-end     { justify-content: flex-end; }
  .tablet\:s-justify-between { justify-content: space-between; }
  .tablet\:s-justify-around  { justify-content: space-around; }
  .tablet\:s-justify-evenly  { justify-content: space-evenly; }

  .tablet\:s-width-auto   { inline-size: auto; max-inline-size: none; }
  .tablet\:s-width-narrow { inline-size: 100%; max-inline-size: 65ch; margin-inline: auto; }

  .tablet\:s-type-role-h1,
  .tablet\:s-type-role-h2,
  .tablet\:s-type-role-h3,
  .tablet\:s-type-role-h4 {
    font-family: var(--font-display);
    font-weight: var(--weight-regular);
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-tight);
  }

  .tablet\:s-type-role-h1 { font-size: var(--text-4xl); }
  .tablet\:s-type-role-h2 { font-size: var(--text-3xl); }
  .tablet\:s-type-role-h3 { font-size: var(--text-2xl); }
  .tablet\:s-type-role-h4 { font-size: var(--text-xl); }

  .tablet\:s-type-role-body,
  .tablet\:s-type-role-body-large,
  .tablet\:s-type-role-small,
  .tablet\:s-type-role-caption {
    font-family: var(--font-body);
    font-weight: var(--weight-regular);
    line-height: var(--leading-normal);
    letter-spacing: var(--tracking-normal);
  }

  .tablet\:s-type-role-body       { font-size: var(--text-base); }
  .tablet\:s-type-role-body-large { font-size: var(--text-md); }
  .tablet\:s-type-role-small      { font-size: var(--text-sm); }
  .tablet\:s-type-role-caption    { font-size: var(--text-xs); letter-spacing: var(--tracking-wide); }

  .tablet\:s-type-role-button {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    line-height: var(--leading-snug);
    letter-spacing: var(--tracking-wide);
  }

  .tablet\:s-fit-cover   { --s-fit: cover; }
  .tablet\:s-fit-contain { --s-fit: contain; }

  .tablet\:s-border-none   { border: none; }
  .tablet\:s-border-subtle { border: 1px solid var(--border-subtle); }
  .tablet\:s-border-strong { border: 1px solid var(--border-strong); }

  .tablet\:s-overlay-none::after { content: none; }

  .tablet\:s-overlay-soft::after,
  .tablet\:s-overlay-strong::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
  }

  .tablet\:s-overlay-soft::after   { background: linear-gradient(transparent, rgb(0 0 0 / 35%)); }
  .tablet\:s-overlay-strong::after { background: linear-gradient(transparent, rgb(0 0 0 / 60%)); }

  .s-container.tablet\:s-min-height-auto[class*="s-cols-"] {
    grid-auto-rows: auto;
  }

  .s-container.tablet\:s-min-height-auto[class*="s-cols-"] > .s-image,
  .s-container.tablet\:s-min-height-auto[class*="s-cols-"] > picture > .s-image {
    block-size: auto;
  }
}

@media (max-width: 767px) {
  [class*="s-"] {
    --s-gap: var(--s-gap-mobile, var(--s-gap-tablet, var(--s-gap-desktop, var(--studio-space-m))));
    --s-pad-block: var(--s-pad-block-mobile, var(--s-pad-block-tablet, var(--s-pad-block-desktop, 0px)));
    --s-pad-inline: var(--s-pad-inline-mobile, var(--s-pad-inline-tablet, var(--s-pad-inline-desktop, 0px)));
    --s-mt: var(--s-mt-mobile, var(--s-mt-tablet, var(--s-mt-desktop, 0px)));
    --s-mb: var(--s-mb-mobile, var(--s-mb-tablet, var(--s-mb-desktop, 0px)));
    --s-radius: var(--s-radius-mobile, var(--s-radius-tablet, var(--s-radius-desktop, 0px)));
    --s-shadow: var(--s-shadow-mobile, var(--s-shadow-tablet, var(--s-shadow-desktop, none)));
    --s-text: var(--s-text-mobile, var(--s-text-tablet, var(--s-text-desktop, inherit)));
    --s-surface: var(--s-surface-mobile, var(--s-surface-tablet, var(--s-surface-desktop, transparent)));
  }

  .mobile\:s-hidden { display: none !important; }

  .mobile\:s-cols-1 { --s-cols: 1; }
  .mobile\:s-cols-2 { --s-cols: 2; }
  .mobile\:s-cols-3 { --s-cols: 3; }
  .mobile\:s-cols-4 { --s-cols: 4; }
  .mobile\:s-cols-5 { --s-cols: 5; }
  .mobile\:s-cols-6 { --s-cols: 6; }

  .mobile\:s-direction-row    { flex-direction: row; }
  .mobile\:s-direction-column { flex-direction: column; }

  .mobile\:s-text-align-start  { text-align: start; }
  .mobile\:s-text-align-center { text-align: center; }
  .mobile\:s-text-align-end    { text-align: end; }

  .mobile\:s-width-full  { inline-size: 100%; max-inline-size: none; }
  .mobile\:s-width-boxed { inline-size: 100%; }

  .mobile\:s-align-start   { align-items: flex-start; }
  .mobile\:s-align-center  { align-items: center; }
  .mobile\:s-align-stretch { align-items: stretch; }

  /* ASPECT AND MIN-HEIGHT WERE MISSING FROM THE RESPONSIVE LAYER ENTIRELY.
     The inspector offers both per device and the compiler emitted
     `mobile:s-aspect-portrait` and `mobile:s-min-height-auto` correctly — and
     no rule anywhere matched either, so setting a phone crop did nothing at
     all. Silent: the class is in the markup, the control remembers the value,
     and the page ignores it. Found by measuring a mobile hero that stayed
     1916px tall after being told to be portrait.

     THE `grid-auto-rows` OVERRIDE IS THE OTHER HALF. A split band stacks to one
     column on a phone and its min-height goes to `auto`, which leaves the row
     indefinite — and `minmax(0, 1fr)` on an indefinite row makes both rows as
     tall as the tallest content, which for an image told to be `block-size:
     100%` is a circle the browser resolves at whatever it likes. A row with no
     definite height sizes to its content, and an image in it keeps its own
     height. */
  .mobile\:s-aspect-auto      { aspect-ratio: auto; }
  .mobile\:s-aspect-square    { aspect-ratio: 1 / 1; }
  .mobile\:s-aspect-portrait  { aspect-ratio: 3 / 4; }
  .mobile\:s-aspect-landscape { aspect-ratio: 4 / 3; }
  .mobile\:s-aspect-wide      { aspect-ratio: 16 / 9; }
  .mobile\:s-aspect-banner    { aspect-ratio: 4 / 3; }

  .mobile\:s-min-height-auto   { min-block-size: auto; }
  .mobile\:s-min-height-s      { min-block-size: var(--studio-space-xxl); }
  .mobile\:s-min-height-m      { min-block-size: calc(var(--studio-space-xxl) * 2); }
  .mobile\:s-min-height-l      { min-block-size: calc(var(--studio-space-xxl) * 3); }
  .mobile\:s-min-height-xl     { min-block-size: calc(var(--studio-space-xxl) * 4); }
  .mobile\:s-min-height-screen { min-block-size: 100svh; }

  /* THE REST OF THE RESPONSIVE SURFACE. Fifty-six classes the compiler emits
     per device were matched by no rule at either breakpoint — wrap, justify,
     align-end, width auto/narrow, every type role, fit, border and overlay.
     Setting any of them for a phone stored the value, rendered the class, and
     changed nothing. `StudioDesignTokensTest` had checked that every class the
     compiler can emit has a rule, but only ever asked about desktop.

     Mirrored from the desktop declarations above rather than rewritten, so the
     two cannot say different things. */
  .mobile\:s-wrap-wrap   { flex-wrap: wrap; }
  .mobile\:s-wrap-nowrap { flex-wrap: nowrap; }

  .mobile\:s-align-end { align-items: flex-end; }

  .mobile\:s-justify-start   { justify-content: flex-start; }
  .mobile\:s-justify-center  { justify-content: center; }
  .mobile\:s-justify-end     { justify-content: flex-end; }
  .mobile\:s-justify-between { justify-content: space-between; }
  .mobile\:s-justify-around  { justify-content: space-around; }
  .mobile\:s-justify-evenly  { justify-content: space-evenly; }

  .mobile\:s-width-auto   { inline-size: auto; max-inline-size: none; }
  .mobile\:s-width-narrow { inline-size: 100%; max-inline-size: 65ch; margin-inline: auto; }

  .mobile\:s-type-role-h1,
  .mobile\:s-type-role-h2,
  .mobile\:s-type-role-h3,
  .mobile\:s-type-role-h4 {
    font-family: var(--font-display);
    font-weight: var(--weight-regular);
    line-height: var(--leading-tight);
    letter-spacing: var(--tracking-tight);
  }

  .mobile\:s-type-role-h1 { font-size: var(--text-4xl); }
  .mobile\:s-type-role-h2 { font-size: var(--text-3xl); }
  .mobile\:s-type-role-h3 { font-size: var(--text-2xl); }
  .mobile\:s-type-role-h4 { font-size: var(--text-xl); }

  .mobile\:s-type-role-body,
  .mobile\:s-type-role-body-large,
  .mobile\:s-type-role-small,
  .mobile\:s-type-role-caption {
    font-family: var(--font-body);
    font-weight: var(--weight-regular);
    line-height: var(--leading-normal);
    letter-spacing: var(--tracking-normal);
  }

  .mobile\:s-type-role-body       { font-size: var(--text-base); }
  .mobile\:s-type-role-body-large { font-size: var(--text-md); }
  .mobile\:s-type-role-small      { font-size: var(--text-sm); }
  .mobile\:s-type-role-caption    { font-size: var(--text-xs); letter-spacing: var(--tracking-wide); }

  .mobile\:s-type-role-button {
    font-family: var(--font-body);
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    line-height: var(--leading-snug);
    letter-spacing: var(--tracking-wide);
  }

  .mobile\:s-fit-cover   { --s-fit: cover; }
  .mobile\:s-fit-contain { --s-fit: contain; }

  .mobile\:s-border-none   { border: none; }
  .mobile\:s-border-subtle { border: 1px solid var(--border-subtle); }
  .mobile\:s-border-strong { border: 1px solid var(--border-strong); }

  .mobile\:s-overlay-none::after { content: none; }

  .mobile\:s-overlay-soft::after,
  .mobile\:s-overlay-strong::after {
    content: '';
    position: absolute;
    inset: 0;
    pointer-events: none;
  }

  .mobile\:s-overlay-soft::after   { background: linear-gradient(transparent, rgb(0 0 0 / 35%)); }
  .mobile\:s-overlay-strong::after { background: linear-gradient(transparent, rgb(0 0 0 / 60%)); }

  .s-container.mobile\:s-min-height-auto[class*="s-cols-"] {
    grid-auto-rows: auto;
  }

  .s-container.mobile\:s-min-height-auto[class*="s-cols-"] > .s-image,
  .s-container.mobile\:s-min-height-auto[class*="s-cols-"] > picture > .s-image {
    block-size: auto;
  }

  /* A CAROUSEL ON A PHONE IS A SWIPE, and one slide at a time unless the
     editor said otherwise. Three slides squeezed into 390px is the shape that
     makes a carousel unreadable rather than compact. */
  .s-carousel-track { grid-auto-columns: calc(100% - var(--studio-space-m)); }

  /* 3:1 at 390px is a 130px letterbox slit. A banner on a phone is closer to
     square, which is what the photograph was framed for anyway. */
  .s-aspect-banner { aspect-ratio: 4 / 3; }
}

/* REDUCED MOTION IS RESPECTED WITHOUT ASKING. A background video that plays
   itself is motion the visitor did not start; the Content Studio already
   honours this for its own video band and Studio must not be the place the
   promise stops. */
@media (prefers-reduced-motion: reduce) {
  .s-video video[autoplay] { display: none; }
  .s-carousel-track { scroll-behavior: auto; }
}
