@import url(https://fonts.googleapis.com/css2?family=Anton&family=Archivo:wght@400;500;600;700;800&family=Zilla+Slab:wght@500;600;700&display=swap);
/*
 * The pre-0.3.0 generic token set. Hand-written, and the only token file in
 * this package that is: it predates rdp-brand-guide.md 1b and none of its
 * values is derivable from brand.json. --rdp-color-primary in particular is
 * an off-brand #1f6feb that the brand guide 0 forbids.
 *
 * It survives because consumers resolve these names today:
 *   - merch-studio/src/js/shop/shop.css reads --rdp-color-surface.
 *   - custom-order-builder/assets/wizard/styles/tailwind.css rebinds the whole
 *     set to its own brand values, precisely so that any @rdp/ui primitive
 *     reaching for a generic name renders on-brand.
 *
 * tokens.css imports this file. tokens.global.css deliberately does not:
 * these names are one namespace away from bare, and :root is the one place
 * that would let them resolve on every element of the page.
 *
 * Retiring them is consumer-migration work (plan 4), not token-ownership work.
 */
:root {
  /* Color */
  --rdp-color-primary: #1f6feb;
  --rdp-color-primary-contrast: #ffffff;
  --rdp-color-danger: #d63638;
  --rdp-color-text: #1f2937;
  --rdp-color-muted: #50575e;
  --rdp-color-border: #c3c4c7;
  --rdp-color-surface: #ffffff;

  /* Spacing scale */
  --rdp-space-1: 4px;
  --rdp-space-2: 8px;
  --rdp-space-3: 12px;
  --rdp-space-4: 16px;
  --rdp-space-6: 24px;

  /* Radius */
  --rdp-radius: 6px;

  /* Per-client brand override: COB palette extraction sets --cob-primary;
     fall back to the default primary when absent. */
  --rdp-brand-primary: var(--cob-primary, var(--rdp-color-primary));
}

/* GENERATED FROM src/tokens/brand.json -- DO NOT EDIT.
   Run `pnpm tokens` after changing brand.json. A hand edit here is a test
   failure (src/tokens/__tests__/parity.test.ts). */

.rdp-ui-scope {
  --rdp-ui-white: #ffffff;
  --rdp-ui-paper: #f7f8fa;
  --rdp-ui-grey-100: #eef0f2;
  --rdp-ui-grey-200: #e1e5ea;
  --rdp-ui-grey-400: #9aa1ab;
  --rdp-ui-grey-600: #5b626c;
  --rdp-ui-ink: #1b1e24;
  --rdp-ui-red: #d2232a;
  --rdp-ui-red-700: #a81b21;
  --rdp-ui-red-100: #fbe3e4;
  --rdp-ui-blue: #1e3a5f;
  --rdp-ui-blue-800: #15293f;
  --rdp-ui-blue-400: #3d6fa6;
  --rdp-ui-blue-100: #e5ecf3;
  --rdp-ui-r-sm: 6px;
  --rdp-ui-radius-sm: 6px;
  --rdp-ui-r-md: 10px;
  --rdp-ui-radius-md: 10px;
  --rdp-ui-r-lg: 16px;
  --rdp-ui-radius-lg: 16px;
  --rdp-ui-hairline: 1px solid var(--rdp-ui-grey-200);
  --rdp-ui-border-emphasis: 1.5px solid var(--rdp-ui-ink);
  --rdp-ui-s-xs: 4px;
  --rdp-ui-s-sm: 8px;
  --rdp-ui-s-12: 12px;
  --rdp-ui-s-md: 16px;
  --rdp-ui-s-lg: 24px;
  --rdp-ui-s-xl: 32px;
  --rdp-ui-s-2xl: 48px;
  --rdp-ui-s-3xl: 64px;
  --rdp-ui-s-4xl: 96px;
  --rdp-ui-container-max: 1080px;
  --rdp-ui-gutter: 24px;
  --rdp-ui-mobile-breakpoint: 780px;
  --rdp-ui-grid: 12;
  --rdp-ui-font-display: 'Anton', Impact, 'Haettenschweiler', sans-serif;
  --rdp-ui-font-body: 'Archivo', 'Helvetica Neue', sans-serif;
  --rdp-ui-font-slab: 'Zilla Slab', Georgia, serif;
  --rdp-ui-scale-display: 56px;
  --rdp-ui-scale-h1: 40px;
  --rdp-ui-scale-h2: 28px;
  --rdp-ui-scale-h3: 20px;
  --rdp-ui-scale-body: 16px;
  --rdp-ui-scale-small: 14px;
  --rdp-ui-scale-caption: 12px;
  --rdp-ui-dur-instant: 110ms;
  --rdp-ui-dur-quick: 200ms;
  --rdp-ui-dur-base: 300ms;
  --rdp-ui-dur-stamp: 440ms;
  --rdp-ui-ease-snap: cubic-bezier(0.16,1,0.3,1);
  --rdp-ui-ease-press: cubic-bezier(0.55,0,0.85,0.4);
  --rdp-ui-ease-settle: cubic-bezier(0.34,1.56,0.64,1);
}

.rdp-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/**
 * A catch-all reduced-motion stylesheet, not the library's only one.
 * Dialog.module.css, Sheet.module.css, Skeleton.module.css and
 * Spinner.module.css already carry their own `prefers-reduced-motion`
 * blocks; this file exists for two things those per-component guards
 * don't cover: a consuming app's own animations inside `.rdp-ui-scope`,
 * and any future animation in this library that ships without its own
 * guard. Shipped as an opt-in stylesheet rather than injected, so a
 * consumer that already has a global reduced-motion rule does not get a
 * second one.
 *
 * Scoped to `.rdp-ui-scope` (rather than `*`) so importing it cannot
 * flatten a consuming app's own motion outside that scope. The selector
 * covers the scoped element itself, not just its descendants: Dialog's
 * `.content` and Sheet's `.sheet` carry `rdp-ui-scope` on the very
 * element that animates (see Dialog.tsx / Sheet.tsx), so a
 * descendants-only selector would match none of this package's own
 * animations.
 */
@media (prefers-reduced-motion: reduce) {
  .rdp-ui-scope,
  .rdp-ui-scope *,
  .rdp-ui-scope::before,
  .rdp-ui-scope::after,
  .rdp-ui-scope *::before,
  .rdp-ui-scope *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}

/**
 * The one stylesheet both frontend bundles import.
 *
 * The font import has to be the file's first rule — a CSS @import must
 * precede every other statement except @charset, and that includes @config.
 * Without it, every var(--rdp-ui-font-display)/var(--rdp-ui-font-slab) in
 * both bundles was silently falling back to Impact/Georgia: the family
 * names were always wired up correctly, the actual font files were never
 * fetched. rdp-brand-guide.md §3 gives this exact URL.
 */

/**
 * Every @import first, then @config, then Tailwind's layers -- so a utility
 * can still win a specificity tie against a component class.
 *
 * The imports must precede @config, not follow it. A CSS @import may be
 * preceded only by @charset and empty @layer, and @config is neither;
 * webpack's postcss-loader tolerated the wrong order silently, but Vite
 * fails the build outright -- which is how Storybook found this.
 */
/*
 * studio.css deleted `.ms-manage__bar-fill`'s hand-rolled
 * prefers-reduced-motion guard along with the bar itself when OverviewPage
 * moved to @rdp/ui's `Progress` (which animates `width` on its own). This
 * is that guard's replacement: brand guide §8's reduced-motion fallback,
 * scoped to `.rdp-ui-scope` so it cannot flatten this app's own motion
 * outside it. Skeleton, Spinner, Dialog and Sheet ship their own guards;
 * this covers the rest, notably Progress.
 */

/**
 * Tokens the brand guide defines that @rdp/ui's .rdp-ui-scope block omits.
 * Declared on the mount so they resolve for the whole SPA subtree, alongside
 * the brand tokens each @rdp/ui component carries via its own .rdp-ui-scope
 * root class.
 *
 * width/min-width: app.php mounts #ms-app as a bare sibling div, not inside
 * GeneratePress's #primary/.content-area -- so GP's own flex-grow rule for
 * that wrapper never reaches it. .site-content is a flex row, and a flex
 * item's main-axis size defaults to its content's size, not the row's, so
 * without an explicit width #ms-app (and every full-bleed screen inside it,
 * studio.css's .ms-upload/.ms-feed included) shrink-wraps instead of
 * filling the 1200px GP container. min-width:0 keeps that same shrink
 * default from clipping a flex item's content once it's stretched wide.
 */
#ms-app {
  width: 100%;
  min-width: 0;
  /*
   * Two container widths, because the studio has two kinds of screen and one
   * number was serving both badly.
   *
   * --ms-container is a READING measure: the upload hero, the store-setup
   * wizard, the shop's product page. Prose and form fields have an optimal
   * width that a bigger monitor does not change, and 1080px is it.
   *
   * --ms-container-wide is a WORKSPACE measure: the dashboard, the feed grid,
   * the tray, the editor's rail-plus-canvas split. These are surfaces you work
   * IN rather than read, and the same 1080px cap left them as a centered strip
   * with a third of a 2560px monitor empty on either side -- reported,
   * accurately, as the studio being afraid to use the page.
   *
   * The gutter does the rest of the work: every consumer pairs these with
   * `padding: 0 var(--ms-gutter)` or sits inside a parent that does, so the
   * cap is a ceiling and never a floor. Nothing here makes a narrow window
   * wider than it is.
   */
  --ms-container: 1080px;
  --ms-container-wide: 1600px;
  --ms-gutter: 24px;

  /*
   * The two breakpoints, named. Neither can be USED where it matters -- a
   * custom property does not resolve inside a media query -- so both are
   * documentation, and the literal in each `@media` must be kept in step by
   * hand. They earn their place by putting the numbers somewhere findable:
   * 780px is where a phone layout becomes a two-column one, 1200px is where
   * there is enough room to spend on the workspace itself (a wider editor
   * rail, a wider dashboard sidebar, a taller canvas) rather than on margins.
   */
  --ms-bp-mobile: 780px;
  --ms-bp-desktop: 1200px;

  /*
   * The type scale is a brand value, and @rdp/ui generates it from the
   * guide's own JSON now. These stay as aliases rather than being deleted:
   * the bundles read --ms-type-* in ~90 rules, and renaming those would
   * turn a token migration into a restyle nobody could review.
   */
  --ms-type-display: var(--rdp-ui-scale-display);
  --ms-type-h1: var(--rdp-ui-scale-h1);
  --ms-type-h2: var(--rdp-ui-scale-h2);
  --ms-type-h3: var(--rdp-ui-scale-h3);
  --ms-type-body: var(--rdp-ui-scale-body);
  --ms-type-small: var(--rdp-ui-scale-small);
  --ms-type-caption: var(--rdp-ui-scale-caption);

  color: var(--rdp-ui-ink);
  font-family: var(--rdp-ui-font-body);
  font-size: var(--ms-type-body);
  line-height: 1.6;
}

/* Display type is Anton, uppercase, tight leading — brand guide §7. */
#ms-app h1,
#ms-app h2,
#ms-app h3 {
  font-family: var(--rdp-ui-font-display);
  font-weight: 400;
  line-height: 0.95;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

#ms-app h1 {
  font-size: var(--ms-type-h1);
}
#ms-app h2 {
  font-size: var(--ms-type-h2);
}
#ms-app h3 {
  font-size: var(--ms-type-h3);
}

.ms-overline {
  font-size: var(--ms-type-caption);
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--rdp-ui-red);
}

/*
 * The block that used to sit here defined fifteen @rdp/ui class names the
 * package referenced from its components and shipped no rules for:
 * spinner, skeleton, empty state, field error, query error and the toasts.
 *
 * @rdp/ui 0.4.0 ships all of them itself, as CSS Modules. The rules are
 * deleted rather than moved, and they could not have been kept even as a
 * fallback: the class names they targeted are hashed now, so not one of
 * them would match anything.
 */

/*
 * create-2-cart's own responsive grid, which is not an @rdp/ui class and
 * stays here. Everything else that used to sit in this block defined
 * @rdp/ui classes and is gone -- see the note above.
 */
.rdp-responsive-product-grid {
  display: grid;
  grid-template-columns: repeat(1, minmax(0, 1fr));
  gap: var(--rdp-ui-s-md);
}

@media (min-width: 640px) {
  .rdp-responsive-product-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 768px) {
  .rdp-responsive-product-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

@media (min-width: 1280px) {
  .rdp-responsive-product-grid {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.container{
  width: 100%;
}

@media (min-width: 640px){
  .container{
    max-width: 640px;
  }
}

@media (min-width: 768px){
  .container{
    max-width: 768px;
  }
}

@media (min-width: 1024px){
  .container{
    max-width: 1024px;
  }
}

@media (min-width: 1280px){
  .container{
    max-width: 1280px;
  }
}

@media (min-width: 1536px){
  .container{
    max-width: 1536px;
  }
}
#ms-app .visible{
  visibility: visible;
}
#ms-app .invisible{
  visibility: hidden;
}
#ms-app .collapse{
  visibility: collapse;
}
#ms-app .static{
  position: static;
}
#ms-app .fixed{
  position: fixed;
}
#ms-app .absolute{
  position: absolute;
}
#ms-app .relative{
  position: relative;
}
#ms-app .sticky{
  position: sticky;
}
#ms-app .isolate{
  isolation: isolate;
}
#ms-app .\!block{
  display: block !important;
}
#ms-app .block{
  display: block;
}
#ms-app .inline-block{
  display: inline-block;
}
#ms-app .inline{
  display: inline;
}
#ms-app .flex{
  display: flex;
}
#ms-app .table{
  display: table;
}
#ms-app .grid{
  display: grid;
}
#ms-app .contents{
  display: contents;
}
#ms-app .hidden{
  display: none;
}
#ms-app .shrink{
  flex-shrink: 1;
}
#ms-app .grow{
  flex-grow: 1;
}
#ms-app .transform{
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
}
#ms-app .resize{
  resize: both;
}
#ms-app .rounded{
  border-radius: 0.25rem;
}
#ms-app .border{
  border-width: 1px;
}
#ms-app .uppercase{
  text-transform: uppercase;
}
#ms-app .lowercase{
  text-transform: lowercase;
}
#ms-app .tabular-nums{
  --tw-numeric-spacing: tabular-nums;
  font-variant-numeric: var(--tw-ordinal) var(--tw-slashed-zero) var(--tw-numeric-figure) var(--tw-numeric-spacing) var(--tw-numeric-fraction);
}
#ms-app .overline{
  text-decoration-line: overline;
}
#ms-app .shadow{
  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
#ms-app .outline{
  outline-style: solid;
}
#ms-app .ring{
  --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
  --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
}
#ms-app .blur{
  --tw-blur: blur(8px);
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
#ms-app .invert{
  --tw-invert: invert(100%);
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
#ms-app .filter{
  filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
}
#ms-app .transition{
  transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  transition-duration: 150ms;
}

.ms-share {
  display: inline-flex;
  align-items: center;
  gap: 0.5em;
}

/**
 * Creator-path rules. Deliberately separate from
 * src/js/shared/ui/frontend.css, which both bundles import and which the
 * foundation branch owns: keeping them apart is what lets this plan and the
 * shopper plan run in parallel worktrees without a merge conflict in one file.
 */

/**
 * The full-screen shell. `ms-app-canvas` is the class `app-canvas.php` prints
 * via `body_class()`, and `#ms-app` is the SPA's mount point.
 *
 * ONE element owns the viewport height, and it is this one — not the screens.
 * Every studio screen used to size itself, which meant every studio screen
 * could forget to, and four of the seven did: `.ms-upload`, `.ms-feed` and
 * `.ms-editor` carried `min-height: 100dvh` while `.ms-tray`, `.ms-wall`,
 * `.ms-store-setup` and `.ms-dashboard` were plain content-height boxes. Since
 * every one of those four renders a short loading skeleton before its data
 * arrives, the page collapsed to a few hundred pixels on entry to the screen
 * and sprang back when the read landed — reported, accurately, as the screen
 * shrinking on transitions.
 *
 * A rule per screen would have fixed the four. This fixes the eighth screen
 * too, the one nobody has written yet: a screen cannot opt out of a height it
 * never sets, and a skeleton is as tall as the finished screen because neither
 * of them is what decides.
 *
 * `min-height`, not `height`: the feed with forty blanks in it is taller than
 * the viewport and must still scroll. The floor is what was missing, never a
 * ceiling.
 *
 * Scoped to the canvas body rather than written bare, for two readers. The
 * shop's `#ms-app` sits inside the theme's header and footer and must NOT
 * claim the whole viewport (studio.css never loads there, so this is belt and
 * braces); and Storybook loads every stylesheet at once over one shared
 * `#ms-app`, where a bare rule would quietly restyle the shop's stories.
 */
body.ms-app-canvas {
  margin: 0;
}

body.ms-app-canvas #ms-app {
  display: flex;
  flex-direction: column;
  /* 100vh first as the fallback for browsers without dvh, exactly as the
     per-screen rules this replaces did: 100vh is measured against the largest
     possible mobile viewport (toolbar collapsed), 100dvh against the visible
     one. */
  min-height: 100vh;
  min-height: 100dvh;
}

/*
 * The screen fills the shell. `min-height: 0` because a flex item defaults to
 * `min-height: auto`, which refuses to shrink below its content — the editor
 * sheet's own scroll box depends on being allowed to.
 */
body.ms-app-canvas #ms-app > * {
  flex: 1 1 auto;
  min-height: 0;
}

/*
 * The centered screens have to ask for their own width, because the idiom they
 * use to center themselves is the thing that stops them getting one.
 *
 * `max-width: <cap>; margin: 0 auto` is the correct recipe for a block in
 * normal flow, and every screen below uses it. `#ms-app` is a flex column,
 * though, and these are its flex items -- so `auto` on the CROSS axis is not
 * "share the leftover equally", it is "absorb all the leftover", and a flex
 * item with an auto cross-axis margin is explicitly excluded from
 * `align-items: stretch`. The item therefore sized to its content and the
 * auto margins centered that, which is why the cap above it never applied:
 * measured on the editor split at a 1920px viewport, the grid came out 476px
 * wide with `grid-template-columns` resolving to `380px 24px` -- the `1fr`
 * editor column collapsed to the width of a spinner, with 722px of margin
 * either side of the whole thing.
 *
 * So the caps in this file were never the reason the studio looked narrow on
 * a desktop; they were never reached. Raising them is still right, but this
 * is the rule that makes them mean anything.
 *
 * `width: 100%` restores a definite cross-axis size, which the `max-width`
 * then clamps and the auto margins center -- the behavior the original
 * recipe intended. `box-sizing: border-box` comes with it and is not
 * optional: these all carry `padding: 0 var(--ms-gutter)` or similar, and
 * under the inherited `content-box` a 100% width plus 48px of padding
 * overflows the viewport by exactly that padding at every width below the
 * cap -- trading a too-narrow desktop for a horizontally scrolling tablet.
 *
 * Listed by class rather than applied to `#ms-app > *` because it is only
 * true of the screens that center themselves. `.ms-upload` and `.ms-feed`
 * are full-bleed flex columns that center their CHILDREN instead, take no
 * auto margin, and stretch correctly already.
 */
.ms-studio--split,
.ms-tray,
.ms-store-setup,
.ms-dashboard {
  width: 100%;
  box-sizing: border-box;
}

.ms-studio__rail,
.ms-studio__main {
  min-width: 0;
}

/* §5.4: above the breakpoint the feed collapses to a persistent left rail. */
@media (min-width: 780px) {
  .ms-studio--split {
    display: grid;
    grid-template-columns: minmax(240px, 320px) 1fr;
    /* One row, filling the split's own height rather than sizing to the
       taller of rail and editor. That is what turns `.ms-editor`'s
       `height: 100%` into a real number on this layout: the shell makes the
       split a definite height, this hands that height to the row, and the
       row hands it to `.ms-studio__main`. Without it the row is `auto`, the
       percentage has nothing to resolve against, and the editor collapses to
       its content again -- the desktop half of the same bug. */
    grid-template-rows: 1fr;
    /* `1fr` only constrains a row when its grid has a definite height. The
       app shell intentionally has a *minimum* viewport height so feed pages
       can grow, but that leaves this editor workspace indefinite: a tall
       right-hand sheet then expands the row and takes the design overview
       along with it. This particular screen is a viewport workspace; its rail
       and sheet already own their internal scrolling, so make that ceiling
       explicit here. */
    height: 100vh;
    height: 100dvh;
    max-height: 100vh;
    max-height: 100dvh;
    gap: var(--rdp-ui-s-lg);
    /* The workspace cap, not the reading one: this row is a feed rail beside
       a garment canvas, and neither is prose. See --ms-container-wide in
       frontend.css for the split between the two measures. */
    max-width: var(--ms-container-wide);
    margin: 0 auto;
    padding: 0 var(--ms-gutter);
  }

  .ms-studio__rail {
    position: sticky;
    top: var(--rdp-ui-s-md);
    align-self: start;
    max-height: calc(100vh - var(--rdp-ui-s-xl));
    overflow-y: auto;
  }

  /* Grid item, default `min-height: auto` -- same trap `.ms-editor__stage`
     and `.ms-editor__sheet` are already fixed against (see their own
     comments in the desktop `.ms-editor` block below), one level up: without
     this, `.ms-editor`'s own content-based min height (not its `height:
     100%`, which intrinsic sizing ignores) sets this item's automatic
     minimum, which can force the split's `1fr` row -- and the whole
     page -- taller than the viewport instead of letting `.ms-editor`'s
     internal stage/sheet scroll on their own. That's what turned "the
     sidebar can't scroll independently" into "the canvas is the same
     height as the sidebar" and, on a still-empty text layer's tall panel,
     into "the new text landed above the garment": the canvas, centered in
     a stage that grew along with the inflated row, just sat lower on a
     taller page. */
  .ms-studio__main {
    min-height: 0;
  }
}

/* Text fields ---------------------------------------------------------
 *
 * One treatment for every plain `<input>` this app draws itself, and one
 * selector shape that survives the site theme.
 *
 * The studio renders on the FRONTEND, inside GeneratePress, which ships
 * `input[type="text"],input[type="email"],…{border:1px solid;border-radius:0;
 * padding:10px 15px}` in its own sheet and prints `{background-color;
 * border-color;color}` for the same selector list again as customizer CSS on
 * `generate-style` -- enqueued AFTER this plugin's stylesheet. Those
 * selectors score (0,1,1), which is exactly what a plain `.ms-wall input`
 * scores, so the theme won every one of those ties on source order: the
 * account, markup, campaign and invite fields were all wearing the theme's
 * form colors, and on this site that is a field with no visible edge at all.
 *
 * `input[type]:not([type="file"])` scores (0,2,1) and settles it with no
 * `!important` and no id -- an id here would outrank every `.ms-*` rule in
 * this file, the same trap shop.css's shield comment describes. The `:not()`
 * keeps the visually hidden file inputs (`.ms-wall__logo-input`) out of it.
 *
 * Every property the theme sets is restated here, not just the border:
 * leaving `background` to the theme is half of why these fields vanished.
 * Anything a placement needs differently (the tray's shorter, bolder markup
 * field) overrides below at the same weight, later in source order.
 */
:where(
  .ms-wall,
  .ms-tray__markup,
  .ms-manage__campaign,
  .ms-people__invite-form
) input[type]:not([type="file"]) {
  min-height: var(--rdp-ui-s-2xl);
  padding: 0 var(--rdp-ui-s-12);
  /* Not --rdp-ui-hairline: that's grey-200, tuned for a divider against a
   * filled card, and reads as no border at all around plain white -- an
   * input needs a stronger edge than a hairline gives it. */
  border: 1px solid var(--rdp-ui-grey-400);
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-white);
  color: var(--rdp-ui-ink);
  font: 400 16px / 1.2 var(--rdp-ui-font-body);
}

:where(
    .ms-wall,
    .ms-tray__markup,
    .ms-manage__campaign,
    .ms-people__invite-form
  )
  input[type]:not([type="file"]):focus-visible {
  outline: none;
  border-color: var(--rdp-ui-blue-400);
  box-shadow: 0 0 0 3px var(--rdp-ui-blue-100);
}

/* Disabled is a real state on these forms -- every one of them disables its
   fields while the request is in flight. */
:where(
  .ms-wall,
  .ms-tray__markup,
  .ms-manage__campaign,
  .ms-people__invite-form
) input[type]:not([type="file"]):disabled {
  background: var(--rdp-ui-grey-100);
  color: var(--rdp-ui-grey-600);
  cursor: not-allowed;
}

/* Upload -------------------------------------------------------------- */

/* Full-bleed shell: the screen itself spans the viewport, but its content
   stays a centered, readable column capped at --ms-container — same recipe
   as the rest of the wizard, just moved from the wrapper onto its children.
   position/overflow are new: they contain .ms-upload__wash below. */
.ms-upload {
  position: relative;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* The steps column made this taller than the old bare drop zone, so the
     flex-start default left a slab of dead space under a short viewport's
     fold. Centering keeps the screen composed rather than top-pinned. */
  justify-content: center;
  padding: var(--rdp-ui-s-2xl) var(--ms-gutter);
  /* No viewport height here any more -- `body.ms-app-canvas #ms-app` at the
     top of this file owns it for every screen. `justify-content: center`
     still needs a tall box to center inside, and now it reliably gets one. */
}

.ms-upload__intro,
.ms-upload__hero {
  width: 100%;
  max-width: var(--ms-container);
}

/* .ms-overline is a shared @rdp/ui-era utility (frontend.css) with no layout
   opinion of its own; scoped here rather than widened globally. */
.ms-upload__intro > .ms-overline {
  margin: 0 0 var(--rdp-ui-s-sm);
}

.ms-upload__lede {
  color: var(--rdp-ui-grey-600);
  margin: 0 0 var(--rdp-ui-s-xl);
  max-width: 46ch;
}

/* Guide §6: "a gradient of screen-print dots, in gray, behind hero blocks
   or as a fade." One quiet corner wash, contained by .ms-upload's own
   overflow. z-index: -1 keeps it under every normal-flow child rather than
   merely earlier in paint order. Static — this screen spends no motion
   budget on decoration. */
.ms-upload__wash {
  position: absolute;
  z-index: -1;
  top: -60px;
  right: -60px;
  width: 320px;
  height: 320px;
  pointer-events: none;
  background-image: radial-gradient(
    var(--rdp-ui-grey-400) 1.4px,
    transparent 1.4px
  );
  background-size: 13px 13px;
  -webkit-mask-image: radial-gradient(circle at 75% 25%, #000, transparent 65%);
  mask-image: radial-gradient(circle at 75% 25%, #000, transparent 65%);
  opacity: 0.4;
}

/* The control and the explanation, side by side once there's room. Below the
   breakpoint they stack in DOM order — drop zone first, because someone who
   already knows what this is shouldn't have to scroll past an explanation to
   reach the thing that does it. */
.ms-upload__hero {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-2xl);
}

@media (min-width: 780px) {
  .ms-upload__hero {
    flex-direction: row;
    align-items: flex-start;
    gap: var(--rdp-ui-s-4xl);
  }

  .ms-upload__form,
  .ms-upload__next {
    flex: 1 1 0;
    min-width: 0;
  }
}

.ms-upload__zone {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Generous, but still a 44px-plus target at the smallest phone width. */
  min-height: 200px;
  padding: var(--rdp-ui-s-lg);
  border: 1.5px dashed var(--rdp-ui-grey-200);
  border-radius: var(--rdp-ui-r-lg);
  background: var(--rdp-ui-paper);
  color: var(--rdp-ui-blue);
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--rdp-ui-dur-quick) var(--rdp-ui-ease-snap);
}

.ms-upload__zone:hover {
  border-color: var(--rdp-ui-blue);
}

/* In flight: red is the "this is the important thing happening right now"
   accent (guide §2), and solid rather than dashed the way a screen sits
   flush once it's actually printing rather than just lined up. */
.ms-upload__form--busy .ms-upload__zone {
  border-style: solid;
  border-color: var(--rdp-ui-red);
}

/* Visually hidden, never display:none — the label targets it and a keyboard
   user must still be able to focus it. Placed before .ms-upload__zone in the
   markup (not just this stylesheet) so the +.ms-upload__zone rule below can
   actually match — it can't reach a sibling that comes before it in the DOM. */
.ms-upload__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.ms-upload__input:focus-visible + .ms-upload__zone,
.ms-upload__zone:focus-within {
  outline: none;
  box-shadow: 0 0 0 3px var(--rdp-ui-blue-100);
}

.ms-upload__hint {
  margin: var(--rdp-ui-s-sm) 0 0;
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

.ms-upload__rejection {
  margin-top: var(--rdp-ui-s-md);
  padding: var(--rdp-ui-s-md);
  border-radius: var(--rdp-ui-r-md);
  background: var(--rdp-ui-red-100);
}

.ms-upload__rejection-file {
  margin: 0;
  font-weight: 600;
}

.ms-upload__rejection-why {
  margin: var(--rdp-ui-s-xs) 0 0;
  color: var(--rdp-ui-red-700);
}

.ms-upload__rejection-next {
  margin: var(--rdp-ui-s-xs) 0 0;
  color: var(--rdp-ui-grey-600);
}

/* The three steps. Deliberately not an h2: frontend.css sets every heading in
   Anton uppercase, which is right for the one h1 on this screen and would
   shout over it here — the same call .ms-overview__selling-head h3 makes for
   the same reason. Gray rather than the red .ms-overline above it, so the
   screen keeps exactly one red label. */
.ms-upload__next-title {
  margin: 0 0 var(--rdp-ui-s-lg);
  font-size: var(--ms-type-caption);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--rdp-ui-grey-600);
}

/* The 48px left padding is the marker gutter: each step's number is pulled
   back out into it, so the copy keeps a single flush left edge and the
   numbers sit clear of it rather than on top of it. */
.ms-upload__steps {
  counter-reset: ms-upload-step;
  list-style: none;
  margin: 0;
  padding: 0 0 0 var(--rdp-ui-s-2xl);
}

.ms-upload__step {
  position: relative;
  padding-bottom: var(--rdp-ui-s-xl);
}

.ms-upload__step:last-child {
  padding-bottom: 0;
}

/* Guide §6's stitch line — a dashed rule, the embroidery nod — drawn per
   step rather than once down the whole list, so it runs from each marker to
   the next one and stops. A single full-height rule overshoots past the last
   step and reads as a loose thread. `bottom: 0` is the li's padding box, and
   the padding below is exactly the gap to the next marker, so the line lands
   on it without either end needing a magic number.

   In blue-400 rather than the guide's own --blue: a vertical run of 3px
   dashes at full saturation reads far heavier than the short horizontal
   divider §6 illustrates. */
.ms-upload__step:not(:last-child)::after {
  content: "";
  position: absolute;
  left: -33.5px;
  top: 36px;
  bottom: 0;
  border-left: 3px dashed var(--rdp-ui-blue-400);
  opacity: 0.5;
}

/* The number is generated, not written into the markup, so it can never
   disagree with the list order. -48px pulls it into the gutter the ol's own
   padding opened up; the 32px box then centers on -32px, which is what the
   stitch line above is drawn against. */
.ms-upload__step::before {
  counter-increment: ms-upload-step;
  content: "0" counter(ms-upload-step);
  position: absolute;
  left: calc(-1 * var(--rdp-ui-s-2xl));
  top: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: 1.5px solid var(--rdp-ui-grey-200);
  border-radius: 50%;
  background: var(--rdp-ui-white);
  font-size: var(--ms-type-caption);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--rdp-ui-grey-600);
}

/* You are here. The one red fill on this screen after the overline — guide
   §2 rations red for exactly this, the single "key highlight". */
.ms-upload__step--now::before {
  border-color: var(--rdp-ui-red);
  background: var(--rdp-ui-red);
  color: var(--rdp-ui-white);
}

.ms-upload__step-name {
  margin: 0;
  padding-top: 5px;
  font-weight: 700;
  line-height: 1.35;
}

.ms-upload__step-note {
  margin: var(--rdp-ui-s-xs) 0 0;
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
  max-width: 42ch;
}

/* The steps ahead sit back a step from the one you're on, without going so
   pale they stop being readable — gray-600 body copy is still the guide's
   own muted-text token, not a washed-out tint. */
.ms-upload__step:not(.ms-upload__step--now) .ms-upload__step-name {
  color: var(--rdp-ui-grey-600);
  font-weight: 600;
}

@media (max-width: 780px) {
  .ms-upload__wash {
    width: 200px;
    height: 200px;
  }
}

@media (prefers-reduced-motion: reduce) {
  .ms-upload__zone {
    transition-duration: 0.001ms;
  }
}

/* Feed ---------------------------------------------------------------- */

/* Full-bleed shell, same recipe as .ms-upload above: the wrapper spans the
   viewport (from `#ms-app`, not from a rule of its own -- see the shell block
   at the top of this file), the content column stays capped and centered. */
.ms-feed {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Bottom padding clears the pinned tray bar plus the home indicator. */
  padding: var(--rdp-ui-s-lg) var(--ms-gutter)
    calc(var(--rdp-ui-s-3xl) + env(safe-area-inset-bottom));
}

/* The workspace cap: a wall of garment tiles is the one thing in the studio
   that gets strictly better with more room, and the heading and filter row
   are capped with it so the three stay left-aligned to the same edge. */
.ms-feed > h1,
.ms-feed__catalog,
.ms-feed__grid {
  width: 100%;
  max-width: var(--ms-container-wide);
}

.ms-feed__catalog {
  display: grid;
  gap: var(--rdp-ui-s-lg);
  align-items: start;
}

.ms-feed__sidebar {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-lg);
  padding: var(--rdp-ui-s-lg);
  border: var(--rdp-ui-hairline);
  border-radius: var(--rdp-ui-r-lg);
  background: #fff;
}

.ms-feed__sidebar h2,
.ms-feed__filter-group p,
.ms-feed__search span {
  margin: 0;
  font-weight: 700;
}

.ms-feed__sidebar h2 {
  font-size: 1.125rem;
}

.ms-feed__search {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-xs, 0.375rem);
}

.ms-feed__search input {
  /* Same box-sizing gap as the feed cards: content-box added this input's
     padding and border outside its declared width, overflowing the
     sidebar's own bounds instead of fitting inside them. */
  box-sizing: border-box;
  width: 100%;
  min-height: 2.75rem;
  padding: 0 var(--rdp-ui-s-sm);
  border: var(--rdp-ui-hairline);
  border-radius: var(--rdp-ui-r-md);
  /* Not `font: inherit` -- that pulled in the ~16px body size, which the
     13-15rem sidebar column (minus its own padding) isn't wide enough to
     show "Shirts, jackets, pants…" in without clipping it. --ms-type-small
     is what the rest of this sidebar's supporting text already uses. */
  font: 400 var(--ms-type-small) / 1.4 var(--rdp-ui-font-body);
}

.ms-feed__filter-group {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--rdp-ui-s-xs, 0.375rem);
}

/*
 * Ghost until chosen, filled once picked. `Chip`'s own resting look (white
 * fill, hairline border) reads as another button sitting in the sidebar;
 * these are filters, so an idle one should sit flush with the page and only
 * the active pick get a fill -- the same "chosen" story `Chip`'s
 * `aria-pressed` already tells for the placement chips in the editor.
 *
 * Keyed off a `className` passed straight to `<Chip>` (`FeedScreen.jsx`),
 * not `.chip` -- that selector never matched anything: `Chip` renders its
 * own CSS-module class (`rdp-chip-*`), which carries a build hash and has
 * never been named `.chip`, so this rule sat dead since the day it was
 * written. Restated unlayered too, following `.ms-editor__chip` above and
 * `.ms-feed__cell > button` below: `Chip`'s hashed class lives inside
 * `@layer rdp-ui`, and a bare `button` in the theme's stylesheet beats
 * anything layered regardless of specificity.
 */
.ms-feed__chip {
  justify-content: flex-start;
  background: transparent;
  border-color: transparent;
  color: var(--rdp-ui-grey-600);
}

.ms-feed__chip:hover:not([aria-pressed="true"]),
.ms-feed__chip:focus-visible:not([aria-pressed="true"]) {
  border-color: var(--rdp-ui-grey-400);
  color: var(--rdp-ui-ink);
}

.ms-feed__chip[aria-pressed="true"] {
  background: var(--rdp-ui-blue);
  border-color: var(--rdp-ui-blue);
  color: var(--rdp-ui-white);
}

.ms-feed__results {
  min-width: 0;
}

@media (min-width: 780px) {
  .ms-feed__catalog {
    grid-template-columns: minmax(13rem, 15rem) minmax(0, 1fr);
  }

  .ms-feed__sidebar {
    position: sticky;
    top: var(--rdp-ui-s-lg);
  }
}

.ms-feed__filters {
  display: flex;
  gap: var(--rdp-ui-s-sm);
  overflow-x: auto;
  padding-bottom: var(--rdp-ui-s-sm);
  margin-bottom: var(--rdp-ui-s-md);
  -webkit-overflow-scrolling: touch;
}

.ms-feed--compact .ms-feed__catalog {
  display: block;
}

.ms-feed--compact .ms-feed__filters {
  align-items: center;
}

.ms-feed--compact .ms-feed__search {
  min-width: 12rem;
}

/* The desktop rail is one column: it sits beside the editor, not instead of it.
   Targets the grid itself, not .ms-feed--compact -- that class lands on the
   outer flex wrapper (.ms-feed), which has no grid-template-columns to
   override, so this rule used to be dead. */
.ms-feed--compact .ms-feed__grid {
  grid-template-columns: 1fr;
}

.ms-feed__media {
  position: relative;
  display: block;
  width: 100%;
  height: 100%;
}

.ms-feed__media img {
  width: 100%;
  height: 100%;
  -o-object-fit: contain;
     object-fit: contain;
}

/* While the tile's preview render is still generating.
 *
 * A pulsing scrim over the photo, deliberately not @rdp/ui's Skeleton bar:
 * a skeleton is the vocabulary for "there is no content here yet", and there
 * IS content here — the real garment photo, which spec §6.2 requires stay
 * visible ("never an empty box"). A bar laid over a real photo reads as a
 * rendering fault; a slow pulse reads as work in progress, which is what this
 * actually is. */
.ms-feed__pulse {
  position: absolute;
  inset: 0;
  display: block;
  background: color-mix(in srgb, var(--rdp-ui-white) 45%, transparent);
  /* A held opacity scrim, not a moving gradient band: the old diagonal sweep
   * read as harsh/fast and, because its background-position keyframe had to
   * snap from 100% back to -100% on every loop restart, popped visibly at
   * the tile's corners each time around. Animating opacity on a fixed scrim
   * has no position to snap, so nothing to glitch, and ease-in-out reads as
   * a calm breathing pulse rather than a streak crossing the photo. */
  animation: ms-feed-pulse 1800ms ease-in-out infinite;
}

@keyframes ms-feed-pulse {
  0%,
  100% {
    opacity: 0;
  }
  50% {
    opacity: 1;
  }
}

/* The pulse is a courtesy, never information — the photo underneath is the
   content either way, so a held mid-opacity scrim says the same thing. */
@media (prefers-reduced-motion: reduce) {
  .ms-feed__pulse {
    animation: none;
    opacity: 0.5;
  }
}

.ms-tray-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 900;
  justify-content: space-between;
  box-shadow: none;
}

.ms-tray-bar__count {
  font-weight: 600;
}

/* The checkbox that marks a blank "design later" instead of choosing it —
   a sibling of the tile's own button (see BlankTile.jsx), positioned over
   its corner so the two hit targets never overlap. */
.ms-feed__cell {
  position: relative;
  /* Plays once, automatically, the moment a card actually mounts -- the
     initial catalog load, or a filter widening back to include it. No JS:
     a CSS animation (unlike a transition) runs on its own the instant an
     element is inserted into the DOM, so entrance needs nothing beyond
     this. `both` holds the resting values before frame one and after the
     last one, so the card never flashes its un-animated end state first. */
  animation: ms-feed-card-in var(--rdp-ui-dur-base) var(--rdp-ui-ease-settle)
    both;
}

@keyframes ms-feed-card-in {
  from {
    opacity: 0;
    transform: translateY(6px) scale(0.98);
  }
}

/*
 * The DOM can't animate its own removal -- by the time a `transition` here
 * would run, React has already dropped the node. `useAnimatedList`
 * (`useAnimatedList.js`) is the other half of this deal: it keeps a
 * filtered-out card mounted with this class for exactly one
 * `--rdp-ui-dur-quick` before actually removing it, which is what gives a
 * `transition` (not `animation` -- this state is held, not just entered)
 * something to run.
 */
.ms-feed__cell--leaving {
  pointer-events: none;
  transition:
    opacity var(--rdp-ui-dur-quick) var(--rdp-ui-ease-press),
    transform var(--rdp-ui-dur-quick) var(--rdp-ui-ease-press);
  opacity: 0;
  transform: scale(0.96);
}

/**
 * The card's own surface, and why this rule has to exist at all.
 *
 * `Tile` renders as a `<button>` whenever it has a click handler, and every
 * style @rdp/ui ships lives inside `@layer rdp-ui`. A rule in a named
 * cascade layer loses to an UNLAYERED rule at any specificity, so the site
 * theme's plain `button { … }` beats `Tile`'s own `background: white`
 * outright — which is why these cards came up in the theme's primary red
 * with nothing anywhere in this plugin or in @rdp/ui declaring red. Nothing
 * was broken; the layer simply handed the theme the argument.
 *
 * These rules are unlayered too, so they win it back. Targeted through
 * `.ms-feed__cell > button` rather than @rdp/ui's own class: those names
 * carry a build hash (`.rdp-tile-l34Yb`) that changes whenever the package
 * is rebuilt, and a selector written against one would silently stop
 * matching on the next upgrade — taking the red straight back.
 */
.ms-feed__cell > button {
  /* Neither this file nor @rdp/ui resets box-sizing globally, so the
     button's inherited content-box adds its border outside the `width:
     100%` Tile itself declares -- the card rendered ~2px wider than its
     grid track, overlapping the next column's gap and throwing every
     row's borders out of alignment. border-box keeps the border inside
     the declared size instead. */
  box-sizing: border-box;
  /* A small frame around Tile's own media block, not just the label strip
     below it: Tile lays media and label flush to the button's edges with no
     gap of its own, so without this the halftone below would only ever show
     in that thin text strip. Padding gives it a visible print-shop-swatch
     border on every side instead. */
  padding: var(--rdp-ui-s-xs);
  /* A halftone dot grid, the print-shop texture this product's whole point
     is putting ink on shirts -- a plain flat grey card behind the photo felt
     like an admin table, not a merch tool. Longhand rather than the
     `background` shorthand: the hover/focus/active rule below only wants to
     change the border color, and the shorthand would reset this image back
     to `none` if either rule used it. */
  background-color: var(--rdp-ui-grey-100);
  background-image: radial-gradient(
    var(--rdp-ui-grey-200) 1px,
    transparent 1.2px
  );
  background-size: 6px 6px;
  color: var(--rdp-ui-ink);
  border: var(--rdp-ui-hairline);
  transition: border-color var(--rdp-ui-dur-quick) var(--rdp-ui-ease-snap);
}

/*
 * The same argument one state further in. The rule above only answers the
 * theme's plain `button { … }`; the theme styles `button:hover` (and the
 * pressed/focused states beside it) as well, and `button:hover` outranks a
 * bare `.ms-feed__cell > button` — so a card sat gray and went red the
 * moment a coach pointed at it.
 *
 * Naming the states here rather than raising specificity on the resting
 * rule: this is where the hover cue itself belongs anyway. Border to ink,
 * which is what a card does everywhere else in this product
 * (`.ms-grid__cell:hover`, shop.css) and the everyday motion register of
 * guide §8.
 *
 * `background-color` has to be restated here too, not just border-color —
 * the resting rule above and the theme's `button:hover` tie on specificity
 * (both a bare element selector plus one class/pseudo-class), so which one
 * actually painted the card's hover background came down to stylesheet
 * load order, and the theme's own color was winning that coin flip. Naming
 * it here, at higher specificity than either, ends the tie instead of
 * hoping to win it. One step darker than the resting grey-100, not ink —
 * ink is the selection/focus cue elsewhere on this card, and a hover using
 * it too would blur the two.
 */
.ms-feed__cell > button:hover,
.ms-feed__cell > button:focus,
.ms-feed__cell > button:active {
  background-color: var(--rdp-ui-grey-200);
  color: var(--rdp-ui-ink);
  border-color: var(--rdp-ui-ink);
}

/*
 * `.selected` is @rdp/ui's own cue for a chosen tile, but it ships as a
 * CSS-module class -- a build-hashed name (`.rdp-tile-l34Yb` style) that
 * changes on every dependency rebuild, the same trap the card-surface
 * comment above already answers for `background`. `aria-pressed` is the
 * attribute Tile sets from that same `selected` prop, so it is a stable hook
 * with no hash to go stale.
 *
 * It also has to repeat here, not just live as a rule against `.selected`:
 * the resting rule above is unlayered and always wins the theme-button
 * fight, so on its own it was ALSO overriding @rdp/ui's `.selected` border
 * every time -- a coach picking a blank got no visible change at all,
 * checkbox tick aside. Red matches the checkbox's own checked color, the
 * one place this screen already marks a selection.
 */
.ms-feed__cell > button[aria-pressed="true"] {
  border: 1.5px solid var(--rdp-ui-red);
}

.ms-feed__cell > button[aria-pressed="true"]:hover,
.ms-feed__cell > button[aria-pressed="true"]:focus,
.ms-feed__cell > button[aria-pressed="true"]:active {
  border-color: var(--rdp-ui-red);
}

/* Motion is the cue's courtesy, never the cue — the border still lands,
   and a card that skips its entrance/exit motion still arrives and leaves
   at the right time, just without the animated trip. */
@media (prefers-reduced-motion: reduce) {
  .ms-feed__cell > button {
    transition-duration: 0.001ms;
  }

  .ms-feed__cell {
    animation: none;
  }

  .ms-feed__cell--leaving {
    transition-duration: 0.001ms;
  }
}

/* The label sits last inside the button; keep its text clear of the
   checkbox now parked in that same corner. */
.ms-feed__cell > button > span + span {
  padding-right: calc(var(--rdp-ui-s-lg) + var(--rdp-ui-s-sm));
}

/* Marks a tile in the compact rail whose blank already has a design for this
   artwork — the coach's own "your blanks" pile, pinned above the catalog by
   FeedScreen. Top-left, clear of the checkbox parked bottom-right. */
.ms-feed__chosen-badge {
  position: absolute;
  top: var(--rdp-ui-s-sm);
  left: var(--rdp-ui-s-sm);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--rdp-ui-s-lg);
  height: var(--rdp-ui-s-lg);
  border-radius: 50%;
  background: var(--rdp-ui-ink);
  color: var(--rdp-ui-white);
  font-size: var(--ms-type-small);
  line-height: 1;
}

/* Heading over the rail's "Your blanks" / "All blanks" split. Plain text, not
   a Chip -- nothing here is clickable, it is just telling the two grids apart. */
.ms-feed__section-label {
  margin: 0 0 var(--rdp-ui-s-sm);
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
  font-weight: 600;
}

/* A real `<input type="checkbox">` repainted, not a `<div>` standing in for
 * one: `appearance: none` drops only the browser's own box-and-tick paint,
 * everything else about it — focus, `:checked`, keyboard toggling, the
 * screen reader role — stays native. The box and tick below are drawn with
 * plain CSS rather than a background-image so no asset ships for two shapes
 * a couple of declarations already cover. */
.ms-feed__select {
  position: absolute;
  /* Bottom right, beside the title, rather than over the photo: the
     photo is the thing being chosen and a control sitting on top of it
     covers the very detail the coach is comparing. */
  bottom: var(--rdp-ui-s-sm);
  right: var(--rdp-ui-s-sm);
  z-index: 1;
  width: var(--rdp-ui-s-lg);
  height: var(--rdp-ui-s-lg);
  margin: 0;
  /* Content-box would add the 1.5px border outside this box, growing it to
     27px against an inset of only 8px into a 10px-radius corner -- enough
     to poke past the card's curve. border-box keeps the checkbox's whole
     rendered size, border included, at the s-lg it's meant to be. */
  box-sizing: border-box;
  display: grid;
  place-content: center;
  -moz-appearance: none;
       appearance: none;
  -webkit-appearance: none;
  border-radius: var(--rdp-ui-r-sm);
  border: var(--rdp-ui-border-emphasis);
  background: var(--rdp-ui-white);
  cursor: pointer;
  transition:
    background var(--rdp-ui-dur-quick) var(--rdp-ui-ease-snap),
    border-color var(--rdp-ui-dur-quick) var(--rdp-ui-ease-snap);
}

.ms-feed__select:hover {
  border-color: var(--rdp-ui-red);
}

/* Same functional focus ring as every other control in this package (see
   `Tile`'s own `:focus-visible` in @rdp/ui) — brand color is for the checked
   state below, not for accessibility cues. */
.ms-feed__select:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 2px var(--rdp-ui-white),
    0 0 0 5px var(--rdp-ui-blue-400);
}

.ms-feed__select:checked {
  background: var(--rdp-ui-red);
  border-color: var(--rdp-ui-red);
}

/* The tick: a square's bottom-right corner, rotated into a check mark.
   Scaled from 0 rather than toggled with `display`, so it animates in. */
.ms-feed__select::before {
  content: "";
  width: 7px;
  height: 12px;
  margin-top: -2px;
  border: solid var(--rdp-ui-white);
  border-width: 0 2px 2px 0;
  transform: rotate(45deg) scale(0);
  transition: transform var(--rdp-ui-dur-instant) var(--rdp-ui-ease-settle);
}

.ms-feed__select:checked::before {
  transform: rotate(45deg) scale(1);
}

@media (prefers-reduced-motion: reduce) {
  .ms-feed__select,
  .ms-feed__select::before {
    transition-duration: 0.001ms;
  }
}

/* The selection/resume bar shares one bottom-of-screen slot with the tray
   bar above — see FeedScreen's batchBar(). Full mode gets the same
   pinned-to-viewport treatment as the tray bar; the compact rail cannot,
   because a fixed-position bar there would cover the editor beside it, so
   it sticks to the bottom of the rail's own scroll area instead. */
.ms-feed__batch-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rdp-ui-s-sm);
}

.ms-feed:not(.ms-feed--compact) .ms-feed__batch-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 900;
  box-shadow: none;
}

.ms-feed--compact .ms-feed__batch-bar {
  position: sticky;
  bottom: 0;
  padding: var(--rdp-ui-s-sm);
  border-top: var(--rdp-ui-hairline);
  background: var(--rdp-ui-white);
}

.ms-feed__batch-count {
  font-weight: 600;
}

/* Editor -------------------------------------------------------------- */

.ms-editor {
  display: flex;
  flex-direction: column;
  /* The viewport height comes from `#ms-app` now (see the shell block at the
     top of this file). There is no theme footer below this screen any more
     either -- `/studio/*` renders app-canvas.php, which prints none. */
  background: var(--rdp-ui-white);
}

/*
 * The editor's own loading render (EditorScreen.jsx) is `.ms-editor` with a
 * lone `<Spinner />` inside it, and above 780px that parent is a two-column
 * grid with named areas. The spinner carries no `grid-area`, so it was
 * auto-placed into the first cell of the `auto`-height banner row: a small
 * spinner pinned to the top-left corner of an otherwise empty white page, at
 * every desktop width.
 *
 * Two classes rather than one, so this does not depend on sitting after the
 * `min-width: 780px` block that sets `display: grid` -- a rule whose
 * correctness rests on source order is a rule the next edit breaks.
 * `height: 100%` from that block still applies and is what gives the
 * centering something to center inside.
 */
.ms-editor.ms-editor--loading {
  display: flex;
  align-items: center;
  justify-content: center;
}

.ms-editor__banner {
  margin: 0;
  padding: var(--rdp-ui-s-sm) var(--ms-gutter);
  background: var(--rdp-ui-red-100);
  color: var(--rdp-ui-red-700);
  font-size: var(--ms-type-small);
}

/* The server refused the write, as opposed to the write not arriving. Both
   are red because both mean "your work is not landing", but this one is the
   coach's to act on rather than wait out, so it carries the extra weight
   that asks to be read rather than watched. */
.ms-editor__banner--stuck {
  border-left: 3px solid var(--rdp-ui-red-700);
  font-weight: 600;
}

/* The sheet's own rename control, first child of `.ms-editor__sheet` --
 * `DesignNameField`'s own input carries every visual property
 * (`.ms-design-name`); this just sets the title-sized type. No padding or
 * background of its own: `.ms-editor__sheet` already supplies both, the
 * same as every other row in it. */
.ms-editor__name {
  display: block;
  font: 600 18px / 1.2 var(--rdp-ui-font-body);
}

/*
 * The phone's stand-in for the desktop rail (see the "mobile pager" comment
 * on EditorScreen's JSX) -- a coach's own way back to the grid, plus (when
 * there's a batch left) browsing it one blank at a time instead of the grid
 * there is no room for. Hidden above 780px: the rail already covers this
 * there, same split as .ms-editor__sheet just below.
 */
.ms-editor__backbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rdp-ui-s-md);
  padding: var(--rdp-ui-s-xs) var(--ms-gutter);
  border-bottom: var(--rdp-ui-hairline);
  background: var(--rdp-ui-white);
}

@media (min-width: 780px) {
  .ms-editor__backbar {
    display: none;
  }
}

.ms-editor__backbar-link {
  border: none;
  background: none;
  padding: 0;
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
}

.ms-editor__pager {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-md);
}

.ms-editor__pager-chevron {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--rdp-ui-s-2xl);
  height: var(--rdp-ui-s-2xl);
  border: none;
  border-radius: var(--rdp-ui-r-sm);
  /* A rest-state pill, not just a hover one: with no border or shadow to
     find, these sat as dead space on a canvas with nothing else to touch
     until a finger landed on the right 48px square by luck. */
  background: var(--rdp-ui-grey-100);
  color: var(--rdp-ui-ink);
  font-size: var(--rdp-ui-s-lg);
  line-height: 1;
}

.ms-editor__pager-chevron:hover:not(:disabled) {
  background: var(--rdp-ui-grey-200);
}

.ms-editor__pager-chevron:disabled {
  color: var(--rdp-ui-grey-600);
  opacity: 0.5;
}

.ms-editor__pager-status {
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
}

.ms-editor__stage {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--rdp-ui-s-md);
  background: var(--rdp-ui-paper);
}

.ms-editor__canvas,
.ms-editor__mount {
  display: block;
  max-width: 100%;
  max-height: 56vh;
  margin: 0 auto;
}

.ms-editor__error {
  margin: var(--rdp-ui-s-sm) 0 0;
  color: var(--rdp-ui-red-700);
  font-size: var(--ms-type-small);
}

.ms-editor__sheet {
  display: flex;
  flex-direction: column;
  /* Condensed one token step (sm -> xs, md -> sm): pure inter-control
     spacing, not a touch target, so tightening it costs nothing but the
     whitespace itself. Horizontal padding stays --ms-gutter -- that is the
     page-wide edge gutter every screen shares, not editor-specific slack. */
  gap: var(--rdp-ui-s-xs);
  padding: var(--rdp-ui-s-sm) var(--ms-gutter)
    max(var(--rdp-ui-s-sm), env(safe-area-inset-bottom));
  border-top: var(--rdp-ui-hairline);
  background: var(--rdp-ui-white);
}

/**
 * The keyboard must never cover the garment (§6.3), which is the whole reason
 * the controls live in a sheet — so the sheet's height is a budget, not a
 * consequence. A2 adds four effect panels to it, and four of them open at once
 * would otherwise push the canvas off a phone entirely. Capped and scrolled
 * internally instead: the stage keeps at least 60vh whatever is expanded, and
 * `.ms-editor__canvas`'s own 56vh sits comfortably inside that.
 *
 * Not applied above 780px, where the same element is a full-height right-hand
 * column and has the room.
 */
@media (max-width: 779px) {
  .ms-editor__sheet {
    max-height: 40vh;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0.5rem;
  }

  /*
   * The scrollbar stays. It was hidden here, and it was the only thing on
   * screen saying that roughly half the sheet — including the primary action
   * and every effect panel — was below the fold. A hidden scrollbar on a
   * container that genuinely overflows is a missing affordance, not a clean
   * one; the sticky action row below is what makes the overflow tolerable, and
   * this is what admits it exists.
   */

  /* The cap only works if the children refuse to shrink.
   *
   * `.ms-editor__sheet` is a flex column, and a flex item's default
   * `flex-shrink: 1` lets it compress below its content when the container
   * runs out of room. So past the 40vh cap the rows collapsed — measured at
   * 9.8px against their real 48px — chip rows overlapping each other, and
   * `scrollHeight === clientHeight`, which means `overflow-y: auto` never had
   * anything to scroll. The sheet silently traded the scroll this block exists
   * to create for unreadable, sub-target-size controls.
   *
   * Found by the first Storybook story, at 390px, on the day it was written. */
  .ms-editor__sheet > * {
    flex-shrink: 0;
  }
}

/*
 * Undo/redo on one side, the save state on the other. Second child of the
 * sheet -- `.ms-editor__name` is first, so the design's own name is set
 * before the coach ever touches an item, and this status row is what a
 * coach's eye finds right under it, on a phone and at the top of the desktop
 * control column alike, when they want to know whether something took.
 */
.ms-editor__status {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rdp-ui-s-xs);
}

.ms-editor__history {
  display: flex;
  gap: var(--rdp-ui-s-xs);
}

/*
 * Was square (`min-width: 44px; padding: 0`) so the two glyphs read as a pair
 * of icons. They are words now — see EditorStatus.jsx for why the glyphs went
 * — so the width is the label's own plus padding, and only the 44px thumb
 * target every other control on this screen meets is kept.
 */
.ms-editor__history-button {
  min-height: 44px;
  font-size: var(--ms-type-small);
  line-height: 1;
}

/* Quiet by construction: this reports that nothing is wrong, and it updates on
   every keystroke. Anything louder would be a flicker in the corner of the eye
   for the entire session. */
.ms-editor__save-state {
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
  /* Reserves the row's height whether or not there are words in it, so that
     the first "Saving…" does not shove the placement chips down the screen. */
  min-height: 1em;
}

/* Every scrolling chip row is a `ChipRow`, which carries this class as well as
   its own. The six selectors that used to be listed here were the same rule
   repeated, and adding a seventh row meant remembering to extend the list. */
.ms-editor__chiprow {
  display: flex;
  /* Condensed sm -> xs: the gap between chips, not the chips' own size --
     each chip is still whatever @rdp/ui's Chip renders as, untouched. */
  gap: var(--rdp-ui-s-xs);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/*
 * The primary action, pinned to the bottom of the sheet's scroll box.
 *
 * Sticky on both layouts, for two different reasons that happen to want the
 * same rule. On a phone the sheet is a 40vh scroll container holding ~600px of
 * controls, so the last child is always off-screen. On a desktop the sheet is a
 * full-height column whose content usually ends around 250px, leaving the
 * action floating in the vertical middle with several hundred pixels of white
 * beneath it; `margin-top: auto` there pushes it to the foot of the column
 * where a primary action belongs.
 *
 * The white band is functional, not decorative: rows scroll underneath it, and
 * without an opaque backing they would show through the gaps around a
 * rounded button.
 */
.ms-editor__actions {
  position: sticky;
  bottom: 0;
  margin-top: auto;
  /* Clears the sheet's own bottom padding so the band reaches the edge, and
     keeps the home-indicator inset the base rule established. */
  padding-block: var(--rdp-ui-s-xs);
  background: var(--rdp-ui-white);
}

.ms-editor__done {
  width: 100%;
}

/* The color row's own header: its label, and the control that switches the
   row between showing a color and choosing which ones sell. On one line
   rather than stacked, because the sheet is a 40vh scroll box on a phone and
   a full-width row costs a chip row's worth of the height the canvas needs. */
.ms-editor__colorhead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rdp-ui-s-xs);
}

.ms-editor__colorheadactions {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-xs);
}

.ms-editor__colorlabel {
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
}

/* The placement chip row's own label, same small-caps-grey style as the
   color row's, above the row rather than beside it -- the chip row has no
   header-level control to share the line with. */
.ms-editor__placementlabel {
  display: block;
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
  margin-bottom: var(--rdp-ui-s-xs);
}

/* Which colorway of a split family this design is, while the CTA below
   reads "Next color →" -- see EditorSheet's splitQueueRemaining. */
.ms-editor__splitstatus {
  margin: 0;
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
}

.ms-editor__coloractions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--rdp-ui-s-xs);
  padding-top: var(--rdp-ui-s-xs);
}

/* Pushed left of the two buttons: the reason Done is dead belongs beside
   Done, not under the strip where it reads as a caption for the chips. */
.ms-editor__colornote {
  flex: 1 1 auto;
  margin: 0;
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
}

/* How many picks from a multi-blank batch are left, and the way out of
   finishing early — see EditorSheet's queueRemaining/onStopQueue. */
.ms-editor__queue {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rdp-ui-s-xs);
  padding: var(--rdp-ui-s-xs) 0;
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
}

/*
 * @rdp/ui ships every component's CSS inside a native `@layer rdp-ui` (see
 * the Tile fix above and the dashboard-link fix below for the same
 * mechanism) -- and a layered rule loses to an UNLAYERED one at any
 * specificity. The theme ships a plain, unlayered `button { … }` (with no
 * opinion of its own about `:disabled`), which is why Undo, Redo,
 * "Choose…", every placement/colorway chip and "Add to my store" all
 * rendered in one flat theme red no matter what `variant` they were given
 * -- Undo included, sitting fully red and looking pressable while
 * `disabled` was on it the whole time. Nothing about the variant system
 * was broken; the theme's rule was simply louder.
 *
 * Restated here, unlayered, keyed to classes this file controls -- not
 * @rdp/ui's build-hashed ones (`.rdp-primary-xwP6m` changes on every
 * package rebuild, and a selector written against one would silently stop
 * matching on the next upgrade).
 */
.ms-editor__history-button,
.ms-editor__colortoggle,
.ms-editor__colorcancel,
.ms-editor__stopqueue {
  background-color: transparent;
  color: var(--rdp-ui-ink);
  border-color: transparent;
}

.ms-editor__history-button:hover:not(:disabled),
.ms-editor__colortoggle:hover,
.ms-editor__colorcancel:hover,
.ms-editor__stopqueue:hover,
.ms-editor__history-button:focus-visible,
.ms-editor__colortoggle:focus-visible,
.ms-editor__colorcancel:focus-visible,
.ms-editor__stopqueue:focus-visible {
  /* `background-color` alone used to leave `color` to the cascade, where
     the theme's own unlayered `button:hover{color:#fff}` (one element type
     selector ahead of this rule's plain class) still won it -- grey
     background, white text. Restated here for the same reason the base
     rule above restates `color` at all. */
  color: var(--rdp-ui-ink);
  background-color: var(--rdp-ui-grey-100);
}

.ms-editor__history-button:disabled,
.ms-editor__history-button[aria-disabled="true"] {
  background-color: var(--rdp-ui-grey-100);
  color: var(--rdp-ui-grey-400);
}

.ms-editor__colordone {
  background-color: transparent;
  color: var(--rdp-ui-blue);
  border-color: var(--rdp-ui-grey-200);
}

.ms-editor__colordone:hover:not(:disabled) {
  background-color: var(--rdp-ui-grey-100);
}

.ms-editor__colordone:disabled,
.ms-editor__colordone[aria-disabled="true"] {
  background-color: var(--rdp-ui-grey-100);
  color: var(--rdp-ui-grey-400);
  border-color: transparent;
}

/* The one primary moment the brand rations to a single red fill (see
   EditorSheet.jsx's comment on the color-picker's Cancel/Done pair) --
   restated for the same reason as the rules above. */
.ms-editor__done {
  background-color: var(--rdp-ui-red);
  color: var(--rdp-ui-white);
  border-color: transparent;
}

.ms-editor__done:hover:not(:disabled) {
  background-color: var(--rdp-ui-red-700);
}

.ms-editor__done:disabled,
.ms-editor__done[aria-disabled="true"] {
  background-color: var(--rdp-ui-grey-100);
  color: var(--rdp-ui-grey-400);
}

/* The placement row's chips -- Chip's own selected/resting states, restated
   for the same reason as the buttons above. `[aria-pressed]` rather than a
   `.selected` class: it's what `Chip` actually toggles, and it survives
   @rdp/ui giving the class a new hash. Colorway rows below are plain
   buttons, not `Chip`, so this no longer applies to them. */
.ms-editor__chip {
  background-color: var(--rdp-ui-white);
  color: var(--rdp-ui-ink);
  border: var(--rdp-ui-hairline);
}

.ms-editor__chip:hover:not([aria-pressed="true"]) {
  background-color: var(--rdp-ui-grey-100);
}

.ms-editor__chip[aria-pressed="true"] {
  background-color: var(--rdp-ui-blue);
  color: var(--rdp-ui-white);
  border-color: var(--rdp-ui-blue);
}

/*
 * The colorway list: every colorway the blank offers, shown once, each with
 * a real photo (`ColorRow` in `EditorSheet.jsx`) instead of a text-only
 * chip. A vertical list rather than a `ChipRow` on purpose -- it scrolls
 * with the sheet itself instead of needing its own horizontal edge fades,
 * and a photo + name + toggle does not fit a chip's width on a phone.
 */
.ms-editor__colorlist {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  border: var(--rdp-ui-hairline);
  border-radius: 10px;
  overflow: hidden;
}

.ms-editor__colorrow {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-xs);
  padding: var(--rdp-ui-s-xs);
}

.ms-editor__colorrow + .ms-editor__colorrow {
  border-top: var(--rdp-ui-hairline);
}

/* The row's left-hand control -- swatch + name -- previews that colorway on
   the canvas. Restated unlayered for the same `@layer rdp-ui` reason as the
   buttons above; this one is a plain `<button>`, never a themed component,
   so there is no build-hashed class to key off in the first place. */
button.ms-editor__colorrowbutton {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-xs);
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border: none;
  padding: 0;
  text-align: left;
  color: var(--rdp-ui-ink);
}

.ms-editor__colorrowbutton:disabled {
  cursor: default;
}

/*
 * The same theme-button trap `.ms-feed__cell > button` answers further up:
 * the theme styles `button:hover` (and `:focus`/`:active` beside it) as a red
 * fill, and `button:hover` outranks the bare class rule above -- so pointing
 * at a color turned its swatch-and-name half into a red block, mid-list.
 *
 * The cue itself moves to the row rather than being restated on the button.
 * The row IS the item -- swatch, name and the for-sale toggle are one thing a
 * coach is pointing at -- and lighting only the button's half drew a box
 * around two thirds of it and left the control on the right outside.
 */
.ms-editor__colorrowbutton:hover,
.ms-editor__colorrowbutton:focus,
.ms-editor__colorrowbutton:active {
  background: transparent;
  color: var(--rdp-ui-ink);
}

.ms-editor__colorrow:hover {
  background-color: var(--rdp-ui-grey-100);
}

.ms-editor__colorswatch {
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  border-radius: 8px;
  overflow: hidden;
  border: var(--rdp-ui-hairline);
  background: var(--rdp-ui-grey-100);
}

.ms-editor__colorswatch img {
  display: block;
  width: 100%;
  height: 100%;
  -o-object-fit: cover;
     object-fit: cover;
}

/* Unowned/not-selected-for-split: the swatch is a real photo either way, but
   nothing is happening on this row right now. */
.ms-editor__colorrow[data-included="false"] .ms-editor__colorswatch {
  opacity: 0.45;
}

.ms-editor__colorrow[data-included="false"] .ms-editor__colorrowname,
.ms-editor__colorrow[data-included="false"] .ms-editor__colorrowmeta {
  color: var(--rdp-ui-grey-600);
}

.ms-editor__colorrowtext {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.ms-editor__colorrowname {
  font-size: var(--ms-type-small);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ms-editor__colorrowmeta {
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

/* The active preview, marked the same way the old chip strip marked its
   selected chip -- a filled ring rather than a background fill, since the
   row's own background still has to read as "for sale" vs. not. */
.ms-editor__colorrowbutton[aria-pressed="true"] .ms-editor__colorswatch {
  outline: 2px solid var(--rdp-ui-blue);
  outline-offset: 1px;
}

/* The row's right-hand control -- include/exclude. A native checkbox under
   the hood so it is keyboard- and screen-reader-operable for free; this is
   only ever its visual track. */
.ms-editor__toggle {
  position: relative;
  flex: 0 0 auto;
  display: inline-flex;
  width: 38px;
  height: 22px;
}

.ms-editor__toggle input {
  position: absolute;
  inset: 0;
  margin: 0;
  opacity: 0;
}

.ms-editor__toggle-track {
  position: absolute;
  inset: 0;
  border-radius: 999px;
  background: var(--rdp-ui-grey-200);
  pointer-events: none;
}

.ms-editor__toggle-track::after {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--rdp-ui-white);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
  transition: transform 0.15s;
}

.ms-editor__toggle input:checked + .ms-editor__toggle-track {
  background: var(--rdp-ui-red);
}

.ms-editor__toggle input:checked + .ms-editor__toggle-track::after {
  transform: translateX(16px);
}

.ms-editor__toggle input:focus-visible + .ms-editor__toggle-track {
  outline: 2px solid var(--rdp-ui-blue);
  outline-offset: 2px;
}

.ms-editor__toggle input:disabled + .ms-editor__toggle-track {
  opacity: 0.5;
}

/* Split mode's row: the whole row is one control (there is nothing to
   preview mid-split -- every candidate here is already owned), so it gets
   the chip's old selected treatment instead of the toggle's. Scoped to the
   `--split` list rather than `[data-included="true"]` alone -- that
   attribute also marks the actively-previewed OWNED row in browse mode,
   which gets the outline ring above instead, not this fill. */
.ms-editor__colorlist--split .ms-editor__colorrowbutton[aria-pressed="true"] {
  background: var(--rdp-ui-blue);
  color: var(--rdp-ui-white);
  border-radius: 8px;
  padding: var(--rdp-ui-s-xs);
  margin: calc(var(--rdp-ui-s-xs) * -1);
}

.ms-editor__colorlist--split
  .ms-editor__colorrowbutton[aria-pressed="true"]
  .ms-editor__colorrowmeta {
  color: var(--rdp-ui-white);
}

/*
 * The edge fade, on whichever side actually has more chips behind it.
 *
 * Was `.ms-editor__placements--overflow`, a right-hand fade switched on by
 * counting chips (>4) on one row out of six. Counting is not measuring — three
 * long labels overflow a 390px row and five short ones do not — and a fade
 * pinned to the right stayed lit at the end of the scroll, promising content
 * that was not there, while never admitting to anything scrolled past on the
 * left. `ChipRow` measures both edges and sets `data-overflow`.
 */
.ms-editor__chiprow[data-overflow="end"] {
  -webkit-mask-image: linear-gradient(to right, #000 85%, transparent 100%);
  mask-image: linear-gradient(to right, #000 85%, transparent 100%);
}

.ms-editor__chiprow[data-overflow="start"] {
  -webkit-mask-image: linear-gradient(to left, #000 85%, transparent 100%);
  mask-image: linear-gradient(to left, #000 85%, transparent 100%);
}

.ms-editor__chiprow[data-overflow="both"] {
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0%,
    #000 15%,
    #000 85%,
    transparent 100%
  );
  mask-image: linear-gradient(
    to right,
    transparent 0%,
    #000 15%,
    #000 85%,
    transparent 100%
  );
}

/*
 * The text layer field panel -- font/style/color pickers, the stroke/shadow
 * toggles, the lines editor, and the fixed/fillable fields -- rendered by
 * `TextLayerPanel` when a text layer is selected (see `EditorSheet.jsx`'s
 * docblock). Replaces the A2 text tool's own controls that used to be
 * styled at this spot, on the never-merged `feature/text-tool` stub.
 *
 * Baseline spacing and touch targets only, matching this sheet's other rows
 * -- `.ms-wall input`'s border/radius/font is the same "generic labelled
 * text field" shape borrowed here -- not a design pass. See `ui-feel-work`
 * on when a feature like this is ready for one.
 */
.ms-text-panel {
  display: flex;
  flex-direction: column;
  /* Tightened from s-sm: this panel is the tallest thing the sheet ever
     shows (font, style, color, two optional effect blocks, two sliders, the
     fillable fields), and most of its rows are now paired side by side
     (`.ms-text-panel__row` below) rather than full-width -- the remaining
     vertical gap between them is the next-biggest lever on its height. */
  gap: var(--rdp-ui-s-xs);
  padding: var(--rdp-ui-s-xs) 0;
  border-top: var(--rdp-ui-hairline);
}

/* Two (or more) controls that would otherwise be full-width, single-line
 * fields stacked on their own rows -- Font+Style, an effect's color+weight,
 * the two offset sliders, Letter spacing+Height, Label+Placeholder. Each
 * child is `flex: 1`, so a row used with only one child (Font with no Style
 * to pair) still fills the line rather than shrinking to its content.
 */
.ms-text-panel__row {
  display: flex;
  gap: var(--rdp-ui-s-sm);
}

.ms-text-panel__row > * {
  flex: 1;
  /* `min-width: 0` -- a flex item's default `min-width: auto` refuses to
     shrink below its content, and a `<select>`'s content-based min-width
     can exceed half the sheet's width on its own, which pushed its row
     partner (or the row itself) wider than the sheet and forced a
     horizontal scrollbar the panel had never had before. */
  min-width: 0;
}

/* One column of a paired row that carries its own caption underneath it
 * (Label/Placeholder, `TextLayerPanel.jsx`) -- `.ms-text-panel__row`'s own
 * children are `flex: 1` regardless of what's inside them, so this only
 * needs to stack the control and its note vertically within that share.
 */
.ms-text-panel__col {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-xs);
  min-width: 0;
}

.ms-text-panel__fieldset {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-xs);
  margin: 0;
  padding: 0;
  border: 0;
}

.ms-text-panel__fieldset legend {
  padding: 0;
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

/* Outline + Shadow's shared disclosure -- same column of rows a fieldset
 * gets, `<details>` has no `<fieldset>`'s own reset to inherit it from.
 */
.ms-text-panel__details {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-xs);
}

/* Same look as `.ms-text-panel__fieldset legend` above -- these two are the
 * only section headings this panel has, and a coach should read them as one
 * family, disclosure triangle aside. */
.ms-text-panel__details > summary {
  cursor: pointer;
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

/* Every other child sits in the gap column above; the summary is the one
 * exception (it is the closed state's only visible content) and needs its
 * own space below it before Outline's checkbox starts. */
.ms-text-panel__details[open] > summary {
  margin-bottom: var(--rdp-ui-s-xs);
}

/* One label above one control -- the same shape `.ms-pricing__input` and
   `.ms-wall__label` already use for a labelled field in this sheet. */
.ms-text-panel__control {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-xs);
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

/* The checkbox rows (Outline, Shadow, "Let the buyer fill this in",
   Required) read left-to-right instead -- the label follows the box, the
   way every checkbox on the web reads. A class rather than `:has()`: no
   other rule in this file leans on it, and this file's own token/selector
   conventions are all plain class selectors. Just layout -- `Checkbox`'s
   own look is the rule below. */
.ms-text-panel__checkbox {
  flex-direction: row;
  align-items: center;
  gap: var(--rdp-ui-s-xs);
}

/*
 * `@rdp/ui`'s `Checkbox` renders as a real `<button>`, and every style
 * `@rdp/ui` ships lives inside `@layer rdp-ui` -- the same trap
 * `.ms-editor__chip` and `.ms-feed__cell > button` already answer for
 * `Chip` and `Tile` elsewhere in this file. An unlayered rule in the
 * surrounding theme beats a layered one at any specificity, so without
 * this the checkbox would come up looking like whatever the theme paints
 * a bare `button` as, not the small square @rdp/ui actually ships.
 * Restated unlayered, keyed off a `className` passed straight to
 * `<Checkbox>` (`TextLayerPanel.jsx`'s `CheckboxField`) rather than
 * `@rdp/ui`'s own hashed module class, which carries a build hash that
 * changes on every package rebuild.
 *
 * Values copied from `@rdp/ui`'s own `Checkbox.module.css` verbatim --
 * this rule's whole job is making sure THOSE values win, not choosing
 * different ones.
 */
.ms-text-panel__checkbox-input {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  padding: 0;
  background-color: var(--rdp-ui-white);
  border: 1px solid var(--rdp-ui-grey-400);
  border-radius: var(--rdp-ui-r-sm);
  cursor: pointer;
}

.ms-text-panel__checkbox-input[data-state="checked"],
.ms-text-panel__checkbox-input[data-state="indeterminate"] {
  background-color: var(--rdp-ui-blue);
  border-color: var(--rdp-ui-blue);
}

.ms-text-panel__checkbox-input:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 2px var(--rdp-ui-white),
    0 0 0 4px var(--rdp-ui-blue-400);
}

.ms-text-panel input[type="text"],
.ms-text-panel select {
  min-height: var(--rdp-ui-s-2xl);
  padding: 0 var(--rdp-ui-s-sm);
  /* Not --rdp-ui-hairline -- see `.ms-wall input`'s own comment on why a
     hairline reads as no border at all around plain white. */
  border: 1px solid var(--rdp-ui-grey-400);
  border-radius: var(--rdp-ui-r-sm);
  font: 400 16px / 1 var(--rdp-ui-font-body);
  color: var(--rdp-ui-ink);
  background: var(--rdp-ui-white);
}

.ms-text-panel input[type="range"] {
  width: 100%;
}

/* The color select plus its "Custom…" swatch (`ColorField`, TextLayerPanel.jsx)
   sit in one row -- the swatch only renders once "Custom…" is picked, so this
   has to stay a plain flex row rather than a fixed two-column grid. */
.ms-text-panel__color {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-xs);
}

.ms-text-panel__color select {
  flex: 1;
}

.ms-text-panel__color input[type="color"] {
  flex: none;
  width: var(--rdp-ui-s-2xl);
  height: var(--rdp-ui-s-2xl);
  padding: 2px;
  border: 1px solid var(--rdp-ui-grey-400);
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-white);
}

/* One line's own row -- `position: relative` is what lets the "x" below sit
   inside the input's own right edge instead of trailing it as a second,
   separately laid-out control. Not `.ms-text-panel__control`: that class
   is the label-above-control shape every other field here uses, and this
   row has no label of its own (the fieldset's legend already says "Text"). */
.ms-text-panel__line {
  position: relative;
  display: flex;
}

.ms-text-panel__line-input {
  /* `.ms-text-panel input[type="text"]` above still supplies the border,
     height, font and colors -- this only reserves room on the right for
     the remove button so typed text never sits under it. Reserved even on
     the one line that has no button of its own (a design with a single
     line), so the width of every line's input matches regardless of how
     many there are -- switching from one line to two would otherwise
     narrow the first input by the button's own width. */
  flex: 1 1 auto;
  padding-right: calc(var(--rdp-ui-s-2xl) + var(--rdp-ui-s-xs));
}

/* Icon-only, inside the input rather than a separate "Remove" button after
   it -- `.ms-text-panel__fieldset button` below would otherwise give this
   the same bordered, white-filled look as "Add a line", which reads as a
   second action competing with the input instead of a small correction to
   it. Scoped through the fieldset ((0,2,0)) to outrank that rule ((0,1,1))
   rather than fight it with `!important`. */
.ms-text-panel__fieldset .ms-text-panel__line-remove {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  /* `.ms-text-panel__fieldset button` (below) sets every button in this
     fieldset's cross-axis alignment to `flex-start` -- fine for "Add a
     line", which sizes to its own text, but it also out-specificities the
     `top`/`bottom` stretch above: `align-self` wins over an absolutely
     positioned box's own inset-driven auto-height whenever it isn't
     `stretch`, so this button was hugging its icon's height and pinning to
     the input's top edge instead of centering in it. `center`, not
     `stretch`: the icon needs a fixed-size hit target here, not one that
     grows to match every input's exact rendered height. */
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--rdp-ui-s-2xl);
  height: var(--rdp-ui-s-2xl);
  min-height: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: var(--rdp-ui-r-sm);
  background: transparent;
  color: var(--rdp-ui-grey-600);
  cursor: pointer;
}

.ms-text-panel__fieldset .ms-text-panel__line-remove:hover,
.ms-text-panel__fieldset .ms-text-panel__line-remove:focus-visible {
  color: var(--rdp-ui-red-700);
}

.ms-text-panel__fieldset .ms-text-panel__line-remove:focus-visible {
  outline: none;
  box-shadow:
    0 0 0 2px var(--rdp-ui-white),
    0 0 0 4px var(--rdp-ui-blue-400);
}

.ms-text-panel__fieldset button {
  align-self: flex-start;
  min-height: var(--rdp-ui-s-2xl);
  padding: 0 var(--rdp-ui-s-sm);
  border: 1px solid var(--rdp-ui-grey-400);
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-white);
  color: var(--rdp-ui-ink);
  font-size: var(--ms-type-small);
}

.ms-text-panel__note {
  margin: 0;
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
}

.ms-text-panel__warning {
  margin: 0;
  color: var(--rdp-ui-red-700);
  font-size: var(--ms-type-small);
}

/* Same visual family as the sheet's other secondary actions
   (`.ms-editor__colortoggle` etc. above) -- unlayered for the same
   @layer-rdp-ui-loses-to-unlayered reason documented on that block. */
.ms-editor__addtext {
  align-self: flex-start;
  background-color: transparent;
  color: var(--rdp-ui-ink);
  border-color: var(--rdp-ui-grey-400);
}

.ms-editor__addtext:hover:not(:disabled) {
  /* Same `color` restatement as `.ms-editor__history-button:hover` above --
     omitting it left the theme's unlayered `button:hover{color:#fff}` in
     charge of the text color while this rule only won the background. */
  color: var(--rdp-ui-ink);
  background-color: var(--rdp-ui-grey-100);
}

.ms-editor__addtext:disabled,
.ms-editor__addtext[aria-disabled="true"] {
  color: var(--rdp-ui-grey-400);
  border-color: var(--rdp-ui-grey-200);
}

.ms-conflict__actions {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-sm);
  margin-top: var(--rdp-ui-s-md);
}

/*
 * Same theme-button-vs-`@layer rdp-ui` argument as the sheet's own buttons
 * above -- `Sheet`'s content portals to `document.body` (see the
 * dashboard-link fix below), but a portal only escapes inheritance, not
 * the stylesheet's layer order, so these needed the same fix regardless of
 * where they render.
 *
 * The safe action ("Keep…") is `variant`-default, i.e. primary -- see
 * ColorLockPrompt.jsx's own comment on why the reversible choice gets the
 * emphasis. Restated here on the bare selector so both dialogs' "Keep"
 * buttons get it without a class of their own; `.ms-conflict__danger` and
 * `.ms-conflict__secondary` override it for the one button per dialog that
 * isn't primary.
 */
.ms-conflict__actions > button {
  background-color: var(--rdp-ui-red);
  color: var(--rdp-ui-white);
  border-color: transparent;
}

.ms-conflict__actions > button:hover:not(:disabled) {
  background-color: var(--rdp-ui-red-700);
}

/*
 * `.ms-conflict__actions > .ms-conflict__danger`, not a bare
 * `.ms-conflict__danger` -- two classes beats the base rule's
 * class+combinator+type (0,2,0 > 0,1,1); a bare single-class selector
 * (0,1,0) would lose to it and the danger button would stay red.
 */
.ms-conflict__actions > .ms-conflict__danger {
  background-color: var(--rdp-ui-red-100);
  color: var(--rdp-ui-red-700);
  border-color: var(--rdp-ui-red);
}

.ms-conflict__actions > .ms-conflict__danger:hover:not(:disabled) {
  background-color: var(--rdp-ui-red-700);
  color: var(--rdp-ui-white);
}

.ms-conflict__actions > .ms-conflict__secondary {
  background-color: transparent;
  color: var(--rdp-ui-blue);
  border-color: var(--rdp-ui-grey-200);
}

.ms-conflict__actions > .ms-conflict__secondary:hover:not(:disabled) {
  /* Same `color` restatement as the editor's ghost buttons above. */
  color: var(--rdp-ui-blue);
  background-color: var(--rdp-ui-grey-100);
}

/* §5.4: the bottom sheet becomes a right-hand control panel. Same element.
 *
 * Explicit `height: 100dvh` plus `grid-template-rows: auto 1fr`, not the
 * previous `min-height: 0` alone: that left the stage/sheet row sized to its
 * own content (mostly the 56vh-capped canvas below), so the grid never had a
 * real height to hand the stage the rest of. The editor read as cramped on a
 * large desktop viewport as a direct result — most of the screen sat unused
 * beneath the canvas. `min-height: 0` is still needed on `.ms-editor__stage`
 * itself: a grid/flex item's default `min-height: auto` would otherwise keep
 * it sized to its content and prevent the row from ever constraining it.
 */
@media (min-width: 780px) {
  .ms-editor {
    display: grid;
    grid-template-columns: 1fr minmax(280px, 360px);
    grid-template-rows: auto 1fr;
    grid-template-areas: "banner banner" "stage sheet";
    /* `100%` of the shell, not `100dvh` of the window. Same number when the
       editor is the whole page, and the right one when it is not: above 780px
       this screen also renders inside `.ms-studio--split`'s right-hand column
       beside the blank rail, where a viewport-height grid would overflow the
       column it sits in by however tall the split's own padding is. */
    height: 100%;
  }

  .ms-editor__banner {
    grid-area: banner;
  }
  .ms-editor__stage {
    grid-area: stage;
    min-height: 0;
  }

  /* The 56vh mobile cap exists so the on-screen keyboard never covers the
     garment (§6.3) -- irrelevant above this breakpoint, where the sheet is a
     side column instead of something that can slide over the canvas. Filling
     the stage's own height (now a real, grid-constrained value) is what
     "take up more space on desktop" actually meant: the garment, not empty
     padding around it, grows into the room a big viewport has to give. */
  .ms-editor__canvas,
  .ms-editor__mount {
    /* `min()` against the viewport, not just the stage: `100%` alone can run
       the garment flush to the browser chrome on a short desktop window, since
       the stage's own height is only ever the shell's, never the viewport's
       directly. 92vh keeps a hairline of daylight above and below it. */
    max-height: min(100%, 92vh);
  }

  .ms-editor__sheet {
    grid-area: sheet;
    border-top: 0;
    border-left: var(--rdp-ui-hairline);
    padding-bottom: var(--rdp-ui-s-sm);
    /* Same reason `.ms-editor__stage` gets it above: a grid item's default
       `min-height: auto` refuses to shrink below its own content, so a tall
       panel here (`TextLayerPanel` opening for a freshly-added, still-empty
       text layer is the tallest thing this column ever shows — every field
       plus the "type something" warning, with nothing to actually draw yet)
       stretched the `1fr` row past the viewport instead of scrolling: the
       whole page grew, and the canvas -- centered in a stage that grew right
       along with it -- ended up sitting far down the inflated page, looking
       like the new text had appeared "above" it. Scoped to desktop, where
       this is a side column with room to scroll on its own; the mobile sheet
       already gets this exact treatment at its own 40vh cap above. */
    min-height: 0;
    overflow-y: auto;
  }

  /* The viewport cap above turns this into a real scroll box. Its controls
     must not flex-shrink to fit inside it, or they collapse until there is no
     overflow left for the scrollbar to expose. This is the desktop equivalent
     of the mobile sheet rule: preserve each control's natural height and let
     the panel, rather than its contents, do the scrolling. */
  .ms-editor__sheet > * {
    flex-shrink: 0;
  }

  /* The sheet is a full-height column here, not the 40vh scroll box it is on
     mobile -- there is vertical room to spare, so a chip row wraps onto a
     second line instead of hiding chips behind a horizontal scroll nobody
     expects on a desktop control panel. */
  .ms-editor__chiprow {
    flex-wrap: wrap;
    overflow-x: visible;
  }
}

/**
 * Creator-path rules for the pricing sheet, the draft tray, the account wall,
 * store setup and store management (plan tasks C7-C11).
 *
 * These arrived as a standalone src/js/studio/studio-creator-b.css so that two
 * parallel branches would not both create this file. Appended here at the UI
 * integration merge and otherwise unchanged; that file is gone. studio.css uses
 * no @import, and nothing imported the standalone sheet, so it reached no
 * bundle until this append.
 *
 * The `--ms-*` layout tokens come from `src/js/shared/ui/frontend.css`
 * (foundation F1); the `--rdp-ui-*` tokens from `@rdp/ui/tokens.css`, which
 * that file imports. No literal brand color appears below.
 */

/* Pricing ------------------------------------------------------------- */

/* Which design "Set your price" is about -- the title alone doesn't say,
   and a coach pricing several split colorways in a row needs to. */
.ms-pricing__subject {
  margin: 0;
  font-weight: 600;
}

.ms-pricing__lede,
.ms-pricing__projection {
  color: var(--rdp-ui-grey-600);
}

.ms-pricing__math {
  display: flex;
  flex-direction: column;
  gap: 1em;
}

.ms-pricing__item {
  display: flex;
  flex-direction: column;
}

.ms-pricing__picks {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-sm);
}

.ms-pricing__input {
  display: flex;
  flex-direction: column;
}

.ms-pricing__label {
  display: block;
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
  margin: 0;
}

/* `[type]`, for the same reason the shared field treatment above carries it:
   the theme paints `input[type="text"]{border;border-radius;padding}` plus
   `{background-color;border-color;color}` at (0,1,1), and a bare
   `.ms-pricing__amount` scores (0,1,0) -- so this borderless field came up
   with the theme's box around it and the theme's padding inside it. */
.ms-pricing__amount[type] {
  position: relative;
  width: 100%;
  min-height: var(--rdp-ui-s-lg);
  padding: 0;
  border: 0;
  background: transparent;
  font: 600 24px / 1 var(--rdp-ui-font-body);
  color: var(--rdp-ui-ink);
}

.ms-pricing__amount::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: var(--rdp-ui-ink);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--rdp-ui-dur-quick) var(--rdp-ui-ease-snap);
}

.ms-pricing__amount:focus-visible {
  outline: none;
}

.ms-pricing__amount:focus-visible::after {
  transform: scaleX(1);
}

.ms-pricing__price {
  margin: var(--rdp-ui-s-md) 0 0;
  font-size: var(--ms-type-h3);
}

.ms-pricing__value {
  font: 600 24px / 1 var(--rdp-ui-font-body);
}

.ms-pricing__failure {
  color: var(--rdp-ui-red-700);
}

.ms-pricing__footer {
  display: flex;
  gap: 2rem;
  justify-content: flex-end;
  align-items: center;

  & p {
    margin: 0;
    flex: 1 1 auto;
    min-width: 0; /* let it actually shrink below text's intrinsic width */
  }

  & button {
    flex: none;
  }
}

/*
 * §5.4's centered pricing sheet is Sheet's own variant="center" now, not an
 * override from here: @rdp/ui 0.4.0 hashes its class names, so
 * .rdp-sheet-backdrop matches nothing. The breakpoint moved into the
 * component, the one place it can be stated once.
 */

/* Tray ---------------------------------------------------------------- */

/* Workspace cap. The tray is a priced line-item list you edit in place, not
   prose you read top to bottom. Its row is already `72px 1fr auto` above
   780px, so the extra width lands where it should without a new rule: on the
   title/price column, with the markup field held at the right-hand edge
   instead of crowding the name. */
.ms-tray {
  max-width: var(--ms-container-wide);
  margin: 0 auto;
  padding: var(--rdp-ui-s-lg) var(--ms-gutter);
}

/*
 * The tray's own loading render (TrayScreen.jsx) is `.ms-tray` with a lone
 * `<Spinner />` inside it and no other content -- the same shape as the
 * editor's loading render above, and the same bug: `.ms-tray` is a plain
 * block box, so the spinner sits at the top-left of it in normal flow. On
 * desktop the box itself is inset by `margin: 0 auto` against
 * `--ms-container-wide`, but below that cap it's full-bleed, so this reads
 * as a spinner pinned to the top-left of the *screen* on mobile.
 *
 * Two classes for the same reason as `.ms-editor--loading`: this must not
 * depend on sitting after any other `.ms-tray` rule in source order.
 */
.ms-tray.ms-tray--loading {
  display: flex;
  align-items: center;
  justify-content: center;
}

.ms-tray__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ms-tray__row {
  display: grid;
  grid-template-columns: 72px 1fr;
  gap: var(--rdp-ui-s-md);
  align-items: start;
  padding: var(--rdp-ui-s-md) 0;
  border-bottom: var(--rdp-ui-hairline);
}

.ms-tray__thumb img {
  display: block;
  width: 72px;
  height: 72px;
  -o-object-fit: cover;
     object-fit: cover;
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-grey-100);
}

.ms-tray__body,
.ms-tray__markup {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-xs);
}

.ms-tray__markup {
  grid-column: 2;
}

/* The shared field treatment above, minus the height: a markup percentage
   sits in a tray row, not on a form of its own. Same weight as that rule,
   later in the file, so these two properties are all it changes. */
:where(.ms-tray__markup) input[type]:not([type="file"]) {
  min-height: 40px;
  font-weight: 600;
}

/*
 * `DesignNameField`'s input -- looks like plain text until the coach hovers
 * or focuses it, so an inline rename control does not read as a form dropped
 * into the middle of a title. `.ms-tray__title` and `.ms-editor__name` set
 * only the surrounding layout each placement needs; every visual property of
 * the field itself lives here, once.
 */
/* `[type]` again, and here it is the whole point of the control: the theme's
   `input[type="text"]{border:1px solid}` and its customizer `{border-color}`
   both outscored a bare `.ms-design-name`, so the dashed underline that says
   "this name is editable" was being painted over in the theme's own border
   color at rest. The `:hover`/`:focus` rules below already score (0,2,0) and
   were winning, which is why the field only looked right once touched. */
.ms-design-name[type] {
  display: block;
  width: 100%;
  border: 1px solid transparent;
  /* The always-on half of the rename hint (the pencil below is the other):
   * a dashed underline, at rest, so the field reads as editable before the
   * coach ever hovers or focuses it. grey-400, not grey-200 -- grey-200 is
   * the hairline color and nearly vanishes against white; the hint needs to
   * actually be seen. Swapped for a plain solid border, same as every other
   * side, the moment they hover or focus -- one box, not a dash peeking out
   * from under a solid one. */
  border-bottom: 1px dashed var(--rdp-ui-grey-400);
  border-radius: var(--rdp-ui-r-sm);
  background: transparent;
  padding: 2px calc(var(--rdp-ui-s-xs) + 16px) 2px var(--rdp-ui-s-xs);
  margin: -2px calc(var(--rdp-ui-s-xs) * -1);
  color: inherit;
}

/* `font` deliberately stays at one class, unlike everything above it: the
   placements that embolden this field (`.ms-manage__item-title`,
   `.ms-manage__item-raise`) set `font-weight` at (0,1,0) and rely on winning
   on source order. Raising this declaration would flatten all of them back to
   the surrounding type. The theme never paints `font` on an input, so it
   does not need the weight. */
.ms-design-name {
  font: inherit;
}

.ms-design-name:hover,
.ms-design-name:focus {
  border: 1px solid var(--rdp-ui-grey-200);
  background: var(--rdp-ui-grey-100);
}

.ms-design-name:disabled {
  opacity: 0.6;
}

/*
 * The pencil that tells the coach `.ms-design-name` is a name they can
 * change, before they ever hover or focus it -- the borderless-at-rest look
 * above is otherwise indistinguishable from a plain heading. Dim at rest,
 * fuller once the coach's pointer or focus lands anywhere in the wrapper, so
 * it reads as a hint rather than another control competing with the name.
 */
.ms-design-name-wrap {
  position: relative;
  display: flex;
  align-items: center;
}

.ms-design-name-icon {
  /* Sits inside the input's own right edge, not beside it -- same
   * background as `.ms-design-name` in both states so the seam between
   * them disappears and the icon reads as part of the field. */
  position: absolute;
  right: calc(var(--rdp-ui-s-xs) + 1px);
  color: var(--rdp-ui-grey-500);
  opacity: 0.45;
  background: transparent;
  transition:
    opacity var(--rdp-ui-dur-quick) var(--rdp-ui-ease-snap),
    background var(--rdp-ui-dur-quick) var(--rdp-ui-ease-snap);
}

.ms-design-name-wrap:hover .ms-design-name-icon,
.ms-design-name-wrap:focus-within .ms-design-name-icon {
  opacity: 0.9;
  background: var(--rdp-ui-grey-100);
}

.ms-tray__title {
  font-weight: 600;
}
.ms-tray__price {
  color: var(--rdp-ui-grey-600);
}
.ms-tray__raise {
  font-weight: 600;
}

/* Not color alone: the flag carries its own words. */
.ms-tray__flag {
  color: var(--rdp-ui-red-700);
  font-size: var(--ms-type-small);
}

.ms-tray__failure {
  color: var(--rdp-ui-red-700);
}

.ms-tray__actions {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-sm);
  margin-top: var(--rdp-ui-s-lg);
}

/* Same `@layer rdp-ui`-loses-to-the-theme's-unlayered-`button{}` fix as the
   editor buttons above -- "Add another" renders solid theme red otherwise. */
.ms-tray__actions > button[data-action="keep-going"] {
  background-color: transparent;
  color: var(--rdp-ui-ink);
  border-color: transparent;
}

.ms-tray__actions > button[data-action="keep-going"]:hover:not(:disabled) {
  /* Same `color` restatement as the editor's ghost buttons -- see
     `.ms-editor__history-button:hover` above. */
  color: var(--rdp-ui-ink);
  background-color: var(--rdp-ui-grey-100);
}

@media (min-width: 780px) {
  .ms-tray__row {
    grid-template-columns: 72px 1fr auto;
  }

  .ms-tray__markup {
    grid-column: auto;
    flex-direction: row;
    align-items: center;
    gap: var(--rdp-ui-s-sm);
  }

  .ms-tray__actions {
    flex-direction: row;
  }
}

/* Account gate ---------------------------------------------------------
 *
 * The full stop before the wall (AccountGate.jsx): "create an account" or
 * "leave and start over", named before either form appears.
 */

.ms-gate {
  display: flex;
  flex-direction: column;
  /* No `gap`: this screen has four blocks with four different distances
     between them (label to headline, headline to stake, stake to the choice,
     the choice to the way back in), and one gap cannot say that. Each block
     below owns its own bottom margin instead. */
  max-width: 560px;
  margin: var(--rdp-ui-s-xl) auto var(--rdp-ui-s-2xl);
  padding: var(--rdp-ui-s-xl) var(--ms-gutter) var(--rdp-ui-s-2xl);
  /* Every other step in this flow is a plain form on the page background.
     This one decides whether unsaved work survives -- reusing the same
     stop-line weight the brand guide already keeps for one-off emphasis
     (see --rdp-ui-border-emphasis's other use above), not a new pattern.
     The hairline finishes the other three edges: with only the top rule the
     card had no sides, and the paper fill bled into the page. */
  background: var(--rdp-ui-paper);
  border: var(--rdp-ui-hairline);
  border-top: var(--rdp-ui-border-emphasis);
  border-radius: 0 0 var(--rdp-ui-r-lg) var(--rdp-ui-r-lg);
}

/* 40px Anton against a 24px gutter was the "scrunched" report: the headline
   filled the card edge to edge. Room on the sides, and more of it than the
   top and bottom have, is what makes a card read as deliberate. */
@media (min-width: 780px) {
  .ms-gate {
    padding-inline: var(--rdp-ui-s-2xl);
  }
}

.ms-gate > h1 {
  margin: 0 0 var(--rdp-ui-s-md);
}

/* An overline, per the brand guide's §3 label spec (12px, uppercase, .16em,
   in red) -- the same treatment `.ms-overline` gives the step markers this
   flow already shows elsewhere. It was set as ordinary small grey text, which
   made it read as a sentence the coach had to parse rather than a position
   marker they can take in at a glance. */
.ms-gate__step {
  margin: 0 0 var(--rdp-ui-s-md);
  font-size: var(--ms-type-caption);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--rdp-ui-red);
}

/* The one sentence that says what is at stake, so it is set as a lede in ink
   rather than as grey helper text below the headline -- h3-sized, on the
   brand's own scale, with a reading measure so it breaks where a sentence
   should. */
.ms-gate__stake {
  max-width: 42ch;
  margin: 0 0 var(--rdp-ui-s-xl);
  font-size: var(--ms-type-h3);
  line-height: 1.5;
  color: var(--rdp-ui-ink);
}

.ms-gate__actions {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-12);
  margin: 0 0 var(--rdp-ui-s-xl);
}

.ms-gate__actions .rdp-ui-scope {
  width: 100%;
}

/* The way back in for a coach who already has an account: a footer under a
   rule, not a fifth line crowding the two choices above it. */
.ms-gate__login {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--rdp-ui-s-xs);
  margin: 0;
  padding-top: var(--rdp-ui-s-lg);
  border-top: var(--rdp-ui-hairline);
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

/* Same `@layer rdp-ui`-loses-to-the-theme's-unlayered-`button{}` fix as the
   editor buttons above -- "Leave and start over" and "Log in" render solid
   theme red instead of ghost otherwise. */
.ms-gate__actions > button[data-action="leave"],
.ms-gate__login > button[data-action="log-in"] {
  background-color: transparent;
  color: var(--rdp-ui-ink);
  border-color: transparent;
}

.ms-gate__actions > button[data-action="leave"]:hover:not(:disabled),
.ms-gate__login > button[data-action="log-in"]:hover:not(:disabled) {
  /* Same `color` restatement as the editor's ghost buttons -- see
     `.ms-editor__history-button:hover` above. */
  color: var(--rdp-ui-ink);
  background-color: var(--rdp-ui-grey-100);
}

@media (min-width: 780px) {
  .ms-gate__actions {
    flex-direction: row;
  }

  .ms-gate__actions .rdp-ui-scope {
    width: auto;
  }
}

/* Account wall ----------------------------------------------------------
 *
 * Shared by every form on this leg of the flow -- AccountCreateForm,
 * SignInForm, StoreNameForm -- each of them a single-purpose screen the
 * gate above hands off to, one form per className rather than one form per
 * mode the way the old combined AccountWall was. AcceptInviteScreen also
 * wears this class (plus its own `.ms-accept-invite` for the mode toggle)
 * since it's the same sign-in-or-create-account shape, just reached from an
 * invite link instead of the store-setup gate.
 */

.ms-wall {
  display: flex;
  flex-direction: column;
  /* Same argument as `.ms-gate` above: a label sits close to its field and
     far from the field before it, which a single `gap` cannot express. The
     label owns both distances. */
  max-width: 520px;
  margin: var(--rdp-ui-s-xl) auto var(--rdp-ui-s-2xl);
  padding: 0 var(--ms-gutter);
}

.ms-wall > h1 {
  margin: 0 0 var(--rdp-ui-s-md);
}

/* The gate's overline, on the forms it hands off to -- one step marker, one
   treatment, all the way through the flow. */
.ms-wall__step {
  margin: 0 0 var(--rdp-ui-s-md);
  font-size: var(--ms-type-caption);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--rdp-ui-red);
}

.ms-wall__stake {
  max-width: 46ch;
  margin: 0 0 var(--rdp-ui-s-xl);
  color: var(--rdp-ui-grey-600);
}

.ms-wall__conflict {
  margin: var(--rdp-ui-s-sm) 0 0;
  color: var(--rdp-ui-red-700);
}

/* A field label, not a sentence: caption-sized and tracked, sitting on its
   own field rather than floating between two of them. The top margin is the
   gap between fields; the bottom one is the tie to the field it names. */
.ms-wall__label {
  display: block;
  margin: var(--rdp-ui-s-lg) 0 var(--rdp-ui-s-xs);
  font-size: var(--ms-type-caption);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--rdp-ui-grey-600);
}

/* The first label follows the headline or the stake line, both of which
   already spent a bottom margin. Written as adjacency rather than
   `:first-of-type`, which would also catch StoreNameForm's logo label -- a
   <span>, and the first span on that form. */
.ms-wall > h1 + .ms-wall__label,
.ms-wall__stake + .ms-wall__label {
  margin-top: 0;
}

/* The submit sits well clear of the last field: this is the button that
   makes the account or the store, and it should not read as another row of
   the form. */
.ms-wall > button[type="submit"] {
  margin-top: var(--rdp-ui-s-xl);
}

/* Store logo (StoreNameForm) ------------------------------------------ */
/* Same drop-zone language as .ms-upload__zone -- a hidden input behind a
 * dashed label -- scaled down for a compact optional field inside a form
 * rather than a full-screen first step. */

.ms-wall .ms-wall__logo-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.ms-wall__logo-zone {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-sm);
  min-height: var(--rdp-ui-s-2xl);
  padding: var(--rdp-ui-s-sm);
  border: 1.5px dashed var(--rdp-ui-grey-400);
  border-radius: var(--rdp-ui-r-sm);
  color: var(--rdp-ui-blue);
  font-weight: 600;
  cursor: pointer;
  transition: border-color var(--rdp-ui-dur-quick) var(--rdp-ui-ease-snap);
}

.ms-wall__logo-zone:hover {
  border-color: var(--rdp-ui-blue);
}

.ms-wall__logo-zone--busy {
  border-style: solid;
  border-color: var(--rdp-ui-red);
}

.ms-wall .ms-wall__logo-input:focus-visible + .ms-wall__logo-zone,
.ms-wall__logo-zone:focus-within {
  outline: none;
  box-shadow: 0 0 0 3px var(--rdp-ui-blue-100);
}

.ms-wall__logo-busy,
.ms-wall__logo-default {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-xs);
}

.ms-wall__logo-thumb {
  width: var(--rdp-ui-s-2xl);
  height: var(--rdp-ui-s-2xl);
  border-radius: var(--rdp-ui-r-sm);
  -o-object-fit: cover;
     object-fit: cover;
  /* Logos are commonly transparent PNGs -- a light checked ground keeps one
   * from disappearing into the page's own white background. */
  background:
    linear-gradient(45deg, var(--rdp-ui-grey-100) 25%, transparent 25%) -6px 0,
    linear-gradient(-45deg, var(--rdp-ui-grey-100) 25%, transparent 25%) -6px 0,
    linear-gradient(45deg, transparent 75%, var(--rdp-ui-grey-100) 75%),
    linear-gradient(-45deg, transparent 75%, var(--rdp-ui-grey-100) 75%);
  background-size: 12px 12px;
}

.ms-wall__logo-thumb--default {
  width: var(--rdp-ui-s-xl);
  height: var(--rdp-ui-s-xl);
  flex-shrink: 0;
}

.ms-wall__logo-preview {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-sm);
  padding: var(--rdp-ui-s-sm);
  border: 1px solid var(--rdp-ui-grey-400);
  border-radius: var(--rdp-ui-r-sm);
}

.ms-wall__logo-preview .ms-wall__logo-thumb {
  flex-shrink: 0;
}

.ms-wall__logo-preview-body {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-2xs);
  min-width: 0;
}

.ms-wall__logo-preview-name {
  margin: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A plain text action, not @rdp/ui's Button -- this sits inline in a small
 * preview row, not on its own line, and doesn't want a button's full weight. */
.ms-wall__logo-clear {
  align-self: flex-start;
  padding: 0;
  border: 0;
  background: none;
  color: var(--rdp-ui-blue);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.ms-wall__logo-hint {
  margin: var(--rdp-ui-s-xs) 0 0;
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

.ms-wall__logo-status {
  margin: var(--rdp-ui-s-xs) 0 0;
  color: var(--rdp-ui-red-700);
}

/*
 * AcceptInviteScreen's own parts, none of which had a rule before this: the
 * class was named once in the comment above and nowhere else in the sheet.
 */

/*
 * A segmented control. Both modes used to be plain `Button`s that rendered as
 * the same solid theme red -- `aria-pressed` was carrying the entire selected
 * state and no rule was reading it, so nothing on screen said which of the two
 * was about to happen.
 *
 * `> button` (0,1,1) is what beats the theme's unlayered `button {}` (0,0,1);
 * see the editor-button fix above for why every variant in this project needs
 * saying out loud. The pressed rule adds an attribute selector on top of that,
 * so it wins over the resting one without `!important`.
 */
.ms-accept-invite__modes {
  display: flex;
  gap: var(--rdp-ui-s-xs);
  margin-bottom: var(--rdp-ui-s-md);
  padding: var(--rdp-ui-s-xs);
  border: var(--rdp-ui-hairline);
  border-radius: var(--rdp-ui-r-md);
  background: var(--rdp-ui-paper);
}

.ms-accept-invite__modes > button {
  flex: 1 1 0;
  background-color: transparent;
  color: var(--rdp-ui-grey-600);
  border-color: transparent;
  font-weight: 600;
}

.ms-accept-invite__modes > button[aria-pressed="true"] {
  background-color: var(--rdp-ui-white);
  color: var(--rdp-ui-ink);
  /* A raised tile rather than a fill: red is the page's one call to action and
     belongs to the submit button, not to a tab. */
  box-shadow: 0 1px 2px rgb(27 30 36 / 12%);
}

.ms-accept-invite__modes > button:hover:not([aria-pressed="true"]) {
  color: var(--rdp-ui-ink);
}

/* The screen's one call to action, spelled out for the same reason. */
.ms-accept-invite__submit {
  margin-top: var(--rdp-ui-s-md);
  background-color: var(--rdp-ui-red);
  color: var(--rdp-ui-white);
  border-color: transparent;
}

.ms-accept-invite__submit:hover:not(:disabled) {
  background-color: var(--rdp-ui-red-700);
}

/* The spinner and its sentence on one line, replacing the `null` this branch
   used to render while two round trips went out. */
.ms-accept-invite__joining {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-sm);
  margin: var(--rdp-ui-s-md) 0 0;
  color: var(--rdp-ui-grey-600);
}

.ms-accept-invite__failure {
  margin: var(--rdp-ui-s-md) 0;
}

.ms-accept-invite__failure p {
  margin: 0;
}

/* `asChild` renders this as an `<a>`, which the theme's `button {}` does not
   reach -- but `@rdp/ui`'s own variant class does not survive the Slot's
   class merge in every case either, so the resting look is stated here. */
.ms-accept-invite__out {
  align-self: flex-start;
  background-color: transparent;
  color: var(--rdp-ui-blue);
  border-color: var(--rdp-ui-grey-200);
  text-decoration: none;
}

.ms-accept-invite__out:hover {
  background-color: var(--rdp-ui-grey-100);
}

.ms-accept-invite__error {
  margin: var(--rdp-ui-s-sm) 0 0;
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-red-700);
}

/* Store setup --------------------------------------------------------- */

.ms-store-setup {
  max-width: var(--ms-container);
  margin: 0 auto;
  padding: var(--rdp-ui-s-lg) var(--ms-gutter);
}

.ms-look__options {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--rdp-ui-s-md);
  margin-bottom: 2rem;
}

.ms-look__option {
  display: block;
  width: 100%;
  min-height: var(--rdp-ui-s-3xl);
  padding: var(--rdp-ui-s-sm);
  border: var(--rdp-ui-hairline);
  border-radius: var(--rdp-ui-r-md);
  background: var(--rdp-ui-white);
  text-align: left;
  cursor: pointer;
}

/* Not color alone: the selected option also gains an emphasis border. */
.ms-look__option--selected {
  border: 1px solid var(--rdp-ui-red);
  color: var(--rdp-ui-red);
  background-color: var(--rdp-ui-red-100);
}

.ms-look__option:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--rdp-ui-blue-100);
}

.ms-look__thumb {
  position: relative;
  display: block;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-blue);
}

.ms-look__thumb img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  -o-object-fit: cover;
     object-fit: cover;
}

/* The scrim's alpha is computed in scrim.js; only its geometry is here. */
.ms-look__scrim {
  position: absolute;
  inset: 0;
}

.ms-look__thumb-text {
  position: absolute;
  left: var(--rdp-ui-s-sm);
  bottom: var(--rdp-ui-s-sm);
  color: var(--rdp-ui-white);
  font: 400 20px / 0.95 var(--rdp-ui-font-display);
  letter-spacing: 0.01em;
}

.ms-look__label {
  display: block;
  font-weight: 600;
  margin-top: var(--rdp-ui-s-xs);
}
.ms-look__description {
  display: block;
  color: var(--rdp-ui-grey-600);
  font-size: var(--ms-type-small);
}
.ms-look__error {
  color: var(--rdp-ui-red-700);
}

.ms-look__hero-label {
  display: block;
  margin-top: var(--rdp-ui-s-md);
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

.ms-look__example-store {
  border-radius: var(--rdp-ui-r-md);
  border: var(--rdp-ui-hairline);
  padding: var(--rdp-ui-s-md);
}

.ms-store-setup__actions {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-sm);
  margin-top: var(--rdp-ui-s-lg);
}

.ms-store-setup__failure {
  color: var(--rdp-ui-red-700);
}

/* Sits below whichever step is on screen (gate, create, login, name), never
 * beside its buttons -- it is a way out of the whole flow, not one of its
 * actions. */
.ms-store-setup__back {
  display: flex;
  justify-content: center;
  margin-top: var(--rdp-ui-s-2xl);
  padding: var(--rdp-ui-s-lg) 0;
  max-width: 100%;
}

/* Same `@layer rdp-ui`-loses-to-the-theme's-unlayered-`button{}` fix as the
   editor buttons above -- "Back to my designs" renders solid theme red
   otherwise. */
.ms-store-setup__back > button {
  background-color: transparent;
  color: var(--rdp-ui-ink);
  border-color: transparent;
}

.ms-store-setup__back > button:hover:not(:disabled) {
  /* Same `color` restatement as the editor's ghost buttons -- see
     `.ms-editor__history-button:hover` above. */
  color: var(--rdp-ui-ink);
  background-color: var(--rdp-ui-grey-100);
}

@media (min-width: 780px) {
  .ms-look__options {
    grid-template-columns: repeat(3, 1fr);
  }
  .ms-store-setup__actions {
    flex-direction: row;
  }
}

/* Store management ---------------------------------------------------- */

.ms-dashboard__intro {
  display: flex;
  gap: var(--rdp-ui-s-xs);
  align-items: center;
}

.ms-dashboard__actions {
  display: flex;
  gap: 1em;
  align-items: center;
}

/* `.ms-manage__bar`/`.ms-manage__bar-fill` (hand-rolled progress bar) were
   deleted here -- OverviewPage now renders `@rdp/ui`'s `Progress`, which
   ships its own module CSS. Grepped src/js: their only remaining references
   were the CSS rules themselves and a test asserting `.ms-manage__bar` is
   gone. Progress keeps the same blue-not-red fill for the same reason: red
   on a fundraising bar reads as a warning, and red stays reserved for
   rejections, destructive buttons and the removed-design badge. */

.ms-manage__items-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rdp-ui-s-md);
}

.ms-manage__add-footer {
  margin-top: var(--rdp-ui-s-lg);
  padding-top: var(--rdp-ui-s-md);
  border-top: var(--rdp-ui-hairline);
}

/* Same `@layer rdp-ui`-loses-to-the-theme's-unlayered-`button{}` fix as the
   editor buttons above -- "Start a new design" renders solid theme red
   otherwise. */
.ms-manage__add-footer > button {
  background-color: transparent;
  color: var(--rdp-ui-ink);
  border-color: transparent;
}

.ms-manage__add-footer > button:hover:not(:disabled) {
  /* Same `color` restatement as the editor's ghost buttons -- see
     `.ms-editor__history-button:hover` above. */
  color: var(--rdp-ui-ink);
  background-color: var(--rdp-ui-grey-100);
}

.ms-manage__items {
  list-style: none;
  margin: 0;
  padding: 0;
}

/*
 * `ItemsPageSkeleton`'s shapes. It renders the real header, list and rows,
 * so everything structural above already applies to it -- these rules only
 * size the placeholders that stand in for the button, the thumbnail, the
 * two lines of price copy and the row menu.
 *
 * `Skeleton` exposes only a `lines` count and one wrapper `className`, so
 * these reach its internal `<span>`s with a descendant selector, the same
 * way the dashboard shell's own skeleton rules below do.
 */
.ms-manage__add-loading span {
  /* @rdp/ui Button, md: 40px tall, and about as wide as "Add item". */
  height: 40px;
  width: 104px;
  margin: 0;
  border-radius: var(--rdp-ui-r-sm);
}

.ms-manage__item-thumb-loading span {
  height: 72px;
  width: 72px;
  margin: 0;
}

.ms-manage__item-title-loading span {
  height: 20px;
  width: 50%;
}

.ms-manage__item-raise-loading span {
  height: 20px;
}

.ms-manage__item-raise-loading span:first-child {
  width: 70%;
}

/* The "Change price" button under the price line. */
.ms-manage__item-raise-loading span:last-child {
  height: 40px;
  width: 132px;
  margin-bottom: 0;
  border-radius: var(--rdp-ui-r-sm);
}

/* `.ms-manage__item-menu`'s own 48px square (--rdp-ui-s-2xl, the touch
   target floor). */
.ms-manage__item-menu-loading span {
  height: var(--rdp-ui-s-2xl);
  width: var(--rdp-ui-s-2xl);
  margin: 0;
  border-radius: var(--rdp-ui-r-sm);
}

.ms-manage__item {
  display: grid;
  grid-template-columns: 72px 1fr;
  gap: var(--rdp-ui-s-md);
  padding: var(--rdp-ui-s-md) 0;
  border-bottom: var(--rdp-ui-hairline);
}

.ms-manage__item-thumb img {
  display: block;
  width: 72px;
  height: 72px;
  -o-object-fit: cover;
     object-fit: cover;
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-grey-100);
}

/* `.ms-manage__confirm-actions` (formerly compounded here) was dropped:
   SettingsPage's close-confirmation is now an AlertDialog and its buttons
   sit in AlertDialogFooter, which brings its own layout. Grepped src/js --
   zero remaining consumers. */
.ms-manage__item-markup,
.ms-manage__item-actions,
.ms-manage__campaign {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-xs);
}

/* A date input holds ten characters and a picker button; stretching it the
   full width of the column reads as a free-text field it isn't. max-width,
   not width, so it still shrinks on a narrow phone. */
.ms-manage__campaign input[type="date"] {
  max-width: 14rem;
}

/* Same reason for the Save beneath it: a column of stretched controls reads
   as a full-width form. align-self, not a width -- the button should be as
   wide as its label and no wider. */
.ms-manage__campaign > button {
  align-self: flex-start;
}

/* `DesignNameField`'s own input when it renders here (`.ms-design-name` sets
   its interactive look), and the plain `<span>` ItemRow shows in its place
   while `heroes` is still loading -- both get the same weight, the same way
   `.ms-tray__title` does for the tray's own copy of this field. */
.ms-manage__item-title {
  font-weight: 600;
}

.ms-manage__item-raise {
  font-weight: 600;
}

/**
 * The row's price column is a flex COLUMN, so its default `stretch` would
 * pull "Change price" the full width of the row. The button is sized by its
 * own label; the price line above it is the thing that runs long.
 */
.ms-manage__item-markup {
  align-items: flex-start;
}

.ms-manage__item-actions {
  flex-direction: row;
  flex-wrap: wrap;
}

/**
 * The row's overflow trigger. One control where three buttons used to sit
 * in a line -- the row's primary job is re-pricing, and the sort and
 * remove actions were competing with the field a coach actually uses.
 */
.ms-manage__item-menu {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--rdp-ui-s-2xl);
  min-height: var(--rdp-ui-s-2xl);
  padding: 0 var(--rdp-ui-s-sm);
  border: var(--rdp-ui-hairline);
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-white);
  color: var(--rdp-ui-ink);
  font: 600 16px / 1 var(--rdp-ui-font-body);
  cursor: pointer;
}

/* Prominent, per spec §12 — closing is how a campaign ever settles. */
.ms-manage__close {
  margin-top: var(--rdp-ui-s-xl);
  padding-top: var(--rdp-ui-s-lg);
  border-top: var(--rdp-ui-hairline);
}

/* The second sentence of SettingsPage's close-confirmation, INSIDE the same
   AlertDialogDescription as the first (a <p> under the hood) so both reach
   aria-describedby -- a <span>, not a second <p>, because <p> cannot
   contain block content. font-weight carries the emphasis a margin no
   longer can from inside a <p>. */
.ms-manage__close-consequence {
  font-weight: 600;
}

@media (min-width: 780px) {
  .ms-manage__item {
    grid-template-columns: 72px 1fr auto;
  }
  .ms-manage__item-actions {
    grid-column: auto;
  }
}

/* People ---------------------------------------------------------------- */

/*
 * PeoplePage's two cards and their rows.
 *
 * Everything from `.ms-people__card` down to `.ms-people__invite-submit` is
 * new because the section had NO rules of its own beyond the invite panel at
 * the bottom of this block: the two lists rendered as default `<ul>` bullets
 * with a name and a button jammed against each other, and a freshly minted
 * invitation printed its raw token into a paragraph.
 *
 * The card is `.ms-share__card`'s treatment rather than a second look for the
 * same idea -- both sections are "one artifact per card" inside the dashboard,
 * and Share got there first. Declared separately rather than by grouping the
 * selectors because the two sections' cards are free to diverge; what must not
 * diverge is by accident.
 */
.ms-people__intro {
  margin: 0 0 var(--rdp-ui-s-lg);
  max-width: 52ch;
  color: var(--rdp-ui-grey-600);
}

.ms-people__card {
  width: 100%;
  margin-bottom: var(--rdp-ui-s-lg);
  padding: var(--rdp-ui-s-lg);
  border: var(--rdp-ui-hairline);
  border-radius: var(--rdp-ui-r-lg);
  background: var(--rdp-ui-white);
}

/* The same eyebrow as `.ms-share__card-title`, and for the same reason:
   `#ms-app h3` is Anton uppercase at 20px, which would give a card's label
   more weight than the names inside it. */
#ms-app .ms-people__card-title {
  margin: 0 0 var(--rdp-ui-s-md);
  font-family: var(--rdp-ui-font-body);
  font-size: var(--ms-type-caption);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rdp-ui-grey-600);
}

.ms-people__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ms-people__row {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-sm);
  padding: var(--rdp-ui-s-sm) 0;
  border-bottom: var(--rdp-ui-hairline);
}

.ms-people__row:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

/* `min-width: 0` because the flex default (`auto`) refuses to shrink below the
   content's own width, and an email address has no spaces to break on --
   without it a long one pushes the badge and the button off a phone. */
.ms-people__name {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: anywhere;
  font-weight: 600;
}

/*
 * Same `@layer rdp-ui`-loses-to-the-theme's-unlayered-`button{}` fix as the
 * editor buttons above. A single class (0,1,0) is enough here -- the theme's
 * rule is a bare type selector (0,0,1) -- unlike `.ms-conflict__danger`,
 * which also had to beat a class+type rule of this stylesheet's own.
 *
 * `danger` is the red TINT, not solid red: brand guide §2 rations red as the
 * lead accent for calls to action, and "Remove" is the loudest thing on this
 * card only if you let it be.
 */
.ms-people__remove {
  background-color: var(--rdp-ui-red-100);
  color: var(--rdp-ui-red-700);
  border-color: var(--rdp-ui-red);
}

.ms-people__remove:hover:not(:disabled) {
  background-color: var(--rdp-ui-red-700);
  color: var(--rdp-ui-white);
}

.ms-people__revoke {
  background-color: transparent;
  color: var(--rdp-ui-blue);
  border-color: var(--rdp-ui-grey-200);
}

.ms-people__revoke:hover:not(:disabled) {
  background-color: var(--rdp-ui-grey-100);
}

/* `Skeleton`'s internal spans, reached the same way the other sections'
   loading rules reach theirs -- sized to a row, not to a line of prose. */
.ms-people__loading span {
  height: var(--rdp-ui-s-2xl);
}

/* PeoplePage's own "invite someone" panel, now the foot of the Invitations
   card rather than something revealed in place of a trigger button. Same
   field treatment as `.ms-manage__campaign input` above. */
.ms-people__invite-form {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--rdp-ui-s-xs);
  max-width: 420px;
  margin-top: var(--rdp-ui-s-lg);
  padding-top: var(--rdp-ui-s-md);
  border-top: var(--rdp-ui-hairline);
}

.ms-people__invite-form label {
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

/* The field and its button on one line, wrapping to two when there is no room
   -- the button keeps its own width and the input takes the rest. */
.ms-people__invite-field {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rdp-ui-s-xs);
}

.ms-people__invite-field input {
  flex: 1 1 12rem;
}

/* Solid brand red, per the theme-button fix above: this is the section's one
   call to action, so it is the one place the ration is spent. */
.ms-people__invite-submit {
  background-color: var(--rdp-ui-red);
  color: var(--rdp-ui-white);
  border-color: transparent;
}

.ms-people__invite-submit:hover:not(:disabled) {
  background-color: var(--rdp-ui-red-700);
}

.ms-people__invite-hint,
.ms-people__invite-error {
  margin: 0;
  font-size: var(--ms-type-small);
}

.ms-people__invite-hint {
  color: var(--rdp-ui-grey-600);
}

.ms-people__invite-error {
  color: var(--rdp-ui-red-700);
}

.ms-people__invite-result {
  margin-top: var(--rdp-ui-s-sm);
}

/*
 * The invitation's address, shown as selectable text beside the copy button.
 * A 64-character token has no break opportunities of its own, so without
 * `anywhere` this one element scrolls the entire dashboard sideways.
 */
.ms-people__invite-url {
  display: block;
  margin: var(--rdp-ui-s-xs) 0;
  padding: var(--rdp-ui-s-xs) var(--rdp-ui-s-sm);
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-grey-100);
  font-size: var(--ms-type-small);
  overflow-wrap: anywhere;
  -webkit-user-select: all;
     -moz-user-select: all;
          user-select: all;
}

/* Everything visual comes from the shared field treatment; the width is the
   one thing a full-width form field asks for that a tray row does not. */
.ms-people__invite-form input {
  width: 100%;
}

/* Store dashboard ------------------------------------------------------- */

/**
 * The sidebar shell that replaced ManageScreen's single scrolling page.
 * `--rdp-ui-radius-sm`, `--rdp-ui-c-accent-soft`, `--rdp-ui-c-border` and a
 * `--rdp-ui-t-*` type scale do not exist in this project -- the real names
 * are `--rdp-ui-r-sm`, `--rdp-ui-blue-100` and `--rdp-ui-hairline`, and text
 * is sized with the `--ms-type-*` scale (frontend.css) the rest of this
 * sheet already uses, not a `--rdp-ui-*` one.
 *
 * One column on a phone, with the whole sidebar behind a drawer; sidebar
 * beside the content at 780px, the same breakpoint the editor split already
 * uses (studio.css:26).
 */
.ms-dashboard {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-lg);
  /* Workspace cap. This is the screen a coach leaves open -- sidebar, stats,
     chart, item and sales tables -- and it is the one that read worst as a
     1080px strip: a fixed 14rem sidebar took a fifth of it, leaving the tables
     narrower than the browser window they sat in the middle of. */
  max-width: var(--ms-container-wide);
  margin: 0 auto;
  padding: var(--rdp-ui-s-lg) var(--ms-gutter);
}

/**
 * Section headings inside the dashboard.
 *
 * `frontend.css` styles `#ms-app h2` at 28px uppercase, which is right for a
 * marketing page and wrong here: every section title came out the same weight
 * as the campaign name above it, so nothing had hierarchy and the whole screen
 * shouted. An id selector outranks a class, so this has to match on the id
 * too rather than fight it with a longer class chain.
 *
 * The campaign-name h1 gets the brand's display face (guide §3: Anton is
 * UPPERCASE, Impact only, `line-height: .95`, slight positive
 * letter-spacing) at the h2 type-scale step. The h2 section headings
 * ("How this campaign is doing", "What has sold") stay Archivo sentence
 * case on purpose -- a deliberate, conversational voice choice for coaches,
 * not an oversight. Do not uppercase them to match h1.
 */
#ms-app .ms-dashboard h1 {
  font-family: var(--rdp-ui-font-display);
  font-size: var(--rdp-ui-scale-h2);
  line-height: 0.95;
  letter-spacing: 0.01em;
  text-transform: uppercase;
}

#ms-app .ms-dashboard h2 {
  font-size: var(--ms-type-h3);
  line-height: 1.25;
  letter-spacing: 0;
  text-transform: none;
}

/* Below 780px the sidebar is the drawer, not a column above the page: this
   aside is hidden and `DashboardShell` renders the same `SidebarPanel`
   inside `Sheet` instead. `display: none` rather than a visibility trick on
   purpose -- it takes the duplicate nav out of the accessibility tree, which
   is the whole reason two render sites are safe. */
.ms-dashboard__sidebar {
  display: none;
  flex-direction: column;
  gap: var(--rdp-ui-s-md);
}

/**
 * The phone drawer: @rdp/ui's `Sheet`, moved from the bottom edge to the
 * left one.
 *
 * Scoped to `#ms-app` deliberately. `Sheet`'s own rules are hashed
 * (`.rdp-sheet-xSPiF`) and injected into <head> at runtime by the package,
 * so they land after this sheet and win every specificity tie -- and its
 * state rules carry a second class-level attribute selector on top. An id
 * outranks all of it without `!important`, and `#ms-app` is already the
 * house tool for exactly this (see the h1/h2 rules above).
 *
 * Everything here is an override of a bottom-sheet declaration: `right`,
 * `max-width` and `margin` are reset because Sheet centers a 560px strip;
 * `max-height` because it caps at 90vh; the radius because a panel against
 * the left edge is rounded on the other side.
 */
#ms-app .ms-dashboard__drawer {
  top: 0;
  right: auto;
  bottom: 0;
  left: 0;
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-md);
  width: min(17rem, 82vw);
  max-width: none;
  max-height: none;
  margin: 0;
  border-radius: 0 var(--rdp-ui-r-lg) var(--rdp-ui-r-lg) 0;
}

/**
 * The scrim `Sheet` leaves behind.
 *
 * A defect in @rdp/ui, not here: Sheet's backdrop is left in the DOM at
 * `data-state="closed"` with an exit animation that never runs, so Radix's
 * `Presence` waits forever for an `animationend` and the scrim stays over the
 * page -- opaque, click-catching, and permanent until a reload. Reproduced on
 * `MarkupSheet` too, so it is every Sheet in the product; the body-scroll half
 * of the same leak is cleared in `DashboardShell.jsx`, which explains the
 * whole thing.
 *
 * A child selector, not a descendant one: the drawer's Sheet is the only one
 * mounted directly under `.ms-dashboard`, and every Radix trigger inside this
 * screen (both dropdown menus) also carries `data-state="closed"` when shut.
 */
#ms-app .ms-dashboard > [data-state="closed"] {
  display: none;
}

/* Same foot-of-the-panel position the account block gets in the column
   above 780px. */
#ms-app .ms-dashboard__drawer .ms-dashboard__account {
  margin-top: auto;
}

/* One section per row. The wrapping row below is what a nav laid across the
   top of a phone has to be; a panel 17rem wide is a column, and the sections
   read down it in `SECTIONS` order the same way they do above 780px. */
#ms-app .ms-dashboard__drawer .ms-dashboard__nav-list {
  flex-direction: column;
  flex-wrap: nowrap;
}

/* The raised figure per campaign is back: it was dropped from the phone
   layout because each entry then claimed a row of the page it was sitting on
   top of. In a drawer that row costs nothing -- the campaign underneath is
   not competing for it. */
#ms-app .ms-dashboard__drawer .ms-dashboard__switcher-raised {
  display: block;
}

/* Sheet's own keyframes translate on Y, which would have this panel rise
   from the floor rather than slide in from the edge. Same durations and
   easings, so the drawer moves like every other surface in the library. */
#ms-app .ms-dashboard__drawer[data-state="open"] {
  animation: ms-dashboard-drawer-in var(--rdp-ui-dur-quick)
    var(--rdp-ui-ease-snap);
}

#ms-app .ms-dashboard__drawer[data-state="closed"] {
  animation: ms-dashboard-drawer-out var(--rdp-ui-dur-instant)
    var(--rdp-ui-ease-press);
}

@keyframes ms-dashboard-drawer-in {
  from {
    transform: translateX(-100%);
  }
  to {
    transform: translateX(0);
  }
}

@keyframes ms-dashboard-drawer-out {
  from {
    transform: translateX(0);
  }
  to {
    transform: translateX(-100%);
  }
}

@media (prefers-reduced-motion: reduce) {
  #ms-app .ms-dashboard__drawer[data-state="open"],
  #ms-app .ms-dashboard__drawer[data-state="closed"] {
    animation-duration: 0.001ms;
  }
}

/**
 * The sticky bar: the `☰` button and the campaign name, the only chrome a
 * phone keeps once the sidebar is behind the drawer.
 *
 * The negative margins bleed it across `.ms-dashboard`'s own gutter, so
 * content scrolling underneath does not show through at the edges of an
 * otherwise opaque bar. Above 780px it is neither sticky nor a bar -- the
 * heading is back in the flow of a header that has a sidebar beside it.
 */
.ms-dashboard__bar {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rdp-ui-s-sm);
  margin: calc(var(--rdp-ui-s-lg) * -1) calc(var(--ms-gutter) * -1)
    var(--rdp-ui-s-md);
  padding: var(--rdp-ui-s-sm) var(--ms-gutter);
  background: var(--rdp-ui-white);
  border-bottom: var(--rdp-ui-hairline);
}

/* Two lines and then an ellipsis. A campaign name is free text, and this is
   a bar that never scrolls away -- three lines of display type at the top of
   a 390x800 viewport is the problem the drawer exists to solve. */
#ms-app .ms-dashboard__bar h1 {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  min-width: 0;
  overflow: hidden;
}

/* 44px, the tap target, from padding rather than a fixed size so the icon
   stays centered if the type scale moves. */
.ms-dashboard__menu {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  padding: var(--rdp-ui-s-sm);
  margin-left: calc(var(--rdp-ui-s-sm) * -1);
  border: 0;
  border-radius: var(--rdp-ui-r-sm);
  background: none;
  color: var(--rdp-ui-ink);
  cursor: pointer;
}

.ms-dashboard__menu:hover {
  background: var(--rdp-ui-grey-100);
}

/**
 * DashboardShell's loading state. `.ms-dashboard--loading` needs no rule of
 * its own -- the loading markup is the same aside+main the loaded screen
 * uses, so it lines up at both widths without a modifier, and on a phone the
 * sidebar skeleton is hidden with the sidebar itself: there is no store yet,
 * so there is nothing for a menu button to open and the heading skeleton
 * below already holds the bar's place.
 *
 * The sidebar and the title bar are the only generic parts left -- both are
 * genuinely uniform shapes, true of every section. The body itself is each
 * section's own `*Skeleton` export (`SECTION_SKELETONS` in
 * `DashboardShell.jsx`), not shaped here: a hand-approximated block was the
 * thing that stopped matching the moment `OverviewPage` grew past three
 * stat cards, and the fix is to stop guessing at a shape this file cannot
 * see. `Skeleton` only exposes a `lines` count and one wrapper `className`,
 * so the sidebar/heading shapes below are plain descendant selectors onto
 * its internal `<span>`s -- no hashed CSS-module class names to know about,
 * and Skeleton renders nothing else that these could collide with.
 */
.ms-dashboard__skeleton-sidebar {
  padding: var(--rdp-ui-s-md) 0;
}

.ms-dashboard__skeleton-sidebar span {
  height: 20px;
}

.ms-dashboard__skeleton-sidebar span:first-child {
  /* Stands in for the BrandMark row. */
  width: 60%;
  height: 28px;
}

.ms-dashboard__skeleton-heading span {
  height: 28px;
  width: 240px;
  margin: 0;
}

/* Wrap on a phone rather than scroll sideways. An earlier revision made both
   of these horizontal scrollers, which hid 107px of the nav at 390px and 177px
   at 320px -- "Settings", the section that closes a campaign, sat entirely
   off-screen with no fade or peeking item to say so. Wrapping costs a second
   row and hides nothing. */
.ms-dashboard__nav-list {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rdp-ui-s-sm);
  margin: 0;
  padding: 0;
  list-style: none;
}

.ms-dashboard__nav-link,
.ms-dashboard__switcher-link {
  white-space: nowrap;
  padding: var(--rdp-ui-s-xs) var(--rdp-ui-s-sm);
  border-radius: var(--rdp-ui-r-sm);
  color: var(--rdp-ui-ink);
  text-decoration: none;
}

.ms-dashboard__nav-link {
  display: flex;
  gap: var(--rdp-ui-s-sm);
  align-items: center;
  justify-content: flex-start;
}

.ms-dashboard__switcher-link {
  display: block;
}

.ms-dashboard__nav-link[aria-current="page"],
.ms-dashboard__switcher-link[aria-current="true"] {
  background: var(--rdp-ui-blue-100);
  font-weight: 600;
}

/* Neither link had any hover feedback -- the current-page tint was the only
   state either rule painted. `:not([aria-current])` so hovering the already
   -active entry doesn't fight its own blue fill. */
.ms-dashboard__nav-link:hover:not([aria-current="page"]),
.ms-dashboard__switcher-link:hover:not([aria-current="true"]) {
  background: var(--rdp-ui-grey-100);
}

/*
 * `DropdownMenuContent` portals to `document.body` (same as the conflict
 * dialog's own content -- see `.ms-conflict__actions` above), which escapes
 * `.ms-dashboard__switcher`'s ancestry entirely, so a descendant selector
 * from this file's normal tree can't reach it. Restated here keyed to the
 * `className` the menu itself now carries, two classes deep -- same
 * `.ms-conflict__actions > .ms-conflict__danger`-style specificity bump,
 * needed because the theme's own dropdown/menu link styling (aimed at ITS
 * nav, assuming a dark background) is a class+type selector that otherwise
 * outranks the single-class rule above and wins `color`: white text on
 * this menu's white background, reported live on prod.
 *
 * `color` is repeated in the current/hover states rather than left to
 * inherit from the base rule for the same reason `.ms-editor__history-
 * button:hover` restates it (see that block's comment): a state-specific
 * theme rule can still outrank an unqualified base one even when this
 * block's own base color already won.
 */
.ms-dashboard__switcher-menu .ms-dashboard__switcher-link {
  color: var(--rdp-ui-ink);
}

.ms-dashboard__switcher-menu .ms-dashboard__switcher-link[aria-current="true"] {
  color: var(--rdp-ui-ink);
}

.ms-dashboard__switcher-menu
  .ms-dashboard__switcher-link:hover:not([aria-current="true"]) {
  color: var(--rdp-ui-ink);
}

.ms-dashboard__switcher-name {
  display: inline;
}

/* Name only on a phone. Carrying each store's raised figure here made every
   entry wide enough to claim a row of its own, and switcher-plus-nav chrome
   was already eating a fifth of a 390x800 viewport before the store name
   appeared. The figure is not lost -- Overview and Payout both lead with it,
   one tap away. The sidebar has room for it again at 780px. */
.ms-dashboard__switcher-raised {
  display: none;
  font-size: var(--rdp-ui-scale-small);
  opacity: 0.75;
}

/**
 * The campaign trigger. Carries the sidebar heading inside itself, so the
 * switcher still says what it picks -- the pill list it replaced needed a
 * separate heading to distinguish it from the section nav below.
 */
.ms-dashboard__switcher-trigger {
  display: block;
  width: 100%;
  padding: var(--rdp-ui-s-xs) var(--rdp-ui-s-sm);
  border: var(--rdp-ui-hairline);
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-white);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.ms-dashboard__switcher-trigger:hover {
  background: var(--rdp-ui-grey-100);
}

.ms-dashboard__figure {
  font-size: var(--ms-type-h3);
  font-weight: 700;
}

/* Overview: the StatGroup that replaced the stacked <p>s. Stat/StatGroup
   bring their own card, type and fill from @rdp/ui's own module CSS -- this
   rule is only the section-level spacing around them. */
.ms-dashboard__stats {
  margin: 0;
}

/* Matches .ms-dashboard__chart-svg's own height below so the chart does not
   jump when the real <svg> replaces this Skeleton. */
.ms-dashboard__chart-skeleton {
  height: 9rem;
  margin: var(--rdp-ui-s-lg) 0 0;
}

/* ------------------------------------------------------------------
 * The Overview section.
 *
 * One punctuation block and everything else quiet, which is guide §4's
 * "occasional full-bleed ink-blue block ... air reads as confidence"
 * spent in one place rather than spread thin. The band carries the
 * campaign's headline reading; the stats, chart and selling strip below
 * it stay on white with hairline borders so the band is the thing the
 * eye lands on.
 * ------------------------------------------------------------------ */
.ms-overview {
  display: flex;
  flex-direction: column;
  gap: var(--rdp-ui-s-lg);
}

.ms-overview__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--rdp-ui-s-md);
  flex-wrap: wrap;
}

#ms-app .ms-overview__head h2 {
  margin: 0;
}

/* The lifecycle pill is @rdp/ui's `Badge` now (0.9.0) -- `tone="active"` is
   guide §2's blue-100/blue-800 tint for a live campaign, `tone="neutral"`
   the gray for one that is not. Deliberately never red -- red is the accent
   this section spends on the progress bar, and a status that is red whether
   or not anything is wrong stops meaning anything. No local CSS needed; kept
   as a selector hook only, via the className passed to `Badge`. */

.ms-overview__hero {
  position: relative;
  padding: var(--rdp-ui-s-xl) var(--rdp-ui-s-lg);
  border-radius: var(--rdp-ui-r-lg);
  background: var(--rdp-ui-blue);
  color: var(--rdp-ui-white);
}

/* Guide §3 puts overlines in --red, which is a rule about ink on white.
   Red on ink-blue is the one pairing §2 rules out, so the reverse form of
   the same overline is white at reduced opacity -- 4.9:1 on #1E3A5F. */
.ms-overview__eyebrow {
  margin: 0 0 var(--rdp-ui-s-sm);
  font-size: var(--ms-type-caption);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.75);
}

/* The one display-face moment in the dashboard body. Anton at the display
   step, reverse on the band: this figure IS the section's headline, and
   setting it in the body face at the h2 step (what `Stat` does, correctly,
   for the row below) would make it just another card. */
.ms-overview__figure {
  margin: 0;
  font-family: var(--rdp-ui-font-display);
  font-size: clamp(40px, 8vw, 56px);
  line-height: 0.95;
  letter-spacing: 0.01em;
  font-variant-numeric: tabular-nums;
  /* Guide §8's press, on the one hero moment this screen has. The whole
	   band arrives at once rather than each figure animating itself --
	   "one orchestrated press beats twenty micro-animations". */
  animation: ms-overview-press var(--rdp-ui-dur-stamp) both;
}

@keyframes ms-overview-press {
  0% {
    transform: translateY(-18px) scale(1.14);
    opacity: 0;
    animation-timing-function: var(--rdp-ui-ease-press);
  }
  52% {
    transform: translateY(0) scale(0.91);
    opacity: 1;
  }
  72% {
    transform: scale(1.05);
  }
  88% {
    transform: scale(0.99);
  }
  100% {
    transform: scale(1);
  }
}

@media (prefers-reduced-motion: reduce) {
  .ms-overview__figure {
    animation: none;
  }
}

.ms-overview__goal {
  margin: var(--rdp-ui-s-md) 0 0;
}

/* The reverse treatment and the 12px headline height are `Progress`'s own
   `tone="inverse"` / `size="lg"` now (0.9.0) -- no CSS override needed.
   `.ms-overview__progress` stays only as the selector hook a few tests use
   to confirm the bar is absent when there is no goal. */

.ms-overview__goal-line {
  margin: var(--rdp-ui-s-sm) 0 0;
  font-size: var(--ms-type-small);
  color: rgba(255, 255, 255, 0.82);
  font-variant-numeric: tabular-nums;
}

/* Guide §6's stitch line -- a dashed rule, the embroidery nod -- earning
   its keep as the divider between what the campaign has raised and what
   that means. */
.ms-overview__pace {
  margin: var(--rdp-ui-s-lg) 0 0;
  padding-top: var(--rdp-ui-s-md);
  border-top: 3px dashed rgba(255, 255, 255, 0.28);
  font-size: var(--ms-type-h3);
  font-weight: 700;
  line-height: 1.25;
}

.ms-overview__verdict {
  margin: var(--rdp-ui-s-xs) 0 0;
  max-width: 52ch;
  color: rgba(255, 255, 255, 0.82);
}

/**
 * `OverviewPageSkeleton`'s own shape, standing in for the band while
 * `GET /stores` is in flight -- `.ms-overview__status-loading`,
 * `-figure-loading`, `-goal-loading` and `-pace-loading` are on `Skeleton`,
 * not on the real elements they stand in for, so none of this collides with
 * the rules above.
 *
 * `Skeleton`'s shimmer reads `--rdp-ui-grey-100`/`-200`, tuned for a light
 * surface -- redefined here to translucent white so the placeholder reads
 * against the ink-blue band instead of vanishing into it. Custom properties
 * inherit, so this one redefinition on the band covers every `Skeleton`
 * inside it; nothing per class below repeats it.
 */
.ms-overview__hero--loading {
  --rdp-ui-grey-100: rgba(255, 255, 255, 0.18);
  --rdp-ui-grey-200: rgba(255, 255, 255, 0.32);
}

.ms-overview__status-loading span {
  height: 22px;
  width: 150px;
}

/* Matches `.ms-overview__figure`'s own line-height-driven footprint closely
   enough that the goal line below does not visibly jump when the real
   figure replaces this. */
.ms-overview__figure-loading span {
  height: 48px;
  width: 55%;
  margin-bottom: var(--rdp-ui-s-md);
}

.ms-overview__goal-loading span {
  height: 12px;
  margin-bottom: var(--rdp-ui-s-sm);
}

.ms-overview__pace-loading {
  margin-top: var(--rdp-ui-s-lg);
  padding-top: var(--rdp-ui-s-md);
  border-top: 3px dashed rgba(255, 255, 255, 0.28);
}

.ms-overview__pace-loading span:first-child {
  height: 20px;
  width: 45%;
}

.ms-overview__pace-loading span:last-child {
  width: 80%;
}

/* Guide §6: registration marks frame "official" moments and card corners.
   One mark, on the one block that is the official moment. `@rdp/ui`'s
   `RegistrationMark` (0.9.0) strokes in `currentColor`, so the reverse
   treatment is `color`, not `stroke`. */
.ms-overview__mark {
  position: absolute;
  top: var(--rdp-ui-s-md);
  right: var(--rdp-ui-s-md);
  color: rgba(255, 255, 255, 0.4);
}

/* Cards on paper rather than white is `Stat`'s own `tone="paper"` now
   (0.9.0): the section's surface is white, and a white card with a hairline
   on white reads as a stray outline. */

.ms-overview__stats-skeleton {
  height: 5rem;
}

/* The chart wrapper is `@rdp/ui`'s `Panel` now (0.9.0) -- guide §11's
   `.card` recipe, verbatim, which is exactly what this block hand-rolled. */
.ms-overview__panel .ms-dashboard__chart {
  margin: 0;
}

/* The draft store's way back into opening -- OverviewPage's OpenStorePanel.
   A Panel like the chart's, with the stake line and the button stacked the
   way the wizard's own gate stacks them. */
.ms-overview__open {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--rdp-ui-s-sm);
}

.ms-overview__open-stake {
  margin: 0;
  max-width: 52ch;
}

.ms-overview__selling-head {
  display: flex;
  align-items: baseline;
  gap: var(--rdp-ui-s-sm);
  flex-wrap: wrap;
}

/* Archivo, sentence case, for the same reason the dashboard's h2 rule gives:
   `frontend.css` sets every heading in the display face, which is right for a
   marketing page and wrong beside a display-face h1. One Anton heading per
   screen -- here that is the campaign name in the bar, not this. */
#ms-app .ms-overview__selling-head h3 {
  margin: 0;
  font-family: var(--rdp-ui-font-body);
  font-size: var(--ms-type-h3);
  line-height: 1.25;
  letter-spacing: 0;
  text-transform: none;
}

.ms-overview__selling-count {
  margin: 0;
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

.ms-overview__selling-link {
  margin-left: auto;
  font-size: var(--ms-type-small);
  font-weight: 700;
}

.ms-overview__selling-list {
  list-style: none;
  margin: var(--rdp-ui-s-md) 0 0;
  padding: 0;
}

.ms-overview__selling-row {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr) auto auto;
  align-items: center;
  gap: var(--rdp-ui-s-md);
  padding: var(--rdp-ui-s-sm) 0;
  border-top: var(--rdp-ui-hairline);
}

.ms-overview__selling-thumb {
  display: block;
  width: 48px;
  height: 48px;
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-grey-100);
  overflow: hidden;
}

.ms-overview__selling-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  -o-object-fit: contain;
     object-fit: contain;
}

.ms-overview__selling-name {
  min-width: 0;
  overflow-wrap: anywhere;
}

.ms-overview__selling-units {
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
  font-variant-numeric: tabular-nums;
}

.ms-overview__selling-money {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

@media (max-width: 520px) {
  .ms-overview__hero {
    padding: var(--rdp-ui-s-lg) var(--rdp-ui-s-md);
  }

  /* Two columns rather than `auto-fit`'s three-into-two-plus-an-orphan: an
	   odd last card takes the whole row instead of sitting half-width beside
	   a gap. The id is needed to outrank StatGroup's own grid, which @rdp/ui
	   injects after this sheet. */
  #ms-app .ms-overview__stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .ms-overview__stat:last-child:nth-child(odd) {
    grid-column: 1 / -1;
  }

  /* The money is the column that must not wrap; the unit count moves under
	   the design name rather than squeezing three columns into a phone. */
  .ms-overview__selling-row {
    grid-template-columns: 40px minmax(0, 1fr) auto;
  }

  .ms-overview__selling-thumb {
    width: 40px;
    height: 40px;
  }

  .ms-overview__selling-units {
    grid-column: 2;
    grid-row: 2;
  }
}

/* Payout: a single Stat inside a StatGroup grid built for several would
   otherwise stretch edge to edge (auto-fit with one child fills the row).
   Capped so the raised figure reads as one card, not a stray full-width
   band. */
.ms-dashboard__payout-stats {
  grid-template-columns: minmax(180px, 320px);
}

@media (min-width: 780px) {
  .ms-dashboard {
    flex-direction: row;
    align-items: flex-start;
  }

  /* The column comes back: below this width it is `display: none` and the
     drawer carries the same panel. */
  .ms-dashboard__sidebar {
    display: flex;
    flex: 0 0 14rem;
    position: sticky;
    top: var(--rdp-ui-s-lg);
    /* Capped to the viewport, not stretched to match a long page of tables --
       same `position: sticky` + `max-height` + `overflow-y: auto` combination
       `.ms-studio__rail` already uses above for the feed's own sticky column.
       Without this, `align-self: stretch` below (still needed for the panel
       background/border to reach the bottom of a SHORT page) made the
       sidebar exactly as tall as `.ms-dashboard__main`'s content -- on a
       campaign with a long sales table that is taller than the screen, so
       the nav and account block just sat at the top of a mostly-empty box
       and scrolled off with the page instead of staying put and scrolling
       their own few items into view. */
    max-height: calc(100vh - var(--rdp-ui-s-lg) * 2);
    overflow-y: auto;
  }

  /* No drawer here, so nothing to open -- and the heading is a heading
     again rather than the left half of a bar. */
  .ms-dashboard__menu {
    display: none;
  }

  .ms-dashboard__header {
    flex-direction: row;
    justify-content: space-between;
    align-items: center;
  }

  .ms-dashboard__bar {
    position: static;
    margin: 0;
    padding: 0;
    background: none;
    border-bottom: 0;
  }

  #ms-app .ms-dashboard__bar h1 {
    display: block;
    overflow: visible;
  }

  .ms-dashboard__nav-list {
    flex-direction: column;
    flex-wrap: nowrap;
  }

  .ms-dashboard__switcher-name,
  .ms-dashboard__switcher-raised {
    display: block;
  }

  .ms-dashboard__main {
    flex: 1 1 auto;
    min-width: 0;
  }

  /* A panel, not a column of loose text. Without a surface and an edge the
     sidebar read as body copy that happened to sit on the left. */
  .ms-dashboard__sidebar {
    align-self: stretch;
    padding: var(--rdp-ui-s-lg) var(--rdp-ui-s-md);
    background: var(--rdp-ui-grey-100);
    /* A card, so radius and no edge -- an earlier revision had both a
       radius and a right border, which reads as neither one thing nor the
       other. */
    border-radius: var(--rdp-ui-r-md);
  }

  /* Pushes the account block to the foot of the panel where it is expected,
     rather than leaving it floating under the nav. */
  .ms-dashboard__account {
    margin-top: auto;
  }
}

/**
 * The sidebar's own furniture: brand at the head, account at the foot.
 *
 * On a phone these sit inline above the content rather than in a panel --
 * the layout is a single column there, and a boxed sidebar would spend a
 * third of the viewport on chrome before the campaign appears.
 */
.ms-dashboard__brand {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-sm);
  text-decoration: none;
  color: var(--rdp-ui-ink);
}

.ms-dashboard__brand-mark {
  display: block;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: var(--rdp-ui-r-sm);
}

.ms-dashboard__brand-name {
  font-family: var(--rdp-ui-font-display);
  font-size: var(--ms-type-h3);
  font-weight: 700;
  line-height: 1.1;
}

.ms-dashboard__account {
  padding-top: var(--rdp-ui-s-md);
  border-top: var(--rdp-ui-hairline);
}

.ms-dashboard__account-label {
  display: block;
  font-size: var(--ms-type-caption);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--rdp-ui-grey-600);
}

.ms-dashboard__account-name {
  display: block;
  font-weight: 600;
  /* A long display name must not widen the sidebar past its track. */
  overflow-wrap: anywhere;
}

/**
 * The two menu items, which are anchors.
 *
 * Every state is pinned, and that is the whole point of this block. The
 * menu content portals to `document.body`, so it lands *outside* `#ms-app`
 * and inherits nothing from it -- what it inherits instead is the theme's
 * own anchor styling, which paints a link red and underlines it on hover.
 * @rdp/ui's own item rules cannot undo that: they ship inside
 * `@layer rdp-ui`, and an unlayered theme rule beats any layered one no
 * matter how specific. So the color has to be stated here, unlayered, in
 * each state the theme has an opinion about.
 *
 * Ink in every one of them, including the highlighted row: `[data-
 * highlighted]` is @rdp/ui's own blue-100 band, and ink on blue-100 is
 * exactly what the section links above already do. One hover language in
 * the column rather than two.
 *
 * No `font-size` here any more -- @rdp/ui's item rule sets it, and the
 * declaration this replaced was restating it.
 */
.ms-dashboard__account-link {
  display: flex;
  align-items: center;
  gap: var(--rdp-ui-s-sm);
  width: 100%;
  color: var(--rdp-ui-ink);
  text-decoration: none;
}

.ms-dashboard__account-link:hover,
.ms-dashboard__account-link:focus,
.ms-dashboard__account-link:focus-visible,
.ms-dashboard__account-link:active,
.ms-dashboard__account-link:visited {
  color: var(--rdp-ui-ink);
  text-decoration: none;
}

/**
 * The account trigger. Reads as the block of text it replaced rather than
 * as a button -- it sits at the foot of the sidebar panel, where a framed
 * control would pull more attention than the least-used thing on the
 * screen deserves.
 *
 * The padding and radius are the section links' (`.ms-dashboard__nav-link`),
 * so the row lines up with the nav above it and can carry the same
 * blue-100 hover. It used to underline the name instead, which read as a
 * link in a column where nothing else underlines.
 */
.ms-dashboard__account-trigger {
  display: block;
  width: 100%;
  padding: var(--rdp-ui-s-xs) var(--rdp-ui-s-sm);
  border: 0;
  border-radius: var(--rdp-ui-r-sm);
  background: none;
  font: inherit;
  color: var(--rdp-ui-ink);
  text-align: left;
  cursor: pointer;
}

.ms-dashboard__account-trigger:hover,
.ms-dashboard__account-trigger:focus-visible,
.ms-dashboard__account-trigger[data-state="open"] {
  background: var(--rdp-ui-blue-100);
}

/**
 * A column on a phone, a row beside the sidebar above 780px.
 *
 * The row is what this has always been, and it is wrong once the heading is
 * half of a sticky bar: as a flex item the bar is only as wide as the space
 * Preview and Share leave it, so the bar's own edge-to-edge background and
 * rule stopped in the middle of the screen with the buttons floating past
 * them. Stacked, the bar spans the viewport and the two actions sit under it
 * where the campaign's own content begins.
 */
.ms-dashboard__header {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--rdp-ui-s-sm);
}

/**
 * The Sales table. It scrolls sideways inside its own box on a phone --
 * `.ms-dashboard__table-scroll` is the overflow boundary, not the page body
 * -- because `.ms-dashboard__main` only gets `min-width: 0` (the thing that
 * lets a flex child shrink below its content) at the 780px split above; on a
 * single-column phone layout nothing else stops a wide `<table>` from
 * pushing the whole page sideways.
 */
.ms-dashboard__table-scroll {
  max-width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.ms-dashboard__table {
  width: 100%;
  border-collapse: collapse;
}

.ms-dashboard__table th,
.ms-dashboard__table td {
  padding: var(--rdp-ui-s-sm) var(--rdp-ui-s-md);
  text-align: left;
  vertical-align: middle;
  white-space: nowrap;
}

/* Money that is not tabular jitters as its digits change, and a left-aligned
   numeric column is the clearest tell of an unconsidered table. SalesPage
   carries this class on both the Sold/You-raised header `th`s and their
   body `td`s, so the whole column -- header and data together -- lines up
   on the right. */
.ms-dashboard__num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* The identity column is the one cell whose content is unbounded -- a design
   slug plus, sometimes, the removed badge. It must be free to wrap, or its
   content overflows into the numbers beside it: with `nowrap` inherited from
   the rule above, the badge measured 73px on top of the Sold figure at every
   phone width, hiding the very number the row exists to report. The numeric
   columns keep `nowrap` so a money value never breaks across lines. */
.ms-dashboard__table tbody th {
  white-space: normal;
}

.ms-dashboard__table tbody tr {
  border-top: var(--rdp-ui-hairline);
}

/*
 * `SalesPageSkeleton`'s cells. It renders the real table, so the row
 * rhythm, the hairlines and the column alignment are the loaded table's --
 * these only size the placeholders inside the cells. The empty
 * `.ms-dashboard__table-thumb` beside them needs no rule at all: that class
 * already draws a 2.5rem gray square, which is exactly what a thumbnail
 * that has not arrived looks like.
 */
.ms-dashboard__sales-slug {
  display: inline-block;
  width: 9rem;
  vertical-align: middle;
}

.ms-dashboard__sales-slug span {
  height: 16px;
  margin: 0;
}

.ms-dashboard__sales-figure span {
  height: 16px;
  margin: 0 0 0 auto;
  width: 3rem;
}

.ms-dashboard__table-thumb {
  display: inline-block;
  width: 2.5rem;
  height: 2.5rem;
  margin-right: var(--rdp-ui-s-sm);
  vertical-align: middle;
  border-radius: var(--rdp-ui-r-sm);
  overflow: hidden;
  background: var(--rdp-ui-grey-100);
}

.ms-dashboard__table-thumb img {
  width: 100%;
  height: 100%;
  -o-object-fit: cover;
     object-fit: cover;
}

.ms-dashboard__table-slug {
  margin-left: var(--rdp-ui-s-xs);
}

/* Unmissable, not a subtle style difference: a design that sold and was
   later pulled from the store still raised real money, so its row gets a
   plainly-worded badge in the same red-on-red-100 the rest of this sheet
   reserves for things a coach must not skim past
   (`.ms-upload__rejection`, `.ms-editor__banner`). */
.ms-dashboard__removed {
  /* Block, so it sits under the design name instead of running along the same
     line and pushing into the next column. `fit-content` keeps the pill hugging
     its words rather than stretching the full cell width. */
  display: block;
  width: -moz-fit-content;
  width: fit-content;
  margin-top: var(--rdp-ui-s-xs);
  padding: var(--rdp-ui-s-xs) var(--rdp-ui-s-sm);
  border-radius: var(--rdp-ui-r-sm);
  background: var(--rdp-ui-red-100);
  color: var(--rdp-ui-red-700);
  font-size: var(--ms-type-caption);
  font-weight: 600;
  white-space: normal;
}

.ms-dashboard__error {
  color: var(--rdp-ui-red-700);
  font-size: var(--ms-type-small);
}

/* Tells the two sidebar lists apart. They are near-identical pill lists with
   the same active highlight, so without these a coach cannot see which one
   picks a campaign and which picks a page.
   A <p>, not an <h2>: `frontend.css` styles `#ms-app h2` at 28px uppercase,
   and an id selector outranks this class, so a heading element here renders
   as large as the section title it sits beside. The surrounding <nav> already
   carries the aria-label, so nothing is lost by not being a heading. */
.ms-dashboard__sidebar-heading {
  margin: 0 0 var(--rdp-ui-s-xs);
  font-family: var(--rdp-ui-font-body);
  font-size: var(--ms-type-caption);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--rdp-ui-grey-600);
}

/* One label is enough on a phone. Two cost ~60px of chrome before any content,
   and once the first list says "Your campaigns" the second is not ambiguous.
   Written as a max-width query rather than a plain rule undone at 780px: this
   block sits *after* the min-width block in this file, so a plain rule here
   would win at every width and the desktop label would never come back. */
@media (max-width: 779px) {
  /**
   * One line, not a stacked block.
   *
   * On a phone the sidebar is not a panel beside the content -- it is chrome
   * stacked above it, so every row it takes is a row the campaign is pushed
   * down by. Stacked, the account block cost ~95px and put the store's name
   * below the fold on a 390x800 screen. Name and links on one line, with the
   * "signed in as" label dropped (the name and a Log out link next to each
   * other say it), costs about a third of that.
   */
  .ms-dashboard__account {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--rdp-ui-s-xs) var(--rdp-ui-s-md);
    padding-top: var(--rdp-ui-s-sm);
  }

  .ms-dashboard__account-label {
    display: none;
  }

  .ms-dashboard__account-name {
    display: inline;
  }

  .ms-dashboard__nav .ms-dashboard__sidebar-heading {
    display: none;
  }
}

/**
 * The trend chart. A fixed 320x96 viewBox stretched by CSS: the shape is what
 * carries the meaning, and `preserveAspectRatio="none"` lets it fill whatever
 * column it lands in without recomputing geometry per breakpoint.
 */
.ms-dashboard__chart {
  margin: var(--rdp-ui-s-lg) 0 0;
}

/* An overline, not a caption: guide §3's 12px uppercase label in --red,
   which is where this section spends the last of its red. */
.ms-dashboard__chart-caption {
  margin-bottom: var(--rdp-ui-s-md);
  font-size: var(--ms-type-caption);
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--rdp-ui-red);
}

.ms-dashboard__chart-svg {
  display: block;
  width: 100%;
  height: 9rem;
  overflow: visible;
}

.ms-dashboard__chart-area {
  fill: var(--rdp-ui-blue-100);
}

.ms-dashboard__chart-line {
  fill: none;
  stroke: var(--rdp-ui-blue);
  stroke-width: 2;
  /* The line is stretched horizontally with the viewBox, so without this the
     stroke thins and thickens with the container width. */
  vector-effect: non-scaling-stroke;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.ms-dashboard__chart-goal {
  stroke: var(--rdp-ui-grey-400);
  stroke-width: 1;
  stroke-dasharray: 4 3;
  vector-effect: non-scaling-stroke;
}

/* The dot at the end of the line: a zero-length round-capped stroke, so its
   diameter is the stroke width and the uneven viewBox scaling cannot
   distort it. */
.ms-dashboard__chart-tip {
  stroke: var(--rdp-ui-blue);
  stroke-width: 7;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.ms-dashboard__chart-axis {
  display: flex;
  justify-content: space-between;
  gap: var(--rdp-ui-s-sm);
  margin: var(--rdp-ui-s-xs) 0 0;
  font-size: var(--ms-type-caption);
  color: var(--rdp-ui-grey-600);
}

/*
 * The drag overlay.
 *
 * Absolutely positioned over `.ms-editor__stage`'s canvas, which means the
 * stage becomes the positioning context. The overlay itself is
 * pointer-events: none so it never eats a tap meant for the canvas; the
 * boxes inside it opt back in.
 */
.ms-editor__stage {
  position: relative;
  /*
   * Without this iOS claims a one-finger drag as a page pan and the gesture
   * never fires. Scoped to the stage on purpose -- the rest of the page must
   * keep scrolling, and browser page zoom outside this element is an
   * accessibility affordance, not a nuisance.
   */
  touch-action: none;
}

/*
 * `inset: 0` is a fallback, not the final geometry. `DragOverlay` overrides
 * left/top/width/height inline so the overlay is pinned to the composited
 * element rather than to the padded, centring stage around it -- the boxes
 * inside are measured from the canvas's own top-left corner, and at `inset: 0`
 * every one of them was displaced by the stage's padding. See that component's
 * `frame`.
 */
.ms-editor__overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

/*
 * The print rect, clipping — `composeToCanvas` clips every layer to it, so a
 * live layer that did not would show artwork the release then cuts off. See
 * LiveLayer.jsx.
 */
.ms-editor__live-clip {
  position: absolute;
  overflow: hidden;
  pointer-events: none;
}

/*
 * The dragged artwork while a finger is down, drawn as a real element instead
 * of composited every frame. See LiveLayer.jsx for why.
 *
 * `transform-origin` is load-bearing, not styling: the component translates
 * and scales from the box the gesture started in, and that arithmetic is only
 * correct from the top-left corner.
 *
 * `max-width: none` overrides the `img { max-width: 100% }` every reset in the
 * stack applies. Without it a live layer wider than the overlay -- which is
 * every artwork scaled past the print rect -- would be silently squashed to
 * the overlay's width and stop matching its own selection box.
 */
.ms-editor__live-layer {
  position: absolute;
  transform-origin: top left;
  /* The selection box under it is the gesture target; this must never eat a
     pointer meant for the box or its handles. */
  pointer-events: none;
  max-width: none;
  will-change: transform;
}

/*
 * The dragged text layer while a finger is down — `LiveText`'s twin of
 * `LiveLayer` above, same `transform-origin` reason and the same reason it
 * must never eat a pointer.
 *
 * `white-space: pre` keeps the `\n`-joined lines on their own rows without
 * wrapping — this element approximates the compositor's line breaks, not its
 * wrapping (there is none; `fitBlock` never wraps a line either). `overflow:
 * hidden` matches the box's own edges the same way an unscaled `<img>` never
 * needs telling to.
 */
.ms-editor__live-text {
  position: absolute;
  transform-origin: top left;
  pointer-events: none;
  will-change: transform;
  white-space: pre;
  overflow: hidden;
}

.ms-editor__layer-box {
  position: absolute;
  pointer-events: auto;
  /* Gray-400, not gray-200: this sits over a garment photo and needs to
     survive a mid-tone background. */
  border: 1px dashed var(--rdp-ui-grey-400);
  background: transparent;
  cursor: move;
}

.ms-editor__layer-box--selected {
  /* Matches .ms-look__option--selected -- the studio's one selected-state
     border treatment, not a second one invented for this component. */
  border: var(--rdp-ui-border-emphasis);
}

/*
 * Deliberately a box-shadow, not an outline -- this is the same focus
 * treatment as .ms-look__option and .ms-upload__zone
 * (studio.css), not a fourth one. Do not "improve" this back into an
 * outline; it was one, and it rendered nothing because it depended on a
 * token (--rdp-ui-accent) that does not exist.
 */
.ms-editor__layer-box:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--rdp-ui-blue-100);
}

/*
 * The one control that takes a layer off the garment, on the selection only.
 *
 * Centered on the top edge, ABOVE the box, and that position is a correctness
 * requirement rather than taste. It was at `top:-12; right:-12` at 24px square,
 * which put it exactly over the NE handle (`right:-6; top:-6`, 12px square) --
 * fully covering it, and painting later as a sibling, so it took the taps too.
 * Reaching for that corner to resize deleted the layer. All four corners carry
 * handles, so the only clear anchors are the edge midpoints; the top edge keeps
 * it off the artwork and away from the sheet.
 *
 * `translate(-50%, -100%)` with a 6px gap, so the button's own box sits wholly
 * above the frame rather than straddling it, and the clearance does not depend
 * on the box's size. `EditorScreen.stories` asserts the no-overlap geometrically
 * in a real browser; jsdom lays nothing out and cannot see a collision.
 */
.ms-editor__layer-delete {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  border: 2px solid var(--rdp-ui-white);
  border-radius: 50%;
  /* Red, not ink: this is the only destructive control on the canvas, and the
     handles beside it are ink. A coach reaching for a corner must not find
     this by accident. */
  background: var(--rdp-ui-red-700);
  color: var(--rdp-ui-white);
  font: 700 14px / 1 var(--rdp-ui-font-body);
  cursor: pointer;
  pointer-events: auto;
}

.ms-editor__layer-delete::before {
  content: "";
  position: absolute;
  inset: -10px;
}

.ms-editor__layer-delete:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--rdp-ui-blue-100);
}

.ms-editor__handle {
  position: absolute;
  width: 12px;
  height: 12px;
  background: var(--rdp-ui-white);
  border: 2px solid var(--rdp-ui-ink);
  border-radius: 50%;
  pointer-events: auto;
}

/*
 * The visual handle is 12px and a fingertip is not. The hit area is grown to
 * 44px with a transparent pseudo-element rather than by making the dot
 * bigger, so the target is thumb-sized while the chrome stays out of the way
 * of the artwork the coach is trying to look at.
 */
.ms-editor__handle::before {
  content: "";
  position: absolute;
  inset: -16px;
}

.ms-editor__handle--nw {
  left: -6px;
  top: -6px;
  cursor: nwse-resize;
}
.ms-editor__handle--ne {
  right: -6px;
  top: -6px;
  cursor: nesw-resize;
}
.ms-editor__handle--se {
  right: -6px;
  bottom: -6px;
  cursor: nwse-resize;
}
.ms-editor__handle--sw {
  left: -6px;
  bottom: -6px;
  cursor: nesw-resize;
}

/* --- Share section ------------------------------------------------------ */

/*
 * Two cards, one per artifact a coach leaves with: a link they send, and a
 * code they print. The section is deliberately not a list of buttons -- the
 * two things behave differently (one is permanent once printed) and are
 * separated so that difference is visible before it matters.
 *
 * Every value below is an @rdp/ui token. The rules this replaced reached for
 * `--rdp-space-4` and `--rdp-color-danger`, which `tokens.css` does not
 * define, so they had been rendering on their hard-coded fallbacks since they
 * were written.
 */
.ms-dashboard__share {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--rdp-ui-s-lg);
}

.ms-share__intro {
  margin: 0;
  max-width: 46ch;
  color: var(--rdp-ui-grey-600);
}

.ms-share__card {
  width: 100%;
  padding: var(--rdp-ui-s-lg);
  border: var(--rdp-ui-hairline);
  border-radius: var(--rdp-ui-r-lg);
  background: var(--rdp-ui-white);
}

/* The JSX comment above these two cards already calls them one artifact
   split in half -- a link a coach sends, a code they print. The dashed edge
   makes that literal on the half that actually leaves as ink: "tear here"
   is what happens to it. (A punched-notch version was cut here -- it needs
   a CSS mask trick this environment can't render to check, so it stays a
   flat dashed line rather than shipping unverified.) */
.ms-share__card--ticket {
  border-top: 2px dashed var(--rdp-ui-grey-200);
}

/*
 * An eyebrow, not a heading in the brand's display voice. `#ms-app h3` is
 * Anton uppercase at 20px (frontend.css), which next to the link below would
 * give a card's label the same weight as its content. The section spends its
 * one loud element on the campaign's name, so everything framing it stays
 * quiet -- the same reasoning that keeps `.ms-dashboard h2` in Archivo.
 */
#ms-app .ms-share__card-title {
  margin: 0 0 var(--rdp-ui-s-md);
  font-family: var(--rdp-ui-font-body);
  font-size: var(--ms-type-caption);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--rdp-ui-grey-600);
}

.ms-share__card-note {
  margin: 0 0 var(--rdp-ui-s-md);
  max-width: 46ch;
}

/*
 * The section's one deliberate piece of emphasis.
 *
 * The address splits so the campaign's own name carries the size and the
 * routing around it stays out of the way: that name is the half a coach says
 * out loud, writes on a whiteboard, and recognizes as theirs.
 *
 * NOT the display face, and this is a correctness constraint rather than a
 * taste one. Brand guide §3 admits Anton only in uppercase, and a slug is
 * part of a URL somebody will retype from a printed page -- rendering
 * `spring-2026` as `SPRING-2026` would show them an address that is not the
 * one in the href. Archivo at its heaviest weight carries the emphasis
 * instead, lowercase and honest.
 */
.ms-share__link {
  display: block;
  margin-bottom: var(--rdp-ui-s-md);
  color: var(--rdp-ui-ink);
  text-decoration: none;
  /* A slug can be long and has no spaces to break on. Without this the card
	   scrolls the whole dashboard sideways on a phone. */
  overflow-wrap: anywhere;
}

.ms-share__link-lead {
  display: block;
  font-size: var(--ms-type-small);
  line-height: 1.4;
  color: var(--rdp-ui-grey-600);
}

.ms-share__link-slug {
  display: block;
  /* Capped at the h2 step, which is where `.ms-dashboard h1` sets the
	   campaign name. Larger would make a section out-shout the page it sits
	   in. */
  font-size: clamp(var(--ms-type-h3), 5vw, var(--rdp-ui-scale-h2));
  font-weight: 700;
  line-height: 1.1;
}

/* The underline is the affordance, and it belongs on the name rather than on
   the routing above it. Keyboard focus gets a real ring, not just the
   underline -- see `:focus-visible` below. */
.ms-share__link:hover .ms-share__link-slug,
.ms-share__link:focus-visible .ms-share__link-slug {
  text-decoration: underline;
}

.ms-share__link:focus-visible {
  outline: var(--rdp-ui-border-emphasis);
  outline-offset: var(--rdp-ui-s-xs);
  border-radius: var(--rdp-ui-r-sm);
}

.ms-share__panel {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--rdp-ui-s-md);
}

/* Capped rather than full-bleed: the preview is a proof, not the artwork, and
   a 1024px code filling the content column reads as a mistake. */
.ms-share__preview {
  width: min(280px, 100%);
}

.ms-share__preview svg {
  display: block;
  width: 100%;
  height: auto;
}

/* Shared by both cards: the link's Copy/Share pair and the code's two
   downloads. Wrapping rather than shrinking, so a narrow phone stacks the
   buttons at full label width instead of truncating them. */
.ms-share__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rdp-ui-s-12);
}

.ms-share__hint,
.ms-share__notice {
  margin: 0;
  max-width: 46ch;
  font-size: var(--ms-type-small);
  color: var(--rdp-ui-grey-600);
}

.ms-share__error {
  margin: 0;
  max-width: 46ch;
  color: var(--rdp-ui-red-700);
}

/* Desktop ---------------------------------------------------------------
 *
 * The second breakpoint. Until this block there was exactly one in the whole
 * sheet -- 780px -- so a 2560px monitor got the same layout as a 800px
 * laptop, only with more empty margin either side of it. Raising the caps to
 * --ms-container-wide is half the fix; this is the other half, because a
 * layout that only stretches is not using the room, it is diluting it.
 *
 * Every rule here spends the extra width on the workspace rather than the
 * gutters, and every one is additive: below 1200px nothing in this block
 * applies, so the phone and tablet layouts above are untouched by definition
 * rather than by care. `min-width` queries only, for that reason.
 *
 * The literal 1200px must stay in step with --ms-bp-desktop in frontend.css;
 * a custom property cannot be used in a media query, so the token is
 * documentation and this is the copy that runs.
 */
@media (min-width: 1200px) {
  /* A wider feed rail. At `minmax(240px, 320px)` the tiles beside the editor
     were the narrowest thing on a wide screen -- the one place a coach picks
     the next garment from, squeezed so the canvas could have room it was
     already capped out of using. */
  .ms-studio--split {
    grid-template-columns: minmax(280px, 380px) 1fr;
  }

  /* Nothing for the canvas here on purpose. The base `max-height: 56vh` is a
     phone budget -- the sheet is a bottom sheet there, capped at 40vh, and the
     two had to fit one viewport -- and the 780px block already lifts it to
     `max-height: 100%` of the stage. A 70vh rule at this width would be a
     smaller number than the one it replaced, shrinking the garment on exactly
     the screens this block exists to give it more room on. */

  /* Two rem more sidebar. Section labels stop being the reason the column is
     as wide as it is, and the campaign switcher gets room for a real store
     name rather than an ellipsis. `flex-basis` alone: the `flex: 0 0 14rem`
     this overrides is inside the 780px block, and restating the grow/shrink
     pair here would just be two more numbers to keep in step. */
  .ms-dashboard__sidebar {
    flex-basis: 16rem;
  }
}

/* A min-width floor the columns flow around, replacing frontend.css's fixed
 * 1/2/3/4-column breakpoints for this grid.
 *
 * Fixed at 320px, not `minmax(320px, 1fr)`: `1fr` let a short results page
 * (a handful of blanks on a wide screen, or one category filtered down to
 * two) grow its few cards to fill the leftover row space, so the same card
 * read a different size depending on how many other cards happened to be
 * showing beside it. `minmax(min(320px, 100%), 320px)` fixes the card at
 * exactly 320px whenever the viewport has room for it, and only lets it
 * shrink (both bounds move together) once 320px itself would overflow a
 * narrow phone. Real per-row column count still falls out of the grid's own
 * layout -- `container width ÷ 320px`, floored -- with no JS measuring a
 * container to compute it. `auto-fit`/`auto-fill` no longer differ once
 * neither bound is a stretchy `fr`: an unused reserved track and a collapsed
 * one both render as nothing, so `auto-fit` stays for the smaller diff
 * against the old rule.
 *
 * Scoped to `.ms-feed__grid` rather than added to
 * `.rdp-responsive-product-grid` in frontend.css, which would have been the
 * obvious place and is wrong: the shop uses that same class and stays capped
 * at --ms-container, so this would cut the shop's 1080px grid down too -- a
 * regression on a screen this change is not about.
 *
 * `:not(.ms-feed--compact)` because the rail is the same grid at a fraction
 * of the width, where the column count is 1 and must stay 1 -- overridden
 * separately below. No media query needed: the three-class specificity
 * already beats frontend.css's single-class rules at every one of their
 * breakpoints, so this one rule replaces all four.
 */
.ms-feed:not(.ms-feed--compact) .ms-feed__grid {
  grid-template-columns: repeat(auto-fit, minmax(min(320px, 100%), 320px));
}

