/* =========================
   Studio Bernhard Shape Generator
   Refined UI
   no presets
   ========================= */

#sb-sg-app{
  --sb-sg-admin-offset: 0px;
  --sb-sg-top-gap: 18px;
  --sb-sg-bottom-gap: 18px;

  /* Transitional (Phase 1): these neutral tokens now reference the shared
     --pf-* tokens from potterform-design-system.css. Each --pf-* fallback
     below is the exact literal this token held before Phase 1, so the
     computed value is unchanged even if the shared stylesheet is absent. */
  --sb-sg-bg: var(--pf-color-background, #ffffff);
  --sb-sg-card: var(--pf-color-surface, #fbfbfb);
  --sb-sg-card-border: var(--pf-color-border, rgba(17,17,17,0.08));
  --sb-sg-card-border-strong: var(--pf-color-border-strong, rgba(17,17,17,0.12));
  --sb-sg-text: var(--pf-color-text, #111111);
  --sb-sg-text-soft: var(--pf-color-text-soft, rgba(17,17,17,0.72));
  --sb-sg-text-faint: var(--pf-color-text-faint, rgba(17,17,17,0.52));
  --sb-sg-line: var(--pf-color-line, rgba(17,17,17,0.08));
  --sb-sg-shadow: 0 10px 30px rgba(0,0,0,0.05);
  --sb-sg-shadow-soft: 0 4px 16px rgba(0,0,0,0.04);
  --sb-sg-radius: 14px;
  --sb-sg-radius-sm: 10px;
  --sb-sg-input-h: 40px;

  /* Phase 1A: the historical orange --sb-sg-accent token is retired. Every
     former consumer now references the correct semantic --pf-* token
     directly (primary/focus-ring/warning/shape/error), each with a literal
     fallback matching its pre-migration computed value. See
     potterform-design-system.css for the token definitions. */

  /* Transitional (Phase 1): references the shared --pf-* primary tokens,
     with the prior literal value kept as the var() fallback. Now the
     Generator's general primary-action colour, not scoped to a single
     button - see ensureUiAccentStyles() and the component rules below. */
  --sb-sg-plum: var(--pf-color-primary, #6E3B5B);
  --sb-sg-plum-deep: var(--pf-color-primary-hover, #5A304C);

  /* UI-1 visual completion pass: the exact two alternating section
     backgrounds from the current production PotterForm landing site
     (PotterForm Website 2.0/site/assets/css/style.css - --pf-canvas is
     .section's default background, --pf-surface is .section-alt's, and the
     two literally alternate down the real landing page). UI-2A centralized
     these as --pf-canvas/--pf-surface in potterform-design-system.css, so
     these local aliases now reference the shared tokens (with the original
     literals kept only as the var() fallback) instead of duplicating the
     hex values. --pf-color-background/--pf-color-surface remain a separate,
     cooler near-white pair unrelated to this warm landing palette. Each
     consumer below references the alias, never the hex directly. */
  --sb-sg-landing-canvas: var(--pf-canvas, #FBF8F4);
  --sb-sg-landing-surface: var(--pf-surface, #F3EEE9);

  /* UI-1b staging correction: a modest left-edge breathing margin so the
     control panel no longer sits glued to the viewport/app edge - reuses
     the existing --sb-sg-top-gap value rather than inventing a new number,
     since that token already defines this exact scale of outer spacing
     (see #sb-sg-app's own padding below, and the max-width:640px reduction
     further down). */
  --sb-sg-left-gutter: var(--sb-sg-top-gap);

  /* UI-1c: centralized resizable-control-panel constants (see the
     accompanying architecture audit). Every consumer below references one
     of these tokens - the literals 368/290/640/400/60 each appear exactly
     once, here. --sb-sg-controls-default-width is the exact pre-resize
     .sb-sg-controls width (368px) - unchanged default experience when
     nothing has been dragged. --sb-sg-controls-min-width (290px) is the
     exact pre-resize desktop min-width, already the floor this codebase
     enforces today. --sb-sg-controls-max-width (640px) and
     --sb-sg-canvas-resize-floor (400px) are staging-validation candidates,
     not permanent product truths (see the audit's own ambiguity notes) -
     changing either only requires editing this one place.
     --sb-sg-tool-rail-width (60px) replaces the three literal 60px/32px
     occurrences .sb-sg-tool-rail used to repeat.
     --sb-sg-controls-user-width is the one authoritative, JS-updated width
     value - initialized to the default, read by both the normal-mode and
     fullscreen-mode .sb-sg-controls resize rules further down, never
     written independently to canvas (canvas keeps consuming the remainder
     through its own existing flex:1 1 auto). */
  --sb-sg-controls-default-width: 368px;
  --sb-sg-controls-min-width: 290px;
  --sb-sg-controls-max-width: 640px;
  --sb-sg-canvas-resize-floor: 400px;
  --sb-sg-tool-rail-width: 60px;
  --sb-sg-controls-user-width: var(--sb-sg-controls-default-width);
}

body.admin-bar #sb-sg-app{
  --sb-sg-admin-offset: 32px;
}

@media screen and (max-width: 782px){
  body.admin-bar #sb-sg-app{
    --sb-sg-admin-offset: 46px;
  }
}

/* Box sizing */
#sb-sg-app,
#sb-sg-app .sb-sg-layout,
#sb-sg-app *,
#sb-sg-app *::before,
#sb-sg-app *::after{
  box-sizing:border-box;
}

#sb-sg-app,
#sb-sg-app .sb-sg-layout{
  overflow:visible;
}

#sb-sg-app{
  color:var(--sb-sg-text);
  font-size:14px;
  line-height:1.45;
  /* UI-1 visual completion pass: the whole mount reads as one continuous
     warm PotterForm surface - including the gap between the controls column
     and the canvas - rather than leaving the WordPress page's own
     background show through around two independently-coloured islands. */
  background:var(--sb-sg-landing-canvas);
  /* UI-1b staging correction: the one place this outer gutter is applied -
     narrowest possible selector (the mount root itself), so the control
     panel gets breathing room from the viewport/app edge without adding a
     second layer of padding inside .sb-sg-controls or any group/card (see
     the accompanying report). box-sizing:border-box (already applied to
     #sb-sg-app above) keeps this from changing any explicitly-set outer
     width. Nothing in the :fullscreen rule below overrides padding, so this
     single declaration also covers fullscreen automatically - verified,
     not assumed. */
  padding-left:var(--sb-sg-left-gutter);
}

/* Layout */
#sb-sg-app .sb-sg-layout{
  display:flex;
  gap:14px;
  align-items:flex-start;
}

/* Controls */
#sb-sg-app .sb-sg-controls{
  width:368px;
  max-width:42vw;
  min-width:290px;
  flex:0 0 auto;

  /* Matches .sb-sg-canvas's sticky/height treatment so both columns share the
     same top boundary and never drift apart - the controls column's own
     content scrolls internally via overflow-y instead of moving the document
     past the canvas's sticky top edge. Reset for the stacked layout inside
     @media (max-width:900px) below. */
  position:sticky;
  top:calc(var(--sb-sg-top-gap) + var(--sb-sg-admin-offset));
  height:calc(100vh - var(--sb-sg-admin-offset) - var(--sb-sg-top-gap) - var(--sb-sg-bottom-gap));
  height:calc(100dvh - var(--sb-sg-admin-offset) - var(--sb-sg-top-gap) - var(--sb-sg-bottom-gap));
  /* Matches .sb-sg-canvas's own min-height:520px floor - without this, a
     short viewport (where the calc() above resolves below 520px) would let
     canvas stay at 520px while controls shrank below it, breaking the
     shared bottom boundary. Reset wherever canvas's own floor is reset,
     below and in the mobile-landscape block. */
  min-height:520px;
  overflow-y:auto;
  overflow-x:hidden;
  overscroll-behavior-y:contain;
  -webkit-overflow-scrolling:touch;
  touch-action:pan-y;

  /* UI-1 visual completion pass: explicit rather than relying on
     transparency over #sb-sg-app's own background - this element is
     position:sticky and scrolls its own content, so an explicit background
     avoids any paint-order ambiguity while scrolling. Same landing surface
     as the mount/canvas, so the panel reads as one continuous PotterForm
     section rather than a nested system-settings panel. */
  background:var(--sb-sg-landing-canvas);
}

/* UI-1 visual completion pass: plain white (the same literal already used
   throughout this file for inputs/selects/cards), not --sb-sg-card
   (#fbfbfb) - against the new warm landing-canvas page background, the old
   cool near-white card read as a mismatched grey box; true white gives the
   same restrained lift with no colour-temperature clash, and needs no new
   token.
   UI-1b staging correction: the boundary itself is now a restrained Plum
   tint (var(--pf-color-primary-border)) instead of the neutral grey
   --sb-sg-card-border - the exact existing token .sb-sg-add-to-compare-btn
   already uses for its own subtle Plum outline, reused here rather than
   inventing a second Plum-border value. Still a plain 1px line, not a
   strong outline - white fill is unchanged. */
#sb-sg-app .sb-sg-group{
  margin-bottom:9px;
  padding:11px 12px 7px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius);
  background:#fff;
  box-shadow:none;
}

#sb-sg-app .sb-sg-shape-group{
  border-color:var(--pf-color-primary-border, rgba(110,59,91,0.28));
  box-shadow:var(--sb-sg-shadow-soft);
}

#sb-sg-app .sb-sg-secondary-group{
  background:rgba(255,255,255,.78);
}

#sb-sg-app .sb-sg-title{
  margin:0 0 10px 0;
  font-size:14px;
  font-weight:600;
  letter-spacing:0.01em;
  color:var(--sb-sg-text);
}

/* UI-3: card-level header treatment (icon + larger title) for the Shape and
   Clay estimate cards specifically - a second class layered on top of
   .sb-sg-title above, not a change to that shared rule, since .sb-sg-title
   is also combined with .sb-sg-subtitle elsewhere (Rim/Foot/Advanced/Profile
   style/pointInspectorName) where this flex+icon layout must not apply. */
#sb-sg-app .sb-sg-card-title{
  display:flex;
  align-items:center;
  gap:8px;
  font-size:15px;
}

/* UI-7: 24x24 is the one consistent size for every sidebar icon (Shape/
   Clay headers, Organic/Faceted, Rim/Foot/Shrinkage, Sharp shoulder/belly -
   see .sb-sg-bool-icon below for the second, identically-sized instance).
   Each icon's own viewBox/path data is unchanged (still authored on a
   0-20 grid); stroke-width was raised from 1.4 to ~1.67 in that same grid
   so it renders as a true ~2px stroke once scaled up to this 24px box -
   see each SB_SG_*_ICON_SVG constant. */
#sb-sg-app .sb-sg-card-icon{
  width:24px;
  height:24px;
  flex:0 0 auto;
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-controls label{
  display:block;
  margin:8px 0;
  font-size:13px;
  font-weight:500;
  color:var(--sb-sg-text);
}

/* Primary card label: reuses .sb-sg-title's weight/colour so a bold
   first label can stand in for a removed redundant card heading.
   Opt-in only (applied to specific label-text spans), never global.
   UI-1: raised from a small technical-label size to read as a real local
   section heading for "Project name"/"Shape name" (see buildProjectGroupMarkup()/
   buildShapeActionsGroupMarkup() in app.js) - still a plain <span> inside the
   real <label>, never a fake heading element. display:block plus its own
   margin-bottom gives it breathing room from the input below, beating the
   parent .sb-sg-controls label's own margin/font-size by class specificity. */
#sb-sg-app .sb-sg-label-primary{
  display:block;
  margin-bottom:6px;
  font-size:15px;
  font-weight:600;
  color:var(--sb-sg-text);
}

/* Compact read-only nominal reference; editing lives in Top's inspector. */
#sb-sg-app .sb-sg-total-height-label{
  display:block;
  font-size:13px;
  font-weight:600;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-total-height-unit{
  color:var(--sb-sg-text-soft);
  font-size:12px;
  font-weight:400;
}

/* Rows */
#sb-sg-app .sb-sg-inline{
  display:flex;
  align-items:center;
  gap:10px;
  min-width:0;
}

#sb-sg-app .sb-sg-val{
  min-width:4ch;
  padding:0 2px;
  text-align:right;
  display:inline-block;
  flex:0 0 auto;
  font-size:12px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-sub{
  padding-top:6px;
}

#sb-sg-app .sb-sg-sub-top{
  margin-top:10px;
}

/* Text input */
#sb-sg-app input[type="text"]{
  width:100%;
  min-height:var(--sb-sg-input-h);
  padding:10px 12px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  background:#fff;
  color:var(--sb-sg-text);
  font:inherit;
  font-family:var(--pf-font-family);
  transition:border-color .18s ease, box-shadow .18s ease, background .18s ease;
}

#sb-sg-app input[type="text"]:focus{
  outline:none;
  border-color:var(--pf-color-focus-ring, rgba(110,59,91,0.35));
  box-shadow:0 0 0 3px var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
}

#sb-sg-app .sb-sg-project-input{
  flex:1;
  min-width:0;
}

/* Track C3a: selected-point numeric inspector. Number inputs reuse the
   same border/radius/focus-ring language as input[type="text"] above -
   deliberately not a third distinct input style. */
#sb-sg-app .sb-sg-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;
}

#sb-sg-app .sb-sg-point-inspector-fields{
  display:flex;
  flex-direction:column;
  gap:2px;
}

#sb-sg-app .sb-sg-total-height-reference{
  display:block;
  margin-top:2px;
  color:var(--sb-sg-text);
  font-size:14px;
  font-weight:600;
}

/* One fixed contextual-inspector working box for both representations.
   278px is derived from the actual tallest approved stacked state: selected
   heading (~32) + two labelled 40px fields (~136) + Segment pill (~49) +
   Corner pill (~39) + existing inter-item gaps, plus an 8px separation from
   the Add action. Shorter states retain the unused space. overflow:visible
   (never auto/scroll) leaves the main controls column as the sole scroll
   owner and prevents selection-driven reflow. */
#sb-sg-app .sb-sg-point-inspector{
  height:278px;
  min-height:278px;
  flex:0 0 278px;
  overflow:visible;
}

#sb-sg-app .sb-sg-advanced-profile-inspector-host{
  display:grid;
  grid-template-rows:minmax(0, 1fr) 32px;
  height:100%;
  min-height:0;
}

/* The primary editing card stays dense enough to remain visible as one
   workflow while retaining 44px targets for its interactive controls. */
#sb-sg-app .sb-sg-shape-group{
  padding:10px 12px;
}

#sb-sg-app .sb-sg-shape-group .sb-sg-card-title{
  margin-bottom:4px;
}

#sb-sg-app .sb-sg-shape-group .sb-sg-sub{
  padding-top:4px;
}

#sb-sg-app .sb-sg-shape-group .sb-sg-sub-top{
  margin-top:4px;
}

#sb-sg-app .sb-sg-shape-group label{
  margin:5px 0;
}

#sb-sg-app .sb-sg-shape-group .sb-sg-toggle.sb-sg-radio-card{
  min-height:44px;
  padding:7px 10px;
}

#sb-sg-app .sb-sg-shape-features{
  display:grid;
  grid-template-columns:minmax(0, 1fr);
  gap:2px;
}

#sb-sg-app .sb-sg-shape-feature{
  min-width:0;
}

#sb-sg-app #lidGalleryFeature{
  grid-column:1 / -1;
}

#sb-sg-app .sb-sg-shape-feature .sb-sg-bool-icon{
  width:20px;
  height:20px;
}

/* Production UI Engineer task (2026-08-24): the one always-present
   trigger for Advanced Profile, directly under the selected-point
   inspector - see app.js's own buildAddProfilePointButtonMarkup(). */
#sb-sg-app .sb-sg-add-profile-point-btn{
  width:100%;
  margin-top:8px;
  min-height:var(--sb-sg-input-h);
  border:1px solid var(--sb-sg-card-border-strong);
  background:var(--sb-sg-bg);
  color:var(--sb-sg-plum-deep);
  border-radius:var(--sb-sg-radius-sm);
  font-size:13px;
  font-weight:600;
  cursor:pointer;
}

#sb-sg-app .sb-sg-add-profile-point-btn:hover:not(:disabled){
  border-color:var(--sb-sg-plum);
}

#sb-sg-app .sb-sg-add-profile-point-btn:disabled{
  opacity:.5;
  cursor:not-allowed;
}

#sb-sg-app .sb-sg-v2-product-boundary{
  margin:8px 0 0;
  padding:7px 9px;
  border-radius:var(--sb-sg-radius-sm);
  background:var(--pf-color-primary-soft, rgba(110,59,91,0.07));
  color:var(--sb-sg-text-soft);
  font-size:11px;
  line-height:1.4;
}

#sb-sg-app .sb-sg-inspector-input{
  width:100%;
  min-height:var(--sb-sg-input-h);
  padding:10px 12px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  background:#fff;
  color:var(--sb-sg-text);
  font:inherit;
  font-family:var(--pf-font-family);
  transition:border-color .18s ease, box-shadow .18s ease, background .18s ease;
}

#sb-sg-app .sb-sg-inspector-input:focus{
  outline:none;
  border-color:var(--pf-color-focus-ring, rgba(110,59,91,0.35));
  box-shadow:0 0 0 3px var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
}

/* Clay Estimate selects (Skill level, Trimming reserve) - .sb-sg-clay-select
   is already the shared class for both equivalent native selects (defined in
   sb-shape-clay.js). Adds the same calm neutral/Plum visual language used
   elsewhere in the app; no JS, no attempt to make it look like a disclosure.
   The native menulist arrow is unreliable/low-contrast across browsers on a
   plain white background, so it is normalized off and replaced with a fixed,
   clearly-visible scoped CSS chevron (a background-image on the select
   itself - no wrapper element, no separate node, no accessibility impact).
   The chevron is a filled triangle (not a stroke) at the same neutral grey
   used by the Saved Variants disclosure arrow (var(--sb-sg-text-faint),
   rendered on white ~ #838383), so both closed-state arrows read as the
   same shape, weight and colour. */
#sb-sg-app .sb-sg-clay-select{
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  background-color:#fff;
  color:var(--sb-sg-text);
  font:inherit;
  font-family:var(--pf-font-family);
  padding:0 34px 0 10px;
  appearance:none;
  -webkit-appearance:none;
  -moz-appearance:none;
  background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M2 2h8L6 7z' fill='%23838383'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:right 12px center;
  background-size:11px 7px;
  transition:border-color .18s ease, box-shadow .18s ease;
}

#sb-sg-app .sb-sg-clay-select:hover{
  border-color:var(--pf-color-focus-ring, rgba(110,59,91,0.35));
}

#sb-sg-app .sb-sg-clay-select:focus{
  outline:none;
  border-color:var(--pf-color-focus-ring, rgba(110,59,91,0.35));
  box-shadow:0 0 0 3px var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
}

/* UI-4: the Plum-tint override that used to live here (Phase 1A) is now
   declared directly in .sb-sg-clay-result's own base rule in
   sb-shape-clay.js's ensureClayStyles() - that file is the single source of
   truth for every .sb-sg-clay-* rule already, so this cross-file
   specificity workaround was removed rather than left to silently fight a
   second declaration of the same property. */

/* Native controls accent */
#sb-sg-app .sb-sg-controls input[type="range"],
#sb-sg-app .sb-sg-controls input[type="radio"],
#sb-sg-app .sb-sg-controls input[type="checkbox"]{
  accent-color:var(--pf-color-primary, #6E3B5B);
}

/* Native controls keyboard focus (PotterForm Phase 2B.2A1 QA fix): the
   segmented profile/advanced radios, Sharp shoulder/belly, Rim/Foot/
   Shrinkage checkboxes, and every range slider are plain native inputs
   with no prior focus styling anywhere in the app, so keyboard focus on
   them was invisible. Same outline treatment already used by the buttons
   below for a consistent PotterForm ring; :focus-visible keeps it
   keyboard-only so a mouse click still shows no ring. Disabled inputs are
   never focusable, so no separate rule is needed to suppress this on them. */
#sb-sg-app .sb-sg-controls input[type="radio"]:focus-visible,
#sb-sg-app .sb-sg-controls input[type="checkbox"]:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-controls input[type="range"]:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:4px;
}

/* Range */
#sb-sg-app .sb-sg-controls input[type="range"]{
  -webkit-appearance:none;
  appearance:none;
  width:100%;
  min-width:0;
  flex:1 1 auto;
  height:20px;
  padding:0;
  border:0;
  background:transparent;
  box-shadow:none;
}

#sb-sg-app .sb-sg-controls input[type="range"]::-webkit-slider-runnable-track{
  width:100%;
  height:4px;
  border:0;
  border-radius:999px;
  background:linear-gradient(
    to right,
    var(--pf-color-primary, #6E3B5B) 0%,
    var(--pf-color-primary, #6E3B5B) var(--sb-sg-range-progress, 0%),
    var(--pf-color-border, rgba(17,17,17,0.08)) var(--sb-sg-range-progress, 0%),
    var(--pf-color-border, rgba(17,17,17,0.08)) 100%
  );
  box-shadow:none;
}

#sb-sg-app .sb-sg-controls input[type="range"]::-webkit-slider-thumb{
  -webkit-appearance:none;
  appearance:none;
  width:16px;
  height:16px;
  margin-top:-6px;
  border:0;
  border-radius:50%;
  background:var(--pf-color-primary, #6E3B5B);
  box-shadow:none;
}

#sb-sg-app .sb-sg-controls input[type="range"]::-moz-range-track{
  width:100%;
  height:4px;
  border:0;
  border-radius:999px;
  background:var(--pf-color-border, rgba(17,17,17,0.08));
  box-shadow:none;
}

#sb-sg-app .sb-sg-controls input[type="range"]::-moz-range-progress{
  height:4px;
  border-radius:999px;
  background:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-controls input[type="range"]::-moz-range-thumb{
  width:16px;
  height:16px;
  border:0;
  border-radius:50%;
  background:var(--pf-color-primary, #6E3B5B);
  box-shadow:none;
}

/* Toggles */
#sb-sg-app .sb-sg-toggle{
  display:flex;
  align-items:center;
  gap:10px;
  margin-top:8px;
  font-size:13px;
  font-weight:500;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-toggle input{
  margin:0;
  flex:0 0 auto;
}

#sb-sg-app .sb-sg-toggle span{
  line-height:1.35;
}

#sb-sg-app .sb-sg-radio-row{
  display:flex;
  align-items:center;
  gap:10px;
  margin:0;
}

/* UI-4: shared pill chrome for every radio/checkbox styled as a clean pill
   control (Organic/Faceted, each Curve/Line pair, Sharp shoulder/belly) -
   .sb-sg-pill carries the shape/colour/state logic once; a second,
   purpose-specific class alongside it (.sb-sg-radio-card / .sb-sg-radio-pill
   / .sb-sg-sharp-row below) only adds layout (size, padding,
   justify-content). The underlying input keeps its existing type/name/
   value/checked/id/behavior completely unchanged - only visually hidden
   (app.js adds the existing .sb-sg-sr-only class directly on the input), so
   screen readers and keyboard Tab/Space/Enter still see and operate a real
   radio/checkbox. :has() degrades gracefully on very old browsers to an
   unhighlighted-but-still-functional control, never a broken one. */
#sb-sg-app .sb-sg-pill{
  position:relative;
  border:1px solid var(--sb-sg-card-border);
  border-radius:999px;
  background:#fff;
  color:var(--sb-sg-text-soft);
  cursor:pointer;
  transition:border-color .16s ease, background-color .16s ease, color .16s ease;
}

/* UI-6: staging feedback was that the light Plum tint here read as too
   muted next to the bold OFF/ON toggles - now a full, solid Plum fill with
   white text, exactly the same active-state language as
   .sb-sg-bool-toggle's own ON segment, so every "this is the selected
   option" control in the Shape card reads with the same weight. */
#sb-sg-app .sb-sg-pill:has(input:checked){
  border-color:var(--pf-color-primary, #6E3B5B);
  background:var(--pf-color-primary, #6E3B5B);
  color:#fff;
  font-weight:600;
}

#sb-sg-app .sb-sg-pill:has(input:focus-visible){
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-pill .sb-sg-card-icon{
  color:currentColor;
}

/* Large pill: Organic/Faceted (Profile style). */
#sb-sg-app .sb-sg-toggle.sb-sg-radio-card{
  margin:0;
  min-height:44px;
  padding:10px 16px;
  justify-content:center;
}

/* Compact pill: each Curve/Line transition-mode option. */
#sb-sg-app .sb-sg-toggle.sb-sg-radio-pill{
  padding:6px 14px;
  font-size:12px;
}

/* UI-7 final revision: shared OFF/ON pill-toggle row for every boolean
   control (Sharp shoulder/belly, Rim shaping, Foot ring, Shrinkage
   compensation - the last three via sb-shape-shrinkage.js's own local
   duplicate of buildBoolToggleRowMarkup() for Shrinkage, app.js for Rim/
   Foot). Both OFF and ON are always visible; :has(input:checked) decides
   which one is "selected" and gets the full Plum fill - exactly the same
   selected/unselected language .sb-sg-pill already uses for Curve/Line, so
   an OFF/ON row and a Curve/Line row read as the same control family. The
   hidden input (.sb-sg-sr-only) is still a real, fully operable checkbox -
   only its native box is no longer painted. */
/* UI-8: #sb-sg-app .sb-sg-controls label{display:block; margin:11px 0; ...}
   (see above) has higher specificity (1 id + 1 class + 1 element) than a
   bare #sb-sg-app .sb-sg-bool-row (1 id + 1 class) and was silently winning
   for every one of these rows, since each is itself a <label> inside
   .sb-sg-controls - display:flex was being overridden back to display:block
   (defeating justify-content:space-between entirely, so the OFF/ON control
   never actually right-aligned) and margin:11px 0 was overriding the
   intended 12px row gap. Individual Curve/Line/Organic/Faceted pills never
   showed this, since each has only one visible child and so never needed
   flex distribution to look correct - .sb-sg-bool-row is the first label
   here that actually depends on being a flex container. Scoping through
   the existing .sb-sg-group ancestor (present for all five rows) adds a
   second class, safely outranking the generic rule on class-count alone,
   regardless of source order. */
#sb-sg-app .sb-sg-group .sb-sg-bool-row{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  width:100%;
  min-height:44px;
  margin:2px 0;
  cursor:pointer;
}

#sb-sg-app .sb-sg-group .sb-sg-bool-row .sb-sg-bool-row-main{
  display:flex;
  align-items:center;
  gap:8px;
  min-width:0;
  margin:0;
  cursor:pointer;
}

#sb-sg-app .sb-sg-bool-row-label{
  font-size:13px;
  font-weight:500;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-bool-icon{
  width:24px;
  height:24px;
  flex:0 0 auto;
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-group .sb-sg-bool-row .sb-sg-bool-toggle{
  position:relative;
  display:flex;
  align-items:center;
  gap:4px;
  flex:0 0 auto;
  margin:0;
  cursor:pointer;
}

#sb-sg-app .sb-sg-bool-inline-action{
  flex:0 0 auto;
  min-width:44px;
  min-height:36px;
  margin-left:auto;
  padding:5px 9px;
  border:1px solid var(--sb-sg-card-border-strong, #E2E2E7);
  border-radius:var(--sb-sg-radius-sm, 8px);
  background:var(--sb-sg-bg, #fff);
  color:var(--pf-color-primary, #6E3B5B);
  font:inherit;
  font-size:12px;
  font-weight:650;
  cursor:pointer;
}

#sb-sg-app .sb-sg-bool-inline-action:hover{
  border-color:var(--pf-color-primary, #6E3B5B);
  background:var(--pf-color-primary-soft, rgba(110,59,91,.07));
}

#sb-sg-app .sb-sg-bool-inline-action:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-bool-inline-action[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-bool-off,
#sb-sg-app .sb-sg-bool-on{
  min-height:30px;
  display:inline-flex;
  align-items:center;
  padding:0 9px;
  border:1px solid var(--sb-sg-card-border-strong, #E2E2E7);
  border-radius:999px;
  background:#fff;
  font-size:13px;
  font-weight:600;
  color:var(--sb-sg-text, #374151);
  transition:border-color .16s ease, background-color .16s ease, color .16s ease;
}

/* Whichever segment matches the checkbox's current state reads as the
   strong, selected Plum fill - OFF when unchecked, ON when checked. Neither
   is a "lesser" default; both get the identical full treatment. */
#sb-sg-app .sb-sg-bool-toggle:not(:has(input:checked)) .sb-sg-bool-off,
#sb-sg-app .sb-sg-bool-toggle:has(input:checked) .sb-sg-bool-on{
  border-color:var(--pf-color-primary, #6E3B5B);
  background:var(--pf-color-primary, #6E3B5B);
  color:#fff;
}

#sb-sg-app .sb-sg-bool-toggle:has(input:focus-visible){
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
  border-radius:999px;
}

#sb-sg-app .sb-sg-small-toggle{
  display:flex;
  align-items:center;
  gap:10px;
  margin:0;
}

#sb-sg-app .sb-sg-small-toggle span{
  font-size:12px;
}

/* Buttons */
#sb-sg-app .sb-sg-project-actions{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:8px;
  margin-top:8px;
}

#sb-sg-app .sb-sg-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:44px;
  padding:12px 14px;
  border-radius:12px;
  text-decoration:none;
  cursor:pointer;
  font:inherit;
  font-size:13px;
  font-weight:600;
  letter-spacing:0.01em;
  transition:transform .14s ease, opacity .14s ease, background .14s ease, border-color .14s ease, box-shadow .14s ease, color .14s ease;
}

#sb-sg-app .sb-sg-btn-primary{
  border:1px solid var(--pf-color-primary, #6E3B5B);
  background:var(--pf-color-primary, #6E3B5B);
  color:#fff;
  box-shadow:0 10px 24px var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
}

#sb-sg-app .sb-sg-btn-primary:hover{
  background:var(--pf-color-primary-hover, #5A304C);
  border-color:var(--pf-color-primary-hover, #5A304C);
}

#sb-sg-app .sb-sg-btn-secondary{
  border:1px solid var(--sb-sg-card-border-strong);
  background:#fff;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-btn-secondary:hover{
  border-color:var(--pf-color-primary-border, rgba(110,59,91,0.28));
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-btn:active{
  transform:translateY(1px);
}

#sb-sg-app .sb-sg-export-btn:disabled{
  cursor:default;
  transform:none;
  box-shadow:none;
}

#sb-sg-app .sb-sg-export-note{
  margin-top:8px;
  font-size:11px;
  color:var(--sb-sg-text-faint);
}

/* Phase E3.1: setCompareStatus() now applies .sb-sg-alert-warning/
   .sb-sg-alert-info (below) instead of this modifier - #compareStatus
   moved out of the legacy Saved-on-this-device disclosure into the always-
   visible independent Compare summary, so it reuses the same alert-banner
   presentation the rest of the Generator's status regions already use.
   This rule is kept, unused, rather than removed, since nothing else in
   this stylesheet currently references .sb-sg-status-warning and deleting
   an otherwise-harmless rule is outside this phase's narrow scope. */
#sb-sg-app .sb-sg-export-note.sb-sg-status-warning{
  padding:5px 7px;
  background:var(--pf-color-warning-soft, rgba(169,77,36,0.08));
  border-left:2px solid var(--pf-color-warning-border, rgba(169,77,36,0.35));
  border-radius:0 4px 4px 0;
  color:var(--pf-color-warning, #A94D24);
}

/* Phase E3.2: a small reusable PotterForm alert-banner presentation for
   routine (non-blocking) status/success/warning/error notices - extends
   the exact pale-background/left-border card language
   .sb-sg-export-note.sb-sg-status-warning already established just above,
   generalized into a small info/success/warning/error semantic set.
   Manganese/plum-based (var(--pf-color-primary)) for info/success, the
   existing warm warning/error tokens for the other two - never default
   WordPress blue, no new brand colors invented. Used by #shapeLoadStatus
   (My Shape Open / ?shape=/?shapePayload= loading) and #variantSaveStatus
   (Save Shape) only - never the destructive Delete confirmation, which
   keeps its own separate .sb-sg-delete-confirm-* dialog styling and dark
   red treatment completely untouched by this. */
#sb-sg-app .sb-sg-alert{
  margin-top:8px;
  padding:7px 9px;
  border-radius:0 6px 6px 0;
  font-size:12px;
  line-height:1.4;
  word-break:break-word;
}

/* The unconditional block/props above have the same specificity as the
   browser's native [hidden]{display:none} rule, and an author rule beats a
   UA rule on a specificity tie (the same E2.1 CSS-specificity lesson) - so
   without this override, an empty/cleared status would still render a
   visible-but-blank banner shell. */
#sb-sg-app .sb-sg-alert[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-alert-info{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  border-left:2px solid var(--pf-color-primary-border, rgba(110,59,91,0.28));
  color:var(--pf-color-text-soft, rgba(17,17,17,0.72));
}

#sb-sg-app .sb-sg-alert-success{
  background:var(--pf-color-primary-soft, rgba(110,59,91,0.07));
  border-left:2px solid var(--pf-color-primary, #6E3B5B);
  color:var(--pf-color-primary, #6E3B5B);
  font-weight:600;
}

#sb-sg-app .sb-sg-alert-warning{
  background:var(--pf-color-warning-soft, rgba(169,77,36,0.08));
  border-left:2px solid var(--pf-color-warning-border, rgba(169,77,36,0.35));
  color:var(--pf-color-warning, #A94D24);
}

#sb-sg-app .sb-sg-alert-error{
  background:var(--pf-color-error-soft, rgba(170,17,17,0.06));
  border-left:2px solid var(--pf-color-error-border, rgba(170,17,17,0.35));
  color:var(--pf-color-error, #AA1111);
  font-weight:600;
}

/* Phase E3.1: originally the independent Compare workspace summary, its own
   region with its own top border rather than nested inside (or implying
   ownership by) the legacy Saved-on-this-device disclosure below it. UI-2
   generalised this into .sb-sg-action-subgroup: the same top-divider
   treatment now wraps each secondary row (Compare, Set) inside the merged
   Shape actions block (see buildShapeActionsGroupMarkup() in app.js) - not
   just the count/status area, the row's label and button pair too. Kept
   compact: a section label, its button pair, a persistent one-line count for
   Compare (#compareSummaryCount), and the existing #compareStatus/
   #teaSetStatus alert (see .sb-sg-alert-* above) - no large permanent
   paragraph. */
#sb-sg-app .sb-sg-action-subgroup{
  margin-top:10px;
  padding-top:8px;
  border-top:1px solid var(--sb-sg-line);
}

/* UI-1: raised from a small caption-style line to a real, persistent
   workspace-state indicator - this is the single authoritative Compare
   count on screen (see updateViewComparisonButton() in app.js, and the
   removed toolbar "N selected" text it replaces while Compare is open).
   A restrained state chip, not an alert: reuses the existing
   --pf-color-primary-soft background .sb-sg-alert-success already relies on
   and the existing --sb-sg-radius-sm token - no new colour or radius value.
   Deliberately never the warning/error palette, even at 4 / 4 - reaching
   capacity is normal workspace state, not a problem. */
#sb-sg-app .sb-sg-compare-summary-count{
  display:inline-flex;
  align-items:center;
  padding:3px 8px;
  border-radius:var(--sb-sg-radius-sm);
  background:var(--pf-color-primary-soft, rgba(110,59,91,0.07));
  font-size:12px;
  font-weight:700;
  color:var(--pf-color-primary, #6E3B5B);
}

/* Group notes */
#sb-sg-app .sb-sg-group-note{
  margin:-2px 0 10px;
  font-size:11px;
  line-height:1.45;
  color:var(--sb-sg-text-faint);
}

#sb-sg-app .sb-sg-subtitle-wrap{
  margin:2px 0 2px;
}

#sb-sg-app .sb-sg-sub .sb-sg-subtitle{
  margin:0 0 8px;
}

/* Advanced */
#sb-sg-app .sb-sg-subtitle{
  margin:0 0 8px;
  font-size:12px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-subtitle-quiet{
  font-size:12px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-advanced-wrap{
  display:flex;
  flex-direction:column;
  gap:6px;
}

/* UI-3: Advanced is now always open (no disclosure button any more - see
   buildAdvancedMarkup() in app.js), so the panel is just a plain section
   under a divider/title, matching Rim/Foot/Shrinkage's own inner heading
   style (.sb-sg-title.sb-sg-subtitle). */
#sb-sg-app .sb-sg-advanced-panel{
  padding-top:8px;
}

#sb-sg-app .sb-sg-internal-canonical-controls[hidden],
#sb-sg-app .sb-sg-wrap-disabled[hidden]{
  display:none !important;
}

#sb-sg-app .sb-sg-toggle-grid{
  display:grid;
  gap:8px;
}

#sb-sg-app .sb-sg-toggle-grid-2{
  grid-template-columns:1fr 1fr;
}

#sb-sg-app .sb-sg-grid-span-2{
  grid-column:1 / -1;
}

#sb-sg-app .sb-sg-advanced-row{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
}

#sb-sg-app .sb-sg-advanced-options{
  display:flex;
  align-items:center;
  gap:10px;
  flex-wrap:wrap;
}

#sb-sg-app .sb-sg-advanced-options .sb-sg-toggle{
  display:flex;
  align-items:center;
  gap:6px;
  margin:0;
}

#sb-sg-app .sb-sg-advanced-options .sb-sg-toggle span{
  font-size:12px;
  font-weight:500;
}

#sb-sg-app .sb-sg-divider{
  height:1px;
  margin:2px 0;
  background:var(--sb-sg-line);
}

#sb-sg-app .sb-sg-adv-hint{
  margin-top:2px;
  font-size:11px;
  color:var(--sb-sg-text-faint);
}

#sb-sg-app .sb-sg-wrap-disabled{
  opacity:.38;
  pointer-events:none;
}

#sb-sg-app .sb-sg-group-compact .sb-sg-title{
  margin-bottom:8px;
}

#sb-sg-app .sb-sg-group-compact .sb-sg-sub{
  padding-top:2px;
}

#sb-sg-app .sb-sg-group-compact label{
  margin:8px 0;
}

/* Canvas */
#sb-sg-app .sb-sg-canvas{
  flex:1 1 auto;
  min-width:280px;
  display:flex;
  align-items:center;
  justify-content:center;
  align-self:flex-start;

  position:sticky;
  top:calc(var(--sb-sg-top-gap) + var(--sb-sg-admin-offset));

  height:calc(100vh - var(--sb-sg-admin-offset) - var(--sb-sg-top-gap) - var(--sb-sg-bottom-gap));
  height:calc(100dvh - var(--sb-sg-admin-offset) - var(--sb-sg-top-gap) - var(--sb-sg-bottom-gap));
  min-height:520px;

  padding:14px;
  border:1px solid var(--sb-sg-card-border);
  border-radius:18px;
  overflow:hidden;
  /* UI-1 visual completion pass: this element is both the canvas region AND
     the actual drawing stage (the SVG sits directly inside it, no separate
     wrapper) - the landing canvas tone (#FBF8F4) was checked against every
     foreground it needs to host (Plum shape outline, body text, warning/
     error banner text) and stays comfortably above WCAG AA in every case
     (>=5.2:1, versus >=5.6:1 on pure white - a difference of a few hundredths
     of a contrast point, not a readability change), so one value safely
     covers both roles without inventing a third colour or a wrapper split. */
  background:var(--sb-sg-landing-canvas);
  box-shadow:var(--sb-sg-shadow);
}

#sb-sg-app .sb-sg-canvas svg{
  display:block;
  width:100%;
  height:100%;
}

/* Track C4: the current 2D editor preview is an application interaction
   surface. It owns one-finger pan and two-finger zoom/pan so the browser does
   not scroll or page-zoom while a potter is manipulating the view. Scoped to
   this transient SVG only: controls, sibling overlays, the surrounding page,
   and any future view retain independent native/gesture contracts. */
#sb-sg-app #sb-sg-preview-svg{
  touch-action:none;
}

/* Track C2b: two persistent children of .sb-sg-canvas (see
   buildCanvasMarkup() in app.js). #canvas-render takes over the exact
   flex-item role the bare <svg> used to have directly (100%/100% of
   .sb-sg-canvas's content box, unaffected by align-items/justify-
   content:center above since 100% already fills the container) - the
   ".sb-sg-canvas svg" rule above still applies unchanged to the SVG
   wherever it is nested, since it is a descendant selector, not a
   direct-child one. #canvas-handles is taken out of flow entirely
   (position:absolute), so it never competes with #canvas-render for the
   single flex slot. It is deliberately NOT pixel-aligned to the SVG's own
   content box here (its inset:0 resolves against .sb-sg-canvas's PADDING
   box, which is slightly larger than the SVG's actual drawn area by
   .sb-sg-canvas's own padding) - harmless today since it has no visible
   content, and future Track C2c handle positions will be computed from
   the live SVG's own getScreenCTM()/getBoundingClientRect() (see
   sb-shape-editor-interaction.js's screenPointToModel()), not from this
   layer's own box, so exact alignment here is not load-bearing. */
#sb-sg-app .sb-sg-canvas-render{
  position:relative;
  display:block;
  width:100%;
  height:100%;
}

#sb-sg-app .sb-sg-canvas-handles{
  position:absolute;
  inset:0;
  pointer-events:none;
}

/* Track C2c: the three direct-manipulation body-point handles. Positioned
   via left/top (set from JS, in CSS px relative to .sb-sg-canvas-handles'
   own rect - see app.js's positionHandleEl()) with transform:translate(
   -50%,-50%) centering the element on that point, rather than negative
   margins - the standard technique for a JS-positioned marker. Reuses the
   existing Plum design tokens (--pf-color-primary and friends) already
   used throughout this file - no new brand colour introduced. The ~42px
   hit target is the invisible/transparent element itself; the visible
   marker is the much smaller ::before dot, matching the touch-target
   pattern already validated by this project's own earlier interaction
   prototypes. */
#sb-sg-app .sb-sg-canvas-handles .sb-sg-point-handle{
  position:absolute;
  left:0;
  top:0;
  transform:translate(-50%, -50%);
  width:42px;
  height:42px;
  display:flex;
  align-items:center;
  justify-content:center;
  pointer-events:auto;
  touch-action:none;
  cursor:grab;
  border-radius:50%;
}

#sb-sg-app .sb-sg-canvas-handles .sb-sg-point-handle::before{
  content:"";
  display:block;
  width:12px;
  height:12px;
  border-radius:50%;
  background:var(--pf-color-primary, #6E3B5B);
  border:2px solid #fff;
  box-shadow:0 1px 3px rgba(0,0,0,0.28);
  pointer-events:none;
  transition:background 0.14s ease, transform 0.14s ease;
}

#sb-sg-app .sb-sg-canvas-handles .sb-sg-point-handle:hover::before{
  background:var(--pf-color-primary-hover, #5A304C);
  transform:scale(1.15);
}

#sb-sg-app .sb-sg-canvas-handles .sb-sg-point-handle.is-selected::before{
  background:var(--pf-color-primary-hover, #5A304C);
}

/* C3 selection-feedback refinement: the persistent selected-state ring. A
   separate ::after circle rather than further scaling ::before, so the exact
   geometry point (and the profile line under it) stay precisely readable
   even while selected - only ::before marks the real point. This ring is
   driven purely by .is-selected (toggled only in selectHandle()/
   clearHandleSelection(), never per-render or per-drag-frame), so it stays
   visible for the point's entire selected lifetime, not just during a brief
   animation, and is unaffected by prefers-reduced-motion. Sized/positioned
   independently of the ::before dot so hover's dot-scale (above) can never
   make an unselected, merely-hovered handle look selected. */
#sb-sg-app .sb-sg-canvas-handles .sb-sg-point-handle::after{
  content:"";
  position:absolute;
  top:50%;
  left:50%;
  width:26px;
  height:26px;
  border-radius:50%;
  border:2px solid var(--pf-color-primary, #6E3B5B);
  box-shadow:0 0 0 3px var(--pf-color-primary-soft, rgba(110,59,91,0.07));
  pointer-events:none;
  opacity:0;
  transform:translate(-50%, -50%) scale(0.6);
  transition:opacity 0.16s ease, transform 0.16s ease;
}

#sb-sg-app .sb-sg-canvas-handles .sb-sg-point-handle.is-selected::after{
  opacity:1;
  transform:translate(-50%, -50%) scale(1);
}

/* C3 selection-feedback refinement: the transient selection-change accent.
   .is-select-pulse is added/removed in app.js's triggerSelectionAccent(),
   called only from selectHandle() only when selectedHandle's identity
   genuinely changes - never on ordinary render/drag/resize/etc, and never on
   reselecting the same point. animation-fill-mode is intentionally NOT
   forwards: once the ~400ms pulse finishes, the handle simply falls back to
   its normal persistent .is-selected::after ring above, exactly matching
   the product decision's "after the accent, canvas handle remains in its
   normal strong persistent selected state" requirement. */
@keyframes sb-sg-handle-select-pulse{
  0%{
    transform:translate(-50%, -50%) scale(1);
    box-shadow:0 0 0 3px var(--pf-color-primary-soft, rgba(110,59,91,0.07));
  }
  40%{
    transform:translate(-50%, -50%) scale(1.55);
    box-shadow:0 0 0 8px rgba(110,59,91,0);
  }
  100%{
    transform:translate(-50%, -50%) scale(1);
    box-shadow:0 0 0 3px var(--pf-color-primary-soft, rgba(110,59,91,0.07));
  }
}

#sb-sg-app .sb-sg-canvas-handles .sb-sg-point-handle.is-select-pulse::after{
  animation:sb-sg-handle-select-pulse 400ms ease-out;
}

#sb-sg-app .sb-sg-canvas-handles .sb-sg-point-handle:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-canvas-handles .sb-sg-point-handle:active{
  cursor:grabbing;
}

/* C3 inspector-heading feedback fix: persistent selected-context styling.
   Root cause of the original "imperceptible" report: #pointInspectorName had
   NO persistent treatment at all - only the transient text-shadow pulse
   below existed, and that pulse alone (a 6px, 0.13-alpha glow on 12px text)
   is too subtle to read as a state change even while it is firing correctly.
   Unconditional on #pointInspectorFields #pointInspectorName - the fields
   block itself is only ever rendered visible when selectedHandle is truthy
   (see updatePointInspector()), so no extra "is a point selected" class is
   needed here; the existing hidden/shown toggle on #pointInspectorFields
   already handles the neutral-prompt vs active-selection switch. Kept as an
   inline-block pill sized to its own text (not full-width), so it never
   shifts the Height/Diameter labels or inputs below it. Deliberately keeps
   #pointInspectorName's existing className (sb-sg-title sb-sg-subtitle)
   untouched - this rule wins on selector specificity (two IDs) rather than
   by adding a class, so no other code that reads that className breaks. */
#sb-sg-app .sb-sg-point-inspector-fields #pointInspectorName{
  display:inline-block;
  margin:0 0 8px;
  padding:3px 10px;
  border-radius:var(--sb-sg-radius-sm);
  background:var(--pf-color-primary-soft, rgba(110,59,91,0.07));
  color:var(--pf-color-primary, #6E3B5B);
  font-size:13px;
  font-weight:700;
}

/* C3 selection-feedback refinement (strengthened): the inspector heading's
   own matching transient accent, synchronized with the canvas handle pulse
   above (same 400ms duration, same trigger point in triggerSelectionAccent()).
   Now pulses the persistent pill's own background/box-shadow rather than
   relying on text-shadow alone, so it reads clearly against the pill instead
   of disappearing into it. box-shadow and background never affect box size,
   so the heading still never shifts when the accent fires. */
@keyframes sb-sg-inspector-heading-pulse{
  0%{
    background:var(--pf-color-primary-soft, rgba(110,59,91,0.07));
    box-shadow:0 0 0 0 rgba(110,59,91,0);
  }
  40%{
    background:var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
    box-shadow:0 0 0 4px var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
  }
  100%{
    background:var(--pf-color-primary-soft, rgba(110,59,91,0.07));
    box-shadow:0 0 0 0 rgba(110,59,91,0);
  }
}

#sb-sg-app .sb-sg-point-inspector-fields #pointInspectorName.sb-sg-inspector-accent{
  animation:sb-sg-inspector-heading-pulse 400ms ease-out;
}

@media (prefers-reduced-motion: reduce){
  /* The persistent .is-selected::after ring above, and the persistent
     inspector-heading pill above, are both plain non-keyframe styling
     (opacity/transform transition and a static background respectively) and
     are intentionally left untouched here - they must still appear
     immediately. Only the two transient keyframe pulses are skipped. */
  #sb-sg-app .sb-sg-canvas-handles .sb-sg-point-handle.is-select-pulse::after{
    animation:none;
  }
  #sb-sg-app .sb-sg-point-inspector-fields #pointInspectorName.sb-sg-inspector-accent{
    animation:none;
  }
}

/* Scrollbars */
#sb-sg-app .sb-sg-controls,
#sb-sg-app .sb-sg-canvas{
  scrollbar-width:thin;
  scrollbar-color:rgba(17,17,17,0.18) transparent;
}

#sb-sg-app .sb-sg-controls::-webkit-scrollbar,
#sb-sg-app .sb-sg-canvas::-webkit-scrollbar{
  width:10px;
  height:10px;
}

#sb-sg-app .sb-sg-controls::-webkit-scrollbar-thumb,
#sb-sg-app .sb-sg-canvas::-webkit-scrollbar-thumb{
  background:rgba(17,17,17,0.16);
  border-radius:999px;
  border:2px solid transparent;
  background-clip:padding-box;
}

#sb-sg-app .sb-sg-controls::-webkit-scrollbar-track,
#sb-sg-app .sb-sg-canvas::-webkit-scrollbar-track{
  background:transparent;
}

/* Responsive */
@media (max-width: 900px){
  #sb-sg-app .sb-sg-layout{
    flex-direction:column;
  }

  #sb-sg-app .sb-sg-controls{
    width:100%;
    max-width:none;
    min-width:0;

    /* Undo the desktop sticky/bounded-height/scroll treatment above so the
       stacked layout returns to plain normal-flow document scrolling, same
       as .sb-sg-canvas's own reset just below. min-height:0 undoes the
       default rule's 520px floor - the columns no longer share a bottom
       boundary once stacked, so no floor is needed here (canvas keeps its
       own, separate 340px floor for shape-preview usability only). */
    position:static;
    top:auto;
    height:auto;
    min-height:0;
    overflow-y:visible;
    overflow-x:visible;
  }

  #sb-sg-app .sb-sg-canvas{
    position:relative;
    top:auto;
    height:auto;
    min-height:340px;
  }

  #sb-sg-app .sb-sg-canvas svg{
    height:320px;
  }
}

@media (max-width: 640px){
  /* UI-1b staging correction: a smaller but still visible left gutter on
     phones - reuses the exact 12px value .sb-sg-group's own mobile padding
     reduction just below already establishes at this same breakpoint,
     rather than a new number. */
  #sb-sg-app{
    padding-left:12px;
  }

  #sb-sg-app .sb-sg-group{
    padding:12px 12px 7px;
  }

  #sb-sg-app .sb-sg-project-actions,
  #sb-sg-app .sb-sg-toggle-grid-2{
    grid-template-columns:1fr;
  }

  #sb-sg-app .sb-sg-advanced-row{
    flex-direction:column;
    align-items:flex-start;
  }

  #sb-sg-app .sb-sg-advanced-options{
    width:100%;
    gap:12px;
  }
}

/* Mobile landscape editing mode */
@media (max-width: 932px) and (orientation: landscape){
  /* Verified by manual browser measurement on staging: in this breakpoint
     the WordPress/theme ancestor chain is already full viewport width, but
     #sb-sg-app itself was measured at exactly 90% width, centred (a rule
     outside this stylesheet - not present in this file, not in this
     repository, and confirmed not to be the theme's container/content-area
     settings). This override neutralises that external sizing however it
     is expressed (width or max-width, since the exact source declaration
     is unknown) and replaces it with an explicit small left inset instead
     of symmetric centring - width/max-width/margin only, nothing else
     about #sb-sg-app is touched. #sb-sg-app:fullscreen (unconditional,
     specificity 1 ID + 1 pseudo-class) already sets its own width:100%/
     max-width:none/margin:0 and outranks this plain-ID rule's specificity
     (1 ID only) for the exact same three properties whenever :fullscreen
     is active, regardless of source order - so this cannot leak into
     Fullscreen, in normal mode or in this browser's Chrome-viewport-quirk
     case (see the width-independent Fullscreen comment further down). */
  #sb-sg-app{
    width:calc(100% - 8px);
    max-width:none;
    margin-left:8px;
    margin-right:0;
  }

  #sb-sg-app .sb-sg-layout{
    flex-direction:row;
    gap:12px;
    align-items:stretch;
  }

  /* 37% (not 36%) of the now-expanded #sb-sg-app: derived from the
     measured tool-rail position so the rail moves by roughly a pixel while
     controls absorb the newly-reclaimed width, not from an independent
     design ratio. Also relieves the Export PDF / Shape archive label wrap
     measured at 932px, as a side effect of the extra width - not solved by
     any change to the buttons themselves. */
  #sb-sg-app .sb-sg-controls{
    width:37%;
    max-width:37%;
    min-width:240px;

    /* Explicit here (matching .sb-sg-canvas's own position/top just below)
       rather than relying on inheritance from the default-rule values above -
       this media query does not always pair with @media (max-width:900px)
       (e.g. a 901-932px landscape viewport matches this block alone), so
       leaving position/top unstated here would leak the default rule's
       different top offset into that narrow band instead of matching
       canvas's top:12px used in this same breakpoint. */
    position:sticky;
    top:12px;
    height:calc(100dvh - var(--sb-sg-admin-offset) - 24px);
    /* Matches canvas's own min-height:0 reset just below - without this, the
       default rule's 520px floor would remain unopposed specifically in the
       narrow 901-932px landscape band (where @media (max-width:900px) does
       not apply), overflowing a short landscape-phone viewport. */
    min-height:0;
    overflow-y:auto;
    overflow-x:hidden;
    padding-right:4px;
    -webkit-overflow-scrolling:touch;
  }

  #sb-sg-app .sb-sg-canvas{
    width:60%;
    flex:1 1 60%;
    min-width:0;
    position:sticky;
    top:12px;
    height:calc(100dvh - var(--sb-sg-admin-offset) - 24px);
    min-height:0;
    padding:12px;
  }

  #sb-sg-app .sb-sg-canvas svg{
    width:100%;
    height:100%;
  }
}

/* Landscape coarse-pointer tablet companion (933-1280px): a physical-device
   measurement (Samsung SM-T585, reported CSS viewport 1280x680) confirmed
   the approved through-932px correction above never activates on wider
   landscape touch tablets, since their CSS width exceeds max-width:932px -
   they fall through to the plain desktop-default rule instead, still
   centred at 90% width. This block reuses the exact same selectors and the
   same width-relevant declarations as the approved rule above (root
   width/margin, controls width/max-width/min-width) rather than a new
   generic device class - it does not touch .sb-sg-controls's own
   position/height/overflow/scroll declarations, which stay whatever the
   untouched desktop-default rule already supplies, since only width
   allocation was requested here. hover:none and pointer:coarse are the
   primary-input conditions (not any-hover/any-pointer - nothing in the
   existing cascade required the "any-" variants) that keep this scoped to
   touch tablets only; an ordinary mouse/trackpad laptop or desktop resized
   into this same 933-1280px landscape band reports hover:hover and
   pointer:fine and does not match this rule at all, leaving the desktop
   default (368px/42vw/290px) completely untouched for that case. This
   media query's own conditions describe only viewport size and input
   capability, not Fullscreen state, so it can still match while #sb-sg-app
   is the native Fullscreen element - and a plain #sb-sg-app/.sb-sg-layout/
   .sb-sg-controls selector would still match those same elements too,
   .sb-sg-layout in particular, since its properties here (flex-direction,
   gap, align-items) are not guaranteed excluded by specificity alone.
   :not(:fullscreen) is added to all three selectors below so each one
   simply fails to match the generator root, or any element found by
   querying inside it, whenever #sb-sg-app is genuinely :fullscreen -
   the rule cannot apply to Fullscreen at all, rather than relying on a
   specificity comparison against the separate Fullscreen-specific rules
   elsewhere in this file. */
@media (orientation:landscape) and (min-width:933px) and (max-width:1280px) and (hover:none) and (pointer:coarse){
  #sb-sg-app:not(:fullscreen){
    width:calc(100% - 8px);
    max-width:none;
    margin-left:8px;
    margin-right:0;
  }

  #sb-sg-app:not(:fullscreen) .sb-sg-layout{
    flex-direction:row;
    gap:12px;
    align-items:stretch;
  }

  #sb-sg-app:not(:fullscreen) .sb-sg-controls{
    width:37%;
    max-width:37%;
    min-width:240px;
  }
}

/* UI-1c: resizable control panel, normal (non-fullscreen) mode. Deliberately
   scoped to (hover:hover) and (pointer:fine) only - genuinely coarse/touch
   layouts (the two blocks just above, and the stacked/rotate-notice blocks
   below) keep their existing fixed behavior entirely untouched, since
   dragging is not a fit for a coarse pointer. The two-branch OR below
   (landscape, or portrait/square at >=901px) exists specifically to exclude
   the one real conflict: @media (max-width:900px) further below stacks
   .sb-sg-layout into a column for portrait viewports at that width - this
   rule must never fight that by handing .sb-sg-controls a narrow fixed-px
   flex-basis while it is stacked. (Landscape at any width already stays
   row-layout via the 932px-landscape block above restoring flex-direction:row,
   which is why the landscape branch here has no width floor of its own.)
   Same #sb-sg-app .sb-sg-controls specificity as the base rule and the
   932px-landscape correction above - this wins by appearing later in the
   file, for the (hover:hover) and (pointer:fine) subset only; the coarse-
   pointer block above is otherwise mutually exclusive with this one by
   media condition and is never affected. flex-shrink:0 (via flex:0 0 <basis>)
   is unchanged from every other width rule in this file - clamp()'s own
   defined behavior (max(MIN, min(VAL, MAX))) already saturates to
   --sb-sg-controls-min-width if the dynamic max ever mathematically drops
   below it, so this can never resolve to an inverted/negative range. */
@media ((hover:hover) and (pointer:fine) and (orientation:landscape)), ((hover:hover) and (pointer:fine) and (min-width:901px)){
  #sb-sg-app .sb-sg-controls{
    flex:0 0 clamp(
      var(--sb-sg-controls-min-width),
      var(--sb-sg-controls-user-width),
      min(
        var(--sb-sg-controls-max-width),
        calc(100% - var(--sb-sg-tool-rail-width) - var(--sb-sg-canvas-resize-floor))
      )
    );
    width:auto;
    max-width:none;
  }
}

/* UI-1c.1: resizable control panel, extended to the existing coarse-pointer
   landscape tablet range (933-1280px, hover:none, pointer:coarse) - the
   exact same breakpoint the "Landscape coarse-pointer tablet companion"
   block above already uses for its own fixed 37% width, reused verbatim
   rather than a new tablet breakpoint. That block's own selector is
   #sb-sg-app:not(:fullscreen) .sb-sg-controls (higher specificity than the
   plain #sb-sg-app .sb-sg-controls the desktop resize rule just above
   uses, because of the :not() pseudo-class) - this rule matches that same
   :not(:fullscreen) selector so it can correctly out-rank it by appearing
   later in the file, for this one breakpoint only. Never touches
   #sb-sg-app's own width/margin or .sb-sg-layout's flex-direction/gap from
   that block - those stay exactly as they already are (the same theme-
   quirk correction the desktop rule's own comment explains further up).
   Still explicitly excludes :fullscreen, matching the task's own
   instruction not to extend fullscreen behavior beyond what was asked. */
@media (orientation:landscape) and (min-width:933px) and (max-width:1280px) and (hover:none) and (pointer:coarse){
  #sb-sg-app:not(:fullscreen) .sb-sg-controls{
    flex:0 0 clamp(
      var(--sb-sg-controls-min-width),
      var(--sb-sg-controls-user-width),
      min(
        var(--sb-sg-controls-max-width),
        calc(100% - var(--sb-sg-tool-rail-width) - var(--sb-sg-canvas-resize-floor))
      )
    );
    width:auto;
    max-width:none;
  }
}

/* =========================
   Portrait-phone rotate notice
   ========================= */

/* Hidden everywhere by default - the notice only ever becomes visible inside
   the narrow-portrait media query below. Static markup, created once with
   the rest of buildAppMarkup(); display:none only affects rendering, so
   .sb-sg-layout's own DOM nodes, input values, checkbox states, and Guide/
   Throwability/Saved Variants state are all fully preserved while this is
   showing - nothing is rebuilt, removed, or reinserted. */
#sb-sg-app .sb-sg-rotate-notice{
  display:none;
}

/* Matches the existing <=900px stacked-layout boundary exactly, but only
   for touch-primary devices (hover:none and pointer:coarse) - the stacked
   layout at this size puts controls above a reduced canvas, which is
   technically usable but not a workable editing experience on a touch
   portrait phone or tablet (the canvas can't be seen while adjusting
   controls). A narrow desktop/laptop window at the same width, using hover
   and a fine pointer, is unaffected and keeps the existing stacked
   generator. Portrait viewports wider than 900px are untouched and keep
   their existing wider layout; the existing max-width:932px landscape
   layout is a separate condition and is also untouched. */
@media (max-width:900px) and (orientation:portrait) and (hover:none) and (pointer:coarse){
  #sb-sg-app .sb-sg-layout{
    display:none;
  }

  #sb-sg-app .sb-sg-rotate-notice{
    display:flex;
    flex-direction:column;
    align-items:center;
    justify-content:center;
    text-align:center;
    gap:8px;
    box-sizing:border-box;
    width:100%;
    min-height:calc(100vh - var(--sb-sg-admin-offset));
    min-height:calc(100dvh - var(--sb-sg-admin-offset));
    padding:24px;
    background:var(--sb-sg-card);
    border:1px solid var(--sb-sg-card-border);
    border-radius:var(--sb-sg-radius);
  }

  #sb-sg-app .sb-sg-rotate-notice-title{
    margin:0;
    font-size:17px;
    font-weight:600;
    color:var(--pf-color-primary, #6E3B5B);
  }

  #sb-sg-app .sb-sg-rotate-notice-body{
    margin:0;
    max-width:32ch;
    font-size:13px;
    line-height:1.5;
    color:var(--sb-sg-text-soft);
  }
}

/* =========================
   Tool rail (landscape devices and hover-capable desktop)
   ========================= */

/* Generic rail between the controls and canvas columns - real flex item,
   never absolutely positioned, so it cannot overlap either column. Holds
   the Full screen action (anchored near the top, by normal flow) and the
   UI-1c.1 resize control (anchored near the bottom, via margin-top:auto on
   the control itself - see below); future tool buttons would stack between
   the two, in normal flow, without needing any layout change here. The
   rail is sized to already read as a real PotterForm tool rail rather than
   a narrow gutter, so a later module can be dropped in without another
   sizing pass. Structural sizing lives in the base rule per the note in
   the media rules below - only display and the layout gap are conditional.
   Width is derived, not guessed: the standard Generator action-control
   height (44px, matching .sb-sg-view-comparison-btn) plus comfortable
   breathing room on each side of the square .sb-sg-fullscreen-toggle/
   .sb-sg-controls-resize-handle buttons (see below). touch-action:none is
   unconditional (matching every other rail-drag-surface rule below) - the
   rail itself carries no scrollable content of its own to protect, and
   pointer-events on its button children already gate whether a touch drag
   can ever actually begin here. */
#sb-sg-app .sb-sg-tool-rail{
  display:none;
  /* UI-1c: centralized - see --sb-sg-tool-rail-width in the root token
     block. */
  flex:0 0 var(--sb-sg-tool-rail-width);
  width:var(--sb-sg-tool-rail-width);
  align-self:stretch;
  flex-direction:column;
  align-items:center;
  justify-content:flex-start;
  gap:12px;
  padding:16px 0;
  /* UI-1c.1: the rail itself is now also an eligible drag surface (see
     app.js's pointerdown listener, attached to the rail rather than only
     the dedicated control) - col-resize below applies here too, and
     buttons within the rail keep their own explicit cursor (already set on
     .sb-sg-fullscreen-toggle), which wins over this by normal CSS
     specificity/inheritance rules. */
  position:relative;
}

@media (hover:hover) and (pointer:fine){
  #sb-sg-app .sb-sg-tool-rail{
    cursor:col-resize;
  }
}

/* UI-1c.1 staging correction: the resize handle is now BOTH the one
   semantic keyboard separator AND the primary, clearly visible resize
   affordance - previously an invisible ~8px seam at the rail's left edge
   (retired entirely; the rail background itself now provides the large
   pointer/touch hit area a seam was trying to approximate - see app.js).
   A real 44x44 button, matching .sb-sg-fullscreen-toggle's own footprint,
   positioned toward the rail's bottom via margin-top:auto (grows/shrinks
   with the rail's own height rather than a fixed pixel gap) and kept
   spatially separate from the Fullscreen button by the rail's own
   flex-direction:column + gap - never placed immediately beside it.
   Belongs to the same restrained PotterForm rail-button family as
   .sb-sg-fullscreen-toggle - white surface, subtle Plum border, Plum icon -
   but uses the wider Generator action-button family's literal 8px radius
   (matching .sb-sg-export-btn and siblings) rather than
   .sb-sg-fullscreen-toggle's own --sb-sg-radius-sm token, per this phase's
   explicit direction. Mildly visible at rest (a real border, always
   present once eligible - not just on hover/focus, unlike the old
   fully-invisible seam) without ever showing the stronger 2px
   :focus-visible outline outside actual keyboard focus. Inert by default
   (display:none, default cursor) outside eligible mode; the media block
   below is the one place this becomes visible/interactive, now for BOTH
   the existing desktop precision-pointer condition and the newly-added
   existing coarse-pointer landscape tablet range (933-1280px) - the exact
   same combined condition .sb-sg-controls's own two resize-clamp rules use
   (see above). Still a real, natively-focusable <button> (role overridden
   to separator, appearance reset below), not a <div> - stays reachable via
   Tab unconditionally in markup regardless of this eligibility gate (a
   keyboard-plus-touchscreen combination should still be able to reach it,
   even though interacting with it is then correctly a no-op). */
#sb-sg-app .sb-sg-controls-resize-handle{
  appearance:none;
  -webkit-appearance:none;
  display:none;
  align-items:center;
  justify-content:center;
  flex:0 0 auto;
  width:44px;
  height:44px;
  margin-top:auto;
  padding:0;
  background:#fff;
  border:1px solid var(--pf-color-primary-border, rgba(110,59,91,0.28));
  border-radius:8px;
  color:var(--pf-color-primary, #6E3B5B);
  cursor:default;
  pointer-events:none;
  touch-action:none;
  -webkit-tap-highlight-color:transparent;
  transition:background-color 0.14s ease, border-color 0.14s ease;
}

#sb-sg-app .sb-sg-controls-resize-icon{
  display:block;
}

@media (hover:hover) and (pointer:fine), (orientation:landscape) and (min-width:933px) and (max-width:1280px) and (hover:none) and (pointer:coarse){
  #sb-sg-app .sb-sg-controls-resize-handle{
    display:inline-flex;
    pointer-events:auto;
  }
}

@media (hover:hover) and (pointer:fine){
  #sb-sg-app .sb-sg-controls-resize-handle{
    cursor:col-resize;
  }
}

#sb-sg-app .sb-sg-controls-resize-handle:hover,
#sb-sg-app .sb-sg-controls-resize-handle--active{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  border-color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-controls-resize-handle:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

@media (orientation:landscape), (hover:hover) and (pointer:fine){
  /* Available in landscape on any device, and in any orientation once the
     primary pointer reports hover:hover and pointer:fine (an ordinary
     desktop mouse/trackpad) - touch-primary phones/tablets in portrait
     match neither condition and keep the rail hidden, unchanged from
     before. No width breakpoint is needed to separate "phone" from
     "tablet" here - both get the same rail tools. The 32px rail itself
     replaces the gap that would otherwise sit between controls and
     canvas - only overridden when the rail is shown; .sb-sg-controls/
     .sb-sg-canvas's own flex:0 0 auto / flex:1 1 auto keep controls at its
     existing width and let canvas alone absorb the difference, both above
     and below 932px.
     Track C1 correction: this rail-level visibility is now independent of
     .sb-sg-fullscreen-supported (previously the rail as a whole was only
     shown when Fullscreen API support was detected, since Fullscreen was
     its only tool - now that the rail also hosts the always-relevant
     Reset View camera control, gating the ENTIRE rail on an unrelated
     Fullscreen capability would incorrectly hide Reset View too on a
     browser lacking Fullscreen support. Fullscreen's own button is still
     individually hidden in that case - see
     .sb-sg-fullscreen-toggle's own rule below - the resize handle already
     had its own independent visibility condition and was only ever hidden
     as a side effect of the rail itself being hidden; this fixes that
     latent coupling for it too. */
  #sb-sg-app .sb-sg-layout{
    gap:0;
  }

  #sb-sg-app .sb-sg-tool-rail{
    display:flex;
    /* UI-1 visual completion pass: the OTHER landing surface (deliberately
       different from both the controls panel and the canvas, which now
       share the same landing-canvas tone either side of it) - the rail
       reads as an intentional PotterForm divider, not a leftover grey
       gutter, and the rail's tools become easier to notice without any
       new colour. */
    background:var(--sb-sg-landing-surface);
    border-left:1px solid var(--sb-sg-card-border);
    border-right:1px solid var(--sb-sg-card-border);
  }
}

/* Track C1: Fullscreen's own capability gate, now independent of the
   rail's visibility (see the media block above) - progressive enhancement
   only, no user-agent detection: app.js adds .sb-sg-fullscreen-supported
   to #sb-sg-app only when document.fullscreenEnabled/requestFullscreen/
   exitFullscreen are all actually present. */
#sb-sg-app:not(.sb-sg-fullscreen-supported) .sb-sg-fullscreen-toggle{
  display:none;
}

#sb-sg-app .sb-sg-fullscreen-toggle{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  /* UI-1b staging correction: raised from 28px to the standard Generator
     action-control height (44px, matching .sb-sg-view-comparison-btn) - a
     square button at that height, so the rail reads as a meaningful action
     rather than a small icon glyph. The 16px icon itself is unchanged (see
     app.js's <svg width="16" height="16">) - only the surrounding button
     grew, giving it more breathing room, exactly as requested. */
  width:44px;
  height:44px;
  padding:0;
  /* UI-1 visual completion pass: a small white chip sitting on the rail's
     new landing-surface tone (rather than matching it) - genuine, gentle
     button-vs-rail contrast improves discoverability at the control level
     too, reusing the same plain white already used throughout this file
     rather than a new colour. Hover/active/focus-visible below are
     unchanged (already the existing Plum family) and stay clearly visible
     against this lighter base. */
  background:#fff;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius-sm);
  box-shadow:var(--sb-sg-shadow-soft);
  color:var(--pf-color-primary, #6E3B5B);
  cursor:pointer;
  touch-action:manipulation;
  transition:background 0.14s ease;
}

#sb-sg-app .sb-sg-fullscreen-toggle:hover{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
}

#sb-sg-app .sb-sg-fullscreen-toggle:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:-2px;
}

#sb-sg-app .sb-sg-fullscreen-toggle:active{
  background:var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
}

/* Track C1: Reset View - same restrained rail-button family as
   .sb-sg-fullscreen-toggle immediately above (white surface, subtle Plum
   border, Plum icon, identical 44x44 footprint and hover/focus/active
   treatment) - a camera-only control, deliberately given no visual
   distinction from Fullscreen beyond its own icon, since both are plain
   PotterForm rail tools. */
#sb-sg-app .sb-sg-reset-view-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:44px;
  height:44px;
  padding:0;
  background:#fff;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius-sm);
  box-shadow:var(--sb-sg-shadow-soft);
  color:var(--pf-color-primary, #6E3B5B);
  cursor:pointer;
  touch-action:manipulation;
  transition:background 0.14s ease;
}

#sb-sg-app .sb-sg-reset-view-btn:hover{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
}

#sb-sg-app .sb-sg-reset-view-btn:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:-2px;
}

#sb-sg-app .sb-sg-reset-view-btn:active{
  background:var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
}

/* Icon switch is fully CSS-driven off the single --active class toggled by
   fullscreenchange - no JS ever sets the SVGs' hidden property. */
#sb-sg-app .sb-sg-fullscreen-icon-enter{
  display:block;
}

#sb-sg-app .sb-sg-fullscreen-icon-exit{
  display:none;
}

#sb-sg-app .sb-sg-fullscreen-toggle--active .sb-sg-fullscreen-icon-enter{
  display:none;
}

#sb-sg-app .sb-sg-fullscreen-toggle--active .sb-sg-fullscreen-icon-exit{
  display:block;
}

#sb-sg-app .sb-sg-fullscreen-toggle--active{
  background:var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
  border-color:var(--pf-color-primary, #6E3B5B);
}

/* Fullscreen root: a real bounded shell. #sb-sg-app's used height is forced
   by the Fullscreen spec to exactly fill the screen while :fullscreen, but
   that alone left overflow:visible (inherited from the base rule) in
   effect, so overflowing content simply ran past the physical screen edge
   instead of being contained - the actual real-device clipping cause.
   display:flex + overflow:hidden here make #sb-sg-app itself the one
   height authority; .sb-sg-layout and its two columns below size themselves
   via flex (flex:1 1 auto / align-items:stretch), not via a percentage
   chain, so they no longer depend on any ancestor's height resolving
   validly through auto-sized layers. */
#sb-sg-app:fullscreen{
  display:flex;
  flex-direction:column;
  width:100%;
  height:100%;
  max-width:none;
  min-height:0;
  margin:0;
  overflow:hidden;
  background:var(--sb-sg-bg);
  --sb-sg-admin-offset:0px;
}

body.admin-bar #sb-sg-app:fullscreen{
  --sb-sg-admin-offset:0px;
}

#sb-sg-app::backdrop{
  background:var(--sb-sg-bg);
}

/* Fullscreen layout, width-independent: Chrome has been observed on a real
   Android phone to report a CSS viewport wider than 932px while #sb-sg-app
   is genuinely :fullscreen and still visually landscape (not reproducible
   in DevTools or in DuckDuckGo Browser on the same device). Every rule
   below is scoped to #sb-sg-app:fullscreen and (orientation:landscape), or
   a hover:hover/pointer:fine primary pointer in any orientation - the same
   condition as the pre-fullscreen entry rail above, so the rail never
   disappears the moment a hover-capable desktop enters fullscreen in
   portrait. No max-width condition is used, so entering fullscreen can no
   longer silently fall back to the desktop rules the moment Chrome's
   reported viewport crosses 932px - it previously took the rail, the
   controls/canvas height correction, and the mobile-landscape column split
   down with it, all at once, since all three depended on that one
   breakpoint remaining matched. Each selector below explicitly restates
   every layout property the corresponding max-width:932px rule used to
   supply, rather than relying on that separate rule continuing to match. */
@media (orientation:landscape), (hover:hover) and (pointer:fine){
  #sb-sg-app:fullscreen.sb-sg-fullscreen-supported .sb-sg-layout{
    display:flex;
    flex-direction:row;
    flex:1 1 auto;
    width:100%;
    height:auto;
    min-height:0;
    align-items:stretch;
    gap:0;
    padding-top:12px;
    padding-bottom:12px;
  }

  #sb-sg-app:fullscreen.sb-sg-fullscreen-supported .sb-sg-controls{
    width:40%;
    max-width:40%;
    min-width:0;
    position:relative;
    top:auto;
    height:auto;
    min-height:0;
    align-self:stretch;
    overflow-y:auto;
    overflow-x:hidden;
    -webkit-overflow-scrolling:touch;
  }

  #sb-sg-app:fullscreen.sb-sg-fullscreen-supported .sb-sg-tool-rail{
    display:flex;
    /* UI-1c: centralized - see --sb-sg-tool-rail-width in the root token
       block (matches the non-fullscreen rail's own sizing above). */
    flex:0 0 var(--sb-sg-tool-rail-width);
    width:var(--sb-sg-tool-rail-width);
    min-width:var(--sb-sg-tool-rail-width);
    align-self:stretch;
    /* UI-1c.1: matches the non-fullscreen rail's own column layout above,
       for the same reason (Fullscreen anchored near the top by normal
       flow, the resize control pushed to the bottom via its own
       margin-top:auto). */
    flex-direction:column;
    align-items:center;
    justify-content:flex-start;
    gap:12px;
    padding:16px 0;
    position:relative;
    /* UI-1 visual completion pass: matches the non-fullscreen rail's own
       landing-surface tone above, for the same reason. */
    background:var(--sb-sg-landing-surface);
    border-left:1px solid var(--sb-sg-card-border);
    border-right:1px solid var(--sb-sg-card-border);
  }

  #sb-sg-app:fullscreen.sb-sg-fullscreen-supported .sb-sg-canvas{
    flex:1 1 60%;
    width:auto;
    min-width:0;
    position:relative;
    top:auto;
    height:auto;
    min-height:0;
    align-self:stretch;
    overflow:hidden;
    padding:12px;
  }
}

/* Fullscreen controls narrowing, approved 740px-class phone tier only
   (<=900px, landscape): overrides only the general Fullscreen rule's
   width/max-width at this width. 900px reuses this file's own existing
   stacked-layout structural boundary (see @media (max-width:900px) far
   above) rather than an arbitrary number, and correctly separates the
   approved 740x430 Fullscreen result (kept at 36%, unchanged) from the
   larger layouts narrowed below. The general rule just above intentionally
   has no max-width condition (Android Chrome viewport-reporting quirk, see
   its own comment) and keeps its original 40% everywhere that quirk
   protection matters, including portrait Fullscreen on a hover-capable
   desktop pointer (untouched by this correction); this block only narrows
   the identical-specificity selector when the viewport is also genuinely
   landscape and at most 900px, and wins that tie by appearing after it. */
@media (max-width:900px) and (orientation:landscape){
  #sb-sg-app:fullscreen.sb-sg-fullscreen-supported .sb-sg-controls{
    width:36%;
    max-width:36%;
  }
}

/* Fullscreen controls narrowing, tablet/large-phone landscape and wider
   (901px and up, landscape only): brings 932x430, 1024x768 and ordinary
   larger-desktop Fullscreen to approximately 30:70, without touching the
   general Fullscreen rule's own 40% value above - which stays live for
   portrait Fullscreen on a hover-capable pointer, explicitly out of scope
   for this correction. 901px is the direct complement of the 900px
   boundary used just above, so the two tiers meet with no gap and no
   overlap. Landscape-only and unbounded above by design - normal
   (non-Fullscreen) mode has no equivalent rule past 932px, since ordinary
   large-desktop normal mode is untouched by this correction. */
@media (min-width:901px) and (orientation:landscape){
  #sb-sg-app:fullscreen.sb-sg-fullscreen-supported .sb-sg-controls{
    width:30%;
    max-width:30%;
  }
}

/* UI-1c: resizable control panel, fullscreen mode - the SAME
   --sb-sg-controls-user-width preference as normal mode (see the rule
   above), never a separate fullscreenWidth. UI-1c.2 staging fix: originally
   scoped to (hover:hover) and (pointer:fine) only - this was the exact,
   confirmed root cause of tablet fullscreen ignoring the user's chosen
   width. isResizeEligible() (app.js) and the resize handle's own
   visibility rule (.sb-sg-controls-resize-handle further up) were both
   ALREADY correctly eligible for the coarse-pointer landscape tablet range
   in fullscreen (neither one's own media condition depends on :fullscreen
   at all) - only THIS rule, the one that actually turns the CSS variable
   into .sb-sg-controls's rendered width, was missing that same tablet
   branch. Without it, dragging the resize control in tablet fullscreen
   already updated --sb-sg-controls-user-width correctly, but nothing
   translated that into a visible width change - the fixed
   width:30%/max-width:30% rule two blocks above (same specificity,
   ordinary landscape+width media query with no pointer/hover condition of
   its own) remained the last-applying rule for that exact profile and won
   by source order, leaving the panel stuck at a fixed percentage
   regardless of what the user dragged to.
   Now scoped to the SAME combined condition isResizeEligible()/the resize
   handle's visibility rule already use: desktop (hover:hover)/(pointer:fine),
   OR the existing approved coarse-pointer landscape tablet range
   (933-1280px) - reused verbatim, never a second tablet range. The fixed
   40%/36%/30% rules above remain the correct, untouched fallback for every
   other pointer/viewport combination (portrait fullscreen, narrow-phone
   landscape fullscreen, etc.) and also serve as this rule's own effective
   "no preference yet" starting point, since --sb-sg-controls-user-width
   itself defaults to --sb-sg-controls-default-width (368px) until a
   preference is read from storage. #sb-sg-app:fullscreen.sb-sg-fullscreen-supported
   .sb-sg-controls has strictly higher specificity than the plain
   #sb-sg-app .sb-sg-controls normal-mode rule above (two extra
   classes/pseudo-classes), so the two rules can never leak into each
   other; this one only needs to out-rank the three fixed-percentage
   fullscreen rules just above, which it does by appearing later at the
   same specificity - now for both eligible profiles, not just one. No
   fullscreen-specific stacked/column layout exists anywhere else in this
   file to conflict with (the rail - and therefore Fullscreen itself - is
   only ever reachable from a layout that was already landscape or
   hover:fine/pointer:fine to begin with), so no portrait-stacking
   exclusion is needed here the way the normal-mode rule above needs one. */
@media (hover:hover) and (pointer:fine), (orientation:landscape) and (min-width:933px) and (max-width:1280px) and (hover:none) and (pointer:coarse){
  #sb-sg-app:fullscreen.sb-sg-fullscreen-supported .sb-sg-controls{
    flex:0 0 clamp(
      var(--sb-sg-controls-min-width),
      var(--sb-sg-controls-user-width),
      min(
        var(--sb-sg-controls-max-width),
        calc(100% - var(--sb-sg-tool-rail-width) - var(--sb-sg-canvas-resize-floor))
      )
    );
    width:auto;
    max-width:none;
  }
}

/* =========================
   Guide Overlay (upper-left of canvas)
   ========================= */

#sb-sg-app .sb-sg-guide{
  position:absolute;
  top:12px;
  left:12px;
  z-index:10;
  max-width:calc(100% - 24px);
  display:flex;
  flex-direction:column;
  align-items:flex-start;
}

/* Collapsed outer shell: same rounded card language as the hidden
   Show Throwability box (.sb-sg-throwability - background/border/radius/
   shadow), applied only while collapsed. The toggle itself stays borderless
   inside it (see .sb-sg-guide-toggle). Auto-width is preserved - no fixed
   200px is copied. Excluded from .sb-sg-guide--expanded, which already has
   its own card styling via .sb-sg-guide-panel. */
#sb-sg-app .sb-sg-guide:not(.sb-sg-guide--expanded){
  background:#fff;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius-sm);
  box-shadow:var(--sb-sg-shadow-soft);
}

/* Definite height basis for the panel's internal scroll: only the expanded
   wrapper gets a concrete height (resolved against .sb-sg-canvas, which has
   its own definite height) - the collapsed wrapper above keeps its natural,
   content-sized dimensions (just the toggle button). min-height:0 lets the
   panel flex item below shrink under its own content height instead of
   overflowing this fixed box. Width matches the expanded Throwability
   panel's own proven formula exactly (same min(720px, calc(100% - 32px)),
   resolved against the same .sb-sg-canvas containing block) - Phase 2B.2B
   widens Guide to be visually comparable to Throwability, reusing its
   already-verified value rather than inventing a new one. */
#sb-sg-app .sb-sg-guide--expanded{
  width:min(720px, calc(100% - 32px));
  height:calc(100% - 24px);
  min-height:0;
}

/* Visual language matched to .sb-sg-throwability-toggle (same typography,
   colour, background/border/radius treatment, padding, hover/focus/active
   behaviour and restraint) - kept as its own rule (no shared class, no
   refactor). Two deliberate exceptions, both structural rather than visual:
   text stays left-aligned (Guide is upper-left, Throwability upper-right)
   and no fixed width/display:block is copied (Guide has no fixed-width
   outer card the way .sb-sg-throwability does), and min-height:44px is
   preserved rather than Throwability's own shorter natural height. */
#sb-sg-app .sb-sg-guide-toggle{
  display:inline-flex;
  align-items:center;
  justify-content:space-between;
  gap:8px;
  min-height:44px;
  padding:6px 10px;
  background:none;
  border:none;
  font:inherit;
  font-size:11px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
  cursor:pointer;
  text-align:left;
  white-space:nowrap;
  flex:0 0 auto;
  transition:background 0.14s ease;
}

#sb-sg-app .sb-sg-guide-toggle:hover{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  text-decoration:underline;
}

#sb-sg-app .sb-sg-guide-toggle:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:-2px;
}

#sb-sg-app .sb-sg-guide-toggle:active{
  background:var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
}

/* Chevron: same filled-triangle shape and 11px x 7px size as the Clay select
   arrows, recoloured to Plum (#6E3B5B, URL-encoded %236E3B5B) instead of
   grey - independent rule, does not touch .sb-sg-clay-select itself. A
   data-URI background-image cannot consume a CSS custom property, so this
   literal must be updated by hand if the Plum primary value ever changes.
   Rotated to point right while collapsed, down while expanded. */
#sb-sg-app .sb-sg-guide-toggle::after{
  content:"";
  display:inline-block;
  width:11px;
  height:7px;
  flex:0 0 auto;
  background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M2 2h8L6 7z' fill='%236E3B5B'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:center;
  background-size:11px 7px;
  transform:rotate(-90deg);
  transition:transform .14s ease;
}

#sb-sg-app .sb-sg-guide-toggle[aria-expanded="true"]::after{
  transform:rotate(0deg);
}

#sb-sg-app .sb-sg-guide-panel{
  margin-top:8px;
  width:100%;
  max-width:100%;
  flex:1 1 auto;
  min-height:0;
  overflow-y:auto;
  padding:14px 16px;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius);
  background:#fff;
  box-shadow:var(--sb-sg-shadow);
  font-size:12px;
  line-height:1.5;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-guide-panel[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-guide-panel-head{
  display:flex;
  align-items:flex-start;
  justify-content:space-between;
  gap:10px;
  margin:0 0 8px;
}

#sb-sg-app .sb-sg-guide-title{
  margin:0;
  font-size:15px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-guide-close{
  flex:0 0 auto;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:44px;
  min-height:44px;
  margin:-10px -8px 0 0;
  border:none;
  border-radius:var(--sb-sg-radius-sm);
  background:none;
  font-size:20px;
  line-height:1;
  color:var(--sb-sg-text-faint);
  cursor:pointer;
  transition:background .14s ease, color .14s ease;
}

#sb-sg-app .sb-sg-guide-close:hover{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-guide-close:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

/* Accordion (Phase 2B.2B): a single vertical list of real button headings,
   each followed by its own content region. Container only supplies a small
   top gap before the first heading - all visual weight lives on the
   trigger/panel rules below. */
#sb-sg-app .sb-sg-guide-accordion{
  margin-top:4px;
}

#sb-sg-app .sb-sg-guide-accordion-heading{
  margin:0;
}

/* Full-width row so the entire heading is a comfortable tap/click target
   (44px, matching every other Guide/Throwability toggle in this file).
   Colour/weight/hover/focus-visible/active states are the same language as
   .sb-sg-guide-toggle just above - deliberately not shared as one class,
   since the two buttons differ structurally (full-width row with a bottom
   rule vs. inline pill). */
#sb-sg-app .sb-sg-guide-accordion-trigger{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
  width:100%;
  min-height:44px;
  padding:10px 4px;
  border:none;
  border-bottom:1px solid var(--sb-sg-card-border);
  background:none;
  font:inherit;
  font-size:13px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
  text-align:left;
  cursor:pointer;
  transition:background .14s ease;
}

#sb-sg-app .sb-sg-guide-accordion-item:last-child .sb-sg-guide-accordion-trigger{
  border-bottom:none;
}

#sb-sg-app .sb-sg-guide-accordion-trigger:hover{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
}

#sb-sg-app .sb-sg-guide-accordion-trigger:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:-2px;
}

#sb-sg-app .sb-sg-guide-accordion-trigger:active{
  background:var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
}

/* Chevron: identical data-URI/size/rotation technique as
   .sb-sg-guide-toggle::after above, reused rather than redefined with new
   values - rotated to point right while collapsed, down while expanded. */
#sb-sg-app .sb-sg-guide-accordion-trigger::after{
  content:"";
  display:inline-block;
  width:11px;
  height:7px;
  flex:0 0 auto;
  background-image:url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M2 2h8L6 7z' fill='%236E3B5B'/%3E%3C/svg%3E");
  background-repeat:no-repeat;
  background-position:center;
  background-size:11px 7px;
  transform:rotate(-90deg);
  transition:transform .14s ease;
}

#sb-sg-app .sb-sg-guide-accordion-trigger[aria-expanded="true"]::after{
  transform:rotate(0deg);
}

#sb-sg-app .sb-sg-guide-accordion-panel{
  padding:10px 4px 14px;
}

#sb-sg-app .sb-sg-guide-accordion-panel[hidden]{
  display:none;
}

/* Readable line length even though the panel itself is now up to 720px
   wide (Guide's expanded width matches Throwability's, see
   .sb-sg-guide--expanded above) - caps prose at a comfortable measure
   without touching font-size/weight/colour (unrelated typography). */
#sb-sg-app .sb-sg-guide-accordion-panel p{
  margin:0;
  max-width:64ch;
}

/* Feedback footer (Phase 2B.2B correction): a separate section below the
   accordion, never a ninth accordion section - no accordion trigger, no
   aria-expanded/aria-controls, just a plain heading/paragraph/action pair.
   The border-top + top spacing is the only thing separating it from the
   accordion above (same divider language as .sb-sg-guide-accordion-trigger
   uses between its own rows). */
#sb-sg-app .sb-sg-guide-footer{
  margin-top:14px;
  padding:14px 4px 4px;
  border-top:1px solid var(--sb-sg-card-border);
}

#sb-sg-app .sb-sg-guide-footer-title{
  margin:0 0 4px;
  font-size:13px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-guide-footer-text{
  margin:0 0 10px;
  max-width:64ch;
}

/* Side by side where space permits, wrapping cleanly below it - flex-wrap
   plus a flexible basis rather than a new breakpoint, consistent with the
   rest of this file's responsive approach. */
#sb-sg-app .sb-sg-guide-footer-actions{
  display:flex;
  flex-wrap:wrap;
  gap:8px;
}

/* Same primary-button language as .sb-sg-export-btn (Export PDF / Save
   Variant) and the former .sb-sg-guide-action: solid Plum background,
   white text, semibold weight. Both actions share this one class so they
   stay visually equal; equal flex-basis keeps them the same width when
   side by side, and each still fills its own row cleanly once wrapped. */
#sb-sg-app .sb-sg-guide-footer-action{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  flex:1 1 160px;
  min-height:44px;
  padding:0 14px;
  border:2px solid var(--pf-color-primary, #6E3B5B);
  border-radius:8px;
  background:var(--pf-color-primary, #6E3B5B);
  color:#fff;
  font-size:12px;
  font-weight:600;
  text-decoration:none;
  text-align:center;
  box-shadow:0 1px 2px rgba(0,0,0,0.08);
  transition:background-color .16s ease, border-color .16s ease, color .16s ease, box-shadow .16s ease, transform .04s ease;
}

#sb-sg-app .sb-sg-guide-footer-action:visited{
  color:#fff;
}

#sb-sg-app .sb-sg-guide-footer-action:hover,
#sb-sg-app .sb-sg-guide-footer-action:focus{
  background:var(--pf-color-primary-hover, #5A304C);
  border-color:var(--pf-color-primary-hover, #5A304C);
  color:#fff;
  text-decoration:none;
  box-shadow:0 2px 8px rgba(0,0,0,0.14);
}

#sb-sg-app .sb-sg-guide-footer-action:active{
  color:#fff;
  transform:translateY(1px);
  box-shadow:0 1px 3px rgba(0,0,0,0.12);
}

#sb-sg-app .sb-sg-guide-footer-action:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-guide-footer-action--outline{
  background:#fff;
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-guide-footer-action--outline:visited{
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-guide-footer-action--outline:hover,
#sb-sg-app .sb-sg-guide-footer-action--outline:focus{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  border-color:var(--pf-color-primary, #6E3B5B);
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-guide-footer-action--outline:active{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  border-color:var(--pf-color-primary, #6E3B5B);
  color:var(--pf-color-primary, #6E3B5B);
}

/* =========================
   Throwability Overlay
   ========================= */

#sb-sg-app .sb-sg-throwability{
  position:absolute;
  top:12px;
  right:12px;
  z-index:10;
  width:200px;
  background:#fff;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius-sm);
  box-shadow:var(--sb-sg-shadow-soft);
  font-size:11px;
  line-height:1.5;
  color:var(--sb-sg-text);
  overflow:hidden;
}

/* Hidden state: the "Show throwability" box only ever contains the short
   toggle label, so it shrink-wraps instead of inheriting the base 200px -
   that 200px was crowding the Guide control on narrow (phone-portrait)
   canvases. right:12px, the rounded shell, shadow and button appearance are
   all untouched (inherited from the base rule above); compact and expanded
   width are untouched (compact still gets the base 200px; expanded already
   has its own override below). */
#sb-sg-app .sb-sg-throwability--hidden{
  width:fit-content;
  max-width:calc(100% - 24px);
}

/* Guide/Throwability collision (audit correction 2): while Guide is open
   (.sb-sg-guide-open on the canvas, toggled only from setGuidePanelExpanded()),
   temporarily remove Throwability from rendering, pointer interaction,
   keyboard focus and the accessibility tree. This never touches Throwability's
   own state/classes/content - .sb-sg-throwability--hidden/--compact/--expanded
   and all computed data remain exactly as they were, and reappear unchanged
   the moment .sb-sg-guide-open is removed. */
#sb-sg-app .sb-sg-canvas.sb-sg-guide-open .sb-sg-throwability{
  display:none;
}

/* Final product rule (confirmed by Borut): Throwability belongs only to
   the BASIC Shape workflow - it is completely absent for the entire time
   Advanced/Profile-point Shape editing is active, which inherently covers
   Handle editing and Lid editing too (both exist only inside a V2
   session). #sb-sg-app.sb-sg-v2-shape-active is the SAME existing signal
   already used elsewhere for V2-session-only presentation (see its other
   rule below) - toggled only by mountV2Session()/
   unmountPrimaryWorkspaceViews() - so this is one coherent rule for the
   entire V2 session lifetime, not a separate workaround per V2 feature.
   Presentation only - never touches Throwability's own state/classes/
   content, and never the calculation itself. */
#sb-sg-app.sb-sg-v2-shape-active .sb-sg-throwability{
  display:none;
}

/* Reverse direction: while Throwability is expanded (.sb-sg-throwability-open
   on the canvas, toggled only from setThrowabilityState()), temporarily
   remove the entire Guide wrapper - collapsed toggle included - from
   rendering, pointer interaction, tab order and the accessibility tree. Never
   rebuilds or mutates the Guide node; Guide's own aria-expanded/hidden state
   and content are untouched and simply reappear the moment
   .sb-sg-throwability-open is removed. */
#sb-sg-app .sb-sg-canvas.sb-sg-throwability-open .sb-sg-guide{
  display:none;
}

/* Compare V1 Phase 3B: same technique as the Guide/Throwability mutual
   exclusion above - a canvas-level class (.sb-sg-compare-active, toggled
   only from enterCompare()/exitCompareView() in app.js) purely hides the
   live preview SVG and both overlays while the Compare view is mounted.
   None of the three are ever removed, replaced, or have their own
   state/classes touched - they reappear exactly as they were the moment
   .sb-sg-compare-active is removed. */
#sb-sg-app .sb-sg-canvas.sb-sg-compare-active #sb-sg-preview-svg{
  display:none;
}

#sb-sg-app .sb-sg-canvas.sb-sg-compare-active .sb-sg-guide{
  display:none;
}

#sb-sg-app .sb-sg-canvas.sb-sg-compare-active .sb-sg-throwability{
  display:none;
}

/* Expanded state: wide readable panel over canvas */
#sb-sg-app .sb-sg-throwability--expanded{
  width:min(720px, calc(100% - 32px));
}

/* Compact signals text hidden in expanded (chips replace it) */
#sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-signals{
  display:none;
}

/* Toggle button */
#sb-sg-app .sb-sg-throwability-toggle{
  display:block;
  width:100%;
  padding:6px 10px;
  background:none;
  border:none;
  border-bottom:1px solid var(--sb-sg-card-border);
  text-align:right;
  font:inherit;
  font-size:11px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
  cursor:pointer;
  white-space:nowrap;
  transition:background 0.14s ease;
}

#sb-sg-app .sb-sg-throwability-toggle:hover{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  text-decoration:underline;
}

#sb-sg-app .sb-sg-throwability-toggle:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:-2px;
}

#sb-sg-app .sb-sg-throwability-toggle:active{
  background:var(--pf-color-primary-soft-active, rgba(110,59,91,0.13));
}

/* In hidden state: panel is gone, toggle needs no bottom border. Also match
   the collapsed Guide shell's box model - the sole cause of the visible
   height mismatch was .sb-sg-guide-toggle's min-height:44px, which this
   toggle never had (padding, background, border, radius, shadow, font-size,
   font-weight, colour and hover/active/focus-visible were already identical
   between the two toggles). Scoped to --hidden only, so compact/expanded are
   untouched. display:flex + justify-content:flex-end keeps the existing
   right-aligned text while vertically centering it in the taller box. */
#sb-sg-app .sb-sg-throwability--hidden .sb-sg-throwability-toggle{
  border-bottom:none;
  display:flex;
  align-items:center;
  justify-content:flex-end;
  min-height:44px;
}

/* Panel */
#sb-sg-app .sb-sg-throwability-panel{
  padding:8px 10px 10px;
}

/* Label */
#sb-sg-app .sb-sg-throwability-label{
  margin:0 0 3px;
  font-size:11px;
  font-weight:600;
  color:var(--sb-sg-text);
}

/* Clay weight estimate */
#sb-sg-app .sb-sg-throwability-clay{
  margin:0 0 4px;
  font-size:11px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

/* Summary */
#sb-sg-app .sb-sg-throwability-summary{
  margin:0;
  font-size:11px;
  color:var(--sb-sg-text-soft);
}

/* Details — expanded only (hidden attribute toggled by JS) */
#sb-sg-app .sb-sg-throwability-details{
  margin-top:8px;
  padding-top:8px;
  border-top:1px solid var(--sb-sg-line);
}

/* Card grid: chips span full width, warning/note cards wrap naturally */
#sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-details{
  display:grid;
  grid-template-columns:repeat(auto-fill, minmax(200px, 1fr));
  gap:6px;
  align-items:start;
}

#sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-chips{
  grid-column:1 / -1;
}

#sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-warning,
#sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-note{
  margin-bottom:0;
}

/* Signal chips (expanded only, inside details) */
#sb-sg-app .sb-sg-throwability-chips{
  display:flex;
  flex-wrap:wrap;
  gap:5px;
  padding-bottom:4px;
}

#sb-sg-app .sb-sg-throwability-chip{
  display:inline-block;
  padding:3px 9px;
  background:var(--pf-color-warning-soft, rgba(169,77,36,0.08));
  border:1px solid var(--pf-color-warning-border, rgba(169,77,36,0.35));
  border-radius:99px;
  font-size:10px;
  font-weight:600;
  color:var(--pf-color-warning, #A94D24);
  line-height:1.4;
  white-space:nowrap;
}

/* Warning items - genuine Throwability caution content, so this keeps the
   dedicated warning family rather than migrating to Plum. */
#sb-sg-app .sb-sg-throwability-warning{
  margin:0 0 6px;
  padding:5px 7px;
  background:var(--pf-color-warning-soft, rgba(169,77,36,0.08));
  border-left:2px solid var(--pf-color-warning-border, rgba(169,77,36,0.35));
  border-radius:0 4px 4px 0;
  font-size:11px;
  color:var(--sb-sg-text);
}

/* Note items (construction, technique, trimming) */
#sb-sg-app .sb-sg-throwability-note{
  margin:0 0 6px;
  padding:5px 7px;
  background:rgba(17,17,17,0.03);
  border-left:2px solid var(--sb-sg-card-border-strong);
  border-radius:0 4px 4px 0;
  font-size:11px;
  font-style:italic;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-throwability-warning:last-child,
#sb-sg-app .sb-sg-throwability-note:last-child{
  margin-bottom:0;
}

/* Responsive — tablet */
@media (max-width:900px){
  #sb-sg-app .sb-sg-throwability--compact{
    width:180px;
  }
}

/* Responsive — mobile portrait */
@media (max-width:640px){
  #sb-sg-app .sb-sg-throwability--compact{
    top:8px;
    right:8px;
    width:160px;
  }

  /* Expanded: bottom-sheet anchored to canvas bottom */
  #sb-sg-app .sb-sg-throwability--expanded{
    top:auto;
    bottom:12px;
    left:12px;
    right:12px;
    width:auto;
  }

  /* Restore scroll only on mobile expanded */
  #sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-details{
    max-height:50vh;
    overflow-y:auto;
    scrollbar-width:thin;
    scrollbar-color:rgba(17,17,17,0.18) transparent;
  }

  #sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-details::-webkit-scrollbar{
    width:4px;
  }

  #sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-details::-webkit-scrollbar-thumb{
    background:rgba(17,17,17,0.16);
    border-radius:999px;
  }

  #sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-details::-webkit-scrollbar-track{
    background:transparent;
  }
}

/* Expanded Throwability, phone-landscape only (641-932px, landscape): at this
   width the mobile-landscape .sb-sg-canvas is short enough that expanded
   Throwability's content (label/clay/summary/details) can exceed the
   available height and get clipped by .sb-sg-canvas{overflow:hidden}, since
   .sb-sg-throwability has no height constraint of its own anywhere else.
   Mirrors Guide's own proven .sb-sg-guide--expanded pattern: the toggle
   stays a fixed-size flex item at the top, the panel becomes the sole
   flex:1 1 auto scroll region below it. Deliberately excludes <=640px (its
   own, different bottom-sheet + .sb-sg-throwability-details scroll stays
   untouched) and desktop/portrait-tablet (content already fits there). */
@media (min-width:641px) and (max-width:932px) and (orientation:landscape){
  #sb-sg-app .sb-sg-throwability--expanded{
    display:flex;
    flex-direction:column;
    height:calc(100% - 24px);
    min-height:0;
  }

  #sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-toggle{
    flex:0 0 auto;
  }

  #sb-sg-app .sb-sg-throwability--expanded .sb-sg-throwability-panel{
    flex:1 1 auto;
    min-height:0;
    overflow-y:auto;
    overscroll-behavior-y:contain;
    -webkit-overflow-scrolling:touch;
  }
}

/* Fullscreen equivalent, width-independent: mirrors the landscape block
   above exactly (same declarations, same values) so expanded Throwability's
   existing scroll mechanism - not a new one - keeps working once
   #sb-sg-app is :fullscreen at a Chrome-reported viewport wider than
   932px. .sb-sg-throwability--expanded's own height:calc(100% - 24px)
   resolves against .sb-sg-canvas, which the block above already gives a
   correct, width-independent height while fullscreen. */
@media (orientation:landscape){
  #sb-sg-app:fullscreen .sb-sg-throwability--expanded{
    display:flex;
    flex-direction:column;
    height:calc(100% - 24px);
    min-height:0;
  }

  #sb-sg-app:fullscreen .sb-sg-throwability--expanded .sb-sg-throwability-toggle{
    flex:0 0 auto;
  }

  #sb-sg-app:fullscreen .sb-sg-throwability--expanded .sb-sg-throwability-panel{
    flex:1 1 auto;
    min-height:0;
    overflow-y:auto;
    overscroll-behavior-y:contain;
    -webkit-overflow-scrolling:touch;
  }
}

/* Saved Variants list (V1.1) */
#sb-sg-app .sb-sg-variants-list{
  margin-top:10px;
  padding-top:10px;
  border-top:1px solid var(--sb-sg-line);
}

#sb-sg-app .sb-sg-variants-toggle{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:8px;
  width:100%;
  min-height:44px;
  padding:10px 12px;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius-sm);
  background:#fff;
  font:inherit;
  font-size:13px;
  font-weight:600;
  color:var(--sb-sg-text);
  cursor:pointer;
  text-align:left;
  transition:border-color .14s ease, background .14s ease;
}

#sb-sg-app .sb-sg-variants-toggle:hover{
  border-color:var(--pf-color-primary-border, rgba(110,59,91,0.28));
}

#sb-sg-app .sb-sg-variants-toggle:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-variants-toggle::after{
  content:"\25B8";
  flex:0 0 auto;
  font-size:11px;
  color:var(--sb-sg-text-faint);
  transition:transform .14s ease;
}

#sb-sg-app .sb-sg-variants-toggle[aria-expanded="true"]::after{
  transform:rotate(90deg);
}

/* Phase 1A note: this single class covers three different severities
   (unavailable/unreadable = error-like, partial-read = warning-like, see
   VARIANTS_LIST_MESSAGES in app.js) with no per-severity styling in JS, so
   no single error or warning token correctly represents every case it is
   used for. Kept neutral (the existing soft-text token) until a later
   accessibility/status phase introduces explicit severity classes - the
   message wording is the only current severity cue, which is acceptable
   only as a temporary state ahead of that phase. */
#sb-sg-app .sb-sg-variants-health{
  margin:8px 0 0;
  font-size:11px;
  line-height:1.45;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-variants-health:empty{
  display:none;
}

#sb-sg-app .sb-sg-variants-panel{
  margin-top:8px;
}

#sb-sg-app .sb-sg-variants-panel[hidden]{
  display:none;
}

/* Compare V1 Phase 3B: "View comparison" fallback control - disabled below
   two selections, reopens Compare after Back to editor or after a refresh
   restores a prior selection (see updateViewComparisonButton() in app.js).
   Same solid Plum/white-text primary-button language as .sb-sg-export-btn
   (Export PDF / Save Variant, see ensureUiAccentStyles() in app.js) -
   full-width, prominent, enabled state reads as clearly actionable rather
   than a secondary/outline control. Disabled state switches to a muted
   neutral fill with faint text, visibly inactive rather than just a
   colour shift on the same solid fill.
   UI-2: .sb-sg-view-set-btn (View Set / Back to Set) shares this exact rule
   set - "open the corresponding view" is the same visual weight in both the
   Compare and Set rows (see buildShapeActionsGroupMarkup() and
   updateTeaSetEntryButton() in app.js). margin-top was dropped (was 8px) now
   that both classes always sit as a grid cell inside .sb-sg-project-actions,
   whose own margin/gap already provides the row's spacing - a per-button top
   margin here would misalign it against its row partner
   (.sb-sg-add-to-compare-btn/.sb-sg-create-set-btn). */
#sb-sg-app .sb-sg-view-comparison-btn,
#sb-sg-app .sb-sg-view-set-btn{
  display:flex;
  align-items:center;
  justify-content:center;
  width:100%;
  min-height:44px;
  padding:10px 14px;
  border:2px solid var(--pf-color-primary, #6E3B5B);
  border-radius:8px;
  background:var(--pf-color-primary, #6E3B5B);
  font:inherit;
  font-size:13px;
  font-weight:600;
  color:#fff;
  cursor:pointer;
  box-shadow:0 1px 2px rgba(0,0,0,0.08);
  transition:background-color .16s ease, border-color .16s ease, color .16s ease, box-shadow .16s ease, transform .04s ease, opacity .16s ease;
}

#sb-sg-app .sb-sg-view-comparison-btn:hover:not(:disabled),
#sb-sg-app .sb-sg-view-comparison-btn:focus:not(:disabled),
#sb-sg-app .sb-sg-view-set-btn:hover:not(:disabled),
#sb-sg-app .sb-sg-view-set-btn:focus:not(:disabled){
  background:var(--pf-color-primary-hover, #5A304C);
  border-color:var(--pf-color-primary-hover, #5A304C);
  color:#fff;
  box-shadow:0 2px 8px rgba(0,0,0,0.14);
}

#sb-sg-app .sb-sg-view-comparison-btn:active:not(:disabled),
#sb-sg-app .sb-sg-view-set-btn:active:not(:disabled){
  transform:translateY(1px);
  box-shadow:0 1px 3px rgba(0,0,0,0.12);
}

#sb-sg-app .sb-sg-view-comparison-btn:focus-visible,
#sb-sg-app .sb-sg-view-set-btn:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-view-comparison-btn:disabled,
#sb-sg-app .sb-sg-view-set-btn:disabled{
  background:var(--sb-sg-card);
  border-color:var(--sb-sg-card-border);
  color:var(--sb-sg-text-faint);
  opacity:0.7;
  cursor:default;
  box-shadow:none;
}

#sb-sg-app .sb-sg-variants-rows{
  margin:0;
  padding:0;
  list-style:none;
  max-height:min(360px, 50vh);
  overflow-y:auto;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius-sm);
  background:#fff;
}

/* Saved Variants list height correction: a taller list, with the same
   finite max-height and the same internal vertical scrollbar the base
   rule above already provides (only max-height changes here - margin/
   padding/border/radius/background are all untouched), for two input
   classes that both have comfortable vertical room to show it in:
   ordinary desktop/laptop (a hover:hover, pointer:fine primary pointer,
   any orientation, from 933px upward) and landscape coarse-pointer
   tablets in the 933-1280px band - the exact same width/input-capability
   condition the controls-column tablet companion rule above already uses
   (physical-device-confirmed against a Samsung SM-T585, reported CSS
   viewport 1280x680), reused here in its own isolated block per this
   file's convention of never editing an approved block just to add an
   unrelated declaration. 740x430 and 932x430 landscape (both narrower
   than 933px) and touch portrait (excluded by orientation:landscape on
   the tablet rule below, and by pointer:fine on the desktop rule) all
   fall through untouched to the base min(360px, 50vh) above. */
@media (hover:hover) and (pointer:fine) and (min-width:933px){
  #sb-sg-app .sb-sg-variants-rows{
    max-height:min(520px, 65vh);
  }
}

@media (orientation:landscape) and (min-width:933px) and (max-width:1280px) and (hover:none) and (pointer:coarse){
  #sb-sg-app .sb-sg-variants-rows{
    max-height:min(520px, 65vh);
  }
}

#sb-sg-app .sb-sg-variant-row{
  padding:8px 10px 8px 7px;
  border-bottom:1px solid var(--sb-sg-line);
  /* Reserved, transparent by default, so the active state below only ever
     changes color, never width - toggling it never shifts row content. */
  border-left:3px solid transparent;
}

#sb-sg-app .sb-sg-variant-row:last-child{
  border-bottom:none;
}

#sb-sg-app .sb-sg-variant-row-name{
  font-size:12px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-variant-row-project{
  margin-top:2px;
  font-size:11px;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-variant-row-meta{
  margin-top:2px;
  font-size:10px;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-variant-row-measure{
  margin-top:4px;
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:2px 8px;
  font-size:11px;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-variant-row-measure-cell{
  min-width:0;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
}

#sb-sg-app .sb-sg-variant-row-empty{
  padding:10px;
  font-size:11px;
  color:var(--sb-sg-text-soft);
}

/* Three-position action row for each Saved Variant: Load in column 1
   (left-aligned), an unused reserved column 2 in the middle (structurally
   available for a future Compare control - deliberately never populated,
   no placeholder/hidden/disabled element), Delete in column 3
   (right-aligned). A dedicated grid wrapper rather than making the whole
   .sb-sg-variant-row a flex/grid row, since that row also holds the
   name/project/meta/measurement content above this. auto/1fr/auto sizes
   Load and Delete to their own content and lets the middle column absorb
   all remaining width, so the two buttons stay pinned to the row's left
   and right edges at any width, including narrow phone widths, without
   ever overlapping (the middle track can shrink to 0 but not negative). */
#sb-sg-app .sb-sg-variant-row-actions{
  display:grid;
  grid-template-columns:auto 1fr auto;
  align-items:center;
  margin-top:6px;
}

#sb-sg-app .sb-sg-variant-row-load{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  justify-self:start;
  min-height:32px;
  padding:6px 12px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  background:#fff;
  font:inherit;
  font-size:11px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
  cursor:pointer;
}

#sb-sg-app .sb-sg-variant-row-load:hover{
  border-color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-variant-row-load:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-variant-row-load:active{
  transform:translateY(1px);
}

/* Same box model, typography, radius and minimum touch/click target as
   .sb-sg-variant-row-load above (32px min-height, matching padding/font) -
   only color changes, to the shared PotterForm error family (--pf-color-error,
   also used by setVariantStatus()'s inline error colour), so Delete reads
   as clearly destructive and clearly distinct from Load. Positioned in the
   grid's third (auto) column via justify-self:end, pinning it to the row's
   far right - see .sb-sg-variant-row-actions above. */
#sb-sg-app .sb-sg-variant-row-delete{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  justify-self:end;
  grid-column:3;
  min-height:32px;
  padding:6px 12px;
  border:1px solid var(--pf-color-error-border, rgba(170, 17, 17, 0.35));
  border-radius:var(--sb-sg-radius-sm);
  background:#fff;
  font:inherit;
  font-size:11px;
  font-weight:600;
  color:var(--pf-color-error, #AA1111);
  cursor:pointer;
}

#sb-sg-app .sb-sg-variant-row-delete:hover{
  border-color:var(--pf-color-error, #AA1111);
  background:var(--pf-color-error-soft, rgba(170, 17, 17, 0.06));
}

#sb-sg-app .sb-sg-variant-row-delete:focus-visible{
  outline:2px solid var(--pf-color-error, #AA1111);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-variant-row-delete:active{
  transform:translateY(1px);
}

/* Compare V1 Phase 2: same box model, typography, radius and minimum
   touch/click target as Load/Delete above (32px min-height, matching
   padding/font) so all three actions stay visually aligned. Sits in the
   grid's middle (1fr) column purely by DOM order (Load, then this, then
   Delete) - see .sb-sg-variant-row-actions above, and no explicit
   grid-column is set here, matching that. justify-self:center keeps the
   button itself compact rather than stretching to fill the 1fr track.
   Unselected uses a neutral border/text so it never reads as identical to
   Load's Plum-coloured look; selected switches to a solid Plum fill -
   this button-level contrast, plus its own text ("Compare" vs "In Compare",
   Phase E3.1 - no longer the internal slot number, "Compare N") and
   aria-pressed, are the primary state signal, not colour alone. */
#sb-sg-app .sb-sg-variant-row-compare{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  justify-self:center;
  min-height:32px;
  padding:6px 12px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  background:#fff;
  font:inherit;
  font-size:11px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
  cursor:pointer;
  white-space:nowrap;
}

#sb-sg-app .sb-sg-variant-row-compare:hover{
  border-color:var(--pf-color-primary, #6E3B5B);
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-variant-row-compare:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-variant-row-compare:active{
  transform:translateY(1px);
}

#sb-sg-app .sb-sg-variant-row-compare[aria-pressed="true"]{
  border-color:var(--pf-color-primary, #6E3B5B);
  background:var(--pf-color-primary, #6E3B5B);
  color:#fff;
}

#sb-sg-app .sb-sg-variant-row-compare[aria-pressed="true"]:hover{
  border-color:var(--pf-color-primary-hover, #5A304C);
  background:var(--pf-color-primary-hover, #5A304C);
  color:#fff;
}

/* Active-Variant indication: subtle Plum background + left border. Only
   background and border-color change here - the row's own border-left
   width/padding are already reserved by the base .sb-sg-variant-row rule
   above, so no size or position ever shifts when this class is toggled.
   Deliberately distinct from (and never reused for) Compare selection just
   below - both classes can be present on the same row at once, and remain
   visually distinguishable from each other. */
#sb-sg-app .sb-sg-variant-row--active{
  background:var(--pf-color-primary-soft, rgba(110,59,91,0.07));
  border-left-color:var(--pf-color-primary, #6E3B5B);
}

/* Compare V1 Phase 2 selected-card state: a dashed outline rather than
   .sb-sg-variant-row--active's solid left border + tinted background, so
   the two states read as clearly different even when both apply to the
   same row at once. Negative outline-offset keeps it just inside the row's
   own border box - it can never extend past the row's edges, so it cannot
   introduce horizontal overflow (.sb-sg-variants-rows scrolls vertically
   only). Colour is a supplement here, not the only signal - the row's own
   Compare button already shows "In Compare" text and aria-pressed="true". */
#sb-sg-app .sb-sg-variant-row--compare-selected{
  outline:1.5px dashed var(--pf-color-primary, #6E3B5B);
  outline-offset:-2px;
}

/* Saved Variant thumbnail (V1.1 first implementation): a fixed-width
   thumbnail column beside the existing name/project/meta/measurement text
   column. minmax(0, 1fr) on the text track (not just flex/auto) is the
   overflow guard - it caps the column at the row's actual remaining width
   regardless of the longest unbreakable text inside it, matching the
   existing .sb-sg-variant-row-measure-cell text-overflow:ellipsis pattern
   just above. No hover/active/cursor rules - the thumbnail is decorative
   only (see aria-hidden on the SVG itself, set in app.js). */
#sb-sg-app .sb-sg-variant-row-upper{
  display:grid;
  grid-template-columns:52px minmax(0, 1fr);
  align-items:start;
  gap:8px;
}

#sb-sg-app .sb-sg-variant-row-thumb{
  display:flex;
  align-items:center;
  justify-content:center;
  width:52px;
  height:64px;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius-sm);
  background:var(--sb-sg-card);
  overflow:hidden;
}

#sb-sg-app .sb-sg-variant-row-thumb-svg{
  display:block;
  width:100%;
  height:100%;
}

/* vector-effect:non-scaling-stroke keeps the stroke a constant screen-pixel
   width regardless of each shape's own viewBox scale (model.w/model.h vary
   per Variant, e.g. a tall narrow vase vs. a wide bowl) - without it, the
   same stroke-width would render visually thinner or thicker depending on
   how much a given shape's own coordinate space gets scaled down to fit
   this fixed 52x64 frame. This is a thumbnail-specific rendering adaptation
   only, not a second geometry interpretation - the path data itself is
   unchanged, sourced only from buildShapeModel(record.shape).pathD. */
#sb-sg-app .sb-sg-variant-row-thumb-path{
  fill:none;
  stroke:var(--pf-color-shape, #6E3B5B);
  stroke-width:1.5;
  stroke-linejoin:round;
  stroke-linecap:round;
  vector-effect:non-scaling-stroke;
}

#sb-sg-app .sb-sg-variant-row-text{
  min-width:0;
}

/* Narrower thumbnail at the existing mobile-landscape breakpoint. A second,
   independent @media block using the exact same condition as the approved
   shell/controls/canvas/tool-rail rule elsewhere in this file (valid CSS -
   multiple blocks with an identical condition behave exactly like one) -
   deliberately kept separate so this card-only change never touches that
   already-approved rule's own selectors or declarations. */
@media (max-width: 932px) and (orientation: landscape){
  #sb-sg-app .sb-sg-variant-row-upper{
    grid-template-columns:44px minmax(0, 1fr);
    gap:6px;
  }

  #sb-sg-app .sb-sg-variant-row-thumb{
    width:44px;
    height:56px;
  }
}

/* Load confirmation dialog - replaces window.confirm(). vw/vh resolve
   against whichever presentation is current (normal page or :fullscreen),
   so this sizes correctly in both without separate rules. Deliberately no
   entrance/exit animation and no fixed height - it sizes to its own short
   content and scrolls internally only if a narrow/low-height viewport ever
   makes that necessary. */
#sb-sg-app .sb-sg-load-confirm-dialog,
#sb-sg-app .sb-sg-reset-confirm-dialog,
#sb-sg-app .sb-sg-tea-set-replace-dialog,
#sb-sg-app .sb-sg-working-draft-dialog,
#sb-sg-app .sb-sg-set-navigation-dialog,
#sb-sg-app .sb-sg-save-lid-dialog,
#sb-sg-app .sb-sg-save-handle-dialog,
#sb-sg-app .sb-sg-lid-replace-dialog,
#sb-sg-app .sb-sg-handle-replace-dialog,
#sb-sg-app .sb-sg-compare-export-error-dialog{
  width:min(360px, calc(100vw - 32px));
  max-height:calc(100vh - 32px);
  overflow-y:auto;
  padding:20px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius);
  background:var(--sb-sg-bg);
  color:var(--sb-sg-text);
  box-shadow:var(--sb-sg-shadow);
}

#sb-sg-app .sb-sg-load-confirm-dialog::backdrop,
#sb-sg-app .sb-sg-reset-confirm-dialog::backdrop,
#sb-sg-app .sb-sg-tea-set-replace-dialog::backdrop,
#sb-sg-app .sb-sg-working-draft-dialog::backdrop,
#sb-sg-app .sb-sg-set-navigation-dialog::backdrop,
#sb-sg-app .sb-sg-save-lid-dialog::backdrop,
#sb-sg-app .sb-sg-save-handle-dialog::backdrop,
#sb-sg-app .sb-sg-lid-replace-dialog::backdrop,
#sb-sg-app .sb-sg-handle-replace-dialog::backdrop,
#sb-sg-app .sb-sg-my-lids-picker-dialog::backdrop,
#sb-sg-app .sb-sg-compare-export-error-dialog::backdrop{
  background:rgba(17,17,17,0.35);
}

/* Working Draft recovery dialog only: its two button labels ("Continue
   previous work" / "Open selected Shape") are longer than the other two
   dialogs' ("Cancel"/"Load Shape", "Cancel"/"Create new Set"), so the
   shared 360px cap above was forcing them onto separate lines. This
   overrides only `width` (same selector/specificity as the shared rule
   above, later in source order so it wins) - every other rule (backdrop,
   heading, message, actions row, button colors/focus) stays exactly the
   shared one above, unchanged. The existing .sb-sg-working-draft-actions
   flex-wrap:wrap (shared, unchanged below) already stacks the two buttons
   gracefully once the available width - still clamped by
   calc(100vw - 32px) - drops below what they need on a narrow/mobile
   viewport, so no separate mobile rule is needed either. */
#sb-sg-app .sb-sg-working-draft-dialog{
  width:min(460px, calc(100vw - 32px));
}

/* Three distinct choices stay in one action row at every supported width.
   The dialog grows on larger screens; on phones the first label may wrap
   inside its own 44px+ touch target, but the actions never form a second
   row or shrink below a comfortable hit area. */
#sb-sg-app .sb-sg-set-navigation-dialog{
  width:min(520px, calc(100vw - 32px));
}

#sb-sg-app .sb-sg-set-navigation-actions{
  display:grid;
  grid-template-columns:minmax(0, 1.6fr) minmax(64px, 0.8fr) minmax(72px, 0.9fr);
  align-items:stretch;
  gap:8px;
}

#sb-sg-app .sb-sg-set-navigation-dialog .sb-sg-set-navigation-leave,
#sb-sg-app .sb-sg-set-navigation-dialog .sb-sg-set-navigation-cancel,
#sb-sg-app .sb-sg-set-navigation-dialog .sb-sg-set-navigation-save{
  width:100%;
  min-width:0;
  min-height:44px;
  padding:8px 10px;
  white-space:normal;
}

#sb-sg-app .sb-sg-load-confirm-heading,
#sb-sg-app .sb-sg-reset-confirm-heading,
#sb-sg-app .sb-sg-tea-set-replace-heading,
#sb-sg-app .sb-sg-working-draft-heading,
#sb-sg-app .sb-sg-set-navigation-heading,
#sb-sg-app .sb-sg-save-lid-heading,
#sb-sg-app .sb-sg-save-handle-heading,
#sb-sg-app .sb-sg-lid-replace-heading,
#sb-sg-app .sb-sg-handle-replace-heading,
#sb-sg-app .sb-sg-compare-export-error-heading{
  margin:0 0 8px;
  font-size:15px;
  font-weight:600;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-load-confirm-message,
#sb-sg-app .sb-sg-reset-confirm-message,
#sb-sg-app .sb-sg-tea-set-replace-message,
#sb-sg-app .sb-sg-working-draft-message,
#sb-sg-app .sb-sg-set-navigation-message,
#sb-sg-app .sb-sg-save-lid-helper,
#sb-sg-app .sb-sg-save-handle-helper,
#sb-sg-app .sb-sg-lid-replace-message,
#sb-sg-app .sb-sg-handle-replace-message,
#sb-sg-app .sb-sg-compare-export-error-message{
  margin:0 0 16px;
  font-size:13px;
  line-height:1.45;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-load-confirm-actions,
#sb-sg-app .sb-sg-reset-confirm-actions,
#sb-sg-app .sb-sg-tea-set-replace-actions,
#sb-sg-app .sb-sg-working-draft-actions,
#sb-sg-app .sb-sg-save-lid-actions,
#sb-sg-app .sb-sg-save-handle-actions,
#sb-sg-app .sb-sg-lid-replace-actions,
#sb-sg-app .sb-sg-handle-replace-actions,
#sb-sg-app .sb-sg-compare-export-error-actions{
  display:flex;
  justify-content:flex-end;
  flex-wrap:wrap;
  gap:10px;
}

/* ML2: Save Lid's Name field - the dialog's own buttons reuse the
   established #sb-sg-app .sb-sg-btn-primary/.sb-sg-btn-secondary classes
   directly (see buildSaveLidDialogNode()/buildLidReplaceConfirmDialogNode()
   in app.js), so only the field/error styling below is new. */
#sb-sg-app .sb-sg-save-lid-field{
  display:grid;
  gap:4px;
  margin-bottom:12px;
}

#sb-sg-app .sb-sg-save-lid-label{
  font-size:12px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-save-lid-name{
  width:100%;
  min-height:40px;
  padding:8px 10px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  font:inherit;
  font-size:14px;
  color:var(--sb-sg-text);
  background:#fff;
}

#sb-sg-app .sb-sg-save-lid-name:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:1px;
}

#sb-sg-app .sb-sg-save-lid-error{
  margin:0 0 12px;
  font-size:12px;
  font-weight:600;
  color:var(--pf-color-error, #AA1111);
}

#sb-sg-app .sb-sg-save-lid-error[hidden]{
  display:none;
}

/* H3-B1: Save Handle's Name field - the same shared field/label/input/error
   styling as Save Lid's own above, scoped to the Handle dialog's own class
   names (see buildSaveHandleDialogNode() in app.js). */
#sb-sg-app .sb-sg-save-handle-field{
  display:grid;
  gap:4px;
  margin-bottom:12px;
}

#sb-sg-app .sb-sg-save-handle-label{
  font-size:12px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-save-handle-name{
  width:100%;
  min-height:40px;
  padding:8px 10px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  font:inherit;
  font-size:14px;
  color:var(--sb-sg-text);
  background:#fff;
}

#sb-sg-app .sb-sg-save-handle-name:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:1px;
}

#sb-sg-app .sb-sg-save-handle-error{
  margin:0 0 12px;
  font-size:12px;
  font-weight:600;
  color:var(--pf-color-error, #AA1111);
}

#sb-sg-app .sb-sg-save-handle-error[hidden]{
  display:none;
}

/* .sb-sg-set-navigation-actions is deliberately excluded from the shared
   flex/wrap rule above: it has its own single-row grid layout (see
   ".sb-sg-set-navigation-actions{ display:grid; ... }" earlier in this
   file). Both selectors share equal specificity, so this generic rule -
   later in source order - was winning and forcing flex-wrap, which (given
   each button's own width:100%) stacked all three actions onto separate
   rows instead of the required single row. */

#sb-sg-app .sb-sg-load-confirm-cancel,
#sb-sg-app .sb-sg-load-confirm-load,
#sb-sg-app .sb-sg-reset-confirm-cancel,
#sb-sg-app .sb-sg-reset-confirm-action,
#sb-sg-app .sb-sg-tea-set-replace-cancel,
#sb-sg-app .sb-sg-tea-set-replace-confirm,
#sb-sg-app .sb-sg-working-draft-discard,
#sb-sg-app .sb-sg-working-draft-continue,
#sb-sg-app .sb-sg-set-navigation-leave,
#sb-sg-app .sb-sg-set-navigation-cancel,
#sb-sg-app .sb-sg-set-navigation-save{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:40px;
  padding:8px 16px;
  border-radius:var(--sb-sg-radius-sm);
  font:inherit;
  font-size:13px;
  font-weight:600;
  cursor:pointer;
}

#sb-sg-app .sb-sg-load-confirm-cancel,
#sb-sg-app .sb-sg-reset-confirm-cancel,
#sb-sg-app .sb-sg-tea-set-replace-cancel,
#sb-sg-app .sb-sg-working-draft-discard,
#sb-sg-app .sb-sg-set-navigation-cancel{
  border:1px solid var(--sb-sg-card-border-strong);
  background:#fff;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-load-confirm-cancel:hover,
#sb-sg-app .sb-sg-reset-confirm-cancel:hover,
#sb-sg-app .sb-sg-tea-set-replace-cancel:hover,
#sb-sg-app .sb-sg-working-draft-discard:hover,
#sb-sg-app .sb-sg-set-navigation-cancel:hover{
  border-color:var(--pf-color-primary, #6E3B5B);
  color:var(--pf-color-primary, #6E3B5B);
}

/* Leave without saving is the destructive/secondary action in this
   three-choice row (required hierarchy: Leave = destructive/secondary,
   Cancel = neutral, Save Set = primary) - it previously shared Cancel's
   neutral styling, which did not distinguish "discard my changes" from
   "do nothing". Uses the same error-red family as the Delete confirmation
   dialog below, never the theme/browser default blue. */
#sb-sg-app .sb-sg-set-navigation-leave{
  border:1px solid var(--pf-color-error, #AA1111);
  background:#fff;
  color:var(--pf-color-error, #AA1111);
}

#sb-sg-app .sb-sg-set-navigation-leave:hover{
  background:var(--pf-color-error-soft, rgba(170,17,17,.06));
  border-color:var(--pf-color-error-hover, #8A0E0E);
  color:var(--pf-color-error-hover, #8A0E0E);
}

#sb-sg-app .sb-sg-load-confirm-load,
#sb-sg-app .sb-sg-reset-confirm-action,
#sb-sg-app .sb-sg-tea-set-replace-confirm,
#sb-sg-app .sb-sg-working-draft-continue,
#sb-sg-app .sb-sg-set-navigation-save{
  border:1px solid var(--pf-color-primary, #6E3B5B);
  background:var(--pf-color-primary, #6E3B5B);
  color:#fff;
}

#sb-sg-app .sb-sg-load-confirm-load:hover,
#sb-sg-app .sb-sg-reset-confirm-action:hover,
#sb-sg-app .sb-sg-tea-set-replace-confirm:hover,
#sb-sg-app .sb-sg-working-draft-continue:hover,
#sb-sg-app .sb-sg-set-navigation-save:hover{
  background:var(--pf-color-primary-hover, #5A304C);
  border-color:var(--pf-color-primary-hover, #5A304C);
}

#sb-sg-app .sb-sg-load-confirm-cancel:focus-visible,
#sb-sg-app .sb-sg-load-confirm-load:focus-visible,
#sb-sg-app .sb-sg-reset-confirm-cancel:focus-visible,
#sb-sg-app .sb-sg-reset-confirm-action:focus-visible,
#sb-sg-app .sb-sg-tea-set-replace-cancel:focus-visible,
#sb-sg-app .sb-sg-tea-set-replace-confirm:focus-visible,
#sb-sg-app .sb-sg-working-draft-discard:focus-visible,
#sb-sg-app .sb-sg-working-draft-continue:focus-visible,
#sb-sg-app .sb-sg-set-navigation-cancel:focus-visible,
#sb-sg-app .sb-sg-set-navigation-save:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

/* Leave stays in the destructive red family on focus-visible too, same
   reasoning as its hover state above - never the shared Plum outline. */
#sb-sg-app .sb-sg-set-navigation-leave:focus-visible{
  outline:2px solid var(--pf-color-error, #AA1111);
  outline-offset:2px;
}

/* Delete confirmation dialog - narrow mirror of the Load confirmation
   dialog rules above (identical sizing/positioning/backdrop rationale), so
   only the destructive Delete button itself is recolored to the same
   error red already used for .sb-sg-variant-row-delete and for
   setVariantStatus()'s inline error color. */
#sb-sg-app .sb-sg-delete-confirm-dialog{
  width:min(360px, calc(100vw - 32px));
  max-height:calc(100vh - 32px);
  overflow-y:auto;
  padding:20px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius);
  background:var(--sb-sg-bg);
  color:var(--sb-sg-text);
  box-shadow:var(--sb-sg-shadow);
}

#sb-sg-app .sb-sg-delete-confirm-dialog::backdrop{
  background:rgba(17,17,17,0.35);
}

#sb-sg-app .sb-sg-delete-confirm-heading{
  margin:0 0 8px;
  font-size:15px;
  font-weight:600;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-delete-confirm-message{
  margin:0 0 16px;
  font-size:13px;
  line-height:1.45;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-delete-confirm-actions{
  display:flex;
  justify-content:flex-end;
  flex-wrap:wrap;
  gap:10px;
}

#sb-sg-app .sb-sg-delete-confirm-cancel,
#sb-sg-app .sb-sg-delete-confirm-delete{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:40px;
  padding:8px 16px;
  border-radius:var(--sb-sg-radius-sm);
  font:inherit;
  font-size:13px;
  font-weight:600;
  cursor:pointer;
}

#sb-sg-app .sb-sg-delete-confirm-cancel{
  border:1px solid var(--sb-sg-card-border-strong);
  background:#fff;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-delete-confirm-cancel:hover{
  border-color:var(--pf-color-primary, #6E3B5B);
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-delete-confirm-delete{
  border:1px solid var(--pf-color-error, #AA1111);
  background:var(--pf-color-error, #AA1111);
  color:#fff;
}

#sb-sg-app .sb-sg-delete-confirm-delete:hover{
  background:var(--pf-color-error-hover, #8A0E0E);
  border-color:var(--pf-color-error-hover, #8A0E0E);
}

#sb-sg-app .sb-sg-delete-confirm-cancel:focus-visible,
#sb-sg-app .sb-sg-delete-confirm-delete:focus-visible{
  outline:2px solid var(--pf-color-error, #AA1111);
  outline-offset:2px;
}

/* =========================
   Compare V1 Phase 3B: on-screen comparison view
   ========================= */

/* Appended inside .sb-sg-canvas (position:sticky, so this is a valid
   containing block) alongside - never instead of - the live preview SVG
   and the Guide/Throwability overlays; .sb-sg-compare-active (see above)
   is what visually hides those three while this is mounted. */
#sb-sg-app .sb-sg-compare-view{
  position:absolute;
  inset:0;
  z-index:20;
  display:flex;
  flex-direction:column;
  gap:10px;
  padding:14px;
  /* UI-1b staging correction: the outer Compare workspace now uses the same
     landing-canvas surface as the normal editor canvas (previously stark
     white) - individual cards (.sb-sg-compare-cell--occupied below) stay
     white/light for separation, unchanged. */
  background:var(--sb-sg-landing-canvas);
}

/* UI-1: the toolbar no longer carries its own "Back to editor" exit or its
   own "N selected" live count (both removed - see buildCompareViewMarkup()
   in app.js) - the left-panel View comparison/Back to editor control and
   Compare X / 4 summary are the one, authoritative version of each. Only
   Export and Clear remain, grouped together at the trailing edge rather
   than spread across the row now that there is no longer a flexible middle
   element to justify-content:space-between against. */
#sb-sg-app .sb-sg-compare-toolbar{
  display:flex;
  align-items:center;
  justify-content:flex-end;
  gap:10px;
  flex:0 0 auto;
}

/* The Set subgroup is the single action surface for both generated and
   reopened Sets. Hidden state wins over the shared button display rules. */
#sb-sg-app .sb-sg-set-name-field{
  display:block;
  margin:0 0 8px;
}

#sb-sg-app .sb-sg-set-subtitle{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:8px;
}

#sb-sg-app .sb-sg-set-dirty-indicator{
  display:inline-flex;
  align-items:center;
  min-height:22px;
  padding:2px 7px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:999px;
  background:var(--sb-sg-bg-soft);
  color:var(--pf-color-primary, #6E3B5B);
  font-size:11px;
  font-weight:600;
  line-height:1.2;
  white-space:nowrap;
}

#sb-sg-app .sb-sg-set-dirty-indicator[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-set-name-field[hidden],
#sb-sg-app .sb-sg-create-set-btn[hidden],
#sb-sg-app .sb-sg-tea-set-save-btn[hidden],
#sb-sg-app .sb-sg-new-shape-btn[hidden],
#sb-sg-app .sb-sg-view-set-btn[hidden],
#sb-sg-app .sb-sg-my-sets-link[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-set-context-btn{
  grid-column:1 / -1;
}

#sb-sg-app .sb-sg-my-sets-link{
  text-decoration:none;
}

/* UI-1b staging correction: unified with the rest of the Generator button
   family (same box-model .sb-sg-export-btn/.sb-sg-archive-btn/
   .sb-sg-view-comparison-btn already share: 44px height, 2px border, 8px
   radius, 13px/600 type) - was 32px/1px/10px-radius/11px, which read as
   unrelated to the panel's own buttons. Secondary/outline language
   (.sb-sg-archive-btn's), auto-width inline in the toolbar row rather than
   width:100% (that part is panel-specific, not part of the shared family). */
#sb-sg-app .sb-sg-compare-clear-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:44px;
  padding:10px 14px;
  border:2px solid var(--pf-color-primary, #6E3B5B);
  border-radius:8px;
  background:#fff;
  font:inherit;
  font-size:13px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
  cursor:pointer;
  white-space:nowrap;
  transition:background-color 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease, transform 0.04s ease;
}

#sb-sg-app .sb-sg-compare-clear-btn:hover,
#sb-sg-app .sb-sg-compare-clear-btn:focus{
  background:#fff;
  border-color:var(--pf-color-primary, #6E3B5B);
  color:var(--pf-color-primary, #6E3B5B);
  box-shadow:0 2px 8px rgba(0,0,0,0.14);
}

#sb-sg-app .sb-sg-compare-clear-btn:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-compare-clear-btn:active{
  transform:translateY(1px);
  box-shadow:0 1px 3px rgba(0,0,0,0.12);
}

/* Primary action of the comparison toolbar - solid Plum fill, white text,
   clearly stronger than the outline-style Clear button above. UI-1b: now
   shares the exact same box-model as .sb-sg-export-btn (the Generator's
   other filled-Plum primary action) rather than its own smaller/rounder
   variant, so the two toolbar buttons read as belonging to the same
   PotterForm family as the rest of the app, differing only in emphasis. */
#sb-sg-app .sb-sg-compare-export-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:44px;
  padding:10px 14px;
  border:2px solid var(--pf-color-primary, #6E3B5B);
  border-radius:8px;
  background:var(--pf-color-primary, #6E3B5B);
  font:inherit;
  font-size:13px;
  font-weight:600;
  color:#fff;
  cursor:pointer;
  white-space:nowrap;
  box-shadow:0 1px 2px rgba(0,0,0,0.08);
  transition:background-color 0.16s ease, border-color 0.16s ease, box-shadow 0.16s ease, transform 0.04s ease, opacity 0.16s ease;
}

#sb-sg-app .sb-sg-compare-export-btn:hover:not(:disabled),
#sb-sg-app .sb-sg-compare-export-btn:focus:not(:disabled){
  background:var(--pf-color-primary-hover, #5A304C);
  border-color:var(--pf-color-primary-hover, #5A304C);
  color:#fff;
  box-shadow:0 2px 8px rgba(0,0,0,0.14);
}

#sb-sg-app .sb-sg-compare-export-btn:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-compare-export-btn:active:not(:disabled){
  transform:translateY(1px);
  box-shadow:0 1px 3px rgba(0,0,0,0.12);
}

/* Also covers the transient "Exporting..." state (handleCompareExportPdfClick()
   sets btn.disabled = true for its duration) - stays solid Plum, dimmed via
   opacity, so a busy export still reads as "the primary action, working"
   rather than "unavailable". */
#sb-sg-app .sb-sg-compare-export-btn:disabled{
  background:var(--sb-sg-plum);
  border-color:var(--sb-sg-plum);
  color:#fff;
  opacity:0.75;
  cursor:default;
  box-shadow:none;
}

#sb-sg-app .sb-sg-compare-grid{
  flex:1 1 auto;
  display:grid;
  gap:12px;
  min-height:0;
}

#sb-sg-app .sb-sg-compare-grid--2{
  grid-template-columns:1fr 1fr;
  grid-template-rows:1fr;
}

#sb-sg-app .sb-sg-compare-grid--4{
  grid-template-columns:1fr 1fr;
  grid-template-rows:1fr 1fr;
}

#sb-sg-app .sb-sg-compare-cell{
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  gap:6px;
  min-width:0;
  min-height:0;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius-sm);
  padding:8px;
  overflow:hidden;
}

/* Same geometry as an occupied cell (border/padding/radius unchanged), so
   a stable empty position never shifts the grid when a slot fills or
   empties - only the dashed border and centred text differ. Stays a plain,
   non-interactive <div> - never a button, never clickable. */
#sb-sg-app .sb-sg-compare-cell--empty{
  border-style:dashed;
  color:var(--sb-sg-text-faint);
  font-size:12px;
  text-align:center;
}

/* Compare UX completion: the occupied cell is a plain, non-interactive
   <div> (see buildCompareOccupiedCellNode() in app.js) - it no longer
   resets native button chrome or carries its own hover/active/focus-visible
   states, since it is no longer clickable itself. It keeps the same card
   look .sb-sg-compare-cell already establishes above; interaction states
   now live on the two real buttons in .sb-sg-compare-cell-actions below. */
#sb-sg-app .sb-sg-compare-cell--occupied{
  width:100%;
  background:#fff;
}

#sb-sg-app .sb-sg-compare-svg{
  flex:1 1 auto;
  width:100%;
  min-height:0;
  display:block;
}

#sb-sg-app .sb-sg-compare-path{
  fill:none;
  stroke:var(--pf-color-shape, #6E3B5B);
  stroke-width:2;
  stroke-linejoin:round;
  stroke-linecap:round;
}

#sb-sg-app .sb-sg-compare-cell-info{
  flex:0 0 auto;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:2px;
  max-width:100%;
  text-align:center;
}

/* display:block keeps the ellipsis truncation below working on this <span>
   (a plain inline element would not accept it) - originally required by
   the cell's earlier button-content-model constraint; kept as-is now that
   the cell is a <div> (see buildCompareOccupiedCellNode() in app.js)
   purely because it is still the correct display value for this rule to
   apply, not because anything still requires it. */
#sb-sg-app .sb-sg-compare-cell-name{
  display:block;
  max-width:100%;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  font-size:12px;
  font-weight:600;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-compare-cell-measure{
  display:flex;
  gap:8px;
  font-size:11px;
  color:var(--sb-sg-text-soft);
}

/* Compare UX completion: the two per-cell actions (Open in editor, Remove)
   as a small row below the preview/name/measurements - see
   buildCompareOccupiedCellNode() in app.js. */
#sb-sg-app .sb-sg-compare-cell-actions{
  display:flex;
  align-items:center;
  justify-content:center;
  gap:8px;
  margin-top:4px;
  flex:0 0 auto;
}

/* UI-1b staging correction: Open in editor and Remove now share one small
   "compact secondary" family - same border/radius language as the rest of
   the Generator's Plum-outline buttons (.sb-sg-archive-btn/.sb-sg-compare-clear-btn),
   just scaled down for a card (per the UI-1b brief: card-level actions must
   share radius/border/typography/Plum interaction states, but do not need
   full toolbar width/padding). The border reuses var(--pf-color-primary-border) -
   the exact existing subtle-Plum-outline token .sb-sg-add-to-compare-btn and
   .sb-sg-group already use elsewhere, not a new value. Remove is
   deliberately styled identically to Open (never the destructive red
   .sb-sg-variant-row-delete treatment) - removing a snapshot here only
   drops it from this temporary Compare workspace, never deletes the
   underlying Shape/My Shape/local record; the label alone carries that
   distinction, not colour. */
#sb-sg-app .sb-sg-compare-cell-open,
#sb-sg-app .sb-sg-compare-cell-remove{
  appearance:none;
  -webkit-appearance:none;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  border:1px solid var(--pf-color-primary-border, rgba(110,59,91,0.28));
  border-radius:8px;
  background:#fff;
  padding:4px 10px;
  margin:0;
  font:inherit;
  font-size:11px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
  cursor:pointer;
  transition:background-color 0.14s ease, border-color 0.14s ease;
}

#sb-sg-app .sb-sg-compare-cell-open:hover,
#sb-sg-app .sb-sg-compare-cell-remove:hover{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  border-color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-compare-cell-open:focus-visible,
#sb-sg-app .sb-sg-compare-cell-remove:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-compare-cell-open:active,
#sb-sg-app .sb-sg-compare-cell-remove:active{
  transform:translateY(1px);
}

/* Narrow landscape widths (phone-landscape and small-tablet-landscape,
   matching the already-approved breakpoint the Saved Variant thumbnail
   block uses elsewhere in this file) - an isolated block scoped only to
   Compare selectors, never editing the approved shell/canvas rule above.
   Tighter padding/gaps and smaller type keep the toolbar to one row and
   both grid shapes readable without introducing horizontal scroll; the
   grid itself stays 2 / 2x2 at every supported width. */
@media (max-width: 932px) and (orientation: landscape){
  #sb-sg-app .sb-sg-compare-view{
    padding:10px;
    gap:8px;
  }

  /* UI-1b: scaled down from the 44px baseline for this tight breakpoint
     only - still clearly the same bordered/filled Plum family, just more
     compact so the toolbar stays one row. */
  #sb-sg-app .sb-sg-compare-clear-btn,
  #sb-sg-app .sb-sg-compare-export-btn{
    min-height:38px;
    padding:8px 10px;
    font-size:11px;
  }

  #sb-sg-app .sb-sg-compare-grid{
    gap:8px;
  }

  #sb-sg-app .sb-sg-compare-cell{
    padding:6px;
  }

  #sb-sg-app .sb-sg-compare-cell-name{
    font-size:11px;
  }

  #sb-sg-app .sb-sg-compare-cell-measure{
    gap:6px;
    font-size:10px;
  }

  #sb-sg-app .sb-sg-compare-cell-actions{
    margin-top:2px;
    gap:6px;
  }

  #sb-sg-app .sb-sg-compare-cell-open,
  #sb-sg-app .sb-sg-compare-cell-remove{
    font-size:10px;
    padding:3px 8px;
  }
}

/* ─── Set Review (productionized Core Create Set, 2026-08-23) ────────────────
   An in-page mode over the canvas, structurally identical in kind to
   Compare above (own canvas-level active class hides the live preview SVG/
   Guide/Throwability the same way .sb-sg-compare-active does; own
   absolute/inset:0 overlay) - only the toolbar/clear-button chrome classes
   are still shared with Compare (.sb-sg-compare-toolbar/-clear-btn), purely
   visual, no capacity coupling. The review body itself is a dedicated
   Set-specific split layout, not Compare's grid: a fixed Source panel
   (left) that never scrolls, alongside an independently-scrolling column
   (right) of five always-visible functional groups (Plates/Bowls/
   Drinkware/Serving/Pouring - see NS.teaSet.GROUP_ORDER in
   sb-shape-tea-set.js), so the Source stays visible while scrolling
   through all 14 generated members. Also explicitly hides #canvas-handles
   (the direct-manipulation handle overlay) - defense-in-depth alongside the
   existing z-index stacking that already keeps it visually and pointer-
   event-wise underneath this view; Compare has the same latent gap and is
   intentionally left alone here (out of scope for this fix). */
#sb-sg-app .sb-sg-canvas.sb-sg-tea-set-active #sb-sg-preview-svg{
  display:none;
}

#sb-sg-app .sb-sg-canvas.sb-sg-tea-set-active .sb-sg-guide{
  display:none;
}

#sb-sg-app .sb-sg-canvas.sb-sg-tea-set-active .sb-sg-throwability{
  display:none;
}

#sb-sg-app .sb-sg-canvas.sb-sg-tea-set-active .sb-sg-canvas-handles{
  display:none;
}

#sb-sg-app .sb-sg-tea-set-view{
  position:absolute;
  inset:0;
  z-index:20;
  display:flex;
  flex-direction:column;
  gap:10px;
  padding:14px;
  background:var(--sb-sg-landing-canvas);
}

/* Retained compatibility actions are no longer part of the visible Set
   Review. Set naming, saving, library navigation, and contextual return all
   live in the existing left-panel Set subgroup. */
#sb-sg-app .sb-sg-tea-set-back-btn{
  width:auto;
  margin-top:0;
}

#sb-sg-app .sb-sg-tea-set-toolbar{
  justify-content:space-between;
  min-width:0;
}

#sb-sg-app .sb-sg-tea-set-toolbar-actions{
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  justify-content:flex-end;
  gap:10px;
  min-width:0;
}

#sb-sg-app .sb-sg-tea-set-heading{
  margin:0;
  font-size:15px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
}

/* The split area fills the remaining review height as two equal primary
   columns. minmax(0,1fr) prevents Source/card content from contributing a
   minimum track width: neither Shape dimensions nor card content can change
   the 50/50 page geometry. min-height:0 lets the right track own its scroll. */
#sb-sg-app .sb-sg-tea-set-split{
  display:grid;
  grid-template-columns:repeat(2, minmax(0, 1fr));
  flex:1 1 auto;
  min-height:0;
  gap:14px;
}

/* Fixed left half: it fills the full split height and never participates in
   the Set member pane's scrolling. The Source card is contained inside this
   pane instead of creating a second review scrollbar. */
#sb-sg-app .sb-sg-tea-set-source-panel{
  min-width:0;
  min-height:0;
  height:100%;
  box-sizing:border-box;
  overflow:hidden;
  display:flex;
  align-items:center;
  justify-content:center;
  background:#fff;
  border:1px solid var(--sb-sg-card-border);
  border-radius:10px;
  padding:16px;
}

#sb-sg-app .sb-sg-tea-set-groups{
  min-width:0;
  min-height:0;
  overflow-y:auto;
  display:flex;
  flex-direction:column;
  gap:18px;
  padding-right:4px;
}

/* Five always-visible functional group sections - no collapse/disclosure
   for V1 (product decision). */
#sb-sg-app .sb-sg-tea-set-group{
  display:flex;
  flex-direction:column;
  gap:8px;
}

#sb-sg-app .sb-sg-tea-set-group-heading{
  margin:0;
  font-size:12px;
  font-weight:700;
  letter-spacing:0.04em;
  text-transform:uppercase;
  color:var(--pf-color-primary, #6E3B5B);
}

/* Every group inherits one right-pane-derived mode and uses the same equal
   fractional tracks. minmax(0,1fr) and stretch make card width a pure
   function of the right pane and the 12px gap; headings remain outside the
   row grid and therefore span the full right-pane width. */
#sb-sg-app .sb-sg-tea-set-group-row{
  display:grid;
  grid-template-columns:repeat(var(--sb-sg-tea-set-columns, 1), minmax(0, 1fr));
  align-items:start;
  justify-items:stretch;
  gap:12px;
}

/* Member cards fill their equal grid tracks. Their temporary SVG/render-box
   content is not allowed to establish card or column width. */
#sb-sg-app .sb-sg-tea-set-card{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:6px;
  width:100%;
  height:100%;
  min-width:0;
  box-sizing:border-box;
  background:#fff;
  border:1px solid var(--sb-sg-card-border);
  border-radius:8px;
  padding:10px;
}

/* The stable render area a member Shape draws inside ("the member card
   and its render box are fixed by the UI layout... the Shape is rendered
   inside that box", product requirement). Width/height are set inline in
   px by buildTeaSetCellNode() (app.js) - identical for every member card,
   from computeTeaSetColumnLayout()'s renderBoxPx, regardless of that
   Shape's own size. The child SVG's own width/height are the Shape's
   actual computed render size (computeTeaSetMemberRenderSize()) - always
   <= this box on both axes by construction - and are simply centered
   inside it here; no CSS clamp is needed or wanted (see the comment on
   .sb-sg-tea-set-card-svg further below). Source never uses this - its
   SVG is sized directly (see buildTeaSetCellNode()). */
#sb-sg-app .sb-sg-tea-set-card-render{
  display:flex;
  align-items:center;
  justify-content:center;
}

#sb-sg-app .sb-sg-tea-set-card--source{
  justify-content:center;
  border:none;
  background:transparent;
  padding:4px;
  overflow:hidden;
}

#sb-sg-app .sb-sg-tea-set-source-badge{
  display:inline-block;
  padding:2px 8px;
  border-radius:999px;
  background:var(--pf-color-primary, #6E3B5B);
  color:#fff;
  font-size:9px;
  font-weight:700;
  letter-spacing:0.05em;
  text-transform:uppercase;
}

/* No max-width/height clamp here for member cards (both earlier attempts
   had one) - a member's SVG width/height are already the exact computed
   render size from computeTeaSetMemberRenderSize() (app.js), always
   <= its fixed .sb-sg-tea-set-card-render box on both axes by
   construction. A CSS clamp here would let the browser independently
   reclamp it if that guarantee were ever wrong - the earlier bugs on
   staging came from exactly that kind of independent per-card
   reclamping, so none is kept "just in case". */
#sb-sg-app .sb-sg-tea-set-card-svg{
  display:block;
}

/* The one legitimate remaining clamp: Source sits alone in a fixed-width
   panel with no other card to compare against, so independently fitting
   it to that panel is not the "auto-fit each Shape independently" problem
   (there is no cross-card relative-size relationship to distort here).
   TEA_SET_SOURCE_RENDER_BOX_PX (app.js) is already chosen to comfortably
   fit the Source panel's own inner width, so this is a defensive
   fallback, not something normally relied on. */
#sb-sg-app .sb-sg-tea-set-card--source .sb-sg-tea-set-card-svg{
  flex:1 1 auto;
  min-height:0;
  max-width:100%;
  max-height:calc(100% - 82px);
  width:auto;
  height:auto;
}

#sb-sg-app .sb-sg-tea-set-card-path{
  fill:none;
  stroke:var(--pf-color-shape, #6E3B5B);
  stroke-width:1.5;
  stroke-linejoin:round;
  stroke-linecap:round;
  vector-effect:non-scaling-stroke;
}

#sb-sg-app .sb-sg-tea-set-card-info{
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:2px;
  max-width:100%;
  text-align:center;
}

#sb-sg-app .sb-sg-tea-set-card-name{
  display:block;
  max-width:100%;
  overflow:hidden;
  text-overflow:ellipsis;
  white-space:nowrap;
  font-size:12px;
  font-weight:600;
  color:var(--sb-sg-text);
}

#sb-sg-app .sb-sg-tea-set-card-measure{
  font-size:11px;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-tea-set-card-actions{
  margin-top:4px;
}

/* Dedicated Open-in-Generator button style (previously borrowed
   .sb-sg-compare-cell-open verbatim - now Set-specific, same visual
   language, no shared class with Compare). */
#sb-sg-app .sb-sg-tea-set-cell-open{
  appearance:none;
  -webkit-appearance:none;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  border:1px solid var(--pf-color-primary-border, rgba(110,59,91,0.28));
  border-radius:8px;
  background:#fff;
  color:var(--pf-color-primary, #6E3B5B);
  font-size:11px;
  font-weight:600;
  padding:5px 10px;
  cursor:pointer;
}

#sb-sg-app .sb-sg-tea-set-cell-open:hover{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  border-color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-tea-set-cell-open:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-tea-set-cell-open:active{
  transform:translateY(1px);
}

/* Presentation bar (staging refinement, 2026-08-26): moved out of a
   full-width bordered card into one compact, right-aligned control
   cluster directly above the split area - the same "plain compact toolbar
   row, no card chrome" language .sb-sg-compare-toolbar already uses
   above, per Compare's own established UX precedent (not its code -
   Set-specific classes throughout, own semantics). Summary text and every
   action button sit in one flex row, pushed to the right edge; wraps only
   if the pane becomes too narrow (phone - see the media query below). */
#sb-sg-app .sb-sg-tea-set-presentation-bar{
  display:flex;
  align-items:center;
  justify-content:flex-end;
  flex-wrap:wrap;
  gap:10px;
  flex:0 0 auto;
}

#sb-sg-app .sb-sg-tea-set-selection-count{
  font-size:12px;
  font-weight:600;
  color:var(--pf-color-primary, #6E3B5B);
  white-space:nowrap;
}

/* Select all / Clear selection - same visual family as
   .sb-sg-tea-set-cell-open above (neutral Plum outline, filled-soft on
   hover) - a routine, non-primary action pair. */
#sb-sg-app .sb-sg-tea-set-selection-btn{
  appearance:none;
  -webkit-appearance:none;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:32px;
  border:1px solid var(--pf-color-primary-border, rgba(110,59,91,0.28));
  border-radius:8px;
  background:#fff;
  color:var(--pf-color-primary, #6E3B5B);
  font:inherit;
  font-size:11px;
  font-weight:600;
  padding:5px 10px;
  cursor:pointer;
  white-space:nowrap;
}

#sb-sg-app .sb-sg-tea-set-selection-btn:hover{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  border-color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-tea-set-selection-btn:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-tea-set-selection-btn:disabled{
  opacity:0.5;
  cursor:default;
}

/* Export Set PDF - the primary action in this bar, same filled-Plum
   language as .sb-sg-compare-export-btn (Compare's own export action). */
#sb-sg-app .sb-sg-tea-set-export-btn{
  appearance:none;
  -webkit-appearance:none;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:36px;
  border:2px solid var(--pf-color-primary, #6E3B5B);
  border-radius:8px;
  background:var(--pf-color-primary, #6E3B5B);
  color:#fff;
  font:inherit;
  font-size:12px;
  font-weight:600;
  padding:6px 12px;
  cursor:pointer;
  white-space:nowrap;
}

#sb-sg-app .sb-sg-tea-set-export-btn:hover:not(:disabled){
  background:var(--pf-color-primary-hover, #5A304C);
  border-color:var(--pf-color-primary-hover, #5A304C);
}

#sb-sg-app .sb-sg-tea-set-export-btn:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-tea-set-export-btn:disabled{
  opacity:0.6;
  cursor:default;
}

/* Per-card presentation checkbox (staging refinement, 2026-08-26): moved
   to the upper-left of the card, ahead of the silhouette - the same
   position/interaction language My Shapes' own "Select for comparison"
   checkbox already uses (a real <label> wrapping a real <input
   type="checkbox">, left-aligned above the preview - see
   .pf-my-shape-card-select in potterform-account.css). align-self
   overrides the card's own centered layout for this one element only;
   min-height:36px on the label itself (not just the checkbox) gives a
   comfortable touch target without visually enlarging the checkbox glyph
   beyond the rest of the app's own native-control sizing (accent-color
   already applied globally via #sb-sg-app input[type="checkbox"] in
   ensureUiAccentStyles()). */
#sb-sg-app .sb-sg-tea-set-card-select{
  display:flex;
  align-items:center;
  align-self:flex-start;
  justify-content:flex-start;
  gap:6px;
  min-height:36px;
  padding:2px 4px;
  cursor:pointer;
}

#sb-sg-app .sb-sg-tea-set-card-select-input{
  width:18px;
  height:18px;
  margin:0;
  flex:0 0 auto;
  cursor:pointer;
}

#sb-sg-app .sb-sg-tea-set-card-select-input:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-tea-set-card-select-label{
  font-size:11px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

/* Phone: preserve the same equal 50/50 primary split and only tighten
   spacing/padding. The Source width is never replaced by a fixed basis. */
@media (max-width:640px){
  #sb-sg-app .sb-sg-tea-set-view{
    padding:8px;
    gap:8px;
  }

  #sb-sg-app .sb-sg-tea-set-toolbar,
  #sb-sg-app .sb-sg-tea-set-toolbar-actions{
    gap:6px;
  }

  #sb-sg-app .sb-sg-tea-set-toolbar-actions .sb-sg-compare-clear-btn,
  #sb-sg-app .sb-sg-tea-set-toolbar-actions .sb-sg-view-comparison-btn{
    min-height:38px;
    padding:8px 9px;
    font-size:11px;
  }

  #sb-sg-app .sb-sg-tea-set-split{
    gap:8px;
  }

  #sb-sg-app .sb-sg-tea-set-source-panel{
    padding:8px;
  }

  #sb-sg-app .sb-sg-tea-set-groups{
    gap:12px;
  }

  /* Row gap stays 12px so the right-pane width calculation and CSS tracks
     use the same one/two-column threshold. */

  #sb-sg-app .sb-sg-tea-set-card{
    padding:6px;
  }

  #sb-sg-app .sb-sg-tea-set-card-name{
    font-size:11px;
  }

  #sb-sg-app .sb-sg-tea-set-card-measure{
    font-size:10px;
  }

  #sb-sg-app .sb-sg-tea-set-presentation-bar{
    justify-content:flex-start;
    gap:6px;
  }

  #sb-sg-app .sb-sg-tea-set-selection-count{
    font-size:11px;
    width:100%;
  }

  #sb-sg-app .sb-sg-tea-set-export-btn{
    min-height:34px;
    font-size:11px;
    padding:5px 10px;
  }
}

/* Shape Representation v2 - Stage 2 internal editor vertical slice (Master
   Plan Section 7). Same technique as .sb-sg-compare-active/
   .sb-sg-tea-set-active above: a canvas-level class hides the live v1
   preview SVG, the persistent handle overlay, and the Guide/Throwability
   overlays while this view is mounted - none of the three are ever
   removed, replaced, or have their own state touched. This class and view
   are only ever added/mounted from an internal, non-public entry point
   (see app.js) - never reachable from ordinary Generator use. */
#sb-sg-app .sb-sg-canvas.sb-sg-v2-session-active #sb-sg-preview-svg{
  display:none;
}

#sb-sg-app .sb-sg-canvas.sb-sg-v2-session-active .sb-sg-canvas-handles{
  display:none;
}

#sb-sg-app .sb-sg-canvas.sb-sg-v2-session-active .sb-sg-throwability{
  z-index:30;
}

/* A visible Lid temporarily removes Throwability from the Assembly canvas.
   app.js toggles only this presentation class; the overlay's own retained
   hidden/compact/expanded state and calculations are never changed. */
#sb-sg-app .sb-sg-canvas.sb-sg-lid-throwability-suspended .sb-sg-throwability{
  display:none;
}

#sb-sg-app .sb-sg-tea-set-toolbar-actions[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-visually-hidden{
  position:absolute;
  width:1px;
  height:1px;
  padding:0;
  margin:-1px;
  overflow:hidden;
  clip:rect(0, 0, 0, 0);
  white-space:nowrap;
  border:0;
}

#sb-sg-app .sb-sg-tea-set-name{
  width:100%;
  max-width:none;
  min-height:44px;
  box-sizing:border-box;
  border:1px solid var(--pf-color-border);
  border-radius:var(--pf-radius-small, 8px);
  padding:7px 9px;
  font:inherit;
  color:var(--pf-color-text);
  background:var(--pf-white, #fff);
}

#sb-sg-app .sb-sg-tea-set-name:focus-visible{
  outline:2px solid var(--pf-color-focus-ring);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-tea-set-save-btn{
  width:100%;
  margin:0;
}

#sb-sg-app .sb-sg-v2-session-view{
  position:absolute;
  inset:0;
  z-index:20;
  display:flex;
  flex-direction:column;
  padding:0;
  background:transparent;
  overflow:hidden;
}

/* The authoritative profile canvas fills the normal Generator canvas; no
   secondary session panel participates in layout. */
#sb-sg-app .sb-sg-v2-body{
  flex:1 1 auto;
  min-height:0;
  display:flex;
  width:100%;
  height:100%;
}

#sb-sg-app .sb-sg-v2-canvas{
  flex:1 1 auto;
  min-width:0;
  display:flex;
}

#sb-sg-app .sb-sg-v2-canvas-inner{
  position:relative;
  flex:1 1 auto;
  min-height:0;
  background:transparent;
}

#sb-sg-app .sb-sg-v2-svg{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  touch-action:none;
}

/* Advanced Profile's complete canvas shell—not only its SVG child—owns
   one-finger pan and pinch. Scope stays inside the active canvas so normal
   page/control-panel scrolling and browser gestures remain available. */
#sb-sg-app .sb-sg-canvas.sb-sg-v2-session-active .sb-sg-v2-session-view,
#sb-sg-app .sb-sg-canvas.sb-sg-v2-session-active .sb-sg-v2-body,
#sb-sg-app .sb-sg-canvas.sb-sg-v2-session-active .sb-sg-v2-canvas,
#sb-sg-app .sb-sg-canvas.sb-sg-v2-session-active .sb-sg-v2-canvas-inner{
  touch-action:none;
}

#sb-sg-app .sb-sg-v2-path{
  fill:none;
  stroke:var(--pf-color-shape, #6E3B5B);
  stroke-width:2;
  stroke-linejoin:round;
  stroke-linecap:round;
}

/* P2A.2: direct object selection - a generous, fully invisible hit target
   sharing the visible path's own `d` (see sb-shape-editor-v2-session.js's
   own refresh()), so a click anywhere near the drawn outline focuses the
   Shape without changing the visible line's own stroke width.
   vector-effect keeps the hit width constant in screen pixels through
   zoom, matching every other constant-screen-size interaction target in
   this canvas (handles, etc.). pointer-events:stroke means only the band
   along the path is actually hit-testable - it never blocks clicks on
   empty canvas (pan) or on anything drawn elsewhere. */
#sb-sg-app .sb-sg-v2-path-hit{
  fill:none;
  stroke:transparent;
  stroke-width:22px;
  vector-effect:non-scaling-stroke;
  pointer-events:stroke;
  cursor:pointer;
}

/* Lid System P1: compact Shape-panel entry and read-only fitted Assembly. */
#sb-sg-app .sb-sg-lid-assembly-entry{
  display:grid;
  gap:8px;
  padding-top:4px;
}

#sb-sg-app .sb-sg-lid-entry-actions,
#sb-sg-app .sb-sg-handle-entry-actions{
  display:flex;
  gap:8px;
}

#sb-sg-app .sb-sg-lid-add-btn,
#sb-sg-app .sb-sg-lid-save-btn,
#sb-sg-app .sb-sg-lid-my-lids-btn,
#sb-sg-app .sb-sg-handle-add-btn,
#sb-sg-app .sb-sg-handle-save-btn,
#sb-sg-app .sb-sg-handle-my-handles-btn{
  flex:1 1 0;
  min-height:44px;
}

/* .sb-sg-btn's own #sb-sg-app .sb-sg-btn{display:inline-flex} rule beats
   the browser's native [hidden]{display:none} rule on specificity (same
   #id + class lesson as .sb-sg-alert[hidden] above) - without this,
   els.addLidBtn.hidden=true (see updateLidAssemblyEntryState() in app.js)
   correctly sets the attribute but the button stays visually displayed. */
#sb-sg-app .sb-sg-lid-add-btn[hidden],
#sb-sg-app .sb-sg-lid-save-btn[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-handle-add-btn[hidden],
#sb-sg-app .sb-sg-handle-save-btn[hidden]{
  display:none;
}

/* Priority 7 (live-staging follow-up): contextual Handle controls,
   revealed by Add Handle - same reveal convention as .sb-sg-lid-details. */
#sb-sg-app .sb-sg-handle-properties{
  display:flex;
  flex-direction:column;
  gap:8px;
  margin-top:8px;
}

#sb-sg-app .sb-sg-handle-properties[hidden]{
  display:none;
}

/* Spout System (S2), same reveal convention as .sb-sg-handle-properties -
   revealed once a SpoutDocument exists (session.spout), independent of
   ON/OFF (see syncSpoutFocusControl() in sb-shape-editor-v2-session.js). */
#sb-sg-app .sb-sg-spout-properties{
  display:flex;
  flex-direction:column;
  gap:8px;
  margin-top:8px;
}

#sb-sg-app .sb-sg-spout-properties[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-spout-status{
  margin:0;
  font-size:12px;
  line-height:1.35;
  color:var(--pf-color-text-muted, #675e64);
}

#sb-sg-app .sb-sg-lid-fit-choice{
  display:grid;
  gap:7px;
  min-width:0;
}

#sb-sg-app .sb-sg-lid-fit-choice[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-lid-fit-choice h3,
#sb-sg-app .sb-sg-lid-control-section h3{
  margin:0;
  color:var(--pf-color-text, #2f2930);
  font-size:12px;
  font-weight:700;
  letter-spacing:.02em;
}

#sb-sg-app .sb-sg-lid-assembly-status,
#sb-sg-app .sb-sg-handle-status{
  margin:0;
  font-size:12px;
  line-height:1.35;
  color:var(--pf-color-text-muted, #675e64);
}

#sb-sg-app .sb-sg-lid-fit-actions{
  display:grid;
  grid-template-columns:repeat(3, 1fr);
  gap:7px;
  min-width:0;
}

#sb-sg-app .sb-sg-lid-fit-actions .sb-sg-btn{
  min-width:0;
  min-height:44px;
  padding:7px 6px;
  border:1px solid var(--pf-color-primary-border, rgba(110,59,91,.28));
  background:var(--pf-white, #fff);
  color:var(--pf-color-primary, #6e3b5b);
  font-size:12.5px;
}

#sb-sg-app .sb-sg-lid-fit-actions .sb-sg-btn:hover:not(:disabled){
  border-color:var(--pf-color-primary, #6e3b5b);
  background:var(--pf-color-primary-soft, rgba(110,59,91,.07));
}

#sb-sg-app .sb-sg-lid-fit-actions .sb-sg-btn:focus-visible{
  outline:2px solid var(--pf-color-primary, #6e3b5b);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-lid-fit-actions .sb-sg-btn.is-active{
  background:var(--pf-color-primary, #6e3b5b);
  border-color:var(--pf-color-primary, #6e3b5b);
  color:#fff;
}

#sb-sg-app .sb-sg-lid-fit-actions .sb-sg-btn:disabled{
  border-color:var(--pf-color-border, #ddd4da);
  background:var(--pf-color-surface-subtle, #f7f4f6);
  color:var(--pf-color-text-muted, #675e64);
  opacity:.62;
  cursor:not-allowed;
}

#sb-sg-app .sb-sg-lid-fit-hint{
  margin:0;
  font-size:12px;
  line-height:1.35;
  color:var(--pf-color-text-muted, #675e64);
}

#sb-sg-app .sb-sg-lid-properties{
  display:grid;
  gap:9px;
  min-width:0;
  padding-top:9px;
  border-top:1px solid var(--pf-color-border, #ddd4da);
}

#sb-sg-app .sb-sg-lid-properties[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-lid-details{
  display:grid;
  gap:8px;
  min-width:0;
}
#sb-sg-app .sb-sg-lid-details[hidden]{ display:none; }
#sb-sg-app .sb-sg-lid-properties .sb-sg-pill{
  display:flex;
}

#sb-sg-app .sb-sg-lid-control-section{
  display:grid;
  gap:7px;
  min-width:0;
  padding-top:8px;
  border-top:1px solid var(--pf-color-border, #ddd4da);
}

#sb-sg-app .sb-sg-lid-properties label{
  display:grid;
  gap:6px;
  font-size:12.5px;
}

#sb-sg-app .sb-sg-lid-properties select,
#sb-sg-app .sb-sg-lid-properties input[type="range"]{
  width:100%;
  max-width:100%;
  min-width:0;
  min-height:38px;
}

#sb-sg-app #knobProperties{
  display:grid;
  gap:7px;
}

#sb-sg-app #knobProperties[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-lid-assembly-status.is-error,
#sb-sg-app .sb-sg-handle-status.is-error{
  color:var(--pf-color-error, #9f2d2d);
}

#sb-sg-app .sb-sg-lid-assembly-lid{
  fill:none;
  stroke:var(--pf-color-shape, #6E3B5B);
  stroke-width:2px;
  stroke-linecap:round;
  stroke-linejoin:round;
  pointer-events:none;
}

/* Temporary-invalid visual state (P2A.2 current-scale contract): the
   current fit is fitTemporarilyUnavailable and this is the CURRENT-scale
   Lid OUTER curve only (an open path - Inner/fit construction is
   intentionally not drawn), never a current valid fit - a subtle dashed
   preview (same dashed language as .sb-sg-lid-assembly-gallery-context
   below). fill:none because the path is open; filling an open path would
   draw a misleading implicit closing wedge. Handles stay fully
   interactive; only the fill/stroke treatment changes. */
#sb-sg-app .sb-sg-lid-assembly-lid--temporary{
  fill:none;
  stroke-dasharray:4 4;
  opacity:.6;
}

/* P2A.2: direct object selection for Lid/Knob - the visible stroke above
   is pointer-events:none, so this dedicated, fully invisible, generous
   hit path (the complete cross-section, using the same constant-screen-width technique as
   the Shape's own .sb-sg-v2-path-hit) is what actually makes clicking the
   drawn Lid/Knob outline focus that object. */
#sb-sg-app .sb-sg-lid-assembly-hit{
  fill:none;
  stroke:transparent;
  stroke-width:22px;
  vector-effect:non-scaling-stroke;
  pointer-events:stroke;
  cursor:pointer;
}

#sb-sg-app .sb-sg-lid-assembly-gallery-context{
  fill:none;
  stroke:var(--pf-color-text-muted, #675e64);
  stroke-width:1.3px;
  stroke-dasharray:4px 2.5px;
  stroke-linecap:round;
  stroke-linejoin:round;
  vector-effect:non-scaling-stroke;
  opacity:.72;
  pointer-events:none;
}

/* Open Lid: a temporary camera/visibility isolate of the SAME mounted V2
   canvas - hides the vessel's own path/inner-profile/handles, leaving the
   Lid overlay's own handles layer (also a .sb-sg-v2-handles sibling, hence
   the :not() exclusion) visible and interactive. */
#sb-sg-app .sb-sg-v2-canvas-inner.sb-sg-lid-open-isolate .sb-sg-v2-path,
#sb-sg-app .sb-sg-v2-canvas-inner.sb-sg-lid-open-isolate .sb-sg-v2-path-hit,
#sb-sg-app .sb-sg-v2-canvas-inner.sb-sg-lid-open-isolate .sb-sg-v2-inner-path,
/* H2C-C: the mug Handle is attached to the vessel itself, so it follows the
   exact same isolate-mode containment as the vessel's own path/hit path
   above - never left visible/clickable while the vessel it is attached to
   is hidden. */
#sb-sg-app .sb-sg-v2-canvas-inner.sb-sg-lid-open-isolate .sb-sg-v2-handle-outer-path,
#sb-sg-app .sb-sg-v2-canvas-inner.sb-sg-lid-open-isolate .sb-sg-v2-handle-inner-path,
#sb-sg-app .sb-sg-v2-canvas-inner.sb-sg-lid-open-isolate .sb-sg-v2-handle-outer-hit,
#sb-sg-app .sb-sg-v2-canvas-inner.sb-sg-lid-open-isolate .sb-sg-v2-handle-inner-hit,
#sb-sg-app .sb-sg-v2-canvas-inner.sb-sg-lid-open-isolate > .sb-sg-v2-handles:not(.sb-sg-lid-overlay-handles){
  display:none;
}

#sb-sg-app .sb-sg-v2-inner-path{
  fill:none;
  stroke:var(--pf-color-text-secondary, #6f6870);
  stroke-width:1.3px;
  stroke-dasharray:4px 2.5px;
  stroke-linejoin:round;
  stroke-linecap:round;
  vector-effect:non-scaling-stroke;
  opacity:.78;
  pointer-events:none;
}

#sb-sg-app .sb-sg-v2-gallery-focus .sb-sg-v2-inner-path{
  stroke-width:1.55px;
  stroke-dasharray:none;
  vector-effect:non-scaling-stroke;
  opacity:1;
}

#sb-sg-app .sb-sg-v2-gallery-focus-action{
  width:100%;
  margin-top:6px;
}

#sb-sg-app .sb-sg-derived-dimensions{
  display:grid;
  gap:6px;
  min-width:0;
  margin:0;
  margin-top:6px;
}

#sb-sg-app .sb-sg-derived-dimensions--three{
  grid-template-columns:repeat(3, minmax(0, 1fr));
}

#sb-sg-app .sb-sg-derived-dimensions--two{
  grid-template-columns:repeat(2, minmax(0, 1fr));
}

#sb-sg-app .sb-sg-derived-dimensions--one{
  grid-template-columns:minmax(0, 1fr);
}

#sb-sg-app .sb-sg-derived-dimensions[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-derived-dimension-card{
  display:grid;
  align-content:space-between;
  gap:3px;
  min-width:0;
  padding:7px 8px;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius-sm);
  background:var(--sb-sg-card);
}

#sb-sg-app .sb-sg-derived-dimension-card dt,
#sb-sg-app .sb-sg-derived-dimension-card dd,
#sb-sg-app .sb-sg-derived-dimension-card output{
  display:block;
  min-width:0;
}

#sb-sg-app .sb-sg-derived-dimension-card dt{
  color:var(--sb-sg-text-faint);
  font-size:10px;
  line-height:1.35;
  overflow-wrap:anywhere;
}

#sb-sg-app .sb-sg-derived-dimension-card dd{
  margin:0;
}

#sb-sg-app .sb-sg-derived-dimension-card output{
  color:var(--sb-sg-text);
  font-size:12px;
  font-weight:600;
  line-height:1.25;
  white-space:nowrap;
  font-variant-numeric:tabular-nums;
}

#sb-sg-app .sb-sg-v2-inner-visibility-control{
  margin-top:4px;
  border-top:1px solid var(--sb-sg-card-border);
  color:var(--pf-color-text-secondary, #514b52);
  font-size:11px;
  line-height:1.35;
}

#sb-sg-app .sb-sg-v2-inner-note{
  margin:-2px 0 5px;
  color:var(--sb-sg-text-faint);
}

#sb-sg-app .sb-sg-v2-inner-note[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-v2-handles{
  position:absolute;
  inset:0;
  pointer-events:none;
}

/* Advanced Profile handle language: filled circle = core landmark, hollow
   circle = Profile point, diamond = Rim/Foot, selected = a Plum halo with
   foreground priority. The handle element itself (.sb-sg-v2-handle) is
   the LARGE INVISIBLE HIT TARGET (44px, well past the ~42px precedent
   already established for v1's own body-handle touch targets - see
   app.js's own HANDLE_DEFS comment); the actual small visual marker is
   the ".sb-sg-v2-handle-marker" child, centered independently, so hit-
   target size and visual size are never the same box. */
#sb-sg-app .sb-sg-v2-handle{
  position:absolute;
  width:44px;
  height:44px;
  margin:0;
  padding:0;
  transform:translate(-50%, -50%);
  display:flex;
  align-items:center;
  justify-content:center;
  appearance:none;
  -webkit-appearance:none;
  background:transparent;
  border:none;
  border-radius:0;
  outline:none;
  box-shadow:none;
  cursor:grab;
  touch-action:none;
  z-index:1;
  pointer-events:auto;
}

#sb-sg-app .sb-sg-v2-handle-marker{
  position:absolute;
  width:12px;
  height:12px;
  border-radius:50%;
  background:var(--pf-color-primary, #6E3B5B);
  border:2px solid #fff;
  box-shadow:0 1px 3px rgba(0,0,0,0.28);
  pointer-events:none;
}

/* Core landmark: filled circle. */
#sb-sg-app .sb-sg-v2-handle--core .sb-sg-v2-handle-marker{
  background:var(--pf-color-primary, #6E3B5B);
}

/* Profile point: hollow circle, visually distinct from a core landmark's
   filled one but still a circle (never a square) - the task's own
   approved hierarchy. */
#sb-sg-app .sb-sg-v2-handle--transition .sb-sg-v2-handle-marker{
  width:12px;
  height:12px;
  background:#fff;
  border-color:var(--pf-color-primary, #6E3B5B);
}

/* Rim/Foot: diamond (a square marker rotated 45deg - the validated
   equivalent feature marker) - contextual, only ever mounted while their
   own feature is active (see computeHandleGeometry()'s own rim/foot
   branches - no handle exists at all when off). Handle roots (H2C-B)
   join this same diamond language: a root is an attachment/anchor point
   on the vessel wall, the same semantic role Gallery's diamond already
   plays, not a curve-shaping point. */
#sb-sg-app .sb-sg-v2-handle--rim .sb-sg-v2-handle-marker,
#sb-sg-app .sb-sg-v2-handle--foot .sb-sg-v2-handle-marker,
#sb-sg-app .sb-sg-v2-handle--gallery .sb-sg-v2-handle-marker,
#sb-sg-app .sb-sg-v2-handle--handle-root .sb-sg-v2-handle-marker{
  width:12px;
  height:12px;
  border-radius:2px;
  transform:rotate(45deg);
  background:#fff;
  border-color:var(--pf-color-primary, #6E3B5B);
}

/* Handle Side points (O1-O3/I1-I3): hollow circle, the same "curve-
   shaping point" language already used for Profile/transition points -
   a Handle Side point shapes the Handle's own contour exactly the way a
   Profile point shapes the Shape's. */
#sb-sg-app .sb-sg-v2-handle--handle-side .sb-sg-v2-handle-marker{
  width:12px;
  height:12px;
  background:#fff;
  border-color:var(--pf-color-primary, #6E3B5B);
}

/* Selected: restrained Plum halo and foreground priority (z-index above
   every unselected handle,
   so a halo or a crowded cluster never gets visually clipped underneath a
   neighbor). */
#sb-sg-app .sb-sg-v2-handle--selected{
  z-index:2;
}

#sb-sg-app .sb-sg-v2-handle--selected .sb-sg-v2-handle-marker,
#sb-sg-app .sb-sg-v2-handle--active .sb-sg-v2-handle-marker,
#sb-sg-app .sb-sg-v2-handle:focus-visible .sb-sg-v2-handle-marker{
  box-shadow:0 0 0 3px #fff, 0 0 0 5px var(--pf-color-primary, #6E3B5B), 0 1px 3px rgba(0,0,0,0.28);
}

/* Handle Outer/Inner paths (H2C-B): the same Plum stroke language as
   Lid's own outer/inner paths above - Outer solid, Inner dashed/muted -
   never a new Handle-specific palette. [data-handle-availability]
   mirrors renderHandlePaths()'s four real states: "off" (Handle
   feature disabled - path stays empty, this rule is inert), "valid"
   (a confirmed full physical evaluation), "pending" (interactive-only
   confirmed so far; the certification Worker has not returned the
   current revision's verdict yet - rendered like "valid", see below),
   "unavailable" (a temporarily-unresolvable
   attachment after an unrelated Shape edit - dimmed, not hidden, and
   never a large warning component). */
#sb-sg-app .sb-sg-v2-handle-outer-path,
#sb-sg-app .sb-sg-v2-handle-inner-path{
  fill:none;
  vector-effect:non-scaling-stroke;
  stroke-linecap:round;
  stroke-linejoin:round;
  pointer-events:none;
}
#sb-sg-app .sb-sg-v2-handle-outer-path{
  stroke:var(--pf-color-primary,#6f315f);
  stroke-width:2.5px;
}
#sb-sg-app .sb-sg-v2-handle-inner-path{
  stroke:var(--pf-color-text-muted,#746b73);
  stroke-width:1.75px;
  stroke-dasharray:6 5;
}
#sb-sg-app .sb-sg-v2-handle-outer-path[data-handle-availability="unavailable"],
#sb-sg-app .sb-sg-v2-handle-inner-path[data-handle-availability="unavailable"]{
  opacity:.35;
}
/* "pending" (certification still running for the current revision) is
   deliberately NOT styled: an accepted interactive Handle renders exactly
   like a confirmed one. Only a matching certification verdict may turn it
   "unavailable" (product decision 2026-10-06: pending must never look
   invalid - a faded outline during multi-second tablet certification read
   as invalid-then-valid). */

/* H2C-C (task item 2): generous invisible click target over each Handle
   path, the exact same constant-screen-width stroke-hit technique as
   .sb-sg-v2-path-hit/.sb-sg-lid-assembly-hit above - clicking the drawn
   Handle outline itself enters Edit Handle. Inert (no pointer-events) while
   off. An unavailable authored Handle remains clickable so the user can
   re-enter its controls and repair it. */
#sb-sg-app .sb-sg-v2-handle-outer-hit,
#sb-sg-app .sb-sg-v2-handle-inner-hit{
  fill:none;
  stroke:transparent;
  stroke-width:22px;
  vector-effect:non-scaling-stroke;
  pointer-events:stroke;
  cursor:pointer;
}
#sb-sg-app .sb-sg-v2-handle-outer-hit[data-handle-availability="off"],
#sb-sg-app .sb-sg-v2-handle-inner-hit[data-handle-availability="off"]{
  pointer-events:none;
}

/* Spout System (S2). Roots: same diamond attachment/anchor language as
   Handle's own roots directly above - a root is an anchor point on the
   vessel wall, not a curve-shaping point. */
#sb-sg-app .sb-sg-v2-handle--spout-root .sb-sg-v2-handle-marker{
  width:12px;
  height:12px;
  border-radius:2px;
  transform:rotate(45deg);
  background:#fff;
  border-color:var(--pf-color-primary, #6E3B5B);
}

/* U1-U3/L1-L3: same hollow-circle "curve-shaping point" language as
   Handle's own O1-O3/I1-I3. Tips: a slightly larger filled variant -
   visually distinct as the authored Side's own terminal point, never
   confused with an ordinary Side-shaping point. End: a small square -
   distinct again, since dragging it moves BOTH Tips together (task
   section 10), not a single point. */
#sb-sg-app .sb-sg-v2-handle--spout-side .sb-sg-v2-handle-marker{
  width:12px;
  height:12px;
  background:#fff;
  border-color:var(--pf-color-primary, #6E3B5B);
}
#sb-sg-app .sb-sg-v2-handle--spout-tip .sb-sg-v2-handle-marker{
  width:14px;
  height:14px;
  background:var(--pf-color-primary, #6E3B5B);
  border-color:#fff;
}
#sb-sg-app .sb-sg-v2-handle--spout-end .sb-sg-v2-handle-marker{
  width:12px;
  height:12px;
  border-radius:3px;
  background:#fff;
  border-color:var(--pf-color-text-muted,#746b73);
  border-style:dashed;
}

/* Spout outer/inner paths (S2/S2.1/S2.2, simplified by the SINGLE-CHANNEL
   REBUILD). The AUTHORED Side outline (spout-outer-path) uses the SAME
   primary Plum stroke language as Handle's own outer path (authored
   controls stay visually primary). The ONE derived inner-wall rail
   (spout-inner-upper/-lower-path, dashed like the Shape's own Inner
   Profile language - upper/lower wall thickness, never a single centroid
   centerline) is a direct projection of the physical free-body channel
   (see renderSpoutPaths()'s own header comment) and is deliberately
   visually SECONDARY - thinner, muted, never competing with the authored
   outline - and never rendered at all while [data-spout-availability] is
   "off" or "unavailable". There is no separate attachment-rail stroke:
   the vessel-embedded attachment/transition geometry is not drawn as a
   second, competing line. */
#sb-sg-app .sb-sg-v2-spout-outer-path,
#sb-sg-app .sb-sg-v2-spout-inner-upper-path,
#sb-sg-app .sb-sg-v2-spout-inner-lower-path{
  fill:none;
  vector-effect:non-scaling-stroke;
  stroke-linecap:round;
  stroke-linejoin:round;
  pointer-events:none;
}
#sb-sg-app .sb-sg-v2-spout-outer-path{
  stroke:var(--pf-color-primary,#6f315f);
  stroke-width:2.5px;
  /* S1-G1 fix (residual root marks are the round endpoint caps of
     .sb-sg-v2-spout-outer-path): this path is deliberately OPEN at
     rootUpper/rootLower (buildSpoutOuterPathD() - see its own header
     comment in sb-shape-editor-v2-session.js - intentionally never
     closes with "Z"), so the shared round linecap above draws a small
     semicircular bump protruding past each open root endpoint, in the
     direction of the path's own tangent there. Overridden to a butt cap
     HERE ONLY (the derived inner rail keeps its own round cap - it was
     not the artifact this fixes, and its own Departure-clipped start
     already avoids an analogous stub). The outlet (upperTip->lowerTip)
     is an INTERNAL path segment, not an open endpoint, so a butt cap
     never touches it - the outlet's own visual appearance is unaffected
     by this. */
  stroke-linecap:butt;
}
/* Dashed - matches the Shape's own Inner Profile language, distinct from
   the authored outline so it never reads as an editable centerline or as
   the outer contour. TWO rails (upper/lower wall thickness), never a
   single centroid line through the Spout's own axis. */
#sb-sg-app .sb-sg-v2-spout-inner-upper-path,
#sb-sg-app .sb-sg-v2-spout-inner-lower-path{
  stroke:var(--pf-color-text-muted,#746b73);
  stroke-width:1.25px;
  stroke-dasharray:3 4;
  opacity:.6;
}
#sb-sg-app .sb-sg-v2-spout-outer-path[data-spout-availability="unavailable"]{
  opacity:.35;
}
#sb-sg-app .sb-sg-v2-spout-inner-upper-path[data-spout-availability="off"],
#sb-sg-app .sb-sg-v2-spout-inner-upper-path[data-spout-availability="unavailable"],
#sb-sg-app .sb-sg-v2-spout-inner-lower-path[data-spout-availability="off"],
#sb-sg-app .sb-sg-v2-spout-inner-lower-path[data-spout-availability="unavailable"]{
  opacity:0;
}
/* S2.4 (task section 5/6 - "hidden during drag beats visibly wrong during
   drag"): canonical evaluation no longer runs mid-drag, so this DERIVED
   rail is necessarily frozen at its last-settled position throughout an
   active Spout drag - substantially faded (never opacity:0/removed, so
   there is no flash-in/flash-out at drag start/end) rather than left at
   full opacity where it would visibly detach from the live-moving
   authored outline. Paired with [data-spout-availability="valid"] so this
   can never fight the off/unavailable rule above (renderSpoutPaths() only
   ever sets data-spout-drag-active="true" in the same branch that already
   implies availability="valid" - see its own header comment). Restored to
   full opacity automatically the instant the drag settles and this
   attribute flips back to "false". */
#sb-sg-app .sb-sg-v2-spout-inner-upper-path[data-spout-drag-active="true"][data-spout-availability="valid"],
#sb-sg-app .sb-sg-v2-spout-inner-lower-path[data-spout-drag-active="true"][data-spout-availability="valid"]{
  opacity:.12;
}

/* Generous invisible click target over the Spout outline, mirroring
   .sb-sg-v2-handle-outer-hit exactly - clicking the drawn Spout outline
   itself enters Spout Focus. An unavailable authored Spout remains
   clickable so the user can re-enter its controls and repair it. */
#sb-sg-app .sb-sg-v2-spout-outer-hit{
  fill:none;
  stroke:transparent;
  stroke-width:22px;
  vector-effect:non-scaling-stroke;
  pointer-events:stroke;
  cursor:pointer;
}
#sb-sg-app .sb-sg-v2-spout-outer-hit[data-spout-availability="off"]{
  pointer-events:none;
}

#sb-sg-app .sb-sg-lid-assembly-inner{
  fill:none;
  stroke:var(--pf-color-shape, #6E3B5B);
  stroke-width:1.3px;
  stroke-dasharray:4px 2.5px;
  stroke-linejoin:round;
  stroke-linecap:round;
  vector-effect:non-scaling-stroke;
  opacity:.78;
  pointer-events:none;
}

#sb-sg-app .sb-sg-v2-handle:active{
  cursor:grabbing;
}

/* Placement mode (task item 4) - a light visual cue that the canvas is
   currently waiting for a click to place a new Profile point. */
#sb-sg-app .sb-sg-v2-canvas-inner.sb-sg-v2-placing{
  cursor:crosshair;
}

/* Guide and Throwability remain the normal canvas overlays above the
   authoritative profile. */
#sb-sg-app .sb-sg-canvas.sb-sg-v2-session-active .sb-sg-guide{
  z-index:30;
}

#sb-sg-app.sb-sg-v2-shape-active .sb-sg-throwability{
  width:min(260px, calc(100% - 24px));
}

#sb-sg-app .sb-sg-v2-node-inspector{
  min-height:0;
}

#sb-sg-app .sb-sg-v2-node-inspector button{
  display:block;
  width:100%;
  margin-top:6px;
  border:1px solid var(--sb-sg-card-border-strong);
  background:var(--sb-sg-bg);
  color:var(--sb-sg-text);
  border-radius:var(--sb-sg-radius-sm);
  padding:6px 8px;
  font-size:12px;
  cursor:pointer;
}

/* Contextual inspector fields (task item 6/7/8/9) - Height from base/
   Diameter/Rim/Foot numeric fields, the Segment-to-X control, and the
   Sharp toggle, all inside the one stable .sb-sg-v2-node-inspector panel. */
#sb-sg-app .sb-sg-v2-field-grid{
  display:grid;
  grid-template-columns:1fr 1fr;
  gap:8px;
  margin-top:6px;
}

#sb-sg-app .sb-sg-v2-field{
  display:flex;
  flex-direction:column;
  gap:3px;
  font-size:11px;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-v2-field-input{
  display:flex;
  align-items:center;
  gap:4px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  padding:4px 6px;
  background:var(--sb-sg-bg);
}

#sb-sg-app .sb-sg-v2-field-input input{
  border:none;
  background:transparent;
  color:var(--sb-sg-text);
  width:100%;
  font-size:12px;
  padding:0;
}

#sb-sg-app .sb-sg-v2-field-input input:focus{
  outline:none;
}

#sb-sg-app .sb-sg-v2-mode-row{
  display:grid;
  grid-template-columns:minmax(0, 1fr) minmax(120px, 68%);
  align-items:center;
  gap:8px;
  margin-top:8px;
  font-size:12px;
}

#sb-sg-app .sb-sg-v2-mode-label{
  min-width:0;
  font-size:11px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-v2-field-note{
  margin:4px 0 0;
  font-size:11px;
  color:var(--sb-sg-text-faint);
}

#sb-sg-app .sb-sg-v2-segmented-control{
  display:flex;
  border:0;
  border-radius:0;
  overflow:visible;
}

#sb-sg-app .sb-sg-v2-mode-row .sb-sg-v2-segmented-control{
  width:100%;
}

#sb-sg-app .sb-sg-v2-segment-btn{
  flex:1 1 0;
  margin:0;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:0;
  background:var(--sb-sg-bg);
  color:var(--sb-sg-text);
  padding:6px 4px;
  font-size:12px;
  cursor:pointer;
}

#sb-sg-app .sb-sg-v2-node-inspector .sb-sg-v2-segment-btn,
#sb-sg-app .sb-sg-point-inspector .sb-sg-v2-segment-btn{
  display:block;
  width:100%;
  margin-top:0;
}

#sb-sg-app .sb-sg-v2-segment-btn:first-child{
  border-radius:var(--sb-sg-radius-sm) 0 0 var(--sb-sg-radius-sm);
}

#sb-sg-app .sb-sg-v2-segment-btn:last-child{
  border-radius:0 var(--sb-sg-radius-sm) var(--sb-sg-radius-sm) 0;
}

#sb-sg-app .sb-sg-v2-segment-btn + .sb-sg-v2-segment-btn{
  margin-left:-1px;
}

#sb-sg-app .sb-sg-v2-segment-btn[aria-pressed="true"]{
  position:relative;
  z-index:1;
  border-color:var(--sb-sg-plum);
  background:var(--sb-sg-plum);
  color:#fff;
}

#sb-sg-app .sb-sg-v2-segment-btn:disabled{
  opacity:.5;
  cursor:not-allowed;
}

#sb-sg-app .sb-sg-v2-sharp-row{
  margin-top:8px;
}

#sb-sg-app .sb-sg-v2-sharp-control{
  width:100%;
}

#sb-sg-app .sb-sg-v2-mode-row .sb-sg-v2-field-note{
  grid-column:1 / -1;
}

#sb-sg-app .sb-sg-v2-status{
  height:32px;
  min-height:32px;
  padding-top:4px;
  font-size:11px;
  line-height:1.25;
  color:var(--sb-sg-plum-deep);
}

/* Compact secondary-tool hierarchy. Shape remains the only emphasized card;
   Shrinkage retains every working dimension in a quieter table. */
#sb-sg-app #sb-sg-shrinkage-group .sb-sg-bool-row{
  margin:0;
}

#sb-sg-app #shrinkageWrap{
  padding-top:4px;
}

#sb-sg-app .sb-sg-shrinkage-note{
  margin:5px 0 0;
  font-size:11px;
  line-height:1.35;
  color:var(--sb-sg-text-faint);
}

#sb-sg-app .sb-sg-shrinkage-results{
  margin-top:7px;
}

#sb-sg-app .sb-sg-shrinkage-results-title{
  margin-bottom:4px;
  font-size:11px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-shrinkage-table{
  width:100%;
  border-collapse:collapse;
  font-size:11px;
}

#sb-sg-app .sb-sg-shrinkage-table th,
#sb-sg-app .sb-sg-shrinkage-table td{
  padding:4px 3px;
  border-bottom:1px solid var(--sb-sg-line);
  text-align:right;
}

#sb-sg-app .sb-sg-shrinkage-table th:first-child{
  text-align:left;
}

/* Narrow canvases expose Guide/Throwability as small access controls. Their
   established expanded states remain temporary overlays; canvas/input DOM is
   not rebuilt and the interaction hot path is untouched. */
@media (max-width:900px), (orientation:landscape) and (max-width:1280px) and (hover:none) and (pointer:coarse){
  #sb-sg-app .sb-sg-guide:not(.sb-sg-guide--expanded){
    top:8px;
    left:8px;
  }

  #sb-sg-app .sb-sg-throwability--compact{
    top:8px;
    right:8px;
    width:auto;
    min-width:0;
  }

  #sb-sg-app .sb-sg-throwability--compact .sb-sg-throwability-panel{
    display:none;
  }

  #sb-sg-app .sb-sg-throwability--compact .sb-sg-throwability-toggle{
    min-width:44px;
    min-height:44px;
    padding:6px 10px;
  }

  #sb-sg-app.sb-sg-v2-shape-active .sb-sg-throwability{
    width:auto;
    max-width:min(190px, calc(100% - 16px));
    top:8px;
    right:8px;
  }

}
/* End Advanced Profile editor styles. */
/* Lid System L4: opt-in standalone interaction prototype. The selectors are
   scoped to its explicit mount state and cannot alter the normal Generator. */
#sb-sg-app.sb-sg-lid-prototype-active{
  min-height:clamp(620px,82vh,900px);
  padding:clamp(14px,2vw,24px);
  background:var(--pf-color-surface,#fff);
  color:var(--pf-color-text,#2f2730);
}
#sb-sg-app .sb-sg-lid-prototype{
  display:grid;
  gap:16px;
  max-width:1440px;
  min-height:inherit;
  margin:0 auto;
}
#sb-sg-app .sb-sg-lid-prototype-header{
  display:flex;
  align-items:flex-end;
  justify-content:space-between;
  gap:16px;
}
#sb-sg-app .sb-sg-lid-prototype-header h2,
#sb-sg-app .sb-sg-lid-group-title h3{
  margin:0;
}
#sb-sg-app .sb-sg-lid-prototype-header p{
  max-width:68ch;
  margin:4px 0 0;
}
#sb-sg-app .sb-sg-lid-prototype-kicker{
  color:var(--pf-color-primary,#6f315f);
  font-size:.75rem;
  font-weight:700;
  letter-spacing:.08em;
  text-transform:uppercase;
}
#sb-sg-app .sb-sg-lid-prototype-layout{
  display:grid;
  grid-template-columns:minmax(270px,340px) minmax(0,1fr);
  align-items:start;
  gap:16px;
  min-height:560px;
}
#sb-sg-app .sb-sg-lid-prototype-controls{
  display:grid;
  align-content:start;
  gap:12px;
}
#sb-sg-app .sb-sg-lid-control-group{
  display:grid;
  gap:10px;
  padding:14px;
  border:1px solid var(--pf-color-border,#ded7dd);
  border-radius:var(--pf-radius-md,12px);
  background:var(--pf-color-surface-subtle,#faf8fa);
}
#sb-sg-app .sb-sg-lid-group-title{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
}
#sb-sg-app .sb-sg-lid-group-title span{
  color:var(--pf-color-text-muted,#746b73);
  font-size:.78rem;
}
#sb-sg-app .sb-sg-lid-control-group label,
#sb-sg-app [data-lid-knob-controls]{
  display:grid;
  gap:6px;
}
#sb-sg-app [data-lid-knob-controls]{
  gap:10px;
}
#sb-sg-app .sb-sg-lid-prototype .sb-sg-lid-measurements{
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:8px;
  margin:0;
}
#sb-sg-app .sb-sg-lid-prototype .sb-sg-lid-measurements div{
  display:grid;
  gap:3px;
  padding:9px 10px;
  border-radius:var(--pf-radius-sm,8px);
  background:var(--pf-color-surface,#fff);
}
#sb-sg-app .sb-sg-lid-prototype .sb-sg-lid-measurements dt{
  color:var(--pf-color-text-muted,#746b73);
  font-size:.76rem;
}
#sb-sg-app .sb-sg-lid-prototype .sb-sg-lid-measurements dd{
  margin:0;
  font-weight:650;
  font-variant-numeric:tabular-nums;
}
#sb-sg-app .sb-sg-lid-control-group select,
#sb-sg-app .sb-sg-lid-control-group input[type="range"]{
  width:100%;
  min-height:44px;
}
#sb-sg-app .sb-sg-lid-control-group output{
  justify-self:end;
  margin-top:-1.45em;
  font-variant-numeric:tabular-nums;
  color:var(--pf-color-text-muted,#746b73);
}
#sb-sg-app .sb-sg-lid-toggle{
  grid-template-columns:auto 1fr auto;
  align-items:center;
  min-height:44px;
}
#sb-sg-app .sb-sg-lid-toggle input{
  width:22px;
  height:22px;
}
#sb-sg-app .sb-sg-lid-prototype-status{
  min-height:44px;
  margin:0;
  padding:10px 12px;
  border-radius:var(--pf-radius-sm,8px);
  background:var(--pf-color-surface-subtle,#faf8fa);
}
#sb-sg-app .sb-sg-lid-prototype-status.is-invalid{
  color:var(--pf-color-danger,#9c2f3f);
}
#sb-sg-app .sb-sg-lid-prototype-canvas{
  position:relative;
  min-width:0;
  height:620px;
  min-height:0;
  overflow:hidden;
  border:1px solid var(--pf-color-border,#ded7dd);
  border-radius:var(--pf-radius-lg,16px);
  background:var(--pf-color-canvas,#f7f3f5);
}
#sb-sg-app .sb-sg-lid-prototype-canvas svg{
  display:block;
  width:100%;
  height:100%;
  min-height:0;
  touch-action:none;
}
#sb-sg-app .sb-sg-lid-outer-path,
#sb-sg-app .sb-sg-lid-closure-path,
#sb-sg-app .sb-sg-lid-inner-path{
  fill:none;
  vector-effect:non-scaling-stroke;
  stroke-linecap:round;
  stroke-linejoin:round;
}
#sb-sg-app .sb-sg-lid-outer-path{
  stroke:var(--pf-color-primary,#6f315f);
  stroke-width:2.5px;
}
#sb-sg-app .sb-sg-lid-closure-path{
  stroke:var(--pf-color-primary,#6f315f);
  stroke-width:2px;
}
#sb-sg-app .sb-sg-lid-inner-path{
  stroke:var(--pf-color-text-muted,#746b73);
  stroke-width:1.75px;
  stroke-dasharray:6 5;
}
#sb-sg-app .sb-sg-lid-handle{
  vector-effect:non-scaling-stroke;
  stroke:#fff;
  stroke-width:2px;
  pointer-events:none;
}
#sb-sg-app .sb-sg-lid-handle-hit{
  fill:transparent;
  stroke:rgba(0,0,0,0);
  stroke-width:36px;
  vector-effect:non-scaling-stroke;
  pointer-events:all;
  cursor:grab;
  touch-action:none;
}
#sb-sg-app .sb-sg-lid-handle-lid{ fill:var(--pf-color-primary,#6f315f); }
#sb-sg-app .sb-sg-lid-handle-knob{ fill:var(--pf-color-accent,#b46a82); }
#sb-sg-app .sb-sg-lid-handle-hit:focus-visible{
  outline:none;
  stroke:color-mix(in srgb,var(--pf-color-focus-ring,#1f6feb) 22%,transparent);
}

/* Lid/Knob overlay handles on the shared V2 canvas reuse the complete Shape
   handle contract: one 44px invisible interaction target and one 12px Plum
   visual point. Object identity comes from contextual focus, not a second
   color language. */
#sb-sg-app .sb-sg-lid-overlay-handle-marker{
  background:var(--pf-color-primary, #6E3B5B);
}
#sb-sg-app .sb-sg-lid-overlay-handle--knob .sb-sg-lid-overlay-handle-marker{
  background:var(--pf-color-primary, #6E3B5B);
}
#sb-sg-app .sb-sg-lid-overlay-handle--fit-depth .sb-sg-lid-overlay-handle-marker{
  width:10px;
  height:18px;
  border-radius:5px;
  background:#fff;
  border-color:var(--pf-color-primary, #6E3B5B);
}
#sb-sg-app .sb-sg-lid-prototype-legend{
  position:absolute;
  right:12px;
  bottom:12px;
  display:flex;
  gap:12px;
  padding:8px 10px;
  border-radius:999px;
  background:rgba(255,255,255,.9);
  font-size:.78rem;
}
#sb-sg-app .sb-sg-lid-prototype-legend span::before{
  content:"";
  display:inline-block;
  width:18px;
  margin-right:6px;
  border-top:2px solid var(--pf-color-primary,#6f315f);
  vertical-align:middle;
}
#sb-sg-app .sb-sg-lid-prototype-legend .is-inner::before{
  border-top-color:var(--pf-color-text-muted,#746b73);
  border-top-style:dashed;
}
@media (max-width:760px){
  #sb-sg-app .sb-sg-lid-prototype-header{ align-items:flex-start; }
  #sb-sg-app .sb-sg-lid-prototype-layout{ grid-template-columns:1fr; }
  #sb-sg-app .sb-sg-lid-prototype-canvas{ height:440px; }
}

/* ML2: the My Lids picker - a wide desktop modal, effectively full-screen
   on mobile (task Section 34). The card grid inside reuses My Shapes' own
   .pf-my-shapes-grid responsive column breakpoints (potterform-account.css)
   completely unchanged - only this dialog's own chrome is new.
   H3-B2: the My Handles library dialog joins the exact same shared chrome
   (task Section 1: "reuse My Lids interaction patterns... do not invent a
   new design system") - every selector group below carries both classes. */
/* ML2 correction pass: `display` must never be authored unconditionally on
   a <dialog> root - only every other non-layout box property is safe here.
   An unconditional `display:flex` on this ID+class selector can outrank the
   UA's own `dialog:not([open]){display:none}` rule on specificity in some
   browsers (the same #id+class lesson this stylesheet's own [hidden]
   overrides elsewhere are built around), which would leave the CLOSED
   dialog still participating in page layout as a flex box. `display` is
   set only below, on the explicit [open] variant, so the closed dialog is
   governed entirely by native <dialog> semantics - never by us trying to
   out-specify them. */
#sb-sg-app .sb-sg-my-lids-picker-dialog,
#sb-sg-app .sb-sg-my-handles-dialog{
  width:min(960px, calc(100vw - 32px));
  max-height:calc(100vh - 48px);
  padding:0;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius);
  background:var(--sb-sg-bg);
  color:var(--sb-sg-text);
  box-shadow:var(--sb-sg-shadow);
  overflow:hidden;
}

#sb-sg-app .sb-sg-my-lids-picker-dialog[open],
#sb-sg-app .sb-sg-my-handles-dialog[open]{
  display:flex;
  flex-direction:column;
}

#sb-sg-app .sb-sg-my-lids-picker-header,
#sb-sg-app .sb-sg-my-handles-header{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:12px;
  padding:16px 20px;
  border-bottom:1px solid var(--sb-sg-card-border-strong);
  flex:0 0 auto;
}

#sb-sg-app .sb-sg-my-lids-picker-heading,
#sb-sg-app .sb-sg-my-handles-heading{
  margin:0;
  font-size:16px;
  font-weight:600;
  color:var(--sb-sg-text);
}

/* Staging polish: reuses the exact compact close-button language
   .sb-sg-guide-close already established (the only other X-close control in
   this codebase) - --sb-sg-radius-sm, subtle hover fill, same
   focus-visible outline - instead of the previous bordered near-circle,
   which rendered visibly oval (box-sizing:border-box keeps the box itself
   36x36, but the circular 50% radius combined with the 1px border and a
   glyph that isn't perfectly optically centered read as egg-shaped at that
   size). min-width/min-height raised to 44px, matching every other tap
   target in this panel (Add Lid/Save Lid/My Lids buttons, the guide close)
   - a larger hit target, never a smaller one.
   Visual polish pass: a borderless treatment read as visually too weak
   next to PotterForm's other compact bordered controls (e.g. the Lid fit
   buttons at .sb-sg-lid-fit-actions .sb-sg-btn just below, or
   .sb-sg-btn-secondary) - all of which share the exact same
   var(--pf-color-primary-border) 1px plum border token. Reused verbatim
   here (never a new color/alpha), box-sizing:border-box keeps the 44x44
   box unchanged. */
#sb-sg-app .sb-sg-my-lids-picker-close,
#sb-sg-app .sb-sg-my-handles-close{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:44px;
  min-height:44px;
  border:1px solid var(--pf-color-primary-border, rgba(110,59,91,0.28));
  border-radius:var(--sb-sg-radius-sm);
  background:none;
  color:var(--sb-sg-text-faint);
  font-size:20px;
  line-height:1;
  cursor:pointer;
  transition:background .14s ease, color .14s ease;
}

#sb-sg-app .sb-sg-my-lids-picker-close:hover,
#sb-sg-app .sb-sg-my-handles-close:hover{
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,0.05));
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-my-lids-picker-close:focus-visible,
#sb-sg-app .sb-sg-my-handles-close:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

#sb-sg-app .sb-sg-my-lids-picker-body,
#sb-sg-app .sb-sg-my-handles-body{
  padding:20px;
  overflow-y:auto;
  flex:1 1 auto;
}

#sb-sg-app .sb-sg-my-lids-picker-status,
#sb-sg-app .sb-sg-my-handles-status{
  margin:0 0 14px;
  font-size:13px;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-my-lids-picker-status.is-error,
#sb-sg-app .sb-sg-my-handles-status.is-error{
  color:var(--pf-color-error, #AA1111);
  font-weight:600;
}

#sb-sg-app .sb-sg-my-lids-picker-status[hidden],
#sb-sg-app .sb-sg-my-handles-status[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-my-lids-picker-retry,
#sb-sg-app .sb-sg-my-handles-retry{
  margin:0 0 14px;
}

#sb-sg-app .sb-sg-my-lids-picker-retry[hidden],
#sb-sg-app .sb-sg-my-handles-retry[hidden]{
  display:none;
}

@media (min-width: 641px){
  #sb-sg-app .sb-sg-my-lids-picker-dialog[open],
  #sb-sg-app .sb-sg-my-handles-dialog[open]{
    width:min(760px, calc(100vw - 48px));
  }
}

@media (min-width: 961px){
  #sb-sg-app .sb-sg-my-lids-picker-dialog[open],
  #sb-sg-app .sb-sg-my-handles-dialog[open]{
    width:min(960px, calc(100vw - 64px));
  }
}

/* Mobile: effectively full-screen (task Section 34) so the card grid and
   dialog controls stay reachable above browser chrome/keyboard, with no
   horizontal scrolling. Scoped to [open] like the base rule above -
   width/height/border-radius/border alone never affect a closed dialog's
   visibility, but this keeps every rule that shapes the picker's on-screen
   box consistently gated on the same explicit [open] condition rather than
   leaving some gated and some not. */
@media (max-width: 640px){
  #sb-sg-app .sb-sg-my-lids-picker-dialog[open],
  #sb-sg-app .sb-sg-my-handles-dialog[open]{
    width:100vw;
    max-width:100vw;
    height:100vh;
    max-height:100vh;
    border-radius:0;
    border:none;
  }
}

/* H3-B2: the single-select card toggle button - the whole
   preview+name/meta area of a My Handles card is a real <button> (task
   Section 25: "do not introduce custom inaccessible card-click handling"),
   reset to plain block layout so it reads visually identical to the
   non-interactive My Lids picker card content above it, with its own
   visible focus-visible outline. Distinct from the pre-existing
   .pf-my-shape-card-select (the Create Set MULTI-select checkbox row) -
   this is a single-select whole-card toggle, a different interaction
   model, so it is deliberately not given that class name. */
#sb-sg-app .sb-sg-handle-card-select{
  display:flex;
  flex-direction:column;
  gap:8px;
  width:100%;
  padding:0;
  border:none;
  background:none;
  font:inherit;
  text-align:left;
  color:inherit;
  cursor:pointer;
}

#sb-sg-app .sb-sg-handle-card-select:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
  border-radius:var(--pf-radius-small, 8px);
}

/* H2D-A master-detail layout: a thin top toolbar over a 20/40/40 three-
   column body - LEFT a read-only A/Mid/B overview, CENTER one large
   active editable canvas, RIGHT the reserved 3D-preview placeholder. */
#sb-sg-app .sb-sg-section-editor-dialog{
  width:min(1180px, calc(100vw - 32px));
  max-height:calc(100vh - 48px);
  padding:0;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius);
  background:var(--sb-sg-bg);
  color:var(--sb-sg-text);
  box-shadow:var(--sb-sg-shadow);
  overflow:hidden;
}

#sb-sg-app .sb-sg-section-editor-dialog[open]{
  display:flex;
  flex-direction:column;
}

/* Thin top toolbar: heading + Organic/Faceted + Apply-to-all + close, all
   one row - the global Section controls used to sit inside the LEFT
   column below the panels, pushing A/Mid/B further down and forcing
   scroll. */
#sb-sg-app .sb-sg-section-editor-header{
  display:flex;
  align-items:center;
  flex-wrap:wrap;
  gap:8px 14px;
  padding:8px 14px;
  border-bottom:1px solid var(--sb-sg-card-border-strong);
  flex:0 0 auto;
}

#sb-sg-app .sb-sg-section-editor-heading{
  margin:0 8px 0 0;
  font-size:14px;
  font-weight:600;
  color:var(--sb-sg-text);
  white-space:nowrap;
}

#sb-sg-app .sb-sg-section-editor-apply-all{
  font-size:12px;
  padding:6px 10px;
}

/* Finishes Section editing deliberately - never a second geometry commit
   (see app.js's own doneBtn/closeSectionEditor() comments). Reuses the
   shared .sb-sg-btn/.sb-sg-btn-primary design-system button, just pushed
   to the toolbar's right edge and sized to stay inside a thin toolbar. */
#sb-sg-app .sb-sg-section-editor-done{
  margin-left:auto;
  min-height:36px;
  padding:8px 16px;
  font-size:12px;
}

/* Focused Section authoring: the active (center) Section gets the largest
   share; the A/Mid/B cards stay visible as a compact selector column. */
#sb-sg-app .sb-sg-section-editor-body{
  display:grid;
  grid-template-columns:minmax(120px,0.8fr) minmax(360px,3fr) minmax(240px,2fr);
  gap:14px;
  padding:14px;
  overflow-y:auto;
  overflow-x:hidden;
  flex:1 1 auto;
  min-width:0;
  min-height:0;
}

/* LEFT (20%): three always-visible, read-only overview cards, evenly
   filling the overview's full height (final visual polish) - the grid's
   own default align-items:stretch already gives this column the SAME
   height as the CENTER/RIGHT columns; flex-grow below just divides that
   height into three equal shares. This is overview-layout polish only -
   it never touches the CENTER editor's own frozen viewport/scale. */
#sb-sg-app .sb-sg-section-editor-overview{
  display:flex;
  flex-direction:column;
  gap:10px;
  min-width:0;
  min-height:0;
}

/* A pure visual Section navigator now (final UX polish) - contour preview
   only, no station text/readouts; identity and dimensions live only in
   the center editor. Each card grows to occupy ~1/3 of the overview's
   height, with its thumbnail centered inside. */
#sb-sg-app .sb-sg-section-editor-card{
  flex:1 1 0;
  min-height:0;
  display:flex;
  align-items:center;
  justify-content:center;
  padding:8px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  cursor:pointer;
  background:none;
}

#sb-sg-app .sb-sg-section-editor-card[data-active="true"]{
  border-color:var(--pf-color-primary, #6E3B5B);
  box-shadow:0 0 0 1px var(--pf-color-primary, #6E3B5B) inset;
}

#sb-sg-app .sb-sg-section-editor-card:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
}

/* Read-only, presentation-fit-scaled - no drag handles ever render here.
   Fills its (now flex-grown) card; the SVG's own default
   preserveAspectRatio ("xMidYMid meet") keeps the contour centered as the
   box grows, matching sectionEditorThumbTransform()'s own centered
   viewBox math in app.js. */
#sb-sg-app .sb-sg-section-editor-thumb{
  width:100%;
  height:100%;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  background:var(--sb-sg-canvas-bg, #fff);
  pointer-events:none;
}

#sb-sg-app .sb-sg-section-editor-thumb-contour{
  fill:var(--pf-color-primary, #6E3B5B);
  fill-opacity:.08;
  stroke:var(--pf-color-primary, #6f315f);
  stroke-width:1.5;
  vector-effect:non-scaling-stroke;
}

#sb-sg-app .sb-sg-section-editor-thumb-contour[data-valid="false"]{
  stroke:var(--pf-color-error, #AA1111);
  stroke-dasharray:4 3;
}

/* CENTER (40%): the one large active editor - "use the available column
   aggressively for editing precision" (task requirement); the SVG's own
   viewBox/math are unchanged from before, so growing its rendered CSS box
   here only makes every mm - and every node's hit target - bigger, never
   changes the frozen physical scale itself. */
#sb-sg-app .sb-sg-section-editor-center{
  display:flex;
  flex-direction:column;
  gap:6px;
  min-width:0;
  min-height:0;
}

#sb-sg-app .sb-sg-section-editor-center-title{
  margin:0;
  font-size:16px;
  font-weight:600;
}

#sb-sg-app .sb-sg-section-editor-center-readouts{
  font-size:13px;
}

/* Responsive layout hardening: the RIGHT preview host must never
   contribute an intrinsic size larger than its assigned grid track/area
   (min-width/min-height:0 override the default min-width:auto a grid item
   otherwise gets, which would let the previewMesh/canvas's own drawing-
   buffer resolution silently force the whole dialog wider). The 260px
   floor for the WIDE-mode column already lives on the grid TRACK itself
   (grid-template-columns' own minmax(260px,2fr) below) - the item's own
   minimum is 0, so the grid, not this element, is the single source of
   truth for how narrow it may get. */
#sb-sg-app .sb-sg-section-editor-right{
  position:relative;
  display:flex;
  align-items:center;
  justify-content:center;
  min-width:0;
  min-height:0;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  overflow:hidden;
}

/* H2D-B1-B: the canvas fills the RIGHT column; sb-handle-preview-renderer.js
   owns everything drawn on it. Absolutely positioned and taken out of
   normal flow (position:absolute;inset:0) so its own intrinsic/drawing-
   buffer size (canvas.width/height - resolution only, never CSS/grid
   layout size) can NEVER contribute to the RIGHT host's - or any ancestor
   grid track's - min-content size. CSS controls only this display box; the
   renderer itself reads that box (devicePixelRatio-aware) to size its own
   drawing buffer, never the other way around. */
#sb-sg-app .sb-sg-section-editor-preview-canvas{
  position:absolute;
  inset:0;
  width:100%;
  height:100%;
  display:block;
}

#sb-sg-app .sb-sg-section-editor-preview-canvas[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-section-editor-preview-placeholder{
  margin:0;
  color:var(--sb-sg-text-faint);
  font-size:13px;
  text-align:center;
  padding:20px;
}

/* H2D-B1-CAMERA: a small secondary control overlaid in the RIGHT preview
   area's own corner - never a dedicated camera-controls toolbar row (task
   requirement). Absolutely positioned against the RIGHT host (already
   position:relative - see .sb-sg-section-editor-right above), so it never
   consumes layout space or shrinks the canvas/frustum fit, and stays
   available unchanged across the wide 20/40/40 and narrow top+50/50
   responsive modes (it is inside .sb-sg-section-editor-right either way -
   see the responsive grid-area rules). Sized down from the toolbar's own
   Apply-to-all button so it stays clearly secondary and legible even at
   the narrowest (~400px) tested viewport. */
#sb-sg-app .sb-sg-section-editor-preview-reset-view{
  position:absolute;
  top:8px;
  right:8px;
  z-index:1;
  font-size:11px;
  padding:4px 10px;
  min-height:0;
  line-height:1.4;
  background:var(--sb-sg-bg, #fff);
  opacity:.92;
}

#sb-sg-app .sb-sg-section-editor-preview-reset-view:hover{
  opacity:1;
}

#sb-sg-app .sb-sg-section-editor-preview-reset-view[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-section-editor-readouts{
  display:flex;
  flex-direction:column;
  gap:2px;
  font-size:11px;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-section-editor-center-readouts{
  flex-direction:row;
  gap:16px;
}

#sb-sg-app .sb-sg-section-editor-readout output{
  font-weight:600;
  color:var(--sb-sg-text);
}

/* The one large active editor - deliberately much taller than a thumbnail
   (96 x/68) so the same viewBox renders with a materially bigger px-per-mm
   ratio and bigger node hit targets, without touching the frozen scale
   math itself (SECTION_EDITOR_WIDTH/HEIGHT/MARGIN in app.js are
   unchanged). */
#sb-sg-app .sb-sg-section-editor-svg{
  flex:1 1 auto;
  width:100%;
  min-height:360px;
  max-height:62vh;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  background:var(--sb-sg-canvas-bg, #fff);
  touch-action:none;
}

#sb-sg-app .sb-sg-section-editor-axis{
  stroke:var(--sb-sg-card-border-strong);
  stroke-width:1;
  stroke-dasharray:4 3;
}

#sb-sg-app .sb-sg-section-editor-contour{
  fill:var(--pf-color-primary, #6E3B5B);
  fill-opacity:.08;
  stroke:var(--pf-color-primary, #6f315f);
  stroke-width:1.75;
  vector-effect:non-scaling-stroke;
}

#sb-sg-app .sb-sg-section-editor-contour[data-valid="false"]{
  stroke:var(--pf-color-error, #AA1111);
  stroke-dasharray:4 3;
}

#sb-sg-app .sb-sg-section-editor-pole{
  fill:var(--sb-sg-text-faint);
  stroke:var(--sb-sg-bg);
  stroke-width:1.5;
}

#sb-sg-app .sb-sg-section-editor-node{
  fill:#fff;
  stroke:var(--pf-color-primary, #6E3B5B);
  stroke-width:2;
  cursor:grab;
}

#sb-sg-app .sb-sg-section-editor-node:hover,
#sb-sg-app .sb-sg-section-editor-node:focus-visible{
  fill:var(--pf-color-primary, #6E3B5B);
  outline:none;
}

#sb-sg-app .sb-sg-section-editor-mode{
  display:flex;
  align-items:center;
  gap:10px;
  font-size:12px;
}

#sb-sg-app .sb-sg-section-editor-mode-label{
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-section-editor-status{
  margin:0;
  font-size:12px;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-section-editor-status[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-section-editor-status[data-rejected="true"]{
  color:var(--pf-color-error, #AA1111);
}

/* Responsive breakpoint, derived (not guessed) from the WIDE grid's own
   minimum content width: grid-template-columns above requires at least
   160+320+260 = 740px of column content, plus the body's own 14px padding
   x2 and 14px column gap x2 = 56px, so the body needs >=796px before it
   fits without overflow. The dialog itself is width:min(1180px,
   calc(100vw - 32px)), so for viewport < 1212px the dialog is exactly
   (viewport - 32)px wide, and 796px of required body content means
   overflow begins at viewport ~= 796 + 32 = 828px. 840px engages narrow
   mode with a small (~12px) safety margin BEFORE that point, confirmed
   against real Chrome measurement at 900/840/800px (see the H2D-B1-B
   responsive report) rather than a generic device breakpoint.
   Narrow tier 1: keeps the master-detail spirit (A/Mid/B still visible
   together, one editable Section, one 3D preview) but stacks it as
   TOP: A | Mid | B (full width) / BOTTOM: Section editor | 3D preview
   (50/50) - reusing the exact same DOM/elements via grid-template-areas,
   never a second/duplicated implementation. */
@media (max-width:840px){
  /* Focused Section authoring (tablet): stacked - A/Mid/B selector row,
     then the active Section at full width, then the 3D preview - so the
     edited Section is never squeezed beside the 3D view. */
  #sb-sg-app .sb-sg-section-editor-body{
    grid-template-columns:minmax(0,1fr);
    grid-template-rows:auto auto minmax(260px,auto);
    grid-template-areas:
      "overview"
      "center"
      "right";
  }
  /* grid-area is scoped to THIS media query only, never declared
     unconditionally on the base .sb-sg-section-editor-overview/-center/
     -right rules - a bare grid-area:<custom-ident> with no matching
     grid-template-areas in scope (i.e. at wide widths, where this file's
     own base rule leaves grid-template-areas:none) does not reliably fall
     back to auto-placement in every engine tested and broke the WIDE
     3-column layout when declared unconditionally (confirmed via direct
     measurement - see the H2D-B1-B responsive report). */
  #sb-sg-app .sb-sg-section-editor-overview{
    grid-area:overview;
    flex-direction:row;
    gap:8px;
  }
  #sb-sg-app .sb-sg-section-editor-center{
    grid-area:center;
  }
  #sb-sg-app .sb-sg-section-editor-right{
    grid-area:right;
  }
  /* A single full-width row of three cards no longer has a tall
     grid-stretched height to divide - fall back to a fixed thumbnail
     height rather than a "100%" that would collapse to 0 against an
     auto-height row. */
  #sb-sg-app .sb-sg-section-editor-thumb{
    height:64px;
  }
  #sb-sg-app .sb-sg-section-editor-readouts{
    flex-direction:row;
    gap:12px;
  }
  #sb-sg-app .sb-sg-section-editor-svg{
    flex:0 0 auto;
    height:auto;
    min-height:0;
    aspect-ratio:220 / 150;
    max-height:62vh;
  }
  #sb-sg-app .sb-sg-section-editor-right{
    min-height:280px;
  }
}

/* A third ("very narrow", fully stacked A/Mid/B -> editor -> preview)
   responsive tier was evaluated and deliberately NOT added: real-Chrome
   measurement at 1280/1024/900/840/828/820/800/700/600/480/400px viewport
   widths (see the H2D-B1-B responsive report) found zero horizontal
   overflow and a still-usable 50/50 Section-editor/3D-preview split at
   every one of those widths, including 400px - well past this product's
   supported range. Per task instruction, a third tier is only warranted
   when actually measured usable widths require it; they did not. */

@media (max-width: 640px){
  #sb-sg-app .sb-sg-section-editor-dialog[open]{
    width:100vw;
    max-width:100vw;
    height:100vh;
    max-height:100vh;
    border-radius:0;
    border:none;
  }
}

/* Spout Sections: the Spout sibling of the Handle Section Editor above -
   reuses every .sb-sg-section-editor-* rule unchanged (same three-column
   overview / focused Section / 3D preview layout as Handle) and only adds
   the visible Attachment / Middle / Outlet card captions. Scoped by the
   --spout modifier (higher specificity), so the Handle dialog is
   untouched at every width. */
#sb-sg-app .sb-sg-section-editor-dialog--spout .sb-sg-section-editor-card{
  flex-direction:column;
  gap:6px;
}

#sb-sg-app .sb-sg-section-editor-dialog--spout .sb-sg-section-editor-thumb{
  flex:1 1 auto;
  min-height:0;
}

#sb-sg-app .sb-sg-section-editor-card-caption{
  font-size:12px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

#sb-sg-app .sb-sg-section-editor-card[data-active="true"] .sb-sg-section-editor-card-caption{
  color:var(--pf-color-primary, #6E3B5B);
}


/* 3D Preview (Assembly Preview, 3D Phase 2): reuses the Section editor
   dialog shell/header/Done styling; only the body is its own - one large
   canvas, a status line and a small usage hint. */
#sb-sg-app .sb-sg-assembly-preview-dialog{
  width:min(980px, calc(100vw - 32px));
}

#sb-sg-app .sb-sg-assembly-preview-reset{
  margin-left:auto;
  font-size:12px;
  padding:6px 10px;
}

#sb-sg-app .sb-sg-assembly-preview-dialog .sb-sg-section-editor-done{
  margin-left:0;
}

#sb-sg-app .sb-sg-assembly-preview-body{
  display:flex;
  flex-direction:column;
  gap:8px;
  padding:14px;
  min-height:0;
}

#sb-sg-app .sb-sg-assembly-preview-canvas{
  width:100%;
  height:min(62vh, 620px);
  min-height:280px;
  border:1px solid var(--sb-sg-card-border-strong);
  border-radius:var(--sb-sg-radius-sm);
  background:#fff;
  cursor:grab;
}

#sb-sg-app .sb-sg-assembly-preview-canvas[hidden],
#sb-sg-app .sb-sg-assembly-preview-status[hidden],
#sb-sg-app .sb-sg-assembly-preview-fallback[hidden]{
  display:none;
}

#sb-sg-app .sb-sg-assembly-preview-status,
#sb-sg-app .sb-sg-assembly-preview-fallback,
#sb-sg-app .sb-sg-assembly-preview-hint{
  margin:0;
  font-size:12px;
  color:var(--sb-sg-text-soft);
}

/* ML2: the same #pf-account .pf-my-shape-card-open hover/focus-visible
   parity block potterform-account.css already defines for itself (that
   file, the rules directly below its own ".pf-my-shape-card-open{...}"
   base rule) - reused here verbatim for the Generator's My Lids picker
   cards, which render this exact same class outside any #pf-account
   ancestor. Same reasoning as that file's own comment: WordPress theme
   link/button CSS can outrank the bare class at rest, so only this
   page's own scoping root (#sb-sg-app) can reliably beat it here too.
   potterform-account.css itself is never edited by this. */
#sb-sg-app .pf-my-shape-card-open{
  color:#fff !important;
}

#sb-sg-app .pf-my-shape-card-open:hover{
  background:var(--pf-color-primary-hover);
  border-color:var(--pf-color-primary-hover);
  color:#fff !important;
}

#sb-sg-app .pf-my-shape-card-open:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
  color:#fff !important;
}

/* H3-B3 hover polish: the same #pf-account .pf-my-shape-card-action/
   .pf-my-shape-card-rename-save/.pf-my-shape-card-rename-cancel/
   .pf-my-shape-card-delete hover/focus-visible parity block
   potterform-account.css already defines for itself (that file, the rules
   directly below its own base ".pf-my-shape-card-action,
   .pf-my-shape-card-rename-save, .pf-my-shape-card-rename-cancel{...}" and
   ".pf-my-shape-card-delete{...}" rules) - reused here verbatim for the
   My Handles library (Rename/Delete), which renders this exact same class
   family outside any #pf-account ancestor and was still falling through to
   Astra's theme button:hover/:focus-visible blue with no #sb-sg-app-scoped
   override at all. Same #sb-sg-app scoping-root reasoning as
   .pf-my-shape-card-open directly above - potterform-account.css itself is
   never edited by this. */
#sb-sg-app .pf-my-shape-card-action:hover,
#sb-sg-app .pf-my-shape-card-rename-save:hover,
#sb-sg-app .pf-my-shape-card-rename-cancel:hover{
  border-color:var(--pf-color-primary-border);
  color:var(--pf-color-primary) !important;
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,.05)) !important;
  background-image:none !important;
}

#sb-sg-app .pf-my-shape-card-action:focus-visible,
#sb-sg-app .pf-my-shape-card-rename-save:focus-visible,
#sb-sg-app .pf-my-shape-card-rename-cancel:focus-visible{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  outline-offset:2px;
  color:var(--pf-color-primary) !important;
  background:var(--pf-color-primary-soft-hover, rgba(110,59,91,.05)) !important;
  background-image:none !important;
}

#sb-sg-app .pf-my-shape-card-rename-save:hover{
  background:var(--pf-color-primary-hover) !important;
  border-color:var(--pf-color-primary-hover);
  color:#fff !important;
}

/* Delete must stay #sb-sg-app-scoped with a matching !important on `color`
   like .pf-my-shape-card-action:hover above - Delete carries both classes,
   and the shared action:hover rule's color is !important, so without an
   equally-important, correctly-ordered override here Delete would lose its
   red hover entirely and turn Plum instead of staying destructive. */
#sb-sg-app .pf-my-shape-card-delete:hover{
  background:var(--pf-color-error-soft, rgba(170,17,17,.06)) !important;
  color:var(--pf-color-error-hover, #8A0E0E) !important;
  border-color:var(--pf-color-error-border, rgba(170,17,17,0.35));
}

#sb-sg-app .pf-my-shape-card-delete:focus-visible{
  background:var(--pf-color-error-soft, rgba(170,17,17,.06)) !important;
  color:var(--pf-color-error-hover, #8A0E0E) !important;
  border-color:var(--pf-color-error-border, rgba(170,17,17,0.35));
  background-image:none !important;
  outline:2px solid var(--pf-color-error, #AA1111);
  outline-offset:2px;
}
