/* ===========================================================================
 * Lam Research OA Mapping Tool — click-dummy design system
 *
 * The ONLY place in this project that may contain a literal colour value.
 * Every token below is a translation of a Qt token; the provenance is named
 * on each line:
 *
 *   theme.py          src/presentation/theme.py            (the constants)
 *   app.qss           src/presentation/styles/app.qss      (the 144 rules)
 *   §11               docs/ui-inventory/05-design-system-und-sprache.md §11
 *   measured live-NN  docs/ui-inventory/bilder/live-NN-*.png, pixel-measured
 *
 * Rules for anyone adding to this file — see also click-dummy/README.md:
 *   1. No hex value outside :root. Ever. Add a token instead.
 *   2. No value that is not either a Qt token, a QSS rule value, or a
 *      measurement of a reference screenshot. Nothing is invented here except
 *      the three things Qt cannot express (shadow / transition / z-index),
 *      which are marked CSS-ONLY.
 *   3. This file is owned by one window. A page that needs a class that does
 *      not exist here adds it to its own assets/page-<slug>.css and reports it
 *      for promotion.
 * ======================================================================== */

:root {
  /* --- Primary palette — theme.py, §11.1 ------------------------------- */
  --color-midnight:     #242437;   /* MIDNIGHT     — all text, icons, logo   */
  --color-mint:         #6ce3c6;   /* MINT         — accent, selection, bar  */
  --color-slate:        #6a7885;   /* SLATE        — secondary text, borders */
  --color-sand:         #e6e3dc;   /* SAND         — cards, hover, breadcrumb*/
  --color-sand-light:   #f0eeea;   /* SAND_LIGHT   — THE ground surface      */
  --color-sand-dark:    #dcd8cf;   /* SAND_DARK    — checked/disabled/track  */
  --color-white:        #ffffff;   /* WHITE                                  */

  /* --- Secondary palette and greys — theme.py, §11.1 ------------------- */
  --color-border:       #d8d4cb;   /* BORDER       — every divider (25 uses) */
  --color-text-muted:   #565664;   /* TEXT_MUTED   — card title only         */
  --color-sand-grey:    #9a958a;   /* SAND_GREY    — the disabled tone       */
  --color-dark-green:   #20A785;   /* DARK_GREEN   — primary fill, focus     */
  --color-orange:       #E89945;   /* ORANGE       — EDGE ONLY, never text
                                      (@orange on sand measures 2.00:1)      */
  --color-burnt-orange: #C74F26;   /* BURNT_ORANGE — error / cancellable     */
  --color-highlight:    #e53e3e;   /* HIGHLIGHT    — canvas percent overlay
                                      and cell grid ONLY. 1.11:1 against
                                      --color-burnt-orange: the two reds are
                                      indistinguishable by eye and have
                                      completely different jobs. Never swap.  */
  /* theme.YELLOW #F2C22E is deliberately absent: §11.1 — the app never uses
     it. Add it only when a screen genuinely needs it.                       */

  /* --- Typography — theme.py, §11.2 ------------------------------------ */
  --font-body:    Arial, Helvetica, sans-serif;            /* FONT_SANS      */
  --font-heading: Georgia, "Times New Roman", serif;       /* FONT_SERIF     */
  --font-mono:    Consolas, Menlo, "Courier New", monospace; /* FONT_MONO    */

  /* NOTE — --font-heading is defined and USED NOWHERE, on purpose.
     Georgia reaches no pixel of the shipped application: its only two QSS
     rules are QLabel#heading (the Heading component is never instantiated)
     and QGroupBox#card::title (Qt does not evaluate font properties in a
     ::title subcontrol — measured twice: ink width 130px, Georgia 20 would
     be 189, Arial 14 is 131). The click-dummy mirrors the application, so
     the token exists and the face is never applied. Do not "fix" this. */

  --fs-heading: 20px;   /* FONT_SIZE_HEADING                                 */
  --fs-base:    14px;   /* FONT_SIZE_BODY   — QWidget base                   */
  --fs-action:  13px;   /* FONT_SIZE_ACTION — buttons, action cards          */
  --fs-sm:      12px;   /* FONT_SIZE_CAPTION                                 */

  --fw-regular:  400;
  --fw-medium:   500;   /* #formFieldLabel, #metaValue, #layerName           */
  --fw-semibold: 600;   /* #sectionHeader, #projectName, #breadcrumbPanel    */

  /* Qt never sets a line-height; CSS needs one. Scale from the old dummy.   */
  --lh-tight:   1.2;
  --lh-normal:  1.5;
  --lh-relaxed: 1.6;

  /* --- Opacity --------------------------------------------------------- */
  --opacity-hidden: 0.4;   /* view_options.py _HIDDEN_DIM — the
                              QGraphicsOpacityEffect on a hidden layer row   */

  /* LETTER_SPACING_ACTION = 112 is Qt PercentageSpacing: every glyph ADVANCE
     is multiplied by 1.12, so the addition is 12% of the LABEL's width — not
     a per-character constant. CSS has no per-advance lever; letter-spacing
     adds a fixed amount after every character. The two agree on short labels
     and drift apart linearly with length.

     §11.2 gives 0.12em and flags the conversion as an approximation "to be
     checked at the result". It was checked at "Run" only, where the two
     models differ by 1.5px. Re-measured across eight labels (Arial 13,
     headless Chrome) against Qt's own model, natural x 1.12 + 48px padding:

       label            natural   Qt(x1.12)+48   Qt measured   0.12em   0.06em
       Run               23.86        74.7           75          +1.8     -0.5
       OK                18.80        69.1           69          +0.9     -0.7
       Cancel            40.47        95.3           96          +4.5     -0.2
       Create Project    83.09       141.1          141         +11.9     +0.9
       Browse...         56.36       111.1            -          +4.2     -1.3
       New Label...      74.44       131.4            -          +6.7     -1.1
       Rename...         62.14       117.6            -          +3.5     -2.0
       Delete...         50.58       104.6            -          +4.9     -0.6

     The three "Qt measured" values are independent: 141 and 96 from the
     widget tree in doc 02 §3.1, 69 pixel-measured in live-19. They confirm
     the model, and the model then condemns 0.12em: at "Create Project" it is
     12px too wide, and the Browse... button no longer fits its half of the
     input row. 0.06em holds all eight within 2px and the four that matter
     within 1. Promoted here from a local override in page-dialoge.css. */
  --tracking-action: 0.06em;

  /* --- Spacing — theme.py, §11.3 --------------------------------------- */
  --space-1: 4px;    /* SPACE_XS */
  --space-2: 8px;    /* SPACE_SM */
  --space-3: 12px;   /* SPACE_MD */
  --space-4: 16px;   /* SPACE_LG */
  --space-6: 24px;   /* SPACE_XL — theme.py knows no step above this        */
  /* Page-margin-only steps (§11.3): never inside a component, or the rhythm
     drifts away from the product.                                          */
  --space-8:  32px;
  --space-12: 48px;

  /* --- Radii — theme.py, §11.3 ----------------------------------------- */
  /* NAME SHIFT, the most dangerous line of the translation: Qt has two radii
     (4 / 8). The old Astro dummy had three (2 / 4 / 6) under the SAME names.
     These follow the Qt values.                                            */
  --radius-sm:   4px;    /* RADIUS_SM — inputs, chips, strip buttons, menus  */
  --radius-md:   8px;    /* RADIUS_MD — cards, action cards                  */
  --radius-pill: 999px;  /* app.qss hard-codes 15px (#primary) and 16px
                            (#secondary, #iconButton) — B-5. At the buttons'
                            real heights (31px / 34px) both are already
                            fully round, so §11.3's 999px is used for both.  */
  --border-width: 1px;   /* 25 occurrences in app.qss                        */

  /* --- Brand geometry — theme.py, §11.4 -------------------------------- */
  --logo-min-width:   100px;  /* LOGO_MIN_WIDTH                             */
  --logo-height:       34px;  /* brand_bar.LOCKUP_HEIGHT                    */
  --logo-clear-space:  19px;  /* round(LOGO_CLEAR_SPACE_RATIO 0.193 * 101)  */
  --icon-size:         16px;  /* ICON_SIZE                                  */

  /* --- Chrome geometry — hard-coded in Python, not in app.qss ---------- */
  /* Every one of these was measured on live-01 / live-08 at 1400x900 @2x
     unless a source is named. Doc 01 §3 quotes some of them at 1100x720;
     where the two disagree the reference IMAGE wins (see README).          */
  --menubar-height:      24px;   /* measured live-01, plus 1px bottom border */
  --menubar-inset:        8px;   /* measured live-01: first item box at 8 */
  --menubar-item-gap:     2px;   /* measured live-01: gap between File and View  */
  --brandbar-height:     72px;   /* 19 + 34 + 19, measured live-01           */
  --strip-width:         38px;   /* measured live-01, plus 1px side border   */
  --strip-button-size:   28px;   /* measured live-08 (checked = sand-dark)   */
  --strip-button-gap:     5px;   /* measured live-08: pitch 41 - 28 - 2*4    */
  --statusbar-height:    32px;   /* SPACE_XL + SPACE_SM, StatusBar.__init__  */
  --statusbar-bar-width:160px;   /* setFixedWidth(160)                       */
  --statusbar-bar-height: 16px;  /* setFixedHeight(SPACE_LG); the PAINTED
                                    bar is capped at 8px by QProgressBar     */
  --dock-titlebar-height: 32px;  /* SPACE_XS + 24 + SPACE_XS                 */
  --dock-pin-size:        24px;  /* ICON_SIZE + SPACE_SM                     */
  --dock-left-width:    360px;   /* the DOCK, not the column: every one of   *
                                 * seven left-panel crops (live-09/10/22/26/ *
                                 * 30/33) is exactly 720px = 360 logical.    *
                                 * 408 measured the column incl. the strip.  */
  --dock-right-width:   300px;   /* measured live-08 = ProjectInfo _MIN_WIDTH*/
  --dock-log-height:    222px;   /* live-12 is 444px tall = 222 logical, and *
                                 * the full window gives 645.5..868 = 222.5  */
  --breadcrumb-height:   43px;   /* measured, doc 01 §3                      */
  --progressbar-height:   8px;   /* app.qss min/max-height                   */
  --scrollbar-width:     12px;   /* app.qss: @space_md                       */
  --action-card-min-width: 130px;/* app.qss QToolButton#actionCard           */
  --action-card-box-width: 159px;/* measured live-01 = 130 min-width + 2*12
                                    padding + 2*1 border + 3px that Qt's
                                    QToolButton adds on its own           */
  --action-card-label-line: 18px;/* measured live-01: Qt's line box for the
                                    13px Arial label under the glyph      */
  --action-card-icon:     24px;  /* ICON_SIZE + SPACE_SM, ActionCard         */
  /* app.qss says QCheckBox::indicator { width: 16px; height: 16px }, but that
     is Qt's CONTENT box: a styled ::indicator adds border and padding OUTSIDE
     it, so the 1px border makes the drawn control 18. This sheet is
     border-box, so 16 here would draw 16. Measured on
     component-checkbox-states.png: the checked indicator's mint fill spans
     x 16..33 and y 16..33 — 18 x 18. (Checked sets background AND border to
     MINT, so the fill covers content plus border.)                          */
  --checkbox-size:       18px;   /* app.qss QCheckBox::indicator 16 + 2 border */
  --menu-indicator-size: 14px;   /* app.qss QMenu::indicator                 */
  --menu-item-line:      16px;   /* measured live-13: item is 24px tall, and
                                    app.qss pads it 4px top and bottom       */

  /* --- Generated indicator images — stylesheet.py _SVG_GLYPHS ----------- */
  /* Qt writes these five SVGs at start-up and points QSS url() at them.
     Same path data, same stroke width, same colours; inlined as data URIs so
     the colour literal stays inside :root. 24x24 viewBox, rendered 12x12.   */
  --icon-chevron-down: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12"><path d="M6 9 L12 15 L18 9" fill="none" stroke="%23242437" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>');
  --icon-chevron-down-disabled: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12"><path d="M6 9 L12 15 L18 9" fill="none" stroke="%239a958a" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>');
  --icon-chevron-right: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12"><path d="M9 6 L15 12 L9 18" fill="none" stroke="%23242437" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>');
  --icon-check: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12"><path d="M5 12.5 L10 17 L19 7" fill="none" stroke="%23242437" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/></svg>');

  /* --- CSS-ONLY: the three things Qt cannot express (§11.5.1) ----------- */
  /* The Qt application has NO shadows, NO transitions and no stacking scale.
     CSS needs all three for anything that floats. Scale taken from the old
     Astro click-dummy — §11.5.1 calls it its most valuable contribution.
     Shadows are used ONLY on surfaces the platform also shadows (menus,
     modal dialogs) and on the screen switcher, which is not part of the app.
     Transitions are used ONLY on the screen switcher: QSS hover changes are
     instantaneous, and an eased hover would read as "web", not as Qt.       */
  --shadow-sm:           0 1px 3px rgba(36, 36, 55, 0.08);
  --shadow-md:           0 2px 8px rgba(36, 36, 55, 0.12);
  --shadow-lg:           0 4px 16px rgba(36, 36, 55, 0.16);
  --shadow-tooltip:      0 2px 12px rgba(36, 36, 55, 0.18);
  --shadow-context-menu: 0 4px 20px rgba(36, 36, 55, 0.22);

  --transition-fast:     120ms ease-out;
  --transition-normal:   200ms ease-out;
  --transition-progress: 600ms linear;

  --z-dock:         100;
  --z-menu:         600;
  --z-tooltip:      500;
  --z-dialog:       800;
  --z-switcher:     900;
}

/* ===========================================================================
 * 1. Base — app.qss §5.1 "Grundlage"
 * ======================================================================== */

/* app.qss QWidget: colour, family and size — and DELIBERATELY no background.
 * A universal background paints every plain container and bleeds over the
 * cards behind it. The ground colour is set on the window instead. */
*,
*::before,
*::after { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
}

body {
  color: var(--color-midnight);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  /* Qt sets no line-height and renders at its font metrics, which is ~1.2.
     The system started at 1.5, and four windows then had to correct it back
     down locally. Measured on live-11: the wrapped hint under "All blocks"
     has its two lines 28 device px apart (198 and 226 in a 2x grab), i.e. 14
     logical on 12px text. The same page rendered at 1.5 puts them 36 apart,
     at 1.2 it puts them 28 — the application's number exactly. Corroborated
     by live-41 (red error block, 14.5px pitch at 12px) and live-37 (log,
     17px at 14px). --lh-normal stays defined; the gallery's own prose and the
     state sheets use --lh-relaxed and are unaffected. */
  line-height: var(--lh-tight);
  background: var(--color-sand-light);   /* app.qss QMainWindow */
  -webkit-font-smoothing: antialiased;
}

/* app.qss QLabel { background: transparent } — labels show the surface behind
 * them. In CSS this is already the default; stated so the rule is findable. */

/* §11.5.1 — "adopt, but as ABSENCE". These containers have deliberately no
 * surface of their own; the ground comes from the parent. Giving any of them
 * a background builds something other than the application. */
.is-transparent,
.projectInfoScroll,
.projectInfoContent,
.labelScroll,
.labelRowsContainer,
.progressScroll,
.progressRowsContainer { background: transparent; border: none; }

/* ===========================================================================
 * 2. The window shell
 * ======================================================================== */

.app {
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  height: 100vh;
  overflow: hidden;
  background: var(--color-sand-light);
}

.app__middle {
  display: grid;
  grid-template-columns: auto 1fr auto;
  min-height: 0;
}

.app__body {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: 1fr auto;
  grid-template-areas:
    "docks-left canvas-host docks-right"
    "dock-log   dock-log    dock-log";
  min-height: 0;
  min-width: 0;
}

/* --- Menu bar — app.qss QMenuBar (Windows in-window bar; on macOS Qt moves
 * it into the screen bar and these three rules never run). The reference
 * screenshots show the in-window bar, so that is what is rebuilt. ------- */
.menubar {
  display: flex;
  align-items: center;
  gap: var(--menubar-item-gap);
  height: calc(var(--menubar-height) + var(--border-width));
  padding: 0 var(--menubar-inset);
  background: var(--color-sand-light);
  color: var(--color-midnight);
  border-bottom: var(--border-width) solid var(--color-border);
}

.menubar__item {
  background: transparent;
  border: none;
  padding: var(--space-1) var(--space-2);
  font: inherit;
  color: inherit;
  line-height: 1;
  cursor: default;
  border-radius: var(--radius-sm);
}
.menubar__item:hover,
.menubar__item[aria-expanded="true"] { background: var(--color-sand); }

/* --- Menu — app.qss QMenu (8 blocks) --------------------------------- */
.menu {
  position: absolute;
  z-index: var(--z-menu);
  min-width: max-content;
  padding: var(--space-1);
  background: var(--color-sand-light);
  border: var(--border-width) solid var(--color-border);
  color: var(--color-midnight);
  /* CSS-ONLY: the platform shadows a popup; app.qss cannot express it. */
  box-shadow: var(--shadow-context-menu);
}

.menu__item {
  display: flex;
  align-items: center;
  /* A <button> keeps a shrink-to-fit width even as a block-level flex box, so
   * without this the highlight paints only as wide as the item's own text and
   * a right-aligned submenu arrow has nothing to be pushed against. Qt paints
   * the whole item. Promoted from page-panels.css, where it was scoped to the
   * context menu — the File and View menus app.js builds have it too. */
  width: 100%;
  padding: var(--space-1) var(--space-4);
  background: transparent;
  border: none;
  font: inherit;
  /* AFTER the font shorthand — `font: inherit` resets line-height. */
  line-height: var(--menu-item-line);
  color: inherit;
  text-align: left;
  white-space: nowrap;
  cursor: default;
}
.menu__item:hover,
.menu__item.is-selected { background: var(--color-mint); color: var(--color-midnight); }
.menu__item:disabled,
.menu__item[aria-disabled="true"] { color: var(--color-sand-grey); }

/* QMenu::indicator — 14x14, margin-left 8. The tick is the generated SVG,
 * not a platform glyph. A non-checkable menu reserves no indicator column
 * (measured: File menu labels start at 21.5px, View menu labels at 44px). */
.menu--checkable .menu__item { padding-left: 0; }
.menu--checkable .menu__indicator {
  flex: 0 0 auto;
  width: var(--menu-indicator-size);
  height: var(--menu-indicator-size);
  margin-left: var(--space-2);
  margin-right: var(--space-4);
  background-repeat: no-repeat;
  background-position: center;
}
.menu__item[aria-checked="true"] .menu__indicator { background-image: var(--icon-check); }

.menu__separator {
  height: var(--border-width);
  background: var(--color-border);
  margin: var(--space-1) 0;
  border: none;
}

/* --- Brand bar — app.qss QToolBar#brandBarHolder + QWidget#brandBar.
 * There is NO dark band: the row IS the clear space. ------------------- */
.brandbar {
  display: flex;
  align-items: center;
  height: var(--brandbar-height);
  padding: var(--logo-clear-space);
  background: var(--color-sand-light);
  border: none;
}

.brandbar__logo {
  display: block;
  height: var(--logo-height);
  min-width: var(--logo-min-width);
  width: auto;
}

/* --- Toggle strips — app.qss QToolBar#leftStrip / #rightStrip -------- */
.strip {
  display: flex;
  flex-direction: column;
  align-items: center;
  width: calc(var(--strip-width) + var(--border-width));
  padding: var(--space-1) 0;
  gap: var(--strip-button-gap);
  background: var(--color-sand-light);
}
.strip--left  { border-right: var(--border-width) solid var(--color-border); }
.strip--right { border-left:  var(--border-width) solid var(--color-border); }

/* The expanding spacer between Structure and Log in the left strip. */
.strip__spacer { flex: 1 1 auto; }

.strip__button {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--strip-button-size);
  height: var(--strip-button-size);
  margin: var(--space-1) 0;
  padding: var(--space-1);
  background: transparent;
  border: var(--border-width) solid transparent;
  border-radius: var(--radius-sm);
  color: var(--color-midnight);
  cursor: default;
}
.strip__button:hover { background: var(--color-sand); }
.strip__button[aria-pressed="true"] { background: var(--color-sand-dark); }
.strip__button[aria-pressed="true"]:hover { border-color: var(--color-dark-green); }

/* --- Canvas — app.qss QWidget#canvas --------------------------------- */
.canvas-host {
  grid-area: canvas-host;
  display: flex;
  flex-direction: column;
  min-width: 0;
  min-height: 0;
}

.canvas {
  flex: 1 1 auto;
  min-height: 0;
  background: var(--color-sand-light);
  overflow: hidden;
}

/* The empty state — the canvas stack's page 0. One-way street: once a
 * project is open the application never returns here. */
.emptyState {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}
.emptyState__cards { display: flex; gap: var(--space-4); }

/* --- Breadcrumb — app.qss QWidget#breadcrumb (block-definition view) -- */
.breadcrumb {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: var(--breadcrumb-height);
  padding: var(--space-2) var(--space-3);
  background: var(--color-sand);
  border-bottom: var(--border-width) solid var(--color-border);
}
.breadcrumb__panel {
  background: transparent;
  border: none;
  padding: 0;
  font: inherit;
  color: var(--color-dark-green);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}
.breadcrumb__panel:hover { color: var(--color-midnight); }
.breadcrumb__crumb { background: transparent; color: var(--color-midnight); }
.breadcrumb__spacer { flex: 1 1 auto; }
.breadcrumb__close {
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  padding: var(--space-1);
  border-radius: var(--radius-sm);
  color: var(--color-midnight);
  cursor: pointer;
}
.breadcrumb__close:hover { background: var(--color-sand-dark); }

/* --- Docks — app.qss QDockWidget + QWidget#dockTitleBar -------------- */
.docks {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.docks--left  { grid-area: docks-left;  width: var(--dock-left-width); }
.docks--right { grid-area: docks-right; width: var(--dock-right-width); }
/* Project and Structure share the space 1:1 — the weight _apply_default_dock_
 * sizes() asks for. Their real heights are content-dependent (doc 01 §9.2).
 * A single dock in a column simply fills it. */
.docks > .dock { flex: 1 1 0; min-height: 0; }

.dock {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--color-sand-light);
  border: var(--border-width) solid var(--color-border);
  color: var(--color-midnight);
}
.dock--log { grid-area: dock-log; height: var(--dock-log-height); }
.dock[hidden] { display: none; }

.dockTitleBar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 0 0 auto;
  height: var(--dock-titlebar-height);
  padding: var(--space-1) var(--space-1) var(--space-1) var(--space-2);
  background: var(--color-sand-light);
  border-bottom: var(--border-width) solid var(--color-border);
}
.dockTitle {
  flex: 1 1 auto;
  color: var(--color-midnight);
  /* _TITLE_TOP_NUDGE = 2 in dock_title_bar.py */
  padding-top: 2px;
}
.dockPin {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--dock-pin-size);
  height: var(--dock-pin-size);
  background: transparent;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--color-midnight);
  cursor: pointer;
}
.dockPin:hover { background: var(--color-sand-dark); }

.dock__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow: auto;
  padding: var(--space-3);
}

/* --- Status bar — app.qss QStatusBar#statusBar ----------------------- */
/* Three visible states, and they are NOT decided by the setters: "task" is
 * the row being visible at all. set_task_text() on an idle bar paints
 * nothing. Hence two independent things: row visible, and row text. */
.statusbar {
  display: flex;
  align-items: center;
  height: var(--statusbar-height);
  padding-left: var(--space-4);          /* task row contents margin */
  background: var(--color-sand-light);
  color: var(--color-midnight);
  border-top: var(--border-width) solid var(--color-border);
}
.statusbar__task {
  display: flex;
  align-items: center;
  gap: var(--space-3);                   /* task row spacing */
}
.statusbar__task[hidden] { display: none; }
.statusbar__progress {
  width: var(--statusbar-bar-width);
  height: var(--statusbar-bar-height);
  display: flex;
  align-items: center;
}
.statusbar__text { color: var(--color-midnight); }
/* A notice replaces the task row for 5000 ms — Qt hides the row while a
 * message is up, so the two are mutually exclusive, never stacked. */
.statusbar__message[hidden] { display: none; }

/* ===========================================================================
 * 3. Components — app.qss §4 and §5.2-§5.4
 *
 * Selector translation (§11.6):
 *   Qt #objectName        -> a class          (#primary -> .btn-primary)
 *   Qt type selector      -> element/base rule
 *   Qt [property="value"] -> data- attribute  ([running] -> [data-running])
 *                            except invalid, which is aria-invalid.
 * The three application state axes are surface / running / invalid.
 * Everything else is a pseudo-class with a direct CSS counterpart.
 * ======================================================================== */

/* --- Text — app.qss QLabel#heading / #body --------------------------- */
/* .heading exists for completeness. It is unreachable in the application
 * (the Heading component is never instantiated), which is why Georgia never
 * renders — see the --font-heading note at the top of this file. */
.heading {
  background: transparent;
  color: var(--color-midnight);
  font-family: var(--font-heading);
  font-size: var(--fs-heading);
}
/* margin: 0, for the same reason as .fieldError further down: this is a <p>
 * standing in for a QLabel, and a QLabel has no paragraph margin. */
.body {
  background: transparent;
  color: var(--color-midnight);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  margin: 0;
}

/* --- Buttons — app.qss QPushButton#primary (7 blocks) ---------------- */
.btn-primary {
  background: var(--color-dark-green);
  color: var(--color-white);
  border: none;
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-6);
  font-family: var(--font-body);
  font-size: var(--fs-action);
  letter-spacing: var(--tracking-action);  /* _track_caps, 112% — NOT caps */
  cursor: pointer;
}
.btn-primary:hover { background: var(--color-midnight); }
.btn-primary[data-surface="midnight"] {
  background: var(--color-mint);
  color: var(--color-midnight);
}
.btn-primary[data-surface="midnight"]:hover { background: var(--color-white); }
.btn-primary:disabled,
.btn-primary[aria-disabled="true"] {
  background: var(--color-sand-dark);
  color: var(--color-slate);
  cursor: default;
}
.btn-primary[data-running="true"] { background: var(--color-burnt-orange); }
.btn-primary[data-running="true"]:hover { background: var(--color-midnight); }

/* --- app.qss QPushButton#secondary (4 blocks) ------------------------ */
.btn-secondary {
  background: transparent;
  color: var(--color-midnight);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-6);
  font-family: var(--font-body);
  font-size: var(--fs-action);
  letter-spacing: var(--tracking-action);
  cursor: pointer;
}
.btn-secondary:hover { border-color: var(--color-slate); }
.btn-secondary[data-surface="midnight"] { color: var(--color-white); }
.btn-secondary[data-surface="midnight"]:hover { border-color: var(--color-white); }

/* --- app.qss QPushButton#iconButton (3 blocks). Fixed 34x34 (2*17, hard
 * in buttons.py). There is deliberately NO midnight rest-state rule: on a
 * midnight surface the button keeps the light border until hovered. ---- */
.btn-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  background: transparent;
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-pill);
  color: var(--color-midnight);
  cursor: pointer;
}
.btn-icon:hover { border-color: var(--color-slate); }
.btn-icon[data-surface="midnight"] { color: var(--color-white); }
.btn-icon[data-surface="midnight"]:hover { border-color: var(--color-white); }

/* Icon + label inside a button: Qt hard-wires ~4px and pads with an
 * EN SPACE U+2002. In CSS the gap is a gap. */
.btn-primary, .btn-secondary { display: inline-flex; align-items: center; gap: var(--space-1); justify-content: center; }

/* A midnight demonstration surface (components gallery only — the shipped
 * application has no dark surface anywhere). */
.surface-midnight {
  background: var(--color-midnight);
  color: var(--color-white);
  padding: var(--space-4);
  border-radius: var(--radius-md);
}

/* --- Action card — app.qss QToolButton#actionCard -------------------- */
.actionCard {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  min-width: var(--action-card-box-width);
  padding: var(--space-3);
  background: var(--color-sand);
  color: var(--color-midnight);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  font-family: var(--font-body);
  font-size: var(--fs-action);
  text-align: left;      /* app.qss; ToolButtonTextUnderIcon centres anyway */
  cursor: pointer;       /* PointingHandCursor */
}
.actionCard:hover {
  background: var(--color-sand-dark);
  border-color: var(--color-mint);
}
.actionCard__icon {
  width: var(--action-card-icon);
  height: var(--action-card-icon);
  display: block;
}
/* A <button> does not inherit line-height from body (UA "normal"), so the
 * label box is stated explicitly to match Qt's. */
.actionCard__label { line-height: var(--action-card-label-line); }

/* --- Card — app.qss QGroupBox#card + ::title ------------------------- */
/* subcontrol-origin: margin means the title sits OUTSIDE the box, in the
 * 24px margin above it. In CSS that is a heading BEFORE the box, never a
 * header inside it. font-family/font-size in the QSS rule do not take
 * effect in Qt (B-18), so the title renders in Arial 14 — reproduced. */
.card-title {
  color: var(--color-text-muted);
  font-family: var(--font-body);
  font-size: var(--fs-base);
  margin-left: var(--space-1);
  padding-bottom: var(--space-1);
}
.card {
  background: var(--color-sand);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  padding: var(--space-2);
}

/* --- Chip — app.qss QLabel#chip -------------------------------------- */
.chip {
  display: inline-block;
  background: transparent;
  color: var(--color-slate);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-1) var(--space-2);
  font-size: var(--fs-sm);
}

/* --- Help icon — app.qss QLabel#helpIcon ----------------------------- */
.helpIcon {
  display: inline-flex;
  width: var(--icon-size);
  height: var(--icon-size);
  background: transparent;
  color: var(--color-slate);
}

/* --- FormField / InputRow / CheckBoxField ---------------------------- */
/* These three carry an objectName with NO QSS rule: pure layout shells. */
.formField { display: flex; flex-direction: column; gap: var(--space-1); }
.formField__labelRow { display: flex; align-items: center; gap: var(--space-1); }
.formFieldLabel {
  background: transparent;
  color: var(--color-midnight);
  font-family: var(--font-body);
  font-weight: var(--fw-medium);
}
/* margin: 0 — this is a <p> standing in for a QLabel, and a QLabel has no
 * paragraph margin. The UA sheet gives <p> 1em top and bottom, which at
 * --fs-sm is 12 + 12 = 24px of air per error line. Measured against doc 02
 * §5.3: a NewProjectDialog field grows 56 -> 74 when its error appears and
 * the dialog 781 -> 817, i.e. 18 per field — the 14px line plus the 4px
 * gap the .formField already carries, and nothing else. Promoted from
 * page-dialoge.css and page-panels.css, which both had to zero it locally. */
.fieldError {
  background: transparent;
  color: var(--color-burnt-orange);
  font-size: var(--fs-sm);
  margin: 0;
}
.fieldError[hidden] { display: none; }
/* app.qss QLabel#fieldNotice — an INFORMATION about a valid result, not an
 * error. The text stays midnight; the attention is a 3px orange edge.
 * Orange text was tried and rejected: @orange on sand measures 2.00:1. */
.fieldNotice {
  background: transparent;
  color: var(--color-midnight);
  font-size: var(--fs-sm);
  border-left: 3px solid var(--color-orange);
  padding-left: var(--space-2);
  margin: 0;                 /* a <p> for a QLabel; see .body above */
}
.inputRow { display: flex; align-items: center; gap: var(--space-2); }
.inputRow > .input { flex: 1 1 auto; }
/* A row that carries a Browse… button splits in half: doc 02 §3.1 measures
 * 141 + 8 + 141 = 290 in the New Project dialog, and 202 + 8 + 202 when the
 * failed state widens it to 496. Half the row MINUS half the gap, not
 * `flex: 1 1 0` — with flex-basis 0 Chrome adds each item's own padding and
 * border on top of the equal share, and a button with 48px of padding comes
 * out 32px wider than the input beside it (measured 157 / 125 in a 290 row).
 * The :has() keeps it off the other InputRow shape, input + chip, where
 * component-inputrow.png shows the input taking the surplus and the chip its
 * natural width. Promoted from page-dialoge.css. */
.inputRow:has(> .btn-secondary) > .input,
.inputRow > .btn-secondary {
  flex: 0 0 calc(50% - var(--space-1));
  min-width: 0;
}
.checkBoxField { display: flex; align-items: center; }
.checkBoxField > .helpIcon { margin-left: auto; }

/* --- Text input — app.qss QLineEdit (3 blocks, styled BY TYPE) -------
 * CASCADE TRAP, reproduced on purpose: [invalid] and :focus have the same
 * specificity and :focus comes later in app.qss, so an invalid field loses
 * its red border the moment the user clicks in to fix it (B-3). Measured
 * border colours: invalid+blurred #c74f26, invalid+focused #20a785.
 * Do NOT add .input[aria-invalid]:focus unless the application changes. */
.input {
  background: var(--color-sand-light);
  color: var(--color-midnight);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  font-family: var(--font-body);
  font-size: var(--fs-base);
}
.input[aria-invalid="true"] { border-color: var(--color-burnt-orange); }
.input:focus { border-color: var(--color-dark-green); outline: none; }
.input::placeholder { color: var(--color-sand-grey); }

/* --- ComboBox — app.qss QComboBox (9 blocks, styled BY TYPE) ---------
 * Focus is MINT here and DARK GREEN on QLineEdit — B-6, kept as measured. */
.combo { position: relative; display: inline-block; }
.combo__button {
  display: flex;
  align-items: center;
  width: 100%;
  background: var(--color-sand-light);
  color: var(--color-midnight);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2);
  padding-right: var(--space-6);            /* ::drop-down width */
  font-family: var(--font-body);
  font-size: var(--fs-base);
  text-align: left;
  cursor: pointer;
}
.combo__button:hover { border-color: var(--color-slate); }
.combo__button:focus,
.combo[data-open="true"] .combo__button {
  border-color: var(--color-mint);
  outline: none;
}
/* ::drop-down: 24px wide, borderless, centre right; ::down-arrow 12x12. */
.combo__arrow {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: var(--space-6);
  background-image: var(--icon-chevron-down);
  background-repeat: no-repeat;
  background-position: center;
  pointer-events: none;
}
.combo__button:disabled { color: var(--color-sand-grey); cursor: default; }
.combo[data-disabled="true"] .combo__arrow { background-image: var(--icon-chevron-down-disabled); }

/* QComboBox QAbstractItemView — the popup. combo_box.py calls setView(
 * QListView()) because macOS would otherwise draw a NATIVE popup that
 * ignores the stylesheet entirely. */
.combo__popup {
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 100%;
  z-index: var(--z-menu);
  background: var(--color-sand-light);
  color: var(--color-midnight);
  border: var(--border-width) solid var(--color-border);
  box-shadow: var(--shadow-md);          /* CSS-ONLY, see :root */
}
.combo__popup[hidden] { display: none; }
.combo__option {
  display: block;
  width: 100%;
  padding: var(--space-1) var(--space-2);
  background: transparent;
  border: none;
  font: inherit;
  color: inherit;
  text-align: left;
  cursor: pointer;
}
.combo__option:hover,
.combo__option[aria-selected="true"] {
  background: var(--color-mint);
  color: var(--color-midnight);
}
.combo__option:disabled { color: var(--color-sand-grey); }

/* --- Checkbox — app.qss QCheckBox (9 blocks). The finest state machine in
 * the application, and the one a click-dummy loses first. -------------- */
.checkbox {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);                    /* QCheckBox spacing */
  background: transparent;
  color: var(--color-midnight);
}
.checkbox input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: var(--checkbox-size);
  height: var(--checkbox-size);
  margin: 0;
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-sm);
  background-color: var(--color-white);
  background-repeat: no-repeat;
  background-position: center;
  cursor: pointer;
}
.checkbox input[type="checkbox"]:hover { border-color: var(--color-slate); }
.checkbox input[type="checkbox"]:checked {
  background-color: var(--color-mint);
  border-color: var(--color-mint);
  background-image: var(--icon-check);
}
.checkbox:has(input:disabled) { color: var(--color-sand-grey); }
.checkbox input[type="checkbox"]:disabled {
  background-color: var(--color-sand);
  border-color: var(--color-sand);
  cursor: default;
}
/* Without this rule ::indicator:disabled wins and the tick disappears —
 * "this module is running too" would be lost exactly when it can no longer
 * be changed. */
.checkbox input[type="checkbox"]:checked:disabled {
  background-color: var(--color-sand-dark);
  border-color: var(--color-sand-dark);
}
/* [running] lifts the row that is currently computing back to full strength,
 * so activity reads as PRESENCE and not as the same absence as the rest. */
.checkbox[data-running="true"]:has(input:disabled) { color: var(--color-midnight); }
.checkbox[data-running="true"] input[type="checkbox"]:checked:disabled {
  background-color: var(--color-mint);
  border-color: var(--color-mint);
}

/* --- Progress bar — app.qss QProgressBar + ::chunk (BY TYPE) --------- */
.progress {
  width: 100%;
  height: var(--progressbar-height);
  background: var(--color-sand-dark);
  border: none;
  border-radius: var(--radius-sm);
  overflow: hidden;
}
.progress__chunk {
  height: 100%;
  background: var(--color-mint);
  border-radius: var(--radius-sm);
}
/* Indeterminate: the status-bar bar is setRange(0, 0). There is no
 * percentage progress anywhere in the window chrome. */
.progress--indeterminate .progress__chunk {
  width: 40%;
  animation: progress-sweep 1.4s linear infinite;
}
@keyframes progress-sweep {
  from { transform: translateX(-100%); }
  to   { transform: translateX(250%); }
}

/* ===========================================================================
 * 4. Panel vocabulary — app.qss §5.7
 * ======================================================================== */

.sectionHeader,
.projectName {
  background: transparent;
  color: var(--color-midnight);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-base);
}
.detailsToggle {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  background: transparent;
  border: none;
  padding: 0;
  color: var(--color-midnight);
  font-family: var(--font-body);
  font-weight: var(--fw-semibold);
  font-size: var(--fs-base);
  cursor: pointer;
}
/* The one divider line in the application — app.qss QFrame#divider. */
.divider {
  border: none;
  border-top: var(--border-width) solid var(--color-border);
  margin: var(--space-3) 0;
}

/* Key/value row: the key steps back, the value leads. */
.metaRow { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-2); }
.metaKey,
.progressName,
.filterCount,
.showBlocksHint,
.resultHint {
  background: transparent;
  color: var(--color-slate);
  font-size: var(--fs-sm);
}
.metaValue {
  background: transparent;
  color: var(--color-midnight);
  font-weight: var(--fw-medium);
}
.progressName[data-running="true"] { color: var(--color-midnight); }

/* --- Layer row — app.qss QWidget#layerRow ---------------------------- */
.layerRow { background: transparent; }
.layerRow__head { display: flex; align-items: center; gap: var(--space-2); }
.layerIcon { background: transparent; display: inline-flex; width: var(--icon-size); height: var(--icon-size); }
.layerName {
  background: transparent;
  color: var(--color-midnight);
  font-weight: var(--fw-medium);
  flex: 1 1 auto;
}
.layerVisibility {
  background: transparent;
  border: none;
  padding: 0;
  color: var(--color-midnight);
  cursor: pointer;
}
/* The opacity sliders render in the PLATFORM's blue: app.qss has no QSlider
 * rule at all (doc 01 §8.9). Do not brand it. What "unstyled" means is a
 * browser question, not a Qt one, and this Chrome paints a grey groove with a
 * grey thumb — neither the blue nor the white thumb every reference image
 * shows. page-canvas.css rebuilds the control from the platform values it
 * keeps as canvas data; this rule only states the width. */
.layerRow input[type="range"] { width: 100%; }
/* A hidden layer is dimmed by a QGraphicsOpacityEffect over the WHOLE row, not
 * recoloured. Measured on live-45: the dimmed "Coverage %" reads #9F9EA3, and
 * MIDNIGHT at 0.4 over SAND_LIGHT computes to #9E9DA2 — one unit per channel,
 * Qt's rounding. SAND_GREY is #9A958A, a visibly warmer tone that does not
 * occur in that image. The decisive evidence is the eye-off glyph and the
 * slider THUMB dimming with it, which no colour rule can do.
 * Promoted from page-canvas.css. */
.layerRow[data-hidden="true"] { opacity: var(--opacity-hidden); }

/* --- Tree — app.qss QTreeWidget#structureTree (10 blocks) ------------ */
.tree {
  background: var(--color-sand-light);
  border: none;
  color: var(--color-midnight);
}
.treeRow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  cursor: default;
}
.treeRow:hover { background: var(--color-sand-dark); color: var(--color-midnight); }
.treeRow[aria-selected="true"] { background: var(--color-mint); color: var(--color-midnight); }
/* The branch column belongs to the row: without matching hover/selected the
 * platform highlight (macOS blue) paints next to the mint selection. */
.treeRow__branch {
  flex: 0 0 auto;
  width: var(--space-4);
  height: var(--space-4);
  background-repeat: no-repeat;
  background-position: center;
}
.treeRow__branch[data-state="closed"] { background-image: var(--icon-chevron-right); }
.treeRow__branch[data-state="open"]   { background-image: var(--icon-chevron-down); }
.treeRow__icon { flex: 0 0 auto; display: inline-flex; width: var(--icon-size); height: var(--icon-size); }
.treeRow__name { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.treeRow__role { color: var(--color-slate); font-size: var(--fs-sm); }
.treeRow__count { color: var(--color-slate); font-size: var(--fs-sm); }

/* Three INDEPENDENT modifiers, set in Python, not in the QSS. They combine
 * and say three different things at once. */
.treeRow--hidden .treeRow__name,
.treeRow--hidden .treeRow__role { text-decoration: line-through; }
.treeRow--hidden .treeRow__name,
.treeRow--hidden .treeRow__role,
.treeRow--filtered .treeRow__name { color: var(--color-sand-grey); }
.treeRow--unplaced .treeRow__name { font-style: italic; }
.treeRow--definition .treeRow__name { font-weight: 700; }
/* The role column bolds with the name. Measured on
   live-46-block-definition-view.png (3x): `cell` and `Die` are both bold while
   `Dummy`/`Loose` beside them and the count 25 stay regular. Promoted from
   page-panels.css, where it had to sit scoped to .structureTree because no
   screen of that window rendered a definition row WITH a role column;
   canvas-block-definition.html does. */
.treeRow--definition .treeRow__role { font-weight: 700; }

/* --- List — app.qss QListWidget#manageLabelsList --------------------- */
.list {
  background: var(--color-sand-light);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-sm);
  color: var(--color-midnight);
}
.list__item { padding: var(--space-1) var(--space-2); cursor: default; }
.list__item[aria-selected="true"] { background: var(--color-mint); color: var(--color-midnight); }

/* --- Log output — app.qss QPlainTextEdit#logOutput ------------------- */
.logOutput {
  background: var(--color-sand-light);
  border: none;
  padding: var(--space-2);
  font-family: var(--font-mono);
  white-space: pre-wrap;
  margin: 0;
}

/* --- Search field: structureSearch has no rule of its own; it is a
 * QLineEdit with a leading glyph. --------------------------------------- */
.searchField { position: relative; display: block; }
.searchField .input { width: 100%; padding-left: var(--space-6); }
.searchField__icon {
  position: absolute;
  left: var(--space-2);
  top: 50%;
  transform: translateY(-50%);
  color: var(--color-slate);
  display: inline-flex;
  pointer-events: none;
}

/* --- Tabs — app.qss QTabBar::tab ------------------------------------- */
.tabs { display: flex; }
.tab {
  background: transparent;
  color: var(--color-slate);
  padding: var(--space-2) var(--space-4);
  border: none;
  border-bottom: 2px solid transparent;
  font: inherit;
  cursor: pointer;
}
.tab[aria-selected="true"] {
  color: var(--color-midnight);
  border-bottom-color: var(--color-mint);
}

/* --- Scroll areas and scrollbars — app.qss §5.8 ---------------------- */
/* The generic QScrollArea is opaque WHITE; the three panel scroll areas are
 * transparent (see .is-transparent above). Vertical only — there are no
 * :horizontal rules, horizontal bars stay native. */
.scrollArea { background: var(--color-white); border: none; overflow: auto; }

/* NOTE: no `scrollbar-width` here. Chrome ignores every ::-webkit-scrollbar
 * rule as soon as the standard property is set, and the standard property
 * cannot express a slate handle with a 4px radius and a 24px minimum. */
.scroll-styled::-webkit-scrollbar { width: var(--scrollbar-width); background: transparent; }
.scroll-styled::-webkit-scrollbar-track { background: transparent; }
.scroll-styled::-webkit-scrollbar-thumb {
  background: var(--color-slate);
  border-radius: var(--radius-sm);
  min-height: var(--space-6);
}
.scroll-styled::-webkit-scrollbar-thumb:hover { background: var(--color-midnight); }
/* Arrow buttons are set to height 0 in app.qss — the web has none anyway. */
.scroll-styled::-webkit-scrollbar-button { display: none; }

/* --- Legend — canvas legend toggle (app.qss QToolButton#legendToggle) */
.legendToggle { background: transparent; border: none; padding: 0; cursor: pointer; color: var(--color-midnight); }
/* The only control that deliberately floats over foreign content, so it
 * keeps a surface and a border to stay legible over dark heatmap regions. */
/* 34x34, doc 04 §3.1 — the same 2*17 as .btn-icon above, and stated as a
 * literal for the same reason: the gallery loads this file WITHOUT
 * page-canvas.css, so a --cv-* token would resolve to nothing there. The size
 * used to live in page-canvas.css's .plot > .canvasReset, which is why the
 * gallery rendered this button with no size at all. Placement inside the plot
 * stays with the canvas; the size is the component's. */
.canvasReset {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  background: var(--color-sand-light);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-md);
  color: var(--color-midnight);
  cursor: pointer;
  padding: var(--space-1);
}
.canvasReset:hover { background: var(--color-sand); border-color: var(--color-mint); }

/* ===========================================================================
 * 5. Dialogs — app.qss §5.1 / §5.10
 *
 * Claimed BY TYPE in Qt so that QMessageBox, QInputDialog and any dialog
 * someone forgets to list are all covered. The four dialogs of the
 * application carry an objectName with NO rule: they live entirely off the
 * dialog frame and the components inside them.
 * ======================================================================== */

.dialogBackdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-dialog);
  display: flex;
  align-items: center;
  justify-content: center;
}
.dialogBackdrop[hidden] { display: none; }

.dialog {
  background: var(--color-sand-light);
  padding: var(--space-4);
  box-shadow: var(--shadow-lg);          /* CSS-ONLY: a floating window */
  max-width: 90vw;
  max-height: 90vh;
  overflow: auto;
}
/* The box's own geometry, all four numbers measured on
 * shell-project-error-box.png -- a 1x grab of the widget itself, 400 x 125
 * (doc 01 5.6). Qt lays a QMessageBox out with the platform style's margins,
 * not the sheet's: the icon sits at (27,18) and the Ok button, 55 x 34, at
 * y 71..104, so the insets are 18 top / 24 right / 20 bottom / 27 left and
 * the gap between symbol and text is 17. 18+42+11+34+20 = 125. A uniform 16
 * with a 12 gap and a 16 button margin comes out 124 -- that was the missing
 * pixel, and 16px of the icon's and the text's left offset with it. */
.messageBox {
  background: var(--color-sand-light);
  padding: 18px 24px 20px 27px;
}
.messageBox__row { display: flex; gap: 17px; align-items: flex-start; }
.messageBox .buttonBox { margin-top: 11px; }
/* The symbol's BOX. 42x42 at (27,18), measured on shell-project-error-box.png.
 * Its siblings .messageBox / __row / __text were already here; only this one
 * sat in page-dialoge.css, which the gallery does not load — so the gallery
 * rendered the symbol at 32. The COLOUR stays out of this file: the platform
 * paints that symbol, and in practice the fill is baked into the SVG path in
 * icons.js, so page-dialoge.css's `color: var(--color-critical)` reaches no
 * pixel at all. See the note there. */
.messageBox__icon {
  flex: 0 0 auto;
  display: inline-flex;
  width: 42px;
  height: 42px;
}
/* The critical symbol is drawn by the PLATFORM and is NOT styled. */
.messageBox__text { font-weight: 700; }

/* app.qss QDialogButtonBox QPushButton — catches the buttons Qt creates
 * itself (Ok/Cancel). They carry no objectName, so #primary/#secondary
 * never reach them. */
.buttonBox { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-4); }
.buttonBox button {
  background: var(--color-sand-light);
  color: var(--color-midnight);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--radius-sm);
  padding: var(--space-2) var(--space-4);
  font: inherit;
  cursor: pointer;
}
.buttonBox button:hover { background: var(--color-sand); border-color: var(--color-slate); }

/* ===========================================================================
 * 6. Screen switcher — NOT PART OF THE APPLICATION
 *
 * A prototype affordance so a reader can reach every screen. It must never
 * be mistaken for navigation the product has: the application has no page
 * navigation at all, and the empty state is a one-way street. It therefore
 * uses the one surface the shipped application never uses — midnight — plus
 * the monospace face and a transition, all three of which mark it as
 * furniture rather than product.
 * ======================================================================== */

.switcher { position: fixed; right: var(--space-3); bottom: var(--space-3); z-index: var(--z-switcher); }

.switcher__toggle {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  background: var(--color-midnight);
  color: var(--color-white);
  border: none;
  border-radius: var(--radius-pill);
  padding: var(--space-1) var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  cursor: pointer;
  box-shadow: var(--shadow-md);
  opacity: 0.72;
  transition: opacity var(--transition-fast);
}
.switcher__toggle:hover { opacity: 1; }
.switcher__dot { width: var(--space-2); height: var(--space-2); border-radius: var(--radius-pill); background: var(--color-mint); }

.switcher__panel {
  position: absolute;
  right: 0;
  bottom: calc(100% + var(--space-2));
  width: 280px;
  max-height: 60vh;
  overflow: auto;
  background: var(--color-midnight);
  color: var(--color-white);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-context-menu);
  padding: var(--space-2);
}
.switcher__panel[hidden] { display: none; }
.switcher__heading {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--color-mint);
  padding: var(--space-1) var(--space-2);
}
.switcher__group {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--color-slate);
  padding: var(--space-2) var(--space-2) var(--space-1);
}
.switcher__link {
  display: block;
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-sm);
  color: var(--color-white);
  font-size: var(--fs-sm);
  text-decoration: none;
  transition: background var(--transition-fast);
}
.switcher__link:hover { background: var(--color-slate); }
.switcher__link[aria-current="page"] { background: var(--color-mint); color: var(--color-midnight); }
.switcher__note {
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  color: var(--color-sand-grey);
  padding: var(--space-2);
  line-height: var(--lh-tight);
}

/* ===========================================================================
 * 7. Utilities
 * ======================================================================== */
[hidden] { display: none !important; }
.u-stack   { display: flex; flex-direction: column; gap: var(--space-2); }
.u-row     { display: flex; align-items: center; gap: var(--space-2); }
.u-grow    { flex: 1 1 auto; }
.u-mono    { font-family: var(--font-mono); }
