/* What the blueprint's stylesheets don't cover.

   shop.css is the house's own design, lifted verbatim, and it does almost all
   of the work. This file only adds what a mock never needed to be: a real page
   rather than a screenshot inside a browser frame, a live concierge dock, and
   the kiosk's portrait reflow.

   Anything here that fights shop.css is a bug. Prefer deleting a rule to
   adding one. */

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

/* The `hidden` attribute is a UA rule at the lowest specificity, so any class
   that sets `display` silently defeats it — which is how the concierge chip and
   the bag counter both rendered while carrying `hidden`. Make the attribute
   mean what it says. The dock animates instead of hiding, so it uses a class. */
[hidden] { display: none !important; }

html, body {
  margin: 0; padding: 0;
  background: var(--vs-ivory);
  color: var(--vs-ink);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}

/* ---------- one container for both views ----------

   The two pages were mocked separately and had different gutters: the
   storefront sat in a 1240px box with 34px of lateral padding, Behind the
   Story in a 1120px box with 56px. Switching between them shifted every edge
   on the page. One measurement now drives both. */
:root {
  /* Lateral gutter, and the ONLY horizontal measurement the page uses. It
     grows on wide screens so a 2560px monitor does not get a 34px hairline
     margin. */
  --page-pad: clamp(16px, 3.4vw, 64px);
}

/* FULL BLEED, like Maple & Co.

   This carried `max-width: 1180px; margin-inline: auto`, which put an empty
   ivory band down both sides of every wide screen. Maple has no page-level
   container at all — its only max-widths are on text blocks and modals — and
   the storefront reads as a real shop rather than a document because of it.

   Nothing balloons as a result: the strand SVG is capped at 420px by the
   house's own stylesheet, hero copy at 40ch, and the piece grid is auto-fill,
   so widening the page adds columns instead of stretching cards. The category
   tiles are the one exception and are handled below. */
.shop-viewport {
  min-height: 100dvh;
  overflow: visible;                 /* the mock clipped; a real page must not */
}

/* The storefront's sections hardcode 34px; make them follow the variable so a
   phone tightens the gutter without a separate rule per section. */
.shop-nav, .shop-band, .shop-trust, .shop-cats, .disclosure, .stage {
  padding-inline: var(--page-pad);
}
.shop-bottom { padding-inline: var(--page-pad); }
.cb-frame, .shop-promo { margin-inline: var(--page-pad); }
.shop-hero .sh-copy { padding-inline: var(--page-pad); }

/* The story page's image slots.

   These rules lived in story.css, which was deleted when the page was folded
   into this document — and the plates went back to being what the mock made
   them: fixed-ratio boxes with a dark fill, sized to hold prompt copy as well
   as a photograph. The image sat at its natural height and the rest of the box
   showed through as a brown band under every shot. */
#view-story .f-img img,
#view-story .s-plate img,
#view-story .c-plate img,
#view-story .fb img { width: 100%; height: 100%; object-fit: cover; display: block; }

#view-story .f-img,
#view-story .s-plate,
#view-story .c-plate,
#view-story .fb { overflow: hidden; }
#view-story .f-img { display: block; }

/* Behind the Story used its own, wider gutter and its own cap. Same edges as
   the storefront now — full bleed, one gutter. Prose inside it keeps its own
   measure, which is what stops lines running the width of a monitor. */
.ab-wrap {
  max-width: none !important;
  padding-inline: var(--page-pad) !important;
}
#view-story .ab-open p,
#view-story .f-copy p,
#view-story .s-copy p { max-width: 62ch; }
#view-story .ab-founder,
#view-story .ab-close { padding-inline: var(--page-pad); }

img { max-width: 100%; }

/* The hero's right half was a prompt plate. Here it is the photograph. */
.sh-img {
  width: 100%; height: 100%;
  min-height: 430px;
  object-fit: cover;
  display: block;
}

/* Category tiles held a prompt plate; now they hold the image directly.

   These are the one thing full bleed does stretch: four fixed columns on a
   2560px screen makes each column 611px, and the house's 3/4 portrait crop
   then runs over 800px tall.

   Do NOT cap this with max-height. `aspect-ratio` preserves the ratio by
   shrinking the WIDTH to honour the height limit, so the tiles came out 345px
   inside a 611px column — ragged gaps between them and 316px of dead space at
   the right edge. Change the ratio instead: the tile keeps its full column and
   simply gets shorter. */
.cat-tile { border: 0; padding: 0; cursor: pointer; font: inherit; }

@media (min-width: 1500px) {
  .cat-tile { aspect-ratio: 4 / 3; }
}
.cat-tile > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; z-index: 1;
}
.cat-tile .cat-label { z-index: 4; }

.shop-nav { position: sticky; top: 0; z-index: 30; background: var(--vs-ivory); }
.sn-links a { text-decoration: none; }
.sn-ask { border: 0; cursor: pointer; font-family: inherit; }
.sh-btns .b1, .sh-btns .b2 { text-decoration: none; cursor: pointer; }
.sp-btn { border: 0; cursor: pointer; font-family: inherit; }

/* Header status furniture. */
.sn-ico { display: inline-flex; align-items: center; gap: 12px; font-size: 12.5px; }
.guest-chip {
  padding: 4px 12px; border-radius: 999px;
  background: var(--vs-ink); color: var(--vs-ivory);
  font-size: 11.5px; letter-spacing: .02em;
}
.bag b {
  display: inline-grid; place-items: center;
  min-width: 19px; height: 19px; padding: 0 6px; margin-left: 4px;
  border-radius: 999px; background: var(--vs-gold); color: #fff; font-size: 11px;
}
.sync-dot {
  width: 7px; height: 7px; border-radius: 50%;
  background: rgba(35,32,28,.15); transition: background .25s, box-shadow .25s;
}
.sync-dot.on { background: var(--vs-gold); box-shadow: 0 0 0 4px rgba(176,141,87,.22); }

/* The builder frame carried the dark build-doc's shadow. On ivory it needs a
   hairline instead, or it looks like it is floating over nothing. */
.cb-frame {
  margin: 0 34px;
  border: 1px solid rgba(35,32,28,.12);
  box-shadow: 0 18px 44px rgba(35,32,28,.07);
}
/* A tool call is in flight — dim without moving anything. */
.cb.is-busy { opacity: .72; pointer-events: none; transition: opacity .15s; }

/* ---------- the stage: where Odette brings pieces on ---------- */

.stage { padding: 8px 34px 0; }
.stage[hidden] { display: none; }
.stage-label {
  font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
  font-weight: 700; color: var(--vs-gold-deep); margin: 26px 0 14px;
}
.grid {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
}
.piece {
  background: #fff; border: 1px solid rgba(35,32,28,.10);
  border-radius: 12px; overflow: hidden; cursor: pointer; text-align: left;
  padding: 0; font: inherit; color: inherit;
  transition: border-color .2s, box-shadow .2s;
  touch-action: manipulation;
}
.piece img { width: 100%; aspect-ratio: 4/5; object-fit: cover; display: block; }
.piece-body { padding: 12px 14px 15px; }
.piece-name { font-family: var(--font-serif); font-size: 19px; margin: 0 0 3px; font-weight: 500; }
.piece-meta { margin: 0; font-size: 12px; color: var(--vs-stone); }
.piece-price { margin: 7px 0 0; font-size: 14px; }
.piece-rating { margin: 3px 0 0; font-size: 11px; color: var(--vs-stone); }
/* A tap leaves :hover stuck on a touchscreen, which reads as frozen UI. */
@media (hover: hover) and (pointer: fine) {
  .piece:hover { border-color: var(--vs-gold-light); box-shadow: 0 2px 18px rgba(35,32,28,.08); }
}
.piece:active { border-color: var(--vs-gold); }

.detail { display: grid; grid-template-columns: 1fr 1fr; gap: 26px; align-items: start; }
.detail-img { width: 100%; aspect-ratio: 4/5; object-fit: cover; border-radius: 12px; }
.detail-name { font-family: var(--font-serif); font-weight: 500; font-size: clamp(26px,3vw,38px); margin: 0; }
.detail-price { font-size: 18px; margin: 6px 0 18px; }
.facts { display: grid; grid-template-columns: auto 1fr; gap: 6px 18px; margin: 0; font-size: 14px; }
.facts dt { color: var(--vs-stone); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; }
.facts dd { margin: 0; }

.cart-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
.cart-item {
  display: flex; align-items: center; gap: 14px; padding: 12px 14px;
  background: #fff; border: 1px solid rgba(35,32,28,.10); border-radius: 10px;
}
/* The bag shows what the guest has been looking at all visit, not a list of
   names. A built strand has no photograph, so its charms are drawn. */
.cart-img {
  width: 56px; height: 56px; flex: 0 0 56px; border-radius: 8px;
  object-fit: cover; background: var(--vs-sand, #F3EEE6);
}
.cart-img-build {
  display: flex; align-items: center; justify-content: center; gap: 3px;
  border: 1px solid rgba(35,32,28,.10);
}
.cart-bead {
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--vs-gold, #B08D4F); opacity: .85;
}
.cart-text { flex: 1 1 auto; display: grid; gap: 2px; min-width: 0; }
.cart-sub { font-size: 12px; color: var(--vs-stone); }
.cart-name { font-family: var(--font-serif); font-size: 18px; }
.cart-price { white-space: nowrap; }

/* ---------- side by side ---------- */
/* Cards are capped, not fluid: the page is full-bleed, and two cards told to
   share 1300px become posters. Centred so a pair does not hug the left edge. */
.compare {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 330px));
  justify-content: center;
  gap: 14px;
}
.cmp {
  background: #fff; border: 1px solid rgba(35,32,28,.10); border-radius: 12px;
  padding: 12px; display: grid; gap: 6px; align-content: start;
}
.cmp-img { width: 100%; aspect-ratio: 4/5; object-fit: cover; border-radius: 8px; }
.cmp-name { font-family: var(--font-serif); font-size: 18px; margin: 4px 0 0; font-weight: 500; }
.cmp-price { margin: 0; font-size: 14px; }
.cmp-facts {
  display: grid; grid-template-columns: auto 1fr; gap: 3px 10px;
  margin: 6px 0 0; font-size: 12.5px;
}
.cmp-facts dt { color: var(--vs-stone); }
.cmp-facts dd { margin: 0; }

/* ---------- the order ---------- */
.order {
  background: #fff; border: 1px solid rgba(35,32,28,.10); border-radius: 12px;
  padding: 18px; display: grid; gap: 4px;
}
.order-eb {
  margin: 0; font-size: 11px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--vs-gold, #B08D4F);
}
.order-ref { font-family: var(--font-serif); font-size: 30px; margin: 2px 0 0; font-weight: 500; }
.order-name { margin: 0 0 10px; color: var(--vs-stone); font-size: 13px; }
.order-note { margin: 10px 0 0; font-size: 12.5px; color: var(--vs-stone); }
.cart-total { font-size: 21px; margin-top: 14px; font-family: var(--font-serif); }
.cart-flash { animation: settle .5s ease-out; }
@keyframes settle { from { transform: translateY(-4px); opacity: .5 } to { transform: none; opacity: 1 } }
.empty { color: var(--vs-stone); }

.ateliers { display: grid; gap: 12px; }
.atelier { padding: 16px; background: #fff; border: 1px solid rgba(35,32,28,.10); border-radius: 10px; }
.atelier h3 { font-family: var(--font-serif); font-weight: 500; margin: 0 0 5px; font-size: 21px; }
.atelier-addr, .atelier-hours, .atelier-slots { margin: 2px 0; font-size: 13px; color: var(--vs-ink-3); }
.atelier-tag { margin: 8px 0 0; font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--vs-gold-deep); }
.faq-q { font-family: var(--font-serif); font-weight: 500; font-size: 24px; margin: 0 0 8px; }
.faq-a { margin: 0; line-height: 1.6; max-width: 60ch; }

/* ---------- the disclosure ---------- */

.disclosure { padding: 16px 34px 26px; background: var(--vs-ivory-2); }
.disclosure p { margin: 0; font-size: 11.5px; line-height: 1.5; color: var(--vs-ink-3); max-width: 80ch; }

/* ---------- the concierge ----------

   A floating panel rather than a side rail: it never takes layout space, so the
   storefront renders exactly as the blueprint draws it, and the guest can push
   her out of the way instead of closing her. Same shape as the other demos —
   bottom-right, draggable by the header, minimising to a bubble — in the
   house's ivory-and-gold rather than their dark chrome. */

.odette {
  position: fixed; right: 20px; bottom: 20px; z-index: 999;
  width: 380px;
  /* Height follows the avatar rather than being fixed. A fixed height with a
     fluid width lets the mount's aspect drift away from the video's, and
     object-fit:cover then eats the difference off the top and bottom — at 756px
     wide that was 45% of the frame height, which is why her head was cut. */
  height: auto; max-height: calc(100dvh - 40px);
  display: flex; flex-direction: column; overflow: hidden;
  border-radius: 16px;
  background: var(--vs-ivory);
  border: 1px solid rgba(35,32,28,.14);
  box-shadow: 0 20px 60px rgba(35,32,28,.22);
  opacity: 0; visibility: hidden; transform: translateY(12px) scale(.98);
  transition: opacity .28s ease, transform .28s cubic-bezier(.22,.61,.36,1),
              width .3s ease, height .3s ease, border-radius .3s ease;
}
.odette.open { opacity: 1; visibility: visible; transform: none; }

/* Minimised: a live bubble, not an icon. The video keeps playing, because a
   talking head is a better affordance than a dot — and stopping the stream to
   shrink it would drop the session. */
.odette.min {
  width: 88px; height: 88px; border-radius: 50%;
}
.odette.min .odette-head,
.odette.min .connect-label,
.odette.min .odette-start { display: none; }
.odette.min .odette-body { height: 100%; padding: 0; }
.odette.min .avatar-mount { border-radius: 50%; border: 0; aspect-ratio: 1; }
.odette.min .odette-expand { display: block; }

/* The SDK draws its own mic/speaker/hang-up bar over the video. At 88px that
   bar is most of the bubble and Odette disappears behind her own controls, so
   it is hidden while minimised — the whole bubble is the expand target
   anyway, and the controls come back the moment she is opened. */
.odette.min .np_companion-avatar-control-container { display: none !important; }

/* While minimised the avatar is decorative: the only thing to do is expand.
   The SDK mounts with its own very high z-index and swallowed every click on
   the expand button, so rather than fight it for stacking order, the whole
   subtree stops taking pointer events and the button underneath receives them. */
.odette.min .odette-body { pointer-events: none; }
.odette.min .odette-expand { pointer-events: auto; }

/* Bias the crop upward: a centred cover crop of a 3:4 portrait puts her collar
   in the circle rather than her face. */
.odette.min .avatar-mount video { object-position: 50% 22%; }

.odette-head {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 11px 12px 11px 16px;
  background: linear-gradient(135deg, var(--vs-ink) 0%, var(--vs-ink-3) 100%);
  cursor: move;                       /* draggable — see makeDraggable() */
  user-select: none; flex: none;
}
.odette-title {
  color: #fff; font-family: var(--font-serif); font-size: 19px; font-weight: 500;
  display: flex; align-items: baseline; gap: 8px;
}
.odette-title .accent {
  font-family: var(--font-body); font-size: 9.5px; font-weight: 600;
  text-transform: uppercase; letter-spacing: .14em; color: var(--vs-gold-light);
}
.odette-title .accent::before { content: "·"; margin-right: 6px; color: rgba(255,255,255,.35); }

.odette-ctl { display: flex; align-items: center; gap: 6px; }
.odette-status {
  font-size: 9px; letter-spacing: .12em; text-transform: uppercase;
  color: rgba(255,255,255,.55); margin-right: 2px;
}
.odette-status[data-state="connected"] { color: var(--vs-gold-light); }
.odette-status[data-state="error"] { color: #E38A78; }
.odette-btn {
  width: 26px; height: 26px; border: 0; border-radius: 50%; cursor: pointer;
  background: rgba(255,255,255,.10); color: #fff;
  font-size: 15px; line-height: 1; display: grid; place-items: center;
}
.odette-btn:active { background: rgba(255,255,255,.2); }

.odette-body {
  flex: 1; min-height: 0; padding: 12px;
  display: flex; flex-direction: column; gap: 8px;
}
.avatar-mount {
  position: relative; width: 100%;
  /* The source is 1084x1416. Matching it means nothing is cropped at all. */
  aspect-ratio: 1084 / 1416;
  flex: 0 1 auto; min-height: 0;
  border-radius: 12px; overflow: hidden;
  background: linear-gradient(160deg, var(--vs-ivory-2), var(--vs-ivory-3));
  border: 1px solid rgba(35,32,28,.10);
}
.avatar-mount video {
  width: 100%; height: 100%; object-fit: cover;
  /* Belt and braces: if max-height ever clamps the panel on a very short
     viewport, the residual crop should come off her chest, not her head. */
  object-position: 50% 18%;
}

.odette-start {
  width: 100%; padding: 12px 18px; min-height: 44px; flex: none;
  border: 0; border-radius: 100px; cursor: pointer; touch-action: manipulation;
  background: var(--vs-ink); color: var(--vs-ivory);
  font-family: var(--font-body); font-size: 13px; font-weight: 600;
}
.odette-start:disabled { opacity: .6; }

.odette-input {
  display: flex; gap: 8px; flex: none;
  border: 1px solid rgba(35,32,28,.16); border-radius: 100px;
  padding: 4px 4px 4px 14px; background: #fff;
}
.odette-input input {
  flex: 1; min-width: 0; border: 0; outline: 0; background: none;
  font-family: var(--font-body); font-size: 13px; color: var(--vs-ink);
}
.odette-input input::placeholder { color: var(--vs-stone); }
.odette-input button {
  width: 34px; height: 34px; flex: none; border: 0; border-radius: 50%;
  background: var(--vs-ink); color: var(--vs-ivory); cursor: pointer;
  display: grid; place-items: center;
}
.odette-input button:disabled { opacity: .45; cursor: default; }
.odette.min .odette-input { display: none; }

/* Covers the whole bubble when minimised so any tap expands her.

   The z-index has to clear the SDK's own root, which mounts inside the avatar
   container and otherwise swallows the click — the button was there and
   visible, but the avatar container intercepted every pointer event. */
.odette-expand {
  display: none; position: absolute; inset: 0; z-index: 20;
  border: 0; background: transparent; cursor: pointer;
}

/* The chip left behind when she is closed mid-session, so a live agent is
   never invisible. */
.dock-resume {
  position: fixed; right: 20px; bottom: 20px; z-index: 998;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 11px 18px; min-height: 44px;
  border: 1px solid var(--vs-line); border-radius: 100px; cursor: pointer;
  background: var(--vs-ivory); color: var(--vs-ink);
  font-family: var(--font-body); font-size: 13px; font-weight: 600;
  box-shadow: 0 8px 24px rgba(35,32,28,.14);
}
.dr-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--vs-gold); }
.dock-resume.live .dr-dot { animation: breathe 2.1s ease-in-out infinite; }

/* ---------- connecting ---------- */

.connect {
  position: absolute; inset: 0; display: grid; place-items: center;
  opacity: 0; visibility: hidden; transition: opacity .4s ease; pointer-events: none;
}
.avatar-mount.is-connecting .connect { opacity: 1; visibility: visible; }
.avatar-mount.is-done .connect { opacity: 0; }
.avatar-mount.is-connecting::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(100deg, transparent 30%, rgba(255,255,255,.55) 50%, transparent 70%);
  transform: translateX(-100%); animation: sweep 2.1s ease-in-out infinite;
}
@keyframes sweep { to { transform: translateX(100%) } }

.connect-ring { width: 34%; height: auto; transform: rotate(-90deg); overflow: visible; }
.connect-track { fill: none; stroke: rgba(35,32,28,.12); stroke-width: 2; }
.connect-arc {
  fill: none; stroke: var(--vs-gold); stroke-width: 2; stroke-linecap: round;
  stroke-dasharray: 339.29;                       /* r=54 → circumference */
  stroke-dashoffset: calc(339.29 * (1 - var(--p, 0)));
  transition: stroke-dashoffset .7s cubic-bezier(.22,.61,.36,1);
}
.connect-dot {
  position: absolute; width: 6px; height: 6px; border-radius: 50%;
  background: var(--vs-gold); animation: breathe 2.1s ease-in-out infinite;
}
@keyframes breathe {
  0%,100% { opacity: .35; transform: scale(.85) }
  50%     { opacity: 1;   transform: scale(1.15) }
}
.connect-label {
  margin: 10px 0 0; min-height: 1.2em; text-align: center;
  font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--vs-stone);
}

/* ---------- trace ---------- */

.trace {
  position: fixed; left: 0; bottom: 0; z-index: 70;
  width: min(440px, 92vw); max-height: 40vh; overflow: auto;
  background: rgba(21,19,15,.95); color: #E8E2D8;
  font: 11px/1.45 ui-monospace, SFMono-Regular, Menlo, monospace;
  padding: 10px 12px;
}
.trace-row { display: grid; gap: 2px; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,.08); }
.trace-tool { color: var(--vs-gold-light); }
.trace-args { color: #9C958A; word-break: break-all; }

/* ---------- portrait: phone AND station ----------

   Width breakpoints cannot do this job. The station is 1080x1920 portrait in
   desktop Chromium, so 1080px reads as "desktop" to every width query and 1920
   is taller than any max-height a desktop layout ships with. Orientation is the
   honest signal, and it makes a phone and the kiosk reflow identically. */

@media (orientation: portrait) {
  .shop-hero { grid-template-columns: 1fr; }
  .shop-trust { grid-template-columns: repeat(2, 1fr); gap: 10px 0; }
  .st-item:nth-child(3) { border-left: 0; }
  .shop-appt { flex-wrap: wrap; }
  .sa-script { display: none; }
  .sh-img { min-height: 280px; max-height: 42vh; }
  .shop-cats { grid-template-columns: repeat(2, 1fr); }
  .cb { grid-template-columns: 1fr; }
  .cb-stage { border-right: 0; border-bottom: 1px solid rgba(35,32,28,.10); }
  .shop-promo { grid-template-columns: 1fr; }
  .detail { grid-template-columns: 1fr; }
  .sn-links { display: none; }            /* the agent is the navigation here */
  /* Cap the WIDTH here, not the height: a near-full-width panel with a fixed
     height is exactly what turned the mount landscape. */
  .odette { width: min(calc(100% - 24px), 340px); right: 12px; bottom: 12px; }
  .odette.min { width: 84px; height: 84px; }
}

@media (orientation: portrait) and (max-width: 820px) {
  .shop-nav { height: 60px; }
  .shop-anno { font-size: 10.5px; }
  .grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
}

/* ---------- station ----------

   The device renders and voices the avatar natively, above the screen, so the
   page must not draw a second one. Scoped to the station attribute only —
   applying it unconditionally would strip the avatar from the web build, where
   it IS the agent. */

[data-surface="station"] .odette,
[data-surface="station"] .dock-resume,
[data-surface="station"] .sn-ask { display: none !important; }

/* After the tap the kiosk opens on the storefront, as the web build does —
   hero, category tiles, the consultant band — and the builder is one touch
   away rather than the first screen. It used to lead with the builder, and a
   guest who had come to look was dropped straight into a configurator.

   The builder cannot be 3000px of swiping away, so it is always reached for
   them: "Start your bracelet" glides to it, and the moment Odette starts or
   changes a strand, renderBuild scrolls it into view.

   Only the footer goes: its links are placeholders, and a dead link under a
   finger reads as a broken kiosk. */
[data-surface="station"] .shop-bottom { display: none; }

/* The station KEEPS a design-width cap, unlike the web build. The panel is
   nominally 1080 but the emulator and any windowed host lay out wider, and
   without a cap the two-column grid balloons — cards went 265px to 670px at
   1500 wide the last time this was left uncapped. Centre it instead. */
[data-surface="station"] .shop-viewport { max-width: 1100px; margin-inline: auto; }
[data-surface="station"] .cb-frame { margin-top: 8px; }
[data-surface="station"] body { font-size: 17px; }
[data-surface="station"] .shop-anno { font-size: 13px; padding: 12px 0; }
[data-surface="station"] .shop-nav { height: 84px; }
[data-surface="station"] .sn-left .wm { font-size: 26px; }
[data-surface="station"] .sh-copy h3 { font-size: 46px; }
[data-surface="station"] .sb-eb { font-size: 26px; }
[data-surface="station"] .cb-stage h4 { font-size: 34px; }
[data-surface="station"] .story-chip { min-height: 48px; font-size: 14px; }
[data-surface="station"] .cb-tab { min-height: 48px; }
[data-surface="station"] .base-opt, [data-surface="station"] .charm-card { min-height: 56px; }
[data-surface="station"] .disclosure p { font-size: 13px; }

/* A guest who escapes the page bricks the kiosk until someone notices. */
[data-surface="station"] a[target="_blank"],
[data-surface="station"] a[download] { display: none !important; }
[data-surface="station"] * { -webkit-user-select: none; user-select: none; }
[data-surface="station"] ::-webkit-scrollbar { width: 0; height: 0; }
[data-surface="station"] button { min-height: 48px; touch-action: manipulation; }

/* Respect a guest who has asked for less movement. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

/* ---------- the appointment modal ---------- */

.modal { position: fixed; inset: 0; z-index: 1200; display: grid; place-items: center; }
.modal-scrim { position: absolute; inset: 0; background: rgba(21,19,15,.55); backdrop-filter: blur(3px); }
.modal-card {
  position: relative; z-index: 1; width: min(520px, calc(100vw - 32px));
  max-height: calc(100dvh - 48px); overflow: auto;
  background: var(--vs-ivory); border-radius: 16px;
  border: 1px solid rgba(35,32,28,.14);
  box-shadow: 0 30px 80px rgba(21,19,15,.35);
  padding: 30px clamp(20px, 4vw, 36px) 28px;
  animation: modal-in .28s cubic-bezier(.22,.61,.36,1);
}
@keyframes modal-in { from { opacity: 0; transform: translateY(10px) scale(.985) } to { opacity: 1; transform: none } }

.modal-x {
  position: absolute; top: 12px; right: 12px; width: 34px; height: 34px;
  border: 0; border-radius: 50%; background: rgba(35,32,28,.06); color: var(--vs-ink-3);
  font-size: 20px; line-height: 1; cursor: pointer;
}
.modal-eb {
  margin: 0 0 8px; font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase;
  font-weight: 700; color: var(--vs-gold-deep);
}
.modal-card h2 { font-family: var(--font-serif); font-weight: 500; font-size: clamp(26px,3.4vw,34px); margin: 0 0 10px; }
.modal-card h3 { font-family: var(--font-serif); font-weight: 500; font-size: 24px; margin: 0 0 8px; }
.modal-sub { margin: 0 0 20px; font-size: 14px; line-height: 1.6; color: var(--vs-ink-3); }

#appt-form { display: grid; gap: 13px; }
#appt-form label {
  display: grid; gap: 5px; font-size: 11px; letter-spacing: .1em;
  text-transform: uppercase; font-weight: 600; color: var(--vs-stone);
}
#appt-form input, #appt-form select, #appt-form textarea {
  font-family: var(--font-body); font-size: 14px; color: var(--vs-ink);
  padding: 11px 13px; border-radius: 8px; background: #fff;
  border: 1px solid rgba(35,32,28,.16); text-transform: none; letter-spacing: 0;
  min-height: 44px; resize: vertical;
}
#appt-form input:focus, #appt-form select:focus, #appt-form textarea:focus {
  outline: 2px solid var(--vs-gold-light); outline-offset: 1px; border-color: var(--vs-gold);
}
#appt-form input[aria-invalid="true"] { border-color: #A4402E; }

.modal-note {
  margin: 2px 0 0; padding: 11px 13px; border-radius: 8px;
  background: var(--vs-ivory-2); border: 1px solid var(--vs-line);
  font-size: 12.5px; line-height: 1.5; color: var(--vs-ink-3);
}
.modal-note[hidden] { display: none; }
.modal-error { margin: 0; font-size: 13px; color: #A4402E; }
.modal-go {
  margin-top: 4px; min-height: 48px; padding: 14px 24px;
  border: 0; border-radius: 100px; cursor: pointer; touch-action: manipulation;
  background: var(--vs-ink); color: var(--vs-ivory);
  font-family: var(--font-body); font-size: 14px; font-weight: 600;
}
.modal-go:disabled { opacity: .6; cursor: default; }
.modal-fine { margin: 2px 0 0; font-size: 11px; line-height: 1.5; color: var(--vs-stone); }
.modal-done { display: grid; gap: 4px; }

@media (orientation: portrait) {
  .modal-card { width: calc(100vw - 24px); padding: 26px 20px 24px; }
}

/* A blocked microphone still connects — she just cannot hear. Say so. */
.connect-label.warn { color: #A4402E; letter-spacing: .04em; text-transform: none; font-size: 12px; }

/* The bag is a control, not a label. */
.sn-ico .bag { cursor: pointer; user-select: none; }
.sn-ico .bag:focus-visible { outline: 2px solid var(--vs-gold); outline-offset: 3px; border-radius: 4px; }

/* ---------- the draft of 18 Sep ----------

   The storefront reshaped to the approved draft: guidance strip, hero caption,
   the four promises under the photo, "Explore the collection" tiles with a
   shade and an arrow, the three steps, and the appointment band that closes
   the page. Shared by web and station; station.css only re-sizes. */

.btn-glyph { font-size: .92em; }
.sh-btns .b1, .sh-btns .b2 {
  display: inline-flex; align-items: center; gap: 10px;
  border: 0; cursor: pointer; font-family: inherit;
}
.sh-btns .b2 { border: 1px solid rgba(35,32,28,.30); background: transparent; color: var(--vs-ink); }

.station-hint .hint-spark, .station-hint .hint-arrow { color: var(--vs-gold-deep); }

/* The wrapper inherits the grid slot the bare <img> used to fill, so the
   photo must be told to fill it — on the web hero the column would otherwise
   collapse to the image's own size. */
.sh-visual { position: relative; line-height: 0; display: grid; }
.sh-visual .sh-img { width: 100%; height: 100%; object-fit: cover; }
.sh-caption {
  position: absolute; right: 6%; top: 20%;
  font-family: var(--font-serif); font-style: italic; line-height: 1.25;
  font-size: 24px; color: var(--vs-gold-deep); text-align: left;
}
.sh-caption::after {
  content: ""; display: block; width: 34px; height: 1px;
  background: var(--vs-gold-deep); margin-top: 10px;
}

/* The four promises, straight under the photo. */
.shop-trust {
  display: grid; grid-template-columns: repeat(4, 1fr);
  margin-top: 0;                        /* shop.css floats its old version */
  padding: 26px var(--page-pad);
  border-top: 0;
  border-bottom: 1px solid rgba(35,32,28,.08);
}
.st-item {
  display: flex; align-items: center; gap: 12px; justify-content: center;
  font-size: 13px; padding: 4px 10px;
}
.st-item + .st-item { border-left: 1px solid rgba(35,32,28,.10); }
.st-item svg { width: 22px; height: 22px; flex: 0 0 22px; color: var(--vs-gold-deep); }
.st-item b { display: block; font-weight: 600; }
.st-item span { color: var(--vs-stone); }

.shop-band { display: flex; align-items: baseline; justify-content: space-between; gap: 18px; }
.sb-link {
  border: 0; background: none; cursor: pointer; font-family: inherit;
  font-size: 13px; color: var(--vs-ink);
}

/* Tiles: title and invitation on the photo, arrow in the corner. */
.cat-tile { position: relative; }
/* shop.css already shades the tile with ::after (z-index 3); a second
   gradient on top of it reads as mud. The element stays for the markup's
   sake and paints nothing. */
.cat-shade { display: none; }
.cat-copy {
  position: absolute; left: 22px; bottom: 20px; right: 84px; z-index: 4;
  display: grid; gap: 4px; text-align: left;
}
.cat-copy .cat-label {
  position: static; font-family: var(--font-serif); font-weight: 500;
  font-size: 30px; color: var(--vs-ivory);
}
.cat-sub { font-size: 13.5px; color: rgba(250,247,242,.85); }
.cat-go {
  position: absolute; right: 20px; bottom: 20px; z-index: 4;
  width: 46px; height: 46px; border-radius: 50%;
  border: 1px solid rgba(250,247,242,.75); color: var(--vs-ivory);
  display: flex; align-items: center; justify-content: center; font-size: 18px;
}

/* Three steps. */
.shop-how { padding: 34px var(--page-pad) 40px; }
.how-head { display: flex; align-items: baseline; gap: 18px; flex-wrap: wrap; }
.how-head h3 { font-family: var(--font-serif); font-weight: 500; font-size: 26px; margin: 0; }
.how-head p { margin: 0; color: var(--vs-stone); font-size: 14px; flex: 1 1 auto; }
.how-link {
  border: 0; background: none; cursor: pointer; font-family: inherit;
  font-size: 13px; color: var(--vs-ink);
}
.how-steps {
  list-style: none; margin: 26px 0 0; padding: 0;
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 22px;
}
.how-steps li { display: flex; gap: 14px; align-items: flex-start; font-size: 13.5px; }
.how-n {
  width: 42px; height: 42px; flex: 0 0 42px; border-radius: 50%;
  background: var(--vs-ivory-2); color: var(--vs-gold-deep);
  font-family: var(--font-serif); font-size: 18px;
  display: flex; align-items: center; justify-content: center;
}
.how-steps b { display: block; font-weight: 600; margin-bottom: 3px; }
.how-steps li span { color: var(--vs-stone); }

/* The appointment band that closes the page. */
.shop-appt {
  position: relative; display: flex; align-items: center; gap: 24px;
  margin: 8px var(--page-pad) 42px; padding: 30px 40px;
  background: var(--vs-ivory-2); border-radius: 14px;
}
.sa-copy { flex: 1 1 auto; }
.sa-copy h3 { font-family: var(--font-serif); font-weight: 500; font-size: 24px; margin: 0 0 4px; }
.sa-copy p { margin: 0; color: var(--vs-stone); font-size: 14px; }
.sa-btn {
  border: 0; cursor: pointer; font-family: inherit; font-weight: 600;
  font-size: 14px; padding: 16px 28px; border-radius: 100px;
  background: var(--vs-ink); color: var(--vs-ivory);
  display: inline-flex; align-items: center; gap: 10px;
}
.sa-script {
  font-family: var(--font-serif); font-style: italic;
  color: var(--vs-gold-deep); font-size: 17px;
  transform: rotate(-4deg); margin-left: 6px;
}
