/* SLAYD — sx-ui.css · PRODUCT DEPICTION LAYER. Owner A1. Spec §7.
   Split out of sx.css to hold sx.css inside the 62 KB budget (§10.4); it is
   the CSS half of js/sx-ui.js and carries the same ownership and prefixes.
   MUST load after css/sx.css (which declares the @layer order and the tokens)
   and before css/slayd.css. Inlines verbatim into a <style> string (§11.3). */
/* PRODUCT (§7) — device frames + the sx-ui-* screen interiors Every depiction is real DOM built from the app's own tok */
/* == PINNED INTERIOR — the phone absorbs the scroll ==
   §4.3 / §7.4 interior parallax, generalised into a reusable capability. When
   the visitor reaches the band, the DEVICE holds still and the content INSIDE
   it advances — the feed swipes card by card, the schedule runs from morning to
   evening — and only once the interior has finished does the page carry on.
   That is what makes a mockup read as a working product rather than a picture.

   Pure CSS scroll timeline: no listener, no rAF, no layout read, off the main
   thread. The band owns a named view-timeline; the reel inside each device
   subscribes to it. Nothing animates at rest (rule 8) — scroll is the only driver.

   -- ADOPTION RECIPE (A2/A3/A4) --------------------------------------------
   ONE DEVICE:
     <section class="sx-b sx-pin sx-pin-stage">
       <div class="sx-pin-sticky">
         ... SlaydUI.phone(SlaydUI.screens.S3({reel:1}), {scale:.72}) ...
       </div>
     </section>

   DIPTYCH (both phones ride the SAME timeline, offset so it feels alive):
     <section class="sx-b sx-b-diptych sx-pin">
       <div class="sx-pin-sticky">
         <div class="sx-diptych">
           <div class="sx-dip sx-dip-pro">    ... S4({reel:1}) ... </div>
           <i class="sx-diptych-seam"></i>
           <div class="sx-dip sx-dip-client"> ... S2({reel:1}) ... </div>
         </div>
       </div>
     </section>

   KNOB: --pin-len on .sx-pin (default 220vh, 180vh under 1100px) sets how much
   scroll the band absorbs. Longer = slower scrub. Nothing else is tunable by a
   page builder; the ranges are the system's.

   GUARANTEES you do not have to code around:
   - No scroll-timeline support -> the mechanism is inert, the band is a normal
     band and every reel shows its first page. Same content, same composition.
   - prefers-reduced-motion     -> identical: first page, normal height (§6.11).
   - Under 821px                -> pin is OFF by design. The diptych is a snap
     carousel there and the user's own thumb IS the interaction; stacking a
     scroll-jack on top of a swipe fights the user. */
@layer layout{
.sx-pin{--pin-len:220vh}
@media (max-width:1100px){.sx-pin{--pin-len:180vh}}
/* Everything below is progressive enhancement. Outside this gate .sx-pin is an
   ordinary band — which IS the fallback, so there is nothing else to write. */
@supports (animation-timeline:view()){
@media (min-width:821px) and (prefers-reduced-motion:no-preference){
.sx-pin{min-height:var(--pin-len);padding-block:0;timeline-scope:--sx-pin;view-timeline-name:--sx-pin;view-timeline-axis:block}
.sx-pin-sticky{position:sticky;top:0;height:100dvh;display:grid;align-content:center;overflow:clip}
.sx-pin-stage .sx-pin-sticky{justify-items:center}
/* Pinned, the band is 100dvh, so the diptych's own band padding would push the
   devices off the plate. */
.sx-pin .sx-dip{padding-block:clamp(20px,4vh,52px)}
.sx-pin .sx-diptych{height:100%}
/* THE SCRUB. `contain`, not `cover`: contain is the pinned period exactly — it
   opens when the band's top meets the viewport top and closes when its bottom
   meets the viewport bottom. A cover range would start the interior moving while
   the phone was still sliding up, the opposite of "it settles, THEN it plays".
   Ending at 70% leaves a beat of stillness before the band releases. */
/* Scoped to .sx-pin on purpose. An unresolved animation-timeline is INACTIVE,
   not absent, and with fill:both Chrome still paints the end state — a reel
   dropped outside a pin band would render scrolled to its LAST group. Scoping
   means no animation at all out there, so group 1 shows, which is the
   documented fallback. */
.sx-pin .sx-ui-reel{animation:sx-reel-2 linear both;animation-timeline:--sx-pin;animation-range:contain 0% contain 70%;will-change:transform}
.sx-pin .sx-reel-3{animation-name:sx-reel-3}
.sx-pin .sx-reel-4{animation-name:sx-reel-4}
/* Offset the two sides so the pair reads as alive rather than mechanical. */
.sx-pin .sx-dip-pro .sx-ui-reel{animation-range:contain 0% contain 62%}
.sx-pin .sx-dip-client .sx-ui-reel{animation-range:contain 8% contain 78%}
}}
/* == STEPPED SWIPE ==
   A thumb does not glide a feed at constant velocity: it holds, flicks, and
   the content settles. So the strip HOLDS on each page, then crosses to the
   next in ~14% of the scrub, and lands with weight.
   Scroll-driven animations interpolate LINEARLY between keyframes, so the
   easing has to live in the keyframe distribution: each swipe is three frames
   — start, a fast frame that overshoots the target by ~1.5% of the strip, then
   the settle. The final swipe eases OUT instead of overshooting, because
   passing the end would expose white below the last page.
   --reel-t is the whole travel (negative); every frame is a fraction of it, so
   the geometry stays self-measuring. Stops land exactly on page boundaries
   because each page is exactly one window tall. */
.sx-ui-reel{--reel-t:min(0px,calc(-100% + 100cqh))}
.sx-ui-reel>*{height:100cqh;overflow:hidden;padding-block-end:var(--reel-gutter,12px)}
/* A feed page is one post filling the page: the media drops its fixed aspect
   and takes the page height, so one swipe = one post, exactly. */
.sx-ui-fill{height:100%;display:flex}
.sx-ui-fill>.sx-ui-media{aspect-ratio:auto;height:100%;width:100%}
/* 2 pages · 1 swipe */
@keyframes sx-reel-2{
0%,30%{translate:0 0}
39%{translate:0 calc(.93 * var(--reel-t))}
46%,100%{translate:0 var(--reel-t)}}
/* 3 pages · 2 swipes */
@keyframes sx-reel-3{
0%,20%{translate:0 0}
29%{translate:0 calc(.523 * var(--reel-t))}
34%,58%{translate:0 calc(.5 * var(--reel-t))}
67%{translate:0 calc(.93 * var(--reel-t))}
74%,100%{translate:0 var(--reel-t)}}
/* 4 pages · 3 swipes */
@keyframes sx-reel-4{
0%,14%{translate:0 0}
23%{translate:0 calc(.348 * var(--reel-t))}
28%,46%{translate:0 calc(.3333 * var(--reel-t))}
55%{translate:0 calc(.681 * var(--reel-t))}
60%,78%{translate:0 calc(.6667 * var(--reel-t))}
87%{translate:0 calc(.94 * var(--reel-t))}
93%,100%{translate:0 var(--reel-t)}}
}

@layer layout{
/* §7.4 Multi-device stacking — decreasing scale, increasing blur, offset on a
   12deg diagonal, never fanned symmetrically. Lives here rather than in sx.css
   because it positions .sx-dev frames, and to hold sx.css inside its budget. */
.sx-stage-stack{position:relative;transform-style:preserve-3d}
.sx-stage-stack>:nth-child(2){position:absolute;inset:0;--dev-scale:.86;translate:22% 5%;z-index:var(--z-base)}
.sx-stage-stack>:nth-child(3){position:absolute;inset:0;--dev-scale:.74;translate:42% 10%;filter:blur(1.5px);z-index:var(--z-base)}
}
@layer motion{
/* §6.10 Cursor ring — desktop only; NEVER hides the native cursor */
.sx-cursor{position:fixed;top:0;left:0;width:26px;height:26px;border-radius:var(--r-1);border:1.2px solid var(--plum);pointer-events:none;z-index:var(--z-dock);translate:-50% -50%;opacity:0;transition:opacity var(--dur-2) var(--ease),width var(--dur-2) var(--ease),height var(--dur-2) var(--ease)}
@media (hover:hover) and (pointer:fine){.sx-cursor.is-on{opacity:1}.sx-cursor.is-lg{width:46px;height:46px}}
}
@layer motion{
/* == POINTER TILT (mined 3) ==
   The device answers the hand: at most 4deg, settling on --spring when the
   pointer leaves. It rides its OWN layer so it composes with, rather than
   fights, the scroll-driven resolve-to-flat on .sx-stage-dev (§7.4) —
   ancestor does scroll, .sx-tilt-layer does pointer, perspective stays on
   .sx-b-stage. One delegated rAF-batched listener in sx-ui.js writes --mx/--my
   and nothing else; it never reads geometry inside the move handler (§10.5). */
@media (hover:hover) and (pointer:fine){
.sx-tilt{--mx:0;--my:0;width:max-content;max-width:100%}
.sx-tilt-layer{transform-style:preserve-3d;
  transform:rotateY(calc(var(--mx) * 4deg)) rotateX(calc(var(--my) * -4deg))}
.sx-tilt.is-settling .sx-tilt-layer{transition:transform var(--dur-4) var(--spring)}
}
}
@layer tokens{
/* CAMPAIGN MEDIA MAP. The only place these filenames appear: point a slot at a
   different file here and every instance follows. When the §8.3 set lands,
   swap the four URLs and nothing else changes. Root-relative so the sheet
   still resolves if it is ever inlined into a <style> string (§11.3).
   Every media slot layers this OVER --tint-tile, which stays as the loading
   and no-image ground — the data-shape law: seed / local / remote, all three
   branches always present. */
:root{
/* Real Slayd salon content. Every slot has a MEANING and the copy pointed at
   it has to match the picture — change what a slot DEPICTS and the captions
   move with it. Each carries a focal point too: the neon sits on the LEFT of
   every one of these frames and a portrait card crops hard, so a centred
   background would throw the brand out of shot. */
--media-1:url("/assets/campaign/service-2.jpg");            --media-1-pos:36% center; /* hair · balayage, back view */
--media-2:url("/assets/campaign/service-1.jpg");            --media-2-pos:40% center; /* makeup · artist at work */
--media-3:url("/assets/campaign/service-5.jpg");            --media-3-pos:34% center; /* styling · press and gloss */
--media-4:url("/assets/campaign/service-3.jpg");            --media-4-pos:42% center; /* nails · almond french */
--media-5:url("/assets/campaign/service-4.jpg");            --media-5-pos:28% center; /* training · reformer class */
--media-6:url("/assets/campaign/media-band-vanity.jpg");    --media-6-pos:center;     /* still life · spare */
}
}
@layer components{
/* == PROGRESSIVE BLUR EDGE == Five stacked backdrop-filter layers, each masked
   to its own window, so the blur RAMPS instead of stepping. Replaces §6.8's
   flat rail fade and softens a cropped device's cut edge. Static, never moves,
   so §10.5's blur ban does not apply. Markup: SlaydUI.blurEdge(dir). */
.sx-blur-edge{position:absolute;pointer-events:none;z-index:var(--z-raise);display:block;
  background:linear-gradient(var(--dir),transparent,var(--edge-to,var(--paper)))}
.sx-blur-edge>i{position:absolute;inset:0;display:block}
.sx-blur-edge-right{--dir:90deg;inset-block:0;inset-inline-end:0;width:var(--edge,80px)}
.sx-blur-edge-bottom{--dir:180deg;inset-inline:0;bottom:0;height:var(--edge,96px)}
@supports (backdrop-filter:blur(1px)){
.sx-blur-edge>i:nth-child(1){backdrop-filter:blur(.5px);mask-image:linear-gradient(var(--dir),transparent 0,#000 18%)}
.sx-blur-edge>i:nth-child(2){backdrop-filter:blur(1.2px);mask-image:linear-gradient(var(--dir),transparent 18%,#000 36%)}
.sx-blur-edge>i:nth-child(3){backdrop-filter:blur(2.4px);mask-image:linear-gradient(var(--dir),transparent 36%,#000 56%)}
.sx-blur-edge>i:nth-child(4){backdrop-filter:blur(5px);mask-image:linear-gradient(var(--dir),transparent 56%,#000 76%)}
.sx-blur-edge>i:nth-child(5){backdrop-filter:blur(10px);mask-image:linear-gradient(var(--dir),transparent 76%,#000 94%)}
}
/* A rail carrying a real ramp drops the flat one. */
.sx-rail-edge:has(.sx-blur-edge)::after{display:none}
}
@layer product{
/* THE SCALE WRAPPER. --dev-scale drives a TRANSFORM on a wrapper, never the
   frame's own pixel values: sx-ui draws every screen at the app's true 390pt
   geometry, so shrinking only the frame leaves 390px of UI inside a 234px box
   and the service rows collapse into each other. The device is always drawn at
   390x844 and the whole layer is scaled — one transform, vector-crisp, and the
   interior geometry is exactly the app's. `zoom` is banned (§7.2); this is not.
   --dev-crop / --dev-off implement §7.4 crop-and-bleed: a screen cut by its
   plate edge implies depth beyond the frame and hides UI density. */
.sx-dev{--dev-scale:1;--dev-crop:1;--dev-off:0;--dev-w:390px;--dev-h:844px;
  position:relative;flex:none;max-width:100%;
  width:calc(var(--dev-w) * var(--dev-scale));
  height:calc(var(--dev-h) * var(--dev-scale) * var(--dev-crop));
  overflow:clip}
.sx-dev-layer{position:absolute;inset-inline-start:0;width:var(--dev-w);height:var(--dev-h);
  top:calc(var(--dev-h) * var(--dev-off) * var(--dev-scale) * -1);
  transform:scale(var(--dev-scale));transform-origin:top left}
.sx-dev-top{--dev-crop:.62}
.sx-dev-mid{--dev-crop:.56;--dev-off:.2}
/* A cropped device loses its bottom bezel by definition, so the drop shadow
   would ring a cut edge. Trade it for the plate-edge lift instead. */
.sx-dev-top .sx-dev-phone,.sx-dev-mid .sx-dev-phone{box-shadow:0 30px 80px rgba(58,31,48,.14)}

/* §7.2 Phone */
.sx-dev-phone{width:390px;aspect-ratio:390/844;border-radius:46px;background:var(--dev-bezel);padding:10px;box-shadow:var(--lift-4);position:relative;flex:none;max-width:100%}
.sx-dev-phone::after{content:"";position:absolute;inset:1px;border-radius:inherit;box-shadow:inset 0 0 0 2px var(--dev-rim);pointer-events:none;z-index:var(--z-raise)}
.sx-dev-screen{position:relative;width:100%;height:100%;overflow:hidden;border-radius:38px;background:var(--paper);container-type:inline-size}
.sx-dev-island{position:absolute;top:9px;left:50%;translate:-50% 0;width:88px;height:6px;border-radius:var(--r-dot);background:var(--dev-bezel);opacity:.55;z-index:var(--z-dock);pointer-events:none}
/* §7.2 Browser — three squared dots at 22% ink, never coloured circles */
.sx-dev .sx-dev-browser{width:var(--dev-w)}
.sx-dev-browser{width:100%;border-radius:var(--r-8);overflow:hidden;background:var(--paper);border:1px solid var(--hairline);box-shadow:var(--lift-4);position:relative}
.sx-dev-bar{height:38px;background:var(--paper-2);border-bottom:1px solid var(--hairline);display:flex;align-items:center;gap:var(--sp-3);padding-inline:var(--sp-4)}
.sx-dev-dots{display:flex;gap:5px;flex:none}
.sx-dev-dots i{width:6px;height:6px;border-radius:var(--r-dot);background:var(--hairline-ink);display:block}
.sx-dev-url{flex:1;background:var(--paper-3);border-radius:var(--r-1);padding:5px 10px;font-family:var(--mono);font-size:11px;color:var(--ink-3);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.sx-dev-act{width:20px;height:20px;border-radius:var(--r-1);background:var(--paper-3);flex:none}
.sx-dev-view{position:relative;background:var(--paper);container-type:inline-size;overflow:hidden}
/* §7.2 Card fragment — the highest-yield depiction mode in the system */
.sx-dev-chip{border-radius:var(--r-6);box-shadow:var(--lift-3);padding:var(--sp-4);max-width:360px;container-type:inline-size}

/* §7.1 sx-ui-* — the app's grammar exactly: 18px gutter, 22px section gap, r-18 cards with a 1px gradient stroke, mono */
/* THE APP IS LIGHT-ONLY (§7.6 — there is no dark mode to depict). Dropping a
   device into an .on-dusk band would otherwise inherit the dusk remap and paint
   a dark app that does not exist, and recipe-A cards inside it would go flat
   (the §2.6 trap). Re-assert the light ground at every device boundary so the
   depiction stays true wherever a builder puts it. */
.on-dusk :is(.sx-dev,.sx-dev-browser,.sx-dev-chip,.sx-ui,.sx-ui-web){
--paper:#FFFFFF;--paper-2:#FDFAFB;--paper-3:#F6F1F4;
--plum:#3a1f30;--ink-2:#5A3D48;--ink-3:#7c6473;--ink-4:rgba(124,100,115,.45);
--hairline:#EFDCE7;--hairline-2:#E6CBDB;--border:#ecc2d6;--border-soft:rgba(236,194,214,.55);
--card-fill:linear-gradient(#FFFFFF,#FFFFFF);--g-border:linear-gradient(135deg,rgba(241,102,168,.42),rgba(128,146,212,.42));
--veil:rgba(255,252,254,.72);--chip-pink:#D1136E;--chip-peri:#4E68C3;--green:#357A57;
--lift-0:inset 0 1px 0 rgba(255,255,255,.72);--lift-1:var(--lift-0),0 1px 3px rgba(58,31,48,.05);
--lift-2:var(--lift-0),0 4px 14px rgba(58,31,48,.07);--lift-3:var(--lift-0),0 14px 38px rgba(58,31,48,.10);
--lift-4:var(--lift-0),0 30px 80px rgba(58,31,48,.12),0 4px 14px rgba(58,31,48,.05)}
.sx-ui{font-family:var(--sans);color:var(--plum);background:var(--paper);height:100%;overflow:hidden;display:flex;flex-direction:column;font-size:13px;line-height:1.4;position:relative}
.sx-ui *{min-width:0}
/* Every glyph in a frame has a size. :where() keeps it at zero specificity so
   any class-based rule below (spark, tile, ghost, media, kpi) still wins. */
:where(.sx-ui,.sx-ui-web,.sx-dev-chip) svg{width:15px;height:15px;flex:none}
/* THE REEL — a continuous strip, self-measuring, no authored heights.
   The window is flex:1, so it always reaches the tab bar: no void underneath.
   Groups keep their NATURAL height, so no group can leave a white tail.
   container-type:size makes 100cqh inside the strip mean "the window", and
   -100% on translate means "the strip's own height", so the travel
   calc(-100% + 100cqh) lands the strip's last pixel flush with the window's
   last pixel — whatever content a builder puts in. min(0px,…) keeps a strip
   shorter than its window from sliding downward. Group 1 is what shows with
   the animation absent, which is the whole fallback story. */
.sx-ui-reelbox{flex:1;min-height:0;overflow:clip;container-type:size}
/* Safety net: a reelbox one level down inside an auto-height stack would have
   nothing to grow into and would collapse to zero, taking the whole screen with
   it. Let that wrapper grow too. */
.sx-ui-scroll>.sx-ui-stack:has(.sx-ui-reelbox){flex:1;min-height:0}
.sx-ui-reel{display:flex;flex-direction:column;gap:var(--reel-gap,22px)}
.sx-ui-scroll{flex:1;overflow:hidden;display:flex;flex-direction:column;gap:22px;padding:18px 0 96px}
.sx-ui-pad{padding-inline:18px}
.sx-ui-stack{display:flex;flex-direction:column;gap:22px}
/* Two families do all the work in a frame: mono micro-label, serif display */
.sx-ui-eyebrow,.sx-ui-mer,.sx-ui-note,.sx-ui-chip,.sx-ui-pill,.sx-ui-dur,.sx-ui-seg span,.sx-ui-dow,.sx-ui-count{font-family:var(--mono);font-weight:500;text-transform:uppercase;line-height:1}
.sx-ui-title,.sx-ui-hero-name,.sx-ui-hero-fig,.sx-ui-front-name,.sx-ui-id-name,.sx-ui-glance-val,.sx-ui-stat b,.sx-ui-time,.sx-ui-mark{font-family:var(--serif);font-style:italic;font-weight:600;margin:0}
.sx-ui-eyebrow{font-size:9.5px;letter-spacing:.15em;color:var(--ink-3);padding-left:2px;margin:0}
.sx-ui-title{font-size:26px;line-height:1.1;letter-spacing:-.02em;color:var(--plum)}
.sx-ui-sub{font-size:11.5px;color:var(--ink-3);margin:0}
.sx-ui-name,.sx-ui-meta,.sx-ui-sub{display:block}
.sx-ui-name{font-weight:600;font-size:14px;color:var(--plum);margin:0}
.sx-ui-meta{font-size:11px;color:var(--ink-3);margin:0}
.sx-ui-sect{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px}
.sx-ui-row{display:flex;align-items:center;gap:12px}
.sx-ui-grow{flex:1;min-width:0}
.sx-ui-trunc{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
/* The hero recipe: gradient · white radial glow · 92pt ghost glyph at -14deg */
.sx-ui-hero{position:relative;overflow:hidden;border-radius:28px;padding:22px;background:var(--gradient-tri);color:var(--on-brand);box-shadow:var(--shadow-rose);display:flex;flex-direction:column;gap:10px}
.sx-ui-hero::before{content:"";position:absolute;inset:0;background:var(--on-brand-glow);pointer-events:none}
.sx-ui-hero>*{position:relative}
.sx-ui-hero .sx-ui-eyebrow{color:var(--on-brand-2)}
.sx-ui-ghost{position:absolute;top:-14px;inset-inline-end:-10px;width:92px;height:92px;color:var(--on-brand-ghost);rotate:-14deg;pointer-events:none}
.sx-ui-ghost svg{width:100%;height:100%;stroke-width:1.2;fill:currentColor}
.sx-ui-hero-name{font-size:30px;line-height:1.05;letter-spacing:-.02em;color:var(--on-brand)}
.sx-ui-hero-fig{font-size:56px;line-height:1;letter-spacing:-.03em;color:var(--on-brand);display:flex;align-items:baseline;gap:2px}
.sx-ui-hero-fig small{font-size:24px;font-weight:600}
.sx-ui-spark{width:100%;height:44px;display:block;margin-top:4px}
.sx-ui-spark path{stroke:var(--on-brand);stroke-width:2.4;fill:none}
/* Cards — slaydSectionCard: white, r-18, 1px brand stroke, pink-tinted lift */
.sx-ui-card{border-radius:18px;box-shadow:0 8px 14px var(--lit-pink-08);padding:14px;display:flex;flex-direction:column;gap:10px}
.sx-ui-card-tight{box-shadow:0 2px 6px var(--lit-pink-08)}
.sx-ui-card-em{border-width:1.8px;background:var(--card-fill) padding-box,var(--g-border-full) border-box;border-radius:20px}
.sx-ui-list{background:var(--paper);border-radius:14px;border:1.2px solid transparent;background:var(--card-fill) padding-box,var(--g-border-full) border-box;padding:12px;display:flex;align-items:center;gap:12px}
.sx-ui-tile{--t:34px;width:var(--t);height:var(--t);border-radius:calc(var(--t)*.29);display:inline-flex;align-items:center;justify-content:center;flex:none}
.sx-ui-tile svg{width:58%;height:58%}
.sx-ui-price{font-weight:700;font-size:14px;color:var(--pink);font-variant-numeric:tabular-nums;margin:0}
.sx-ui-time{font-size:22px;line-height:1}
.sx-ui-mer,.sx-ui-note{font-size:9px;letter-spacing:.1em;color:var(--ink-3);margin:0}
.sx-ui-note{display:flex;align-items:center;gap:7px}
.sx-ui-note svg{width:11px;height:11px;flex:none}
.sx-ui-chip{display:inline-flex;align-items:center;gap:5px;font-size:9px;letter-spacing:.1em;padding:4px 8px;border-radius:8px;background:var(--paper-3);color:var(--ink-3)}
.sx-ui-chip::before{content:"";width:4px;height:4px;border-radius:var(--r-dot);background:currentColor;flex:none}
.sx-ui-chip-ok{background:var(--green-bg);color:var(--green)}
/* Status inks solved for 4.5:1 on their own tint (9px mono needs the text
   floor, not the 3:1 non-text one): green 4.55, pink 4.53, peri 4.50.
   There is no amber variant on purpose — Rule 7 keeps --gold/--amber to
   portal status. The app has an awaiting-payment state; the brochure
   does not feature it. */
.sx-ui-chip-pink,.sx-ui-chip-now{background:var(--lit-pink-14);color:var(--chip-pink)}
.sx-ui-chip-peri{background:var(--lit-peri-14);color:var(--chip-peri)}
.sx-ui-cal b,.sx-ui-chip-grad,.sx-ui-count,.sx-ui-book,.sx-ui-slot.is-on,.sx-ui-seg .is-on{background:var(--gradient-h);color:var(--on-brand)}
.sx-ui-chip-grad::before{background:var(--on-brand)}
/* Pills and capsules are legal ONLY here: inside a device frame reproducing the app (§7.1) */
.sx-ui-pill,.sx-ui-dur,.sx-ui-glass,.sx-ui-seg,.sx-ui-seg span,.sx-ui-tabs{border-radius:999px}
.sx-ui-pill{display:inline-flex;align-items:center;gap:6px;align-self:flex-start;padding:6px 11px;background:var(--on-brand-fill);border:1px solid var(--on-brand-line);color:var(--on-brand);font-size:9.5px;letter-spacing:.12em}
.sx-ui-pill svg{width:11px;height:11px;stroke-width:2}
.sx-ui-dur{display:inline-flex;align-items:center;padding:4px 9px;background:var(--lit-pink-08);color:var(--pink);font-size:9px;letter-spacing:.1em}
.sx-ui-book{display:inline-flex;align-items:center;gap:5px;padding:7px 13px;border-radius:12px;font-weight:700;font-size:11.5px;line-height:1;box-shadow:var(--shadow-rose)}
.sx-ui-book svg{width:12px;height:12px;stroke-width:2}
/* Photo-free media: the app's own blush placeholder with a ghost glyph */
.sx-ui-media{position:relative;overflow:hidden;border-radius:22px;display:flex;align-items:center;justify-content:center;color:var(--lit-pink-22);aspect-ratio:4/5;
  background-image:var(--media-src,none),var(--tint-tile);background-size:cover;background-position:var(--media-pos,center);background-repeat:no-repeat}
.sx-ui-media>svg{width:34%;height:34%;stroke-width:1}
/* With a photo behind it the ghost glyph is noise, and the scrims have to do
   real work. A LINEAR ramp is not enough: measured over these four photos the
   average was fine (5-11:1) but specular highlights — a candle, a lit strand —
   drove the worst pixel under the caption to 1.68:1, so the copy vanished in
   patches. These ramps stay soft where there is no text and bite hard exactly
   where the caption and the name sit, which keeps the frame reading as a
   photograph rather than a black bar. Measured worst pixel after: >=4.5:1. */
.sx-ui-media.has-media>svg{display:none}
.sx-ui-media.has-media .sx-ui-scrim-t{height:110px;
  background:linear-gradient(rgba(0,0,0,.72) 0,rgba(0,0,0,.55) 55%,rgba(0,0,0,0) 100%)}
.sx-ui-media.has-media .sx-ui-scrim-b{height:240px;
  background:linear-gradient(rgba(0,0,0,0) 0,rgba(0,0,0,.28) 42%,rgba(0,0,0,.72) 72%,rgba(0,0,0,.86) 100%)}
.sx-ui-media-brand{background:var(--gradient-tri);color:var(--on-brand-ghost)}
.sx-ui-scrim-t{position:absolute;inset:0 0 auto;height:84px;background:var(--scrim-top)}
.sx-ui-scrim-b{position:absolute;inset:auto 0 0;height:132px;background:var(--scrim-bot)}
.sx-ui-over{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:space-between;padding:12px}
.sx-ui-over :is(.sx-ui-name,.sx-ui-meta){color:var(--on-brand)}
.sx-ui-cap{font-size:14px;font-weight:800;color:var(--on-brand);line-height:1.3;margin:0;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
/* Glass is sanctioned inside a device frame — it mirrors ultraThinMaterial */
.sx-ui-glass{display:inline-flex;align-items:center;gap:5px;padding:5px 10px;background:var(--glass-dev);backdrop-filter:blur(10px);border:1px solid var(--on-brand-line);color:var(--on-brand);font-weight:700;font-size:10.5px;line-height:1}
.sx-ui-acts{display:flex;align-items:center;gap:14px;color:var(--on-brand);font-size:11.5px;font-weight:600}
.sx-ui-acts svg{width:17px;height:17px}
.sx-ui-acts span{display:inline-flex;align-items:center;gap:4px}
/* Storefront hero — the hero IS the gradient */
.sx-ui-front{position:relative;background:var(--gradient-tri);padding:52px 18px 16px;display:flex;flex-direction:column;gap:5px;color:var(--on-brand)}
.sx-ui-front-name{font-size:34px;line-height:1.02;letter-spacing:-.025em;color:var(--on-brand)}
.sx-ui-front-facts{display:flex;align-items:center;gap:5px;color:var(--on-brand-2);font-size:11.5px}
.sx-ui-front-facts svg{width:13px;height:13px;flex:none}
.sx-ui-navbtn{position:absolute;top:14px;width:34px;height:34px;border-radius:12px;background:var(--scrim-top);border:1px solid var(--on-brand-line);color:var(--on-brand);display:inline-flex;align-items:center;justify-content:center}
.sx-ui-navbtn svg{width:15px;height:15px}
/* 2x2 quick actions (S7), month grid + slot grid (S5), week grid (S4) */
.sx-ui-2up{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}
.sx-ui-quick{position:relative;border-radius:16px;box-shadow:0 3px 8px var(--lit-pink-08);padding:14px;display:flex;flex-direction:column;gap:8px}
.sx-ui-quick .sx-ui-tile{--t:42px}
.sx-ui-quick b{font-size:18px;font-weight:700;color:var(--plum);line-height:1.15}
.sx-ui-chev{position:absolute;top:12px;inset-inline-end:12px;width:26px;height:26px;border-radius:8px;background:var(--paper);box-shadow:var(--shadow-subtle);display:inline-flex;align-items:center;justify-content:center;color:var(--ink-3)}
.sx-ui-chev svg{width:11px;height:11px;stroke-width:2}
.sx-ui-cal{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:4px;font-variant-numeric:tabular-nums}
.sx-ui-cal :is(span,b){display:flex;align-items:center;justify-content:center;aspect-ratio:1;border-radius:9px;font-size:11.5px;font-weight:500;color:var(--ink-2)}
.sx-ui-cal .sx-ui-dow{font-size:8.5px;letter-spacing:.08em;color:var(--ink-3)}
.sx-ui-cal .is-off{color:var(--ink-4)}
.sx-ui-cal b{font-weight:700}
.sx-ui-slots{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px}
.sx-ui-slot{display:flex;align-items:center;justify-content:center;padding:11px 6px;border-radius:12px;border:1px solid var(--hairline);background:var(--paper-2);font-size:12px;font-weight:600;color:var(--plum);font-variant-numeric:tabular-nums;position:relative}
.sx-ui-slot.is-on{border-color:transparent;box-shadow:var(--shadow-rose)}
.sx-ui-slot.is-taken{background:var(--paper-3);color:var(--ink-4);border-color:var(--mid)}
.sx-ui-slot svg{position:absolute;top:4px;inset-inline-end:5px;width:9px;height:9px}
/* The date rail (a 24-hour grid at 390pt is thin smudged bars, not a product).
   Legible day tiles, then the agenda cards do the actual selling. */
.sx-ui-days{display:grid;grid-template-columns:repeat(7,minmax(0,1fr));gap:5px}
.sx-ui-day{display:flex;flex-direction:column;align-items:center;gap:3px;padding:8px 0 7px;border-radius:12px;background:var(--paper-2);border:1px solid var(--hairline)}
.sx-ui-day em{font-family:var(--mono);font-style:normal;font-size:8.5px;font-weight:500;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-3)}
.sx-ui-day b{font-family:var(--serif);font-style:italic;font-weight:600;font-size:17px;line-height:1;color:var(--plum)}
.sx-ui-day.is-on{background:var(--gradient-h);border-color:transparent;box-shadow:var(--shadow-rose)}
.sx-ui-day.is-on :is(em,b){color:var(--on-brand)}
/* The app's live now-line, as a list-mode gap row: legible where a 1.5px rule
   across a 24h grid is not. */
.sx-ui-nowrow{display:flex;align-items:center;gap:8px;padding-block:2px}
.sx-ui-nowrow::after{content:"";flex:1;height:1.5px;background:var(--gradient-h)}
.sx-ui-nowrow b{font-family:var(--mono);font-size:8.5px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--pink);display:flex;align-items:center;gap:5px}
.sx-ui-nowrow b::before{content:"";width:5px;height:5px;border-radius:var(--r-dot);background:var(--pink)}
/* Staff colour: the strip down the agenda card's leading edge (§P3). */
.sx-ui-appt-b::before{background:var(--gradient-rev)}
.sx-ui-appt-c::before{background:var(--gradient-rose)}
/* Agenda card (S4) — 4px category strip, serif gradient time column */
.sx-ui-appt{position:relative;overflow:hidden;padding-inline-start:16px}
.sx-ui-appt::before{content:"";position:absolute;inset-block:0;inset-inline-start:0;width:4px;background:var(--gradient)}
.sx-ui-appt-time{width:58px;flex:none;display:flex;flex-direction:column;gap:3px;align-items:flex-start}
/* Glance grid (S8) */
/* Reviews (S2), the full-width confirm (S6), a two-up split (S8). */
.sx-ui-stars{display:flex;gap:2px;color:var(--pink)}
.sx-ui-stars svg{width:11px;height:11px}
.sx-ui-quote{font-family:var(--serif);font-style:italic;font-weight:500;font-size:15px;line-height:1.35;color:var(--plum);margin:0}
.sx-ui-cta{display:flex;align-items:center;justify-content:center;gap:7px;padding:15px;border-radius:16px;background:var(--gradient-h);color:var(--on-brand);font-weight:700;font-size:15px;line-height:1;box-shadow:var(--shadow-rose)}
.sx-ui-cta-q{display:flex;align-items:center;justify-content:center;padding:13px;border-radius:16px;background:none;color:var(--ink-3);font-weight:600;font-size:13px}
.sx-ui-split{display:flex;height:8px;border-radius:var(--r-dot);overflow:hidden;background:var(--paper-3)}
.sx-ui-split i{display:block;height:100%;background:var(--gradient-h)}
.sx-ui-glance{border-radius:16px;padding:12px;display:flex;flex-direction:column;gap:6px}
.sx-ui-glance .sx-ui-tile{--t:36px}
.sx-ui-glance-val{font-size:26px;line-height:1;color:var(--plum)}
/* Stat bar and identity hero (S10) — the 14x1.5 pink tick, 84px avatar */
.sx-ui-stats{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));background:linear-gradient(var(--blush),var(--paper));border-radius:18px;border:1px solid var(--border-soft);position:relative;overflow:hidden;padding:14px 8px}
.sx-ui-stats::before{content:"";position:absolute;inset:0 0 auto;height:2px;background:var(--gradient-h)}
.sx-ui-stat{display:flex;flex-direction:column;align-items:center;gap:5px}
.sx-ui-stat b{font-size:24px;line-height:1;color:var(--plum)}
.sx-ui-stat i{width:14px;height:1.5px;background:var(--gradient-h);display:block;flex:none}
.sx-ui-id{border-radius:22px;padding:16px;display:flex;align-items:center;gap:14px;background:var(--gradient-tri);border:2px solid transparent;color:var(--on-brand);box-shadow:var(--shadow-rose)}
.sx-ui-id .sx-ui-av{--av:84px;border-radius:26px;box-shadow:0 0 0 3px var(--on-brand);background:var(--paper);color:var(--pink)}
.sx-ui-id-name{font-size:25px;line-height:1.05;color:var(--on-brand)}
/* Journal segmented control (S9) and the floating capsule tab bar */
.sx-ui-seg{display:flex;gap:3px;padding:4px;border:1.2px solid transparent;background:var(--card-fill) padding-box,var(--g-border-strong) border-box}
.sx-ui-seg span{flex:1;display:inline-flex;align-items:center;justify-content:center;gap:4px;padding:8px 6px;font-size:9px;letter-spacing:.1em;color:var(--ink-3)}
.sx-ui-seg span svg{width:11px;height:11px}
.sx-ui-seg .is-on{box-shadow:var(--shadow-rose)}
.sx-ui-nav{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:24px 18px 0}
.sx-ui-mark{font-weight:700;font-size:26px;line-height:1;letter-spacing:-.03em}
.sx-ui-navicons{display:flex;align-items:center;gap:12px;color:var(--plum)}
.sx-ui-navicons svg,.sx-ui-tab svg{width:19px;height:19px}
.sx-ui-tabs{position:absolute;inset-inline:18px;bottom:16px;height:58px;display:flex;align-items:center;justify-content:space-around;padding-inline:8px;background:var(--veil);backdrop-filter:var(--glass-blur);border:1px solid var(--border-soft);box-shadow:0 6px 16px rgba(42,15,26,.22);z-index:var(--z-dock)}
.sx-ui-tab{display:flex;flex-direction:column;align-items:center;gap:4px;color:var(--plum);font-size:9px;font-weight:600;line-height:1}
.sx-ui-tab.is-on{color:var(--pink);font-weight:800}
/* Web screens — the live portal and the public storefront, in sx-dev-browser */
.sx-ui-web{display:grid;grid-template-columns:180px minmax(0,1fr);font-family:var(--sans);color:var(--plum);background:var(--paper)}
.sx-ui-web-side{background:var(--paper-2);border-inline-end:1px solid var(--hairline);padding:16px 12px;display:flex;flex-direction:column;gap:4px}
.sx-ui-web-side a{display:flex;align-items:center;gap:9px;padding:8px 10px;border-radius:10px;font-size:12px;font-weight:600;color:var(--ink-2);text-decoration:none}
.sx-ui-web-side a svg{width:15px;height:15px}
.sx-ui-web-side a.is-on{background:var(--lit-pink-08);color:var(--pink)}
.sx-ui-count{margin-inline-start:auto;font-size:9px;border-radius:6px;padding:2px 5px}
.sx-ui-web-main{padding:18px;display:flex;flex-direction:column;gap:16px;min-width:0}
.sx-ui-web-hero{border-radius:18px;padding:18px;background:var(--wash-cool);border:1px solid var(--hairline);display:flex;align-items:flex-end;justify-content:space-between;gap:14px}
.sx-ui-kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:10px}
.sx-ui-kpi{border-radius:14px;padding:11px;display:flex;flex-direction:column;gap:4px}
.sx-ui-kpi b{font-size:19px;line-height:1}
.sx-ui-kpi svg{width:100%;height:16px;stroke:var(--pink);stroke-width:1.6}
.sx-ui-web-store .sx-ui-front{padding:34px 24px 18px}
.sx-ui-web-body{padding:18px 24px 24px;display:flex;flex-direction:column;gap:14px}
}
