/* =========================
   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);
}

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;
}

/* Layout */
#sb-sg-app .sb-sg-layout{
  display:flex;
  gap:20px;
  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;
}

#sb-sg-app .sb-sg-group{
  margin-bottom:14px;
  padding:14px 14px 8px;
  border:1px solid var(--sb-sg-card-border);
  border-radius:var(--sb-sg-radius);
  background:var(--sb-sg-card);
  box-shadow:var(--sb-sg-shadow-soft);
}

#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);
}

#sb-sg-app .sb-sg-controls label{
  display:block;
  margin:11px 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. */
#sb-sg-app .sb-sg-label-primary{
  font-weight:600;
  color:var(--sb-sg-text);
}

/* 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;
}

/* 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));
}

/* Inner Clay result card: Phase 1A migrated this from the historical
   pale-orange background to the Plum primary-soft tint - Clay Estimate is
   an ordinary calculated-result surface, not a warning, so it must not use
   the warning family (see the Throwability guidance card, which keeps its
   own genuine warning colour, further down this file). The base
   rule for .sb-sg-clay-result is injected at runtime by sb-shape-clay.js
   (equal specificity, loads after this stylesheet), so the existing
   #sb-sg-clay-estimate id is added here purely to win the cascade - no
   markup change, no new class. */
#sb-sg-app #sb-sg-clay-estimate .sb-sg-clay-result{
  background:var(--pf-color-primary-soft, rgba(110,59,91,0.07));
}

/* 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;
}

#sb-sg-app .sb-sg-radio-card{
  margin:0;
  min-height:42px;
  padding:10px 12px;
  border:1px solid var(--sb-sg-card-border);
  border-radius:12px;
  background:#fff;
}

#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:10px;
  margin-top:12px;
}

#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);
}

/* Genuine, non-blocking warning tone (Compare's four-Variant maximum and
   persistence warnings) - added/removed only via setCompareStatus()'s
   tone parameter, so it can never survive a later message change or an
   empty clear. Same pale-background/left-border warning-card language as
   .sb-sg-throwability-warning, kept scoped to this one modifier class
   rather than reused globally. */
#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);
}

/* 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;
}

#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;
  background:#fff;
  box-shadow:var(--sb-sg-shadow);
}

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

/* 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){
  #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;
  }
}

/* =========================
   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. Currently
   holds only the Full screen action; future tool buttons are out of scope.
   Structural sizing lives in the base rule per the note in the media rules
   below - only display and the layout gap are conditional. */
#sb-sg-app .sb-sg-tool-rail{
  display:none;
  flex:0 0 32px;
  width:32px;
  align-self:stretch;
  align-items:center;
  justify-content:center;
}

@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. Also gated on .sb-sg-fullscreen-supported (added only when the
     Fullscreen API is detected, see app.js), so an unsupported browser
     never shows this either way. No width breakpoint is needed to separate
     "phone" from "tablet" here - both get the same Fullscreen action. 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. */
  #sb-sg-app.sb-sg-fullscreen-supported .sb-sg-layout{
    gap:0;
  }

  #sb-sg-app.sb-sg-fullscreen-supported .sb-sg-tool-rail{
    display:flex;
    background:var(--sb-sg-card);
    border-left:1px solid var(--sb-sg-card-border);
    border-right:1px solid var(--sb-sg-card-border);
  }
}

#sb-sg-app .sb-sg-fullscreen-toggle{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:28px;
  height:28px;
  padding:0;
  background:var(--sb-sg-card);
  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));
}

/* 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;
    flex:0 0 32px;
    width:32px;
    min-width:32px;
    align-self:stretch;
    align-items:center;
    justify-content:center;
    background:var(--sb-sg-card);
    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%;
  }
}

/* =========================
   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;
}

/* 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. */
#sb-sg-app .sb-sg-view-comparison-btn{
  display:flex;
  align-items:center;
  justify-content:center;
  width:100%;
  min-height:44px;
  margin-top:8px;
  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){
  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){
  transform:translateY(1px);
  box-shadow:0 1px 3px rgba(0,0,0,0.12);
}

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

#sb-sg-app .sb-sg-view-comparison-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 "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 "Compare N" 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{
  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{
  background:rgba(17,17,17,0.35);
}

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

#sb-sg-app .sb-sg-load-confirm-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{
  display:flex;
  justify-content:flex-end;
  flex-wrap:wrap;
  gap:10px;
}

#sb-sg-app .sb-sg-load-confirm-cancel,
#sb-sg-app .sb-sg-load-confirm-load{
  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{
  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{
  border-color:var(--pf-color-primary, #6E3B5B);
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-load-confirm-load{
  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{
  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{
  outline:2px solid var(--pf-color-primary, #6E3B5B);
  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;
  background:#fff;
}

#sb-sg-app .sb-sg-compare-toolbar{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:10px;
  flex:0 0 auto;
}

#sb-sg-app .sb-sg-compare-back-btn,
#sb-sg-app .sb-sg-compare-clear-btn{
  display:inline-flex;
  align-items:center;
  justify-content: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-compare-back-btn:hover,
#sb-sg-app .sb-sg-compare-clear-btn:hover{
  border-color:var(--pf-color-primary, #6E3B5B);
  color:var(--pf-color-primary, #6E3B5B);
}

#sb-sg-app .sb-sg-compare-back-btn:focus-visible,
#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-back-btn:active,
#sb-sg-app .sb-sg-compare-clear-btn:active{
  transform:translateY(1px);
}

/* Primary action of the comparison toolbar - solid Plum fill, white text,
   clearly stronger than the outline-style Back/Clear buttons above. Shares
   their box-model (min-height/padding/radius/font-size) so the three
   toolbar buttons still read as one row, differing only in emphasis. */
#sb-sg-app .sb-sg-compare-export-btn{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-height:32px;
  padding:6px 14px;
  border:1px solid var(--sb-sg-plum);
  border-radius:var(--sb-sg-radius-sm);
  background:var(--sb-sg-plum);
  font:inherit;
  font-size:11px;
  font-weight:600;
  color:#fff;
  cursor:pointer;
  white-space:nowrap;
  box-shadow:0 2px 6px rgba(90,48,76,0.28);
  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(--sb-sg-plum-deep);
  border-color:var(--sb-sg-plum-deep);
  color:#fff;
  box-shadow:0 3px 10px rgba(90,48,76,0.36);
}

#sb-sg-app .sb-sg-compare-export-btn:focus-visible{
  outline:2px solid rgba(110,59,91,0.35);
  outline-offset:2px;
}

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

/* 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-toolbar-count{
  flex:1 1 auto;
  text-align:center;
  font-size:12px;
  font-weight:600;
  color:var(--sb-sg-text-soft);
}

#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;
}

/* Occupied cells are real <button> elements (see buildCompareOccupiedCellNode()
   in app.js) - these reset the inherited native button chrome back to the
   same card look .sb-sg-compare-cell already establishes above, so the cell
   still reads primarily as a comparison card, not as a conventional form
   button. Hover/active are a subtle Plum tint only (the same restrained
   language .sb-sg-fullscreen-toggle:hover already uses elsewhere in this
   file) - deliberately far less visually dominant than the solid
   .sb-sg-view-comparison-btn or .sb-sg-export-btn treatment. */
#sb-sg-app .sb-sg-compare-cell--occupied{
  appearance:none;
  -webkit-appearance:none;
  width:100%;
  background:#fff;
  font:inherit;
  text-align:center;
  cursor:pointer;
  transition:border-color .14s ease, background-color .14s ease;
}

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

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

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

#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 is required here now that this is a <span> (button content
   model does not allow a block-level <div> - see buildCompareOccupiedCellNode()
   in app.js): the ellipsis truncation below only applies to a block or
   inline-block box, not a plain inline element. */
#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);
}

/* Restrained call-to-action label, visible for touch users as well as
   pointer/keyboard users - small, uppercase, Plum-coloured but far less
   prominent than a solid button, so the cell still reads primarily as a
   comparison card. Sits last inside .sb-sg-compare-cell-info's column flex
   stack, below name and measurements, so it can never overlap them. */
#sb-sg-app .sb-sg-compare-cell-open-label{
  display:block;
  margin-top:2px;
  font-size:10px;
  font-weight:600;
  letter-spacing:0.02em;
  text-transform:uppercase;
  color:var(--pf-color-primary, #6E3B5B);
  opacity:0.85;
}

/* 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;
  }

  #sb-sg-app .sb-sg-compare-back-btn,
  #sb-sg-app .sb-sg-compare-clear-btn,
  #sb-sg-app .sb-sg-compare-export-btn{
    padding:6px 10px;
    font-size:10px;
  }

  #sb-sg-app .sb-sg-compare-toolbar-count{
    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-open-label{
    margin-top:1px;
    font-size:9px;
  }
}