/* Magpie Explorer layout + sidebar styling. All colors inline here —
   not tied to Polaris tokens because the explorer chrome itself is
   deliberately plain so it doesn't compete visually with the previews
   it's rendering. */

/* Dev-tool chrome; intentionally uses raw hex + px so we don't depend
   on Polaris tokens that might change or drift. */
/* stylelint-disable color-no-hex -- dev tool chrome, no tokens. */
/* stylelint-disable polaris/border/declaration-property-unit-disallowed-list -- dev tool chrome, no tokens. */
/* stylelint-disable polaris/space/declaration-property-unit-disallowed-list -- dev tool chrome, no tokens. */
/* stylelint-disable polaris/typography/declaration-property-unit-disallowed-list -- dev tool chrome, no tokens. */
/* stylelint-disable polaris/typography/polaris/declaration-property-value-disallowed-list -- dev tool chrome, no tokens. */
/* stylelint-disable polaris/z-index/declaration-property-value-allowed-list -- dev tool chrome, no tokens. */
/* stylelint-disable polaris/shadow/declaration-property-unit-disallowed-list -- dev tool chrome, no tokens. */
/* stylelint-disable local/content-no-strings -- dev tool chrome disclosure chevrons. */
/* stylelint-disable polaris/color/function-disallowed-list -- dev tool chrome uses translucent layers. */
/* stylelint-disable selector-max-specificity -- compound state selectors needed for hover/active overrides. */
/* stylelint-disable polaris/motion/declaration-property-unit-disallowed-list -- dev tool chrome, no tokens. */

._layout_loymm_20 {
  display: grid;
  grid-template-columns: 280px 1fr;
  height: 100vh;
  color: #202223;
  background: #f1f2f4;
}

._layout_loymm_20[data-status-open="true"] {
  grid-template-columns: 340px 360px 1fr;
}

._statusToggle_loymm_32 {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 7px;
  background: #fff;
  color: #5c5f62;
  cursor: pointer;
  /* box-shadow: 0 1px 0 rgba(0, 0, 0, 0.03); */
}

._statusToggle_loymm_32:hover {
  background: #f6f6f7;
  color: #202223;
  border-color: #babfc3;
}

._statusToggleActive_loymm_53 {
  background: #202223;
  border-color: #202223;
  color: #fff;
}

._statusToggleActive_loymm_53:hover {
  background: #303336;
  color: #fff;
}

/* Sidebar ------------------------------------------------------------ */

._sidebar_loymm_66 {
  position: relative;
  top: 0;
  display: flex;

  /* Reserve scrollbar space at all times so the layout doesn't shift
   * (and the sidebar header doesn't drift) when the route list is short
   * enough to not need scrolling. */
  overflow-y: scroll;
  scrollbar-gutter: stable;
  flex-direction: column;
  border-inline-end: 1px solid #d8dadd;
  background: #fbfbfc;
}

._sidebarHeader_loymm_81 {
  position: sticky;
  z-index: 1;
  top: 0;
  padding: 12px;
  border-bottom: 1px solid #d8dadd;
  background:
    radial-gradient(
      240px 120px at 20% -20%,
      rgba(44, 110, 203, 0.08),
      transparent 70%
    ),
    linear-gradient(180deg, #fff 0%, #fbfbfc 100%);
  box-shadow: 0 1px 0 rgba(0, 0, 0, 0.02);
}

/* Electron-only: a thin strip pinned to the top of the sidebar that acts
 * as the window drag region. Sits behind the macOS traffic lights
 * (which Electron positions at x=12, y=12). Absolutely positioned so it
 * has zero impact on document flow regardless of whether the sidebar is
 * scrollable — prevents the layout drifting when content fits without
 * a scrollbar. The sidebar header below has top padding to clear it. */
._dragRail_loymm_103 {
  position: absolute;
  top: 0;
  inset-inline: 0;
  z-index: 2;
  height: 28px;
  background: #fbfbfc;
  -webkit-app-region: drag;
}

._sidebarHeaderApp_loymm_113 {
  /* Header itself is also draggable so the strip extends through the title. */
  -webkit-app-region: drag;

  /* Top padding clears the absolutely-positioned 28px drag rail above. */
  padding-top: 36px;
  padding-inline-start: 10px;
}

._sidebarHeaderApp_loymm_113 button,
._sidebarHeaderApp_loymm_113 input,
._sidebarHeaderApp_loymm_113 label,
._sidebarHeaderApp_loymm_113 a {
  /* Interactive elements must opt out of the drag region or clicks become
   * window-drag gestures. */
  -webkit-app-region: no-drag;
}

._sidebarTopbar_loymm_131 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

._sidebarBrand_loymm_138 {
  display: flex;
  align-items: center;
  gap: 9px;
  min-width: 0;
}

._sidebarLogo_loymm_145 {
  width: 30px;
  height: 30px;
  padding: 2px;
  border-radius: 8px;
  object-fit: contain;
  flex-shrink: 0;
}

/* Same 30px box as .sidebarLogo, for an INLINE SVG brand mark rather than the
   raster logo (the public-facing Shopify bag). A separate class because the
   two are laid out differently: `object-fit`/`padding` inset a replaced <img>,
   while an inline <svg> needs a flex box around it to centre and to stop its
   own intrinsic size deciding the row height. */
._sidebarLogoMark_loymm_159 {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  flex-shrink: 0;
}

._sidebarBrandText_loymm_168 {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.15;
}

._sidebarBrandName_loymm_175 {
  color: #6d7175;
  font-size: 10px;
  font-weight: 750;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

._sidebarProjectName_loymm_183 {
  color: #202223;
  font-size: 14px;
  font-weight: 720;
  letter-spacing: -0.01em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

._sidebarActions_loymm_193 {
  display: flex;
  align-items: center;
  flex-shrink: 0;
}

._filterField_loymm_199 {
  position: relative;
  display: block;
  margin-top: 12px;
}

._filterIcon_loymm_205 {
  position: absolute;
  inset-inline-start: 10px;
  top: 50%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  color: #8c9196;
  transform: translateY(-50%);
  pointer-events: none;
}

._filterInput_loymm_217 {
  width: 100%;
  padding: 8px 10px 8px 31px;
  border: 1px solid #d8dadd;
  border-radius: 8px;
  font-size: 12px;
  background: #fff;
  color: #202223;
  box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.02);
}

._filterInput_loymm_217:focus {
  outline: none;
  border-color: #2c6ecb;
  box-shadow:
    inset 0 1px 0 rgba(0, 0, 0, 0.02),
    0 0 0 2px rgba(44, 110, 203, 0.16);
}

._filterInput_loymm_217:focus + ._filterIcon_loymm_205,
._filterField_loymm_199:focus-within ._filterIcon_loymm_205 {
  color: #2c6ecb;
}

._filterInput_loymm_217::placeholder {
  color: #8c9196;
}

/* Status filter buttons — label + count badge */

._statusFilters_loymm_247 {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 4px;
  margin-top: 10px;
}

._statusFilterBtn_loymm_254 {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  padding: 6px 4px;
  border: 1px solid #babfc3;
  border-radius: 4px;
  background: #fff;
  font-size: 11px;
  line-height: 1.2;
  cursor: pointer;
  color: #5c5f62;
}

._statusFilterLabel_loymm_270 {
  font-size: 11px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}

._statusFilterCount_loymm_278 {
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

._statusFilterBtn_ok_loymm_284 {
  color: #008060;
}

._statusFilterBtn_error_loymm_288 {
  color: #d72c0d;
}

._statusFilterBtn_loymm_254:hover {
  background: #f6f6f7;
}

._statusFilterBtnActive_loymm_296,
._statusFilterBtnActive_loymm_296._statusFilterBtn_ok_loymm_284,
._statusFilterBtnActive_loymm_296._statusFilterBtn_error_loymm_288 {
  background: #202223;
  color: #fff;
  border-color: #202223;
}

/* Status dots next to state names */

._statusDotOk_loymm_306,
._statusDotError_loymm_307,
._statusDotUnknown_loymm_308 {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  margin-inline-end: 7px;
  vertical-align: middle;
  flex: 0 0 auto;
}

._statusDotOk_loymm_306 {
  background: #008060;
}

._statusDotError_loymm_307 {
  background: #d72c0d;
}

._statusDotUnknown_loymm_308 {
  background: #c9cccf;
}

._sidebarBody_loymm_330 {
  padding: 8px 0;
  flex: 1;
}

._sidebarEmpty_loymm_335 {
  padding: 16px 12px;
  color: #6d7175;
  font-size: 12px;
}

/* Group / route / state list ---------------------------------------- */

._group_loymm_343 {
  margin-bottom: 4px;
}

._groupLabel_loymm_347 {
  padding: 6px 12px;
  font-size: 10px;
  font-weight: 700;
  color: #6d7175;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  cursor: pointer;
  user-select: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

._groupLabel_loymm_347:hover {
  color: #202223;
  background: #f1f2f4;
}

._groupLabel_loymm_347::after {
  content: "▾";
  font-size: 16px;
  color: #8c9196;
}

._groupCollapsed_loymm_372 ._groupLabel_loymm_347::after {
  content: "▸";
}

._groupCollapsed_loymm_372 ._routeLabel_loymm_376,
._groupCollapsed_loymm_372 ._stateLink_loymm_377 {
  display: none;
}

._sidebarFolder_loymm_381 {
  margin: 0;
}

._sidebarFolderLabel_loymm_385 {
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  min-height: 24px;
  padding: 4px 8px 4px 12px;
  border: 0;
  background: transparent;
  color: #424548;
  cursor: pointer;
  font-size: 11px;
  text-align: start;
}

._sidebarFolderLabel_loymm_385:hover {
  background: #f1f2f4;
  color: #202223;
}

._sidebarSectionLabel_loymm_405 {
  min-height: 30px;
  margin-top: 8px;
  padding-block: 6px;
  border-top: 1px solid #e1e3e5;
  background: #fafbfb;
  color: #5d5d5d;
  font-size: 12px;
  font-weight: 750;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

._group_loymm_343:first-child ._sidebarSectionLabel_loymm_405 {
  margin-top: 0;
  border-top: 0;
}

/* A nested folder is a HEADING, not another renderable row. It used to share
 * the route rows' size and colour exactly, so a folder and a component read as
 * the same kind of thing and the tree looked flat. Heavier + darker + a touch
 * of tracking, with a little air above it, so the eye can find the group
 * boundaries without reading labels. */
._sidebarNestedFolderLabel_loymm_428 {
  margin-block-start: 5px;
  color: #14181b;
  font-size: 12px;
  font-weight: 650;
  letter-spacing: 0.01em;
}

/* Caret + folder glyph follow the heading's darker tone rather than the dimmer
 * renderable-icon grey, so the whole row reads as one unit. */
._sidebarNestedFolderLabel_loymm_428 ._sidebarFolderCaret_loymm_438,
._sidebarNestedFolderLabel_loymm_428 ._renderableIcon_loymm_439 {
  color: #5c5f62;
}

/* Leading disclosure caret on every folder row (section, nested, and a route
 * that also heads a folder). It marks "this row expands" without waiting for
 * the reader to notice the folder glyph, and sits in the gutter the row's
 * padding gives back -- see CARET_SLOT in ui/Sidebar.tsx. */
._sidebarFolderCaret_loymm_438 {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 12px;
  width: 12px;
  height: 12px;
  color: #8c9196;
}

._sidebarFolderText_loymm_457 {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

._routeBlock_loymm_464 {
  position: relative;
}

._routeLabel_loymm_376 {
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 5px 8px 4px 20px;
  font-size: 12px;
  color: #424548;
  min-height: 24px;
}

._renderableIcon_loymm_439 {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 12px;
  width: 12px;
  height: 12px;
  color: #8c9196;
}

._routeLabel_loymm_376:hover ._renderableIcon_loymm_439 {
  color: #6d7175;
}

._routeLabelActive_loymm_492 ._renderableIcon_loymm_439 {
  color: #2c6ecb;
}

._routeLabelText_loymm_496 {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  text-align: start;
  padding: 0;
  margin: 0;
  border: none;
  background: transparent;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: none;
}

._routeLabelText_loymm_496:hover {
  color: #005bd3;
}

/* Icon + label of a folder-heading route: a single expand/collapse control
   that fills the row so clicking anywhere on it toggles (rather than links). */
._routeToggle_loymm_519 {
  display: flex;
  align-items: center;
  gap: 4px;
  flex: 1;
  min-width: 0;
  padding: 0;
  border: 0;
  background: transparent;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-align: start;
}

/* A route that also heads a folder (its path is a prefix of other routes):
 * keep the route affordances, borrow the folder heading's weight so it still
 * reads as a container. Yields colour to the selected-row styling below. */
._routeLabelFolder_loymm_537 ._routeLabelText_loymm_496 {
  font-weight: 650;
}

._routeLabelFolder_loymm_537:not(._routeLabelActive_loymm_492):not(._routeLabelChanged_loymm_541)
  ._routeLabelText_loymm_496 {
  color: #14181b;
}

._routeLabelActive_loymm_492 {
  background: #f1f7ff;
  border-inline-start: 3px solid #2c6ecb;
  color: #003a70;
  font-weight: 700;
  margin-inline-start: -3px;
}

._routeLabelActive_loymm_492 ._routeLabelText_loymm_496 {
  color: #003a70;
}

._routeLabelPanelOpen_loymm_558:not(._routeLabelActive_loymm_492) {
  background: #f1f7ff;
}

/* Route has a CONFIRMED visual change (from the deployed pixelmatch
 * results, `magpie-vrt-results.json` — see state/vrtResults.ts). Shown
 * only on the Quick deploy preview build, where that manifest exists.
 * Subtle left-edge accent + a small dot at the right edge of the row —
 * loud enough to scan, quiet enough not to fight with the
 * `routeLabelActive` selection styling.
 *
 * `routeLabelActive` keeps its blue accent + bold text on the
 * currently selected route, even when that route is also
 * `routeLabelChanged`. The dot still appears so reviewers can see
 * the selected row is on the diff list. */
._routeLabelChanged_loymm_541:not(._routeLabelActive_loymm_492) {
  background: #fff4e5;
}
._routeLabelChanged_loymm_541:not(._routeLabelActive_loymm_492) ._routeLabelText_loymm_496 {
  color: #8a4b00;
  font-weight: 600;
}
._routeChangedDot_loymm_580 {
  flex: 0 0 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #e0901c;
  margin-inline-end: 4px;
}

/* An individual state/variant row with a confirmed visual change. Amber
 * text to match the route rollup; the leading dot (`.stateChangedDot`)
 * replaces the health status dot so the changed variant reads at a glance
 * without stacking two dots. Compound-selector + `:not(.stateLinkActive)` so
 * it outranks the later base `.stateLink` color rule (equal single-class
 * specificity would otherwise let the base win by source order) yet still
 * yields the selected row to `.stateLinkActive`, mirroring
 * `.routeLabelChanged:not(.routeLabelActive)`. */
._stateLink_loymm_377._stateLinkChanged_loymm_597:not(._stateLinkActive_loymm_592) {
  color: #8a4b00;
  font-weight: 600;
}
._stateChangedDot_loymm_590 {
  display: inline-block;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  margin-inline-end: 7px;
  vertical-align: middle;
  flex: 0 0 auto;
  background: #e0901c;
}

._routeRebuildBtn_loymm_612 {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: #6d7175;
  cursor: pointer;

  /* Hidden by default — only revealed on hover of the route row, or
   * forced visible when actively rebuilding (so the spinner stays on
   * screen even if the dev moves their pointer away). */
  opacity: 0;
  transition:
    opacity 100ms ease,
    color 100ms ease,
    background 100ms ease;
}

._routeBlock_loymm_464:hover ._routeRebuildBtn_loymm_612,
._routeRebuildBtn_loymm_612:focus-visible,
._routeRebuildBtnRunning_loymm_638 {
  opacity: 1;
}

._routeRebuildBtn_loymm_612:hover:not(:disabled) {
  background: #ebf5ff;
  color: #005bd3;
  border-color: #b4d7ff;
}

._routeRebuildBtn_loymm_612:disabled {
  cursor: not-allowed;
}

._routeRebuildBtnRunning_loymm_638 {
  color: #c25d00;
}

._routeRebuildBtnRunning_loymm_638 svg {
  animation: _magpie-route-rebuild-spin_loymm_1 0.8s linear infinite;
  transform-origin: 50% 50%;
}

@keyframes _magpie-route-rebuild-spin_loymm_1 {
  from {
    transform: rotate(0deg);
  }

  to {
    transform: rotate(360deg);
  }
}

._stateLink_loymm_377 {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 22px;
  padding: 2px 12px 2px 32px;
  font-size: 12px;
  color: #424548;
  background: transparent;
  text-decoration: none;
  border-inline-start: 4px solid transparent;
}

._stateLink_loymm_377:hover {
  background: #f1f2f4;
  color: #202223;
}

._stateLinkActive_loymm_592 {
  color: #003a70;
  background: #dff0ff;
  border-inline-start-color: #2c6ecb;
  font-weight: 700;
}

/* Main pane --------------------------------------------------------- */

._main_loymm_698 {
  /*
   * Flex column so the sticky header sits on top, then the
   * `.mainContent` row claims the rest. Without an explicit flex
   * column + `overflow: hidden`, the cell defaults to block layout
   * and `overflow: auto` — meaning a tall config panel scrolls the
   * whole right-hand pane, taking the preview iframe with it. The
   * grid cell is already height-bounded by `.layout { height: 100vh }`,
   * so all we need to do here is hand vertical extent to the body.
   */
  display: flex;
  flex-direction: column;
  min-height: 0;
  overflow: hidden;
  background: #f1f2f4;
}

._mainHeader_loymm_715 {
  position: sticky;
  z-index: 2;
  top: 0;
  padding: 4px 14px;
  border-bottom: 1px solid #d8dadd;
  font-size: 12px;
  color: #6d7175;
  display: flex;
  gap: 10px;
  align-items: center;
  background: #fbfbfc;
}

/* Electron-only: make the right-pane top bar a drag region too, so the
 * user can move the window from anywhere along the top of the app, not
 * just the sidebar's drag rail. Buttons inside opt out via no-drag. */
._mainHeaderApp_loymm_732 {
  -webkit-app-region: drag;
}

._mainHeaderApp_loymm_732 button,
._mainHeaderApp_loymm_732 input,
._mainHeaderApp_loymm_732 a {
  -webkit-app-region: no-drag;
}

._mainHeaderRouteId_loymm_742 {
  display: inline-block;
  padding: 2px 6px;
  margin: -2px -6px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  color: #202223;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: inherit;
  font-weight: 650;
  cursor: pointer;
  -webkit-app-region: no-drag;
  transition:
    background 100ms ease,
    border-color 100ms ease;
}

._mainHeaderRouteId_loymm_742:hover {
  background: #f1f2f4;
  border-color: #d8dadd;
}

._mainHeaderRouteId_loymm_742:active {
  background: #e4e5e7;
}

._mainHeaderRouteIdCopied_loymm_769,
._mainHeaderRouteIdCopied_loymm_769:hover {
  background: #e3f1df;
  border-color: #93c990;
  color: #1d6a3a;
}

/* Iframe that hosts the actual preview page. `block` + full size fills
   the main pane below the header; border:0 so it doesn't draw a frame. */
._previewFrame_loymm_778 {
  display: block;
  margin: 14px;
  width: calc(100% - 28px);
  height: calc(100vh - 45px);
  border: 0;
}

/* Split-view (main vs local) ---------------------------------------- */

._splitToggle_loymm_788 {
  padding: 4px 8px;
  border: 1px solid #babfc3;
  border-radius: 4px;
  background: #fff;
  font-size: 11px;
  cursor: pointer;
  color: #5c5f62;
}

._splitToggle_loymm_788:hover {
  background: #f6f6f7;
  color: #202223;
}

._splitToggleActive_loymm_803 {
  background: #202223;
  color: #fff;
  border-color: #202223;
}

._splitContainer_loymm_809 {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1px;
  height: calc(100vh - 35px);
  background: #d8dadd;
}

._splitPane_loymm_817 {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: calc(100vh - 48px);
  background: #f1f2f4;
  overflow: hidden;
}

/* Diff overlay ------------------------------------------------------ */

/* Pane that hosts the stacked diff overlay. Same box as .localPane. */
._diffPane_loymm_829 {
  position: relative;
  flex: 1;
  min-width: 0;
  min-height: calc(100vh - 48px);
  background: #f1f2f4;
  overflow: hidden;
}

/* Sized "card" that hosts a compare mode (diff / slide / blink). Sized via inline
 * width/height (sized presets) or the .diffStackFill child rule (fill
 * preset), mirroring .viewportFrame. */
._diffStack_loymm_839 {
  position: relative;
  flex-shrink: 0;
  background: #fff;
  box-shadow:
    2px 3px 10px 0px rgba(0, 0, 0, 0.1),
    -1px -1px 1px #fff;
}

._diffStackFill_loymm_839 {
  width: 100%;
  height: 100%;
}

/* Off-screen host for the reusable pink-diff SVG filter def. */
._diffFilterDefs_loymm_856 {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* Stable positioned wrapper around the iframe group.
 *
 * It exists so the changed-pixels CONTEXT pane can be a sibling of the group
 * with exactly the group's box: the pane has to line up with the overlay to
 * the pixel, so it cannot hang off `.compareStage` (which also holds the label
 * bar and would offset it by the bar's height), and it cannot live INSIDE the
 * group, whose filter would eat it along with everything else in there.
 *
 * Always rendered, in every mode, so base + local keep one parent for the
 * lifetime of the session — the same reason the group itself is unconditional.
 * In 2-up it is an inert flex passthrough. */
._compareStack_loymm_874 {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
}

/* The readable UI under the pink overlay: a third render of the route,
 * mounted only while the overlay is up. It sits at the bottom of the stack;
 * the filtered group above it is transparent everywhere the two compared
 * renders agree, which is exactly what lets the UI show through. */
._changedPixelsUnderlay_loymm_886 {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  background: #fff;
}

/* Diff mode: base ⊕ local run through the pink SVG filter.
 *
 * The group stacks the base (opaque, behind) and the local render blended
 * with `mix-blend-mode: difference` (opaque backing on both so transparent
 * preview pixels cancel to black where unchanged). `filter: url(#…)` then
 * turns that difference raster into solid pink where changed + transparent
 * elsewhere (see the filter def in Main.tsx), so ONLY the changed pixels
 * show, over the white .diffStack. `isolation` confines the blend to the
 * group. While the base is warming, `.diffGroupWarming` shows the plain
 * local render with no filter (the local iframe keeps a stable position so
 * it doesn't remount when the base arrives — only the classes flip). */
._diffFilterGroup_loymm_908,
._diffGroupWarming_loymm_905 {
  /* Flex-fills the stage below the label bar; the base/local iframes stack
   * absolutely inside it (inset:0). Relative + isolate keeps the
   * mix-blend-difference blend confined to this group. */
  position: relative;
  /* Above `.changedPixelsUnderlay`, so the highlight paints over the
   * readable UI rather than under it. */
  z-index: 1;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  isolation: isolate;
}

._diffFilterGroup_loymm_908 {
  filter: url("#magpie-diff-pink");
  /* Render the pink diff slightly translucent (tunable). */
  opacity: 0.9;
}

._diffFrameBase_loymm_929,
._diffFrameDiff_loymm_930,
._diffFrameContext_loymm_931 {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  background: #fff;
}

._diffFrameBase_loymm_929 {
  z-index: 0;
}

._diffFrameDiff_loymm_930 {
  z-index: 1;
  mix-blend-mode: difference;
}

/* Blink (A/B) mode: base and local stacked at identical geometry, exactly
 * one visible. Same absolute box as the slide frames — a change must not
 * move between the two renders for any reason other than the change itself. */
._blinkFrameBase_loymm_953,
._blinkFrameLocal_loymm_954 {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  border: 0;
  background: #fff;
}

._blinkFrameBase_loymm_953 {
  z-index: 0;
}

._blinkFrameLocal_loymm_954 {
  z-index: 1;
}

/* Hiding is `opacity: 0`, deliberately NOT `display: none` / `visibility:
 * hidden`: an iframe that stops being rendered is relaid out (and can be
 * re-rasterized) when it comes back, which costs a frame, resets the scroll
 * position, and de-registers the two renders — the exact thing blink mode
 * exists to keep pixel-perfect. An opacity flip is compositor-only, so the
 * swap lands in the same frame as the click. `pointer-events` follows so
 * scrolling and hovering reach whichever pane is actually on screen. */
._blinkFrameHidden_loymm_979 {
  opacity: 0;
  pointer-events: none;
}

._splitFrame_loymm_984 {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}

._splitPlaceholder_loymm_991 {
  display: grid;
  place-items: center;
  color: #8c9196;
  font-size: 12px;
}

._diffBadge_loymm_998 {
  display: inline-block;
  padding: 2px 5px;
  border: 1px solid #babfc3;
  border-radius: 3px;
  font-size: 10px;
  font-weight: 650;
  background: #fff;
  color: #5c5f62;
  margin-inline-start: 4px;
}

/* Splash / error ---------------------------------------------------- */

._splash_loymm_1012 {
  display: grid;
  place-items: center;
  width: 100%;
  min-height: 100vh;
  color: #6d7175;
}

._splashBody_loymm_1020 {
  text-align: center;
}

._splashTitle_loymm_1024 {
  margin: 0;
  font-weight: 600;
  color: #202223;
  font-size: 16px;
}

._splashDescription_loymm_1031 {
  margin-top: 8px;
  font-size: 13px;
}

._mainContent_loymm_701 {
  position: relative;
  display: flex;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

._mainContentCompare_loymm_1044 {
  gap: 1px;
  background: #d8dadd;
}

/* ── Unified compare stage ──────────────────────────────────────────
 * One stable container that hosts BOTH preview iframes across every mode
 * (single / side-by-side / diff / slide / blink). Switching modes only flips the
 * modifier classes below — the iframe elements keep a stable key + parent
 * chain, so React never remounts them (no pane reload on a mode switch).
 *
 * `.compareStage` is the outer flex cell. Inside it, ONE inner group
 * (`.compareRow` in non-overlay, `.diffFilterGroup`/`.diffGroupWarming` in
 * overlay) holds both iframes as direct siblings:
 *   SINGLE:        `.compareRow` fills; only the local iframe is present.
 *   SIDE-BY-SIDE:  `.compareRow` is a flex row of two checker frames.
 *   OVERLAY:       the group is an isolated stacking context; iframes are
 *                  absolutely stacked (base behind, local in front) so the
 *                  mix-blend-difference filter composes across BOTH. */
/* Stable scroll viewport wrapping the stage. Transparent flex passthrough by
 * default (the stage's own flex:1 fills it identically to before). */
._compareViewport_loymm_1065 {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  min-height: 0;
}

/* Overlay + SIZED viewport: scroll + center the oversized card so a viewport
 * larger than the pane reaches its rest via scrollbars instead of being
 * clipped by `.mainContent`'s overflow:hidden (restores the old ViewportCanvas
 * behaviour, which the stage lost when it became the sized card).
 *
 * ── Why `safe center` and not `center` ───────────────────────────────
 * A plain `center` in a scroll container is a data-loss bug the moment the
 * item is WIDER than the container: the overflow is split EQUALLY across
 * both sides, and the start-side half is unreachable, because `scrollLeft`
 * bottoms out at 0 and there is no scrollable region before the origin. So
 * picking "Desktop (1280×800)" on a laptop pane narrower than 1280 hid the
 * preview's left edge under the sidebar AND ran its right edge off the pane,
 * with no way to scroll to either — the two halves of the same bug.
 *
 * `safe center` centers while the card fits and silently degrades to start
 * alignment the instant it doesn't, which puts ALL the overflow on the end
 * side where the scrollbar can reach it. Nothing measures anything: the
 * fallback is a property value the layout engine resolves per frame.
 *
 * The plain `center` above it is the fallback for engines without `safe`
 * (they drop the whole second declaration as invalid). Losing centering is
 * the right way to fail here; losing the left edge is not.
 *
 * Same reasoning applies to `.compareRow` and `.viewportCanvas` below. */
._compareViewportScroll_loymm_1096 {
  overflow: auto;
  align-items: flex-start;
  justify-content: center;
  justify-content: safe center;
}

._compareStage_loymm_867 {
  position: relative;
  display: flex;
  /* Column so the flow label bar (.compareLabelBar) sits ABOVE the iframe
   * group in every mode instead of the labels overlaying the preview. */
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  min-height: calc(100vh - 48px);
  box-sizing: border-box;

  /* The label bar + iframe(s) share ONE padded checker surface, so the bar
   * sits flush on top of the preview with the SAME gutter around the whole
   * block as the iframe already has on its other sides (moved here from
   * `.compareRow`). Overlay modes reset this — they are the fill/sized card
   * themselves (see `.compareStageOverlay`). */
  padding: 5px;
  background: #f1f2f4;
  background-image:
    linear-gradient(45deg, #e4e5e7 25%, transparent 25%),
    linear-gradient(-45deg, #e4e5e7 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, #e4e5e7 75%),
    linear-gradient(-45deg, transparent 75%, #e4e5e7 75%);
  background-size: 16px 16px;
  background-position:
    0 0,
    0 8px,
    8px -8px,
    -8px 0;
}

._compareStageHidden_loymm_1134 {
  display: none;
}

/* Overlay (diff/slide/blink): the stage is the sized "card" hosting the label bar +
 * the two absolutely-stacked iframes (mirrors the old .diffStack). Kept a
 * column flex (like the base stage) so the bar stacks above the iframe group;
 * the group itself carries the isolate stacking context + overflow clip. */
._compareStageOverlay_loymm_1118 {
  flex: 1 1 auto;
  /* Reset the base stage's padded checker: the overlay IS the fill/sized card
   * (white), with the bar attached at its top and the iframes filling below.
   * `background: #fff` also clears the inherited checker background-image. */
  padding: 0;
  background: #fff;
}

/* Compare-mode label bar: a flow strip above the preview. In side-by-side /
 * slide it holds one small bar PER FRAME (base + local); in the diff overlay it
 * holds a single full-width caption. The bar itself is transparent — each child
 * paints its own light-gray strip, so the 8px inter-pane gap shows the checker
 * between them and the labels read as a bar attached to each frame rather than
 * one strip spanning both. Flow element — never overlaps the iframe(s). */
._compareLabelBar_loymm_1106 {
  display: flex;
  align-items: stretch;
  /* Same 8px as the inter-pane gap in `.compareRow`, so the gap between the
   * per-frame bars lines up with the gap between the panes below. */
  gap: 8px;
  flex: 0 0 auto;
  background: transparent;
  font-size: 10px;
  font-weight: 650;
  line-height: 1.4;
}

/* Each label is its OWN bar sitting directly on top of its frame: it claims an
 * equal share of the row (matching the fill-mode 50/50 panes) and paints the
 * light-gray strip + bottom divider itself. `.compareLabelDiff` spans the full
 * width because the diff overlay stacks the two frames into a single card. */
._compareLabelMain_loymm_1174,
._compareLabelLocal_loymm_1175,
._compareLabelDiff_loymm_1172 {
  flex: 1 1 0;
  min-width: 0;
  padding: 4px 8px;
  background: #f1f2f4;
  border-bottom: 1px solid #d8dadd;
}

/* The caption sticks to the visible edge of its own column while that column
 * scrolls. Locking the label strip to its frame (`.compareColumnSized`) fixes
 * WHICH pane a strip belongs to, but at a sized preset the strip is as wide as
 * the frame, so its text scrolls away and the two strips are then
 * indistinguishable — they share one background and differ only in text
 * colour. Sticky is clamped to its containing block, which is the column, so
 * the caption stays readable without ever drifting over the neighbouring
 * frame. Inert when nothing scrolls (the auto preset), so it needs no
 * mode-specific class. */
._compareLabelText_loymm_1193 {
  position: sticky;
  inset-inline-start: 0;
  display: inline-block;
}

._compareLabelMain_loymm_1174 {
  color: #916a00;
}

._compareLabelLocal_loymm_1175 {
  color: #008060;
}

._compareLabelDiff_loymm_1172 {
  color: #5c3fbf;
}

/* Blink mode's bar IS its control: one full-width toggle, painted in the
 * colour of the render it is currently showing (the same green/amber the
 * side-by-side bars use for local/base). Solid and 12px rather than the 10px
 * caption the other modes use, because in blink mode “which one is this?” is
 * the question the whole mode hangs on — it has to be answerable from the
 * corner of the eye, mid-swap. Both fills clear 4.5:1 against white text. */
._blinkToggle_loymm_1217 {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 0;
  min-width: 0;
  appearance: none;
  padding: 6px 10px;
  border: 0;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  line-height: 1.4;
  color: #fff;
  text-align: start;
  cursor: pointer;
}

._blinkToggleLocal_loymm_1235 {
  background: #008060;
}

._blinkToggleLocal_loymm_1235:hover {
  background: #006e52;
}

._blinkToggleBase_loymm_1243 {
  background: #916a00;
}

._blinkToggleBase_loymm_1243:hover {
  background: #7a5900;
}

._blinkGlyph_loymm_1251 {
  font-size: 13px;
}

._blinkHint_loymm_1255 {
  margin-inline-start: auto;
  padding-inline-start: 8px;
  font-size: 10px;
  font-weight: 550;
  opacity: 0.85;
}

/* The iframe stack carries the requested viewport size (see Main.tsx: sizing
 * the CARD would spend the label bar's height out of the preview's), so it
 * must not also stretch. */
._compareStackSized_loymm_1266 {
  flex: 0 0 auto;
}

._compareStageSized_loymm_1270 {
  flex: 0 0 auto;
  /* The scroll viewport (.compareViewportScroll) centers + scrolls; the card
   * just needs its own min-height reset (else the base .compareStage
   * min-height:100vh would stretch it) + a small gutter for the shadow. */
  min-height: 0;
  margin: 5px;
  box-shadow:
    2px 3px 10px 0px rgba(0, 0, 0, 0.1),
    -1px -1px 1px #fff;
}

/* Non-overlay inner group: transparent flex row (single fill OR side-by-side)
 * that centers its iframe(s). The padded checker surface now lives on
 * `.compareStage` so the label bar + iframe(s) share one gutter; this row just
 * lays the iframes out over that surface. The inter-pane `gap` is the
 * separator letting the stage's checker show between the two panes. */
._compareRow_loymm_1056 {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  gap: 8px;
  align-items: flex-start;
  /* `safe center` — see `.compareViewportScroll` above for why a plain
   * `center` makes the start-side overflow unreachable. */
  justify-content: center;
  justify-content: safe center;
  overflow: auto;
  background: transparent;
}

/* Non-overlay + SIZED viewport: the STAGE owns the horizontal scroll, so the
 * label bar travels with the frames it names.
 *
 * `.compareLabelBar` is a SIBLING of `.compareRow`, not a child of it. While a
 * sized frame shrank to the pane that was invisible — both tracks were pane
 * width and the 50/50 labels sat exactly over the 50/50 frames. Now that a
 * sized frame keeps its width, a row scrolling on its own would slide the
 * frames under a label bar parked at pane width, and that is worse than a
 * cosmetic misalignment: the strip above the visible frame can read "local"
 * while you are looking at the base render, on the exact oversized-preset
 * workflow this is all for. Scrolling ONE box that contains both keeps every
 * label over its own frame at every scroll position — no measuring, nothing to
 * synchronise, no way for the two to drift apart. */
._compareStageScroll_loymm_1314 {
  overflow: auto;
}

/* Both tracks size to their content (the fixed-width columns plus the shared
 * 8px gap), so they are the same width by construction, and both are laid out
 * by the same alignment. `min-width: 100%` keeps them filling the pane when
 * the content is NARROWER than it (a 390px mobile preset in an 800px pane),
 * where the centring is what has to agree — otherwise the row centres its
 * frames while the bar left-aligns its labels, and they part company in the
 * other direction.
 *
 * Note the row is a GRANDCHILD: `.compareStack` sits between it and the
 * stage. Writing these as `> .compareRow` silently stopped matching when that
 * wrapper was introduced, which handed the row back its own scrollbar and
 * reinstated the exact label/frame decoupling this section exists to prevent
 * — a bug that hides, because the rules still applied to the label bar, so
 * only one of the two tracks changed behaviour. */
._compareStageScroll_loymm_1314 > ._compareLabelBar_loymm_1106,
._compareStageScroll_loymm_1314 > ._compareStack_loymm_874,
._compareStageScroll_loymm_1314 > ._compareStack_loymm_874 > ._compareRow_loymm_1056 {
  width: max-content;
  min-width: 100%;
  justify-content: center;
  justify-content: safe center;
}

/* Neither the wrapper nor the row may keep a scroller inside the stage's, or
 * the two tracks decouple again and we are back to the bug. */
._compareStageScroll_loymm_1314 > ._compareStack_loymm_874,
._compareStageScroll_loymm_1314 > ._compareStack_loymm_874 > ._compareRow_loymm_1056 {
  overflow: visible;
}

/* Moving the scroll up to the stage moved BOTH axes, not just the one this is
 * for. A preset taller than the pane would then carry the label bar out of the
 * scrollport on the way down and leave the lower half of both previews with no
 * pane identity at all — which the row-level scroller never did, because the
 * bar sat outside it.
 *
 * Pin it to the block start. Sticky only takes the axis it is given an inset
 * for, so the bar still travels horizontally in lockstep with the frames (the
 * whole point of the shared scroller) while staying put vertically.
 *
 * It needs its own opaque fill to do that: the bar is transparent by design,
 * so the checker shows through the 8px inter-pane gap, but a transparent
 * sticky bar shows the previews sliding underneath instead. `#f1f2f4` is both
 * the checker's base colour and the exact fill each label already paints, so
 * the strip stays visually continuous — the gap goes flat rather than
 * checkered, over 8px. */
._compareStageScroll_loymm_1314 > ._compareLabelBar_loymm_1106 {
  position: sticky;
  top: 0;
  z-index: 1;
  background: #f1f2f4;
}

/* A column sitting over (or beside) a fixed-width frame takes that frame's
 * exact width instead of an equal share of the pane. Specificity matches
 * `.compareRow > .splitPlaceholder` and beats `.compareLabelMain`'s
 * `flex: 1 1 0`, so no `!important` is needed.
 *
 * `box-sizing` is not incidental here. There is no global border-box reset in
 * this stylesheet, so a label left on the default content-box would add its
 * own `padding: 4px 8px` on top of the width it was handed and sit 16px wider
 * than the frame underneath it — a misalignment that grows by a column with
 * every pane. Border-box makes the width it is given the OUTER width, which
 * is the number the frame uses. */
._compareRow_loymm_1056 > ._compareColumnSized_loymm_1185,
._compareLabelBar_loymm_1106 > ._compareColumnSized_loymm_1185 {
  flex: 0 0 auto;
  box-sizing: border-box;
}

/* Mobile is the exception, because the frame's gutter is a 16px border on a
 * content-box element (`.compareColumnMobile`), making its outer width
 * width+32. A column matches that by taking the same treatment rather than
 * restating 32 anywhere: same class, same border, content-box — and no inline
 * padding, so the two totals agree exactly and the caption starts at the
 * rendered page's own edge instead of 8px inside it. */
._compareRow_loymm_1056 > ._compareColumnSized_loymm_1185._compareColumnMobile_loymm_1389,
._compareLabelBar_loymm_1106 > ._compareColumnSized_loymm_1185._compareColumnMobile_loymm_1389 {
  box-sizing: content-box;
  padding-inline: 0;
}

/* Each iframe is a flex item; fill preset stretches it to the row's share,
 * a sized preset keeps its inline width/height and top-centers it.
 *
 * `flex-shrink: 0` is load-bearing. This rule used to say `flex: 0 1 auto`,
 * whose shorthand re-enabled the shrinking `.viewportFrame` had deliberately
 * turned off — so on a pane narrower than the chosen preset the iframe
 * quietly shrank to the pane instead of overflowing. That is the worst
 * available outcome for a viewport picker: the preview renders at a width
 * nobody asked for while the header still says 1280, so you are looking at
 * the wrong breakpoint and nothing tells you. Keeping the requested width
 * and letting the row scroll is the honest failure, and it now matches the
 * sized-overlay path (`.compareStageSized`, which is already `flex: 0 0
 * auto`) instead of contradicting it. `.compareFrameFill` still overrides
 * this for the fill preset, where stretching IS the requested behaviour. */
._compareRow_loymm_1056 > ._viewportFrame_loymm_840 {
  flex: 0 0 auto;
  min-width: 0;
  align-self: stretch;
}

/* Fill preset: the frame(s) claim equal shares of the row and full height. */
._compareFrameFill_loymm_1412 {
  flex: 1 1 0 !important;
  width: 100%;
  height: 100%;
}

/* The side-by-side warming placeholder occupies the base column. */
._compareRow_loymm_1056 > ._splitPlaceholder_loymm_991 {
  flex: 1 1 0;
  align-self: stretch;
}

/* Mobile preset gutter (single/side-by-side): 16px of flush white on either
 * side of the rendered page, mirroring admin-web's --p-space-400.
 * content-box so the border adds outside the chosen mobile content width. */
._compareColumnMobile_loymm_1389:not(._compareFrameFill_loymm_1412) {
  box-sizing: content-box;
  border-inline: 16px solid #f1f2f4;
}

/* Non-blocking "an HMR update is being applied" badge. Floats over the
 * top-center of the preview area (doesn't intercept pointer events, doesn't
 * reflow the panes) so the dev sees that a save is landing during the
 * few-second Fast-Refresh / re-bundle window. Fades in; auto-removed when
 * the update settles (see hmrBusy in Main.tsx). */
._hmrBadge_loymm_1446 {
  position: absolute;
  top: 60px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 20;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 12px;
  border-radius: 999px;
  background: rgba(32, 34, 35, 0.9);
  color: #fff;
  font-size: 12px;
  font-weight: 500;
  line-height: 1;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
  pointer-events: none;
  animation: _magpie-hmr-badge-in_loymm_1 0.12s ease-out;
}

._hmrBadge_loymm_1446 svg {
  width: 14px;
  height: 14px;
  animation: _magpie-route-rebuild-spin_loymm_1 0.8s linear infinite;
  transform-origin: 50% 50%;
}

@keyframes _magpie-hmr-badge-in_loymm_1 {
  from {
    opacity: 0;
    transform: translateX(-50%) translateY(-4px);
  }

  to {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }
}

._localPane_loymm_828 {
  flex: 1;
  min-width: 0;

  /* Force the pane to claim the full viewport minus the main header
   * (48px). Without this, the flex chain doesn't give the viewport
   * canvas a definite vertical extent and the checker collapses to
   * less than full height in sized presets. */
  min-height: calc(100vh - 48px);
  background: #f1f2f4;
}

._mainContent_loymm_701 ._previewFrame_loymm_778 {
  flex: 1;
}

._configToggle_loymm_1502 {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  min-height: 28px;
  padding: 4px 8px;
  border: 1px solid transparent;
  border-radius: 4px;
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  color: #6d7175;
  line-height: 1;
}

._configToggle_loymm_1502:hover:not(:disabled) {
  background: #f1f2f4;
  border-color: #babfc3;
  color: #202223;
}

._configToggle_loymm_1502:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

._configToggleActive_loymm_1529 {
  background: #202223;
  color: #fff;
  border-color: #202223;
}

._configToggleBusy_loymm_1535 {
  cursor: wait;
}

/* Right-aligned action cluster on the preview pane header. Pushes
 * Compare / Open-in-editor / Screenshot / Config to the far right of
 * the header, separated from the route id + state name on the left. */
._mainHeaderActions_loymm_1542 {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-inline-start: auto;
}

/* Viewport size picker (preset dropdown + optional WxH inputs). Sits
 * inline at the start of mainHeaderActions so it's the first "how is the
 * preview presented?" control before Compare / Screenshot / Config. */
._viewportPicker_loymm_1552 {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding-inline-end: 4px;
  margin-inline-end: 2px;
  border-inline-end: 1px solid #e1e3e5;
}

._storybookGlobalsPicker_loymm_1561 {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding-inline-end: 4px;
  margin-inline-end: 2px;
  border-inline-end: 1px solid #e1e3e5;
}

._storybookGlobalControl_loymm_1570 {
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

._storybookGlobalLabel_loymm_1576 {
  color: #6d7175;
  font-size: 11px;
  font-weight: 650;
}

._storybookGlobalSelect_loymm_1582 {
  min-width: 118px;
}

._viewportSelect_loymm_1586 {
  height: 26px;
  padding: 0 6px;
  border: 1px solid #c9cccf;
  border-radius: 4px;
  background: #fff;
  color: #202223;
  font: inherit;
  font-size: 11px;
  cursor: pointer;

  /* Header has `-webkit-app-region: drag` for the Electron title bar.
   * Without `no-drag` here the select swallows clicks as window-drag
   * gestures and never opens. Same fix used by .configToggle and .a. */
  -webkit-app-region: no-drag;
}

._viewportSelect_loymm_1586:hover {
  border-color: #8c9196;
}

/* The changed-pixels switch. Labelled rather than an icon: it changes what
 * the pixels on screen MEAN, which is not a thing to leave to a glyph. The
 * swatch is the colour the overlay actually paints, so the control names its
 * own output. */
._changedPixelsToggle_loymm_1611 {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 26px;
  padding: 0 8px;
  border: 1px solid #c9cccf;
  border-radius: 4px;
  background: #fff;
  color: #202223;
  font: inherit;
  font-size: 11px;
  font-weight: 550;
  cursor: pointer;

  /* Header has `-webkit-app-region: drag` for the Electron title bar; without
   * `no-drag` the button swallows clicks as window-drag gestures. */
  -webkit-app-region: no-drag;
}

._changedPixelsToggle_loymm_1611:hover:not(:disabled) {
  border-color: #8c9196;
}

._changedPixelsToggle_loymm_1611:disabled {
  color: #8c9196;
  cursor: not-allowed;
  opacity: 0.7;
}

._changedPixelsToggleActive_loymm_1641 {
  border-color: #ff1e8c;
  background: #fff0f7;
  color: #8a0f4d;
}

._changedPixelsSwatch_loymm_1647 {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  background: #ff1e8c;
}

._viewportCustom_loymm_1654 {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

._viewportInput_loymm_1660 {
  width: 56px;
  height: 26px;
  padding: 0 6px;
  border: 1px solid #c9cccf;
  border-radius: 4px;
  background: #fff;
  color: #202223;
  font: inherit;
  font-size: 11px;
  text-align: center;
  -webkit-app-region: no-drag;
}

._viewportInput_loymm_1660::-webkit-outer-spin-button,
._viewportInput_loymm_1660::-webkit-inner-spin-button {
  appearance: none;
  margin: 0;
}

._viewportInput_loymm_1660[type="number"] {
  appearance: textfield;
  appearance: textfield;
}

._viewportTimes_loymm_1685 {
  color: #8c9196;
  font-size: 10px;
}

/* Canvas around the iframe when a fixed viewport is selected. Centers
 * the iframe and provides scrollbars when the chosen size is bigger
 * than the available pane (so a 1280-wide "desktop" preview still
 * works on a narrow Electron window). The iframe itself has its own
 * inline width/height; the canvas only handles layout + chrome. */

/* Canvas around the iframe. Always rendered (even in fill mode) so the
 * wrapper's DOM structure is stable across viewport switches — React
 * keeps the same iframe instance, no contentWindow remount, no need
 * to re-handshake/re-send a navigate postMessage. */
._viewportCanvas_loymm_1095 {
  display: flex;
  align-items: flex-start;
  /* `safe center` — see `.compareViewportScroll` for why a plain `center`
   * puts half the overflow somewhere no scrollbar can reach. */
  justify-content: center;
  justify-content: safe center;
  width: 100%;
  height: 100%;
  padding: 5px;
  overflow: auto;
  background: #f1f2f4;
  background-image:
    linear-gradient(45deg, #e4e5e7 25%, transparent 25%),
    linear-gradient(-45deg, #e4e5e7 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, #e4e5e7 75%),
    linear-gradient(-45deg, transparent 75%, #e4e5e7 75%);
  background-size: 16px 16px;
  background-position:
    0 0,
    0 8px,
    8px -8px,
    -8px 0;
}

/* The single iframe class used inside a viewport canvas. No intrinsic
 * size — sizing comes from inline width/height (sized presets) or from
 * the .viewportCanvasFill child rule below (fill preset). */
._viewportFrame_loymm_840 {
  display: block;
  flex-shrink: 0;
  margin: 0;
  border: 0;
  background: #f1f2f4;
  box-shadow:
    2px 3px 10px 0px rgba(0, 0, 0, 0.1),
    -1px -1px 1px #fff;

  /* Border-box so the 1px border (added by `.viewportCanvas > .viewportFrame`)
   * counts inside `height: 100%` instead of overflowing it by 2px. */
  box-sizing: border-box;
}

/* No bezel — the iframe sits flat on the canvas surface. Removed the
 * hairline border + drop shadow + radius so previewed pages don't get
 * a fake "device frame" treatment. The canvas padding still keeps the
 * checker visible around the iframe. */

/* Fill mode: keep the same padded canvas + checker frame as sized
 * presets, but switch to `display: block` so the iframe can resolve
 * `width: 100%; height: 100%` against the canvas's content box. With
 * the default `display: flex; align-items: flex-start`, the iframe
 * would shrink to its intrinsic size (~150px tall) instead of
 * stretching. Also drop overflow:auto so we don't get a phantom
 * scrollbar from sub-pixel rounding when the iframe is exactly
 * canvas-content-box sized. */
._viewportCanvasFill_loymm_1727 {
  display: block;
  overflow: hidden;
}

._viewportCanvasFill_loymm_1727 > ._viewportFrame_loymm_840 {
  width: 100%;
  height: 100%;
}

._viewportCanvasLocal_loymm_1766 {
  /* No-op variant: kept for future per-pane tweaks. */
}

._viewportCanvasSplit_loymm_1770 {
  /* In split-view each pane is half-width — use a smaller padding so
   * the sized iframe gets more room before scrollbars appear. */
  padding: 8px;
}

/* Mobile preset: 16px of white-space on either side of the rendered
 * content, mirroring admin-web's --p-space-400 mobile page gutter.
 * Implemented with inline borders on the iframe itself + content-box
 * sizing so:
 *   - the border adds 32px to the iframe's outer inline dimension,
 *   - the iframe's CONTENT area (where admin-web actually renders)
 *     stays at the chosen mobile width (e.g. 390px),
 *   - 16px of solid white shows immediately left/right of the rendered
 *     page — not centered far away with canvas padding, but flush
 *     against the page edges, which matches what real admin-web shows.
 *
 * `box-sizing: content-box` is scoped to mobile so it doesn't conflict
 * with fill mode's `box-sizing: border-box`. */
._viewportCanvasMobile_loymm_1789:not(._viewportCanvasFill_loymm_1727) > ._viewportFrame_loymm_840 {
  box-sizing: content-box;
  border-inline: 16px solid #f1f2f4;
}

/* Collapsible sidebar */
._sidebarCollapsed_loymm_1795 {
  grid-template-columns: 0px 1fr;
}
._sidebarCollapsed_loymm_1795[data-status-open="true"] {
  grid-template-columns: 0px 360px 1fr;
}
._sidebarCollapsed_loymm_1795 ._sidebar_loymm_66 {
  overflow: hidden;
  width: 0;
  min-width: 0;
  border: none;
  padding: 0;
}
._sidebarToggle_loymm_1808 {
  position: fixed;
  z-index: 10;
  top: 8px;
  left: 8px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid #d8dadd;
  border-radius: 6px;
  background: #fbfbfc;
  color: #5c5f62;
  cursor: pointer;
  font-size: 14px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
._sidebarToggle_loymm_1808:hover {
  background: #f1f2f4;
  color: #202223;
  border-color: #babfc3;
}
/* Dev-tool chrome; intentionally uses raw hex + px so we don't depend on Polaris tokens. */
/* stylelint-disable color-no-hex -- dev tool chrome, no tokens. */
/* stylelint-disable polaris/border/declaration-property-unit-disallowed-list -- dev tool chrome, no tokens. */
/* stylelint-disable polaris/space/declaration-property-unit-disallowed-list -- dev tool chrome, no tokens. */
/* stylelint-disable polaris/typography/declaration-property-unit-disallowed-list -- dev tool chrome, no tokens. */
/* stylelint-disable polaris/typography/polaris/declaration-property-value-disallowed-list -- dev tool chrome, no tokens. */
/* stylelint-disable selector-max-specificity -- compound state selectors needed for hover/active overrides. */

._panel_3e0pb_9 {
  width: 300px;
  min-width: 300px;
  background: #fbfbfc;
  border-inline-start: 1px solid #d8dadd;
  display: flex;
  flex-direction: column;
  min-height: 0;
  max-height: 100%;
  overflow: hidden;
}

._panelHeader_3e0pb_21 {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid #d8dadd;
  background: #fbfbfc;
}

._panelTitle_3e0pb_31 {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.7px;
  color: #5c5f62;
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

._resetButton_3e0pb_42 {
  font-size: 11px;
  padding: 3px 8px;
  border: 1px solid #d8dadd;
  border-radius: 4px;
  background: #fff;
  color: #5c5f62;
  cursor: pointer;
}

._resetButton_3e0pb_42:hover:not(:disabled) {
  background: #f1f2f4;
  color: #202223;
}

._resetButton_3e0pb_42:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

._panelBody_3e0pb_62 {
  overflow-y: auto;
  flex: 1;
  padding: 0;
}

._note_3e0pb_68 {
  padding: 10px 12px;
  font-size: 11px;
  color: #6d7175;
  line-height: 1.4;
}

._warnNote_3e0pb_75 {
  padding: 8px 12px;
  margin: 8px;
  font-size: 11px;
  color: #8a6116;
  background: #fff6e6;
  border: 1px solid #ffe2a8;
  border-radius: 6px;
  line-height: 1.4;
}

._table_3e0pb_86 {
  display: flex;
  flex-direction: column;
}

._group_3e0pb_91 {
  display: block;
}

._categoryHeader_3e0pb_95 {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 6px 12px;
  font-size: 10px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: #5c5f62;
  background: #f1f2f4;
  border-top: 1px solid #d8dadd;
  border-bottom: 1px solid #d8dadd;
}

._subcategoryHeader_3e0pb_110 {
  padding: 5px 12px 5px 20px;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.3px;
  color: #6d7175;
  background: #f8f8f9;
  border-bottom: 1px solid #ededed;
}

._row_3e0pb_120 {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: 8px;
  align-items: start;
  padding: 8px 12px;
  border-bottom: 1px solid #ededed;
}

._rowEdited_3e0pb_129 {
  background: #f2f7ff;
}

._label_3e0pb_133 {
  font-size: 12px;
  font-weight: 600;
  color: #202223;
  padding-top: 4px;
  overflow-wrap: anywhere;
}

._labelMeta_3e0pb_141 {
  display: block;
  font-size: 9px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  color: #9b9fa3;
  margin-top: 2px;
}

._field_3e0pb_151 {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

._input_3e0pb_158,
._select_3e0pb_159,
._textarea_3e0pb_160 {
  width: 100%;
  box-sizing: border-box;
  font-size: 12px;
  padding: 4px 6px;
  border: 1px solid #c9cccf;
  border-radius: 4px;
  background: #fff;
  color: #202223;
  font-family: inherit;
}

._textarea_3e0pb_160 {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  min-height: 48px;
  resize: vertical;
}

._textareaError_3e0pb_179 {
  border-color: #d82c0d;
}

._checkboxRow_3e0pb_183 {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding-top: 4px;
}

._radioGroup_3e0pb_190 {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
}

._radioGroupInline_3e0pb_196 {
  flex-wrap: nowrap;
}

._radioLabel_3e0pb_200 {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  font-size: 12px;
  color: #202223;
}

._colorRow_3e0pb_208 {
  display: flex;
  align-items: center;
  gap: 6px;
}

._colorSwatch_3e0pb_214 {
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid #c9cccf;
  border-radius: 4px;
  background: #fff;
  cursor: pointer;
}

._rangeRow_3e0pb_224 {
  display: flex;
  align-items: center;
  gap: 8px;
}

._rangeValue_3e0pb_230 {
  font-size: 11px;
  color: #6d7175;
  min-width: 32px;
  text-align: end;
}

._none_3e0pb_237 {
  font-size: 12px;
  color: #9b9fa3;
  padding-top: 4px;
}

._errorMsg_3e0pb_243 {
  font-size: 10px;
  color: #d82c0d;
}

._empty_3e0pb_248 {
  padding: 24px 16px;
  text-align: center;
  color: #9b9fa3;
  font-size: 12px;
  line-height: 1.5;
}
