﻿/* ==========================================================================
   Cordis IDE — global styles
   --------------------------------------------------------------------------
   This file is the app's whole design layer (no more Radzen theme fork): the
   Material Symbols @font-face, the Cordis brand palette + IDE-shell chrome
   tokens (--cordis-*), the full-viewport shell reset, and the .cordis-* class
   styles consumed by the components under Components/Shared/.
   ========================================================================== */

/* THE TOKEN BLOCK THAT USED TO BE HERE IS GONE (decision D10, 2026-08).
   This file declared the whole --cordis-* family as literal hexes on :root, and the
   asset layer declared the same names on the same selector with the same specificity
   in the same origin — so which palette an application rendered in came down to
   DOCUMENT ORDER between two stylesheets, silently, with nothing in the DOM to look
   at. That was the single worst trap in this repository, and it is now impossible
   rather than merely documented: there is exactly one declaration of every token, in
   the asset layer's css/tokens.css, plus the Studio-only additions in the compat shim.

   The dark block that followed it is gone for the same reason — the shim bridges the
   dark semantic layer onto .cordis-shell.cordis-dark, so the IDE's class-based dark
   mode keeps working from one declaration instead of two.

   What this file still owns is RULES, which is what a component stylesheet is for. If
   you find yourself adding a `--cordis-…:` declaration below, it belongs in tokens.css
   (if every profile has it) or in the Studio layer (if only Studio does). */

/* The box model for THIS layer (issue #402; the count corrected under #893). This used to be inherited by
   accident: the consuming web head used to link `bootstrap.min.css`, whose reboot declares the very
   same rule, while a desktop head's index.html links no framework CSS at all. So every component that
   combined `width: 100%` with padding laid out border-box in the browser and content-box
   in the installed desktop app — a divergence that reproduces on desktop ONLY and that no browser e2e
   can see (it surfaced as a permanent horizontal scrollbar in the References / Problems result lists).
   Same value bootstrap already applies, so the web head is unchanged; desktop converges onto the
   behaviour every layout here was authored and verified against. Genuinely global — Bucket A.

   IT IS DECLARED TWICE IN THE ASSET LAYER, DELIBERATELY, AND NEITHER COPY IS SPARE (issue #893). The
   header above used to claim this file owned the reset "in ONE place for BOTH heads", and it does not:
   css/cordis.css declares the same rule, because that sheet is the whole global layer of the SHELL
   profile — a plain-HTML/React consumer links css/tokens.css + css/cordis.css and no app.css at all (see
   css/CLAUDE.md §"Consuming it from plain HTML"; this repository's own Shell consumer,
   reference/index.html, links the asset layer only — tokens.css, cordis.css, cordis-studio.css — and
   nothing out of src/). Blazor consumers, by contrast, link all five sheets in the contracted order.
   Strike the copy there and every non-Blazor application in the suite silently reverts to content-box.
   The copy HERE is what makes this component layer correct on its own terms: its rules pair `width: 100%`
   with padding throughout, and #402 IS the defect of a layer inheriting its box model from a sheet it
   does not own.

   The stale claim was not harmless. #402 is a class-1 release-test probe whose mutate-and-confirm told
   the operator to delete "the" declaration and watch two tests go red; with a second copy live both
   stayed green, so a probe that measured nothing was recorded as HOLDS (found the hard way under #878).
   Consumers pin the pair now — cordis-ide's SharedCssContractTests.SharedCss_TheBoxSizingResetStaysGlobal
   asserts the reset sits in EXACTLY these two sheets of the consumed global layer, in both directions —
   so a tidy-up that drops either one, or adds a third, goes red. If you change the number of copies,
   change that contract and the #402 row in cordis-ide's docs/release-tests/probe-catalogue.md with it. */
*, *::before, *::after {
    box-sizing: border-box;
}

html, body {
    font-family: var(--cordis-font);
    margin: 0;
}

/* Only a document that actually contains the app shell gets a locked, full-height viewport.
   This used to be unconditional on html, body -- height:100% plus overflow:hidden -- which is
   correct for Studio, where .cordis-shell is a three-row 100vh grid that owns its own scrolling,
   and wrong for everybody else. Any other consumer that linked this sheet silently lost document
   scrolling: content taller than the viewport was simply unreachable, with no scrollbar and no
   error. The showcase hit it immediately, and Support Assistant, the Server front end and any
   documentation page would have hit it too.

   :has() rather than an opt-in class on <html> so no host has to change anything: the rule
   applies exactly when the shell it is describing is present. Descendant, not child: in a Blazor
   host the shell is rendered inside the root component's mount element (#app), so a child
   combinator would never match. */
html:has(.cordis-shell),
body:has(.cordis-shell) {
    height: 100%;
    /* `auto`, not `hidden` — and the difference only ever shows below the floor below (issue #771). At any
       usable size the shell is exactly one viewport (100vh, one minmax(0,1fr) column), so nothing overflows
       and no scrollbar appears; the shell still owns all scrolling inside itself. Under the floor the
       document scrolls instead, which is what makes the floor a FLOOR rather than a clip: every control is
       still reachable in a window too small to hold them, where `hidden` would leave the theme toggle and
       the caption buttons cut off with no way to get at them. */
    overflow: auto;
}

.cordis-shell {
    height: 100vh;

    /* THE APP'S MINIMUM SIZE (issue #771). Below this the chrome stops being a layout and starts being a
       pile: the title bar has folded its menus to a hamburger and its command centre to a glyph, the
       activity rail and both side panels are at their own minimums, and the next pixel taken has to come
       out of the editor. So the shell stops shrinking here and the document scrolls (above) — degraded but
       whole, and every gesture still reachable.

       A floor in CSS is the only one a browser tab can be given; a window that can be dragged smaller
       needs the OS to refuse, which is the desktop head's Window.Minimum* (App.xaml.cs) and is set to the
       same two numbers. The tokens are declared in css/cordis-studio.css beside the other chrome metrics,
       deliberately WITHOUT a fallback here: a host that does not load the Studio profile has different
       chrome and no business inheriting this floor, and an unresolved custom property drops the whole
       declaration — which is exactly "no floor" and the right answer there. */
    min-width: var(--cordis-shell-min-w);
    min-height: var(--cordis-shell-min-h);
}

/* ==========================================================================
   Shell layout
   ========================================================================== */

.cordis-shell {
    display: grid;
    /* title · main · status bar (the terminal now docks inside the editor pane) */
    grid-template-rows: var(--cordis-titlebar-h) 1fr var(--cordis-statusbar-h);
    /* Explicit 1fr (not the implicit auto-sized single column this used to fall back to): an
       auto column sizes to its content's max-content width, which — once the WB-1 menu bar
       (issue #51) added a fixed-width File/Edit/View/Deploy/Help cluster to the title bar row —
       could exceed the viewport at ~1280px and silently overflow the whole shell sideways
       (grid's own children still fit the container's real width fine; only the *implicit column
       track's own sizing* needed pinning). 1fr always resolves to the container's available
       width, so this can't blow out regardless of a row's content. */
    grid-template-columns: minmax(0, 1fr);
    background: var(--cordis-body-bg);
    color: var(--cordis-text);
}

/* The title-bar track follows the bar's zoom floor (issue #24). Where the host owns the native
   title bar, CordisTitleBar pins itself to at least 100% with `zoom: var(--cordis-chrome-comp)`,
   which scales its rendered HEIGHT — a 40px bar renders 60px at comp 1.5. The track is a fixed
   length, so without this the pinned bar overflows into the main row and covers the top of the
   activity rail and the tab strip. Its WIDTH deliberately gets no such treatment: an auto-width
   grid item under `zoom` still fills its track exactly (measured), and compensating it under-fills
   the row. comp is 1 everywhere else, so this resolves to the track above for every other consumer. */
.cordis-shell--native-chrome {
    grid-template-rows:
        calc(var(--cordis-titlebar-h) * var(--cordis-chrome-comp))
        1fr
        var(--cordis-statusbar-h);
}

.cordis-main {
    display: flex;
    min-height: 0; /* allow children to scroll */
    overflow: hidden;
}

.cordis-workarea {
    flex: 1;
    min-width: 0;
}

/* Title bar FRAME (.cordis-titlebar / __brand / __file / __menu / __center / __right) → migrated to
   Cordis.IDE.UI/Components/Shared/CordisTitleBar.razor.css (#214). The right-cluster action buttons
   (.cordis-titlebar__action / __toggle / __layout / __sep) are the shell's RightContent → migrated to
   MainLayout.razor.css. */

/* ---- Base icon glyph (Material Symbols) — replaces Radzen's old .rzi ----
   The @font-face and the whole glyph rule moved to css/cordis.css under decision D8,
   where `.cordis-icon { … }` styles both profiles from one declaration. This
   sheet loads AFTER cordis.css (the order is documented on AddCordisUI and is
   load-bearing), so the only thing left here is the part that is genuinely ours.

   The FOUT guard stays app-owned on purpose. Because the glyph is the ligature's own
   name, a Material Symbols icon that has not loaded reads as the literal text
   "chevron_right"; hiding it until cordis-ui.js confirms the face is resolved and sets
   html.cordis-icons-ready avoids that. It cannot go into cordis.css, because a Shell
   consumer that loads the CSS without our JS would then render no icons at all —
   fail-visible there, fail-quiet here. */
.cordis-icon {
    visibility: hidden;
}

/* `inherit`, NEVER `visible` -- and this is the whole of a reported flicker (2026-08). `visibility` is
   inherited, but a descendant that declares its OWN value wins over an ancestor's `hidden`: it is the one
   property a parent cannot use to hide a subtree. So `visible` here punched every glyph straight through
   the visibility:hidden frames an overlay renders while it measures itself (CordisContextMenu's two
   pre-placement passes -- see its OnAfterRenderAsync). The panel's background and its labels stayed
   correctly invisible, so what a user saw was a floating constellation of ICONS -- row glyphs, chevrons,
   check ticks -- painted at the caller's first-frame openUp/openLeft GUESS, blinking out as the placed
   panel appeared one frame later somewhere else. Reported on the consuming IDE's diagram dock Tools menu,
   the one caller that guesses openLeft, which is why it read as a leftward-menu bug: with no guess flags
   the ghost icons sit exactly where the real ones land and nothing appears to move.
   `inherit` keeps the guard's own job intact -- an icon whose face has not loaded is still hidden by the
   rule above -- while making a loaded one mean "visible unless something above me is hidden".
   VisibilityPunchThroughTests holds the whole repository to this. */
html.cordis-icons-ready .cordis-icon {
    visibility: inherit;
}

/* Panel-toggle fill state + the layout-control cluster + separators + the native-chrome (desktop
   Win11) title-bar layout tweaks → migrated with the rest of the title bar (CordisTitleBar.razor.css
   for the native-chrome bar rules; MainLayout.razor.css for the toggle/layout/sep action cluster). #214 */

/* Search / command bar (CordisSearchBar: .cordis-search* / .cordis-kbd / .cordis-palette*) → migrated
   to Cordis.IDE.Workbench/Components/Shared/CordisSearchBar.razor.css (#214). */

/* Activity rail (CordisActivityBar) → migrated to Components/Shared/CordisActivityBar.razor.css (#214).
   The settings-gear spin (.cordis-gear-icon, an inline <svg> the shell renders into the rail's
   BottomContent) → migrated to MainLayout.razor.css (#214). */

/* Animated phase icons (CordisPhaseIcon) → migrated to Components/Shared/CordisPhaseIcon.razor.css (#214). */

/* Generic panel (CordisPanel) → migrated to Components/Shared/CordisPanel.razor.css (#214). */

/* CordisSplitter → migrated to Components/Shared/CordisSplitter.razor.css (#214). */

.cordis-inspector__empty {
    color: var(--cordis-text-dim);
    font-size: 13px;
    padding: 16px;
}
/* The explorer empty-state's "Open Model…" link (spec-model-lifecycle §3 UX-1.1) — a plain inline
   link-button, not a full CordisButton (this sits inside a one-line text block, not a toolbar). */
.cordis-inspector__emptylink {
    display: inline;
    margin-left: 4px;
    padding: 0;
    border: 0;
    background: transparent;
    color: var(--cordis-blue);
    font: inherit;
    font-size: 13px;
    text-decoration: underline;
    cursor: pointer;
}
.cordis-inspector__emptylink:hover { color: var(--cordis-blue-dark); }

/* ==========================================================================
   SPECIFY pane — the pane FRAME (.cordis-specify / -tabs / __body + the per-kind editor action-row
   pinning) migrated to Cordis.IDE.Specify/…/CordisSpecifyPanel.razor.css (#214); the class editor's
   single-owner chrome (groups / stacked header / toolbar / sections container) migrated to
   CordisClassSpecify.razor.css. What stays GLOBAL here (Bucket B — genuinely shared across components):
   the editor HEADER chrome (.cordis-specify__head / __name / __head--stacked / __title / __kind / __flags /
   -props / __note / .cordis-doc*, used by every child editor; the --stacked override depends on __head
   keeping its low single-class specificity) AND the category sections (.cordis-specify-cat*, below) —
   rendered by CordisClassSpecify AND every structure editor (ChildGuards / Compositions / Usages /
   Observations / ExecutionOrder ×2 / Mappings), so they can't be scoped. The member row band
   (.cordis-specify-member*) was here too until issue #502 and is now scoped to its single author.
   ========================================================================== */

/* Editable name header (class + element fallback). Full-bleed: the -12px margin cancels the body
   gutter so the divider spans edge to edge, while its own 12px padding keeps the name aligned with
   the padded content below. */
.cordis-specify__head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin: 0 -12px;
    padding: 12px 12px 10px;
    border-bottom: 1px solid var(--cordis-border);
}
.cordis-specify__name {
    flex: 1;
    min-width: 0;
    border: 1px solid transparent;
    border-radius: var(--cordis-radius-sm);
    background: transparent;
    color: var(--cordis-text);
    font: inherit;
    font-size: 14px;
    font-weight: 600;
    padding: 3px 6px;
}
.cordis-specify__name:hover { border-color: var(--cordis-border); }
.cordis-specify__name:focus {
    outline: none;
    border-color: var(--cordis-blue);
    background: var(--cordis-body-bg);
}

/* --- The STACKED header variant: kind glyph + name on the first row, a quiet flags row of switches
   beneath. Global (Bucket B) because TWO panes render it — CordisClassSpecify («Machine») and
   CordisConceptSpecify («Physical») — and it was scoped to the class pane until the second one arrived:
   the concept pane's flags row shipped UNSTYLED, because Blazor CSS isolation rewrites a scoped selector
   to match only the markup its own component authored, so borrowing another pane's class matches nothing
   at all, silently. Any third pane with a flags row uses these same classes.
   NB the `--stacked` modifier stays a SINGLE class here, exactly like the `.cordis-specify__head` base it
   overrides, so source order (this block is after the base) is what makes the column layout win. Don't
   raise either one's specificity. */
.cordis-specify__head--stacked {
    flex-direction: column;
    align-items: stretch;
    /* The name row to the flags row. Deliberately roomier than the 8px inside a row: the switch reads as
       a property OF the named element, and at 4px it crowded the name field's focus ring (user feedback). */
    gap: 10px;
}

/* The name ROW inside the header — kind glyph, the editable name (or a readout), the ⓘ.
   `flex: 1` is what makes the row SPAN the header, and it belongs here rather than in any pane: without it
   the row is a flex item at its default `0 1 auto`, i.e. content width, and an <input>'s content width is
   the browser's ~180px intrinsic default. The row then stops short with the right half of the header empty
   — which is exactly what the requirement pane shipped the moment it stopped using `--stacked`, because
   that variant's `align-items: stretch` had been supplying the full width by accident. Any pane using the
   plain (row) header got nothing.
   Harmless under `--stacked`: that variant makes __head a COLUMN of auto height, so its free space is zero
   and `flex-grow` distributes nothing. One declaration, right in both directions. */
.cordis-specify__title {
    display: flex;
    align-items: center;
    gap: 8px;
    flex: 1;
    min-width: 0;
}

/* The static kind glyph (machine-part cube / external chip / concept shapes) left of the name. */
.cordis-specify__kind {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
}

.cordis-specify__flags {
    display: flex;
    align-items: center;
    gap: 16px;
    /* Lines the switch up with the name field's own 6px text inset (the field carries a 1px transparent
       border), so the flag reads as hanging under the name rather than under the glyph. */
    padding-left: 7px;
}


/* The collapsible category sections (.cordis-specify-cat*) LEFT this sheet in cordis-studio #1459. They
   were Bucket B — seven cordis-studio components wrote the head row as raw markup — and are now the
   CordisListSection component (Forms/), whose scoped sheet carries the same values plus the section-owned
   row indent. `.cordis-specify-cat` survives only as cordis-studio's own structural hook on that component. */

/* The member row band (.cordis-specify-member*) LEFT this sheet in issue #502. It was Bucket B because
   three components rendered it — CordisClassSpecify plus the Compositions and Usages editors — and those
   two now render the shared CordisMemberRow instead, so the band has exactly ONE author and belongs in
   CordisClassSpecify.razor.css, where its --return / __generic / .cordis-specify-map* siblings already
   lived. SharedCssContractTests is what said so: with the family down to one consumer it failed with
   "either the family really has one owner now (then scope it), or the matcher stopped matching". */

/* Element fallback + documentation */
.cordis-specify-props {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: 4px 12px;
    margin: 0;
    padding: 12px 0;
    font-size: 12.5px;
}
.cordis-specify-props dt { color: var(--cordis-text-dim); }
.cordis-specify-props dd { margin: 0; color: var(--cordis-text); }
.cordis-specify-props__path { overflow-wrap: break-word; }
.cordis-specify__note {
    color: var(--cordis-text-dim);
    font-size: 12px;
    padding: 0;
}

.cordis-doc {
    display: flex;
    flex-direction: column;
    gap: 6px;
    height: 100%;
    padding: 12px 0;
    box-sizing: border-box;
}
.cordis-doc__label { font-size: 12px; color: var(--cordis-text-dim); }

/* The pane header ROW: the label on the left, the read/edit action pushed right (issue #423).
   Separate from __label rather than making that flex, because the label's content is inline prose
   ("Documentation — <strong>Name</strong>") that a flex container would split into gapped items. */
.cordis-doc__head {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 22px;
}
.cordis-doc__head .cordis-doc__label { flex: 1 1 auto; min-width: 0; }

/* The read-only rendered body takes the pane and scrolls; the editor does its own scrolling. */
.cordis-doc__read {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* The editing surface itself is CordisRichTextEditor (scoped CSS, issue #95) — the old
   .cordis-doc__text textarea rules left with the textarea it styled. */
.cordis-doc__hint { font-size: 11px; color: var(--cordis-text-dim); }

/* Filter / search field — SHARED cross-component look (Bucket B, stays global). The shared
   CordisFilterBox component (#214) renders it for the explorer tree / Problems / SPECIFY filters,
   but it is ALSO rendered as raw `.cordis-filter` markup by sibling components with their own
   behaviour — the docs sidebar search (CordisDocsSidebar, keyboard hit-nav + Search glyph), the
   settings sidebar search (CordisSettingsSidebar, same), and the Manage-members filter
   (CordisManageMembersDialog). A class rendered by multiple siblings can't be scoped to one owner,
   so this look lives here, defined once. (Scoping it into CordisFilterBox.razor.css was the #218
   regression: those three raw consumers went unstyled.) Per-context layout stays with the consumers:
   .cordis-filter--panel is the explorer/sidebar margin; .cordis-ptoolbar .cordis-filter (below) +
   the SPECIFY toolbar's ::deep tweak size it inside their toolbars; .cordis-members__tools .cordis-filter
   stretches it in the dialog. */
.cordis-filter {
    display: flex;
    align-items: center;
    gap: 6px;
    height: var(--cordis-control-h);
    padding: 0 var(--cordis-control-px);
    /* The canonical field edge. This is a FIELD, not a surface: it takes --cordis-control-h and
       --cordis-control-px like every other one, so it has to take the edge too. It read
       --cordis-border until 2026-08, one ramp step lighter than the .cordis-input it sits beside in
       a toolbar. See CordisNumericInput.razor.css for why the family shares these tokens. */
    border: 1px solid var(--cordis-border-strong);
    border-radius: var(--cordis-radius-sm);
    color: var(--cordis-text-muted);
    background: var(--cordis-body-bg);
}
/* The focus ring belongs to the box the user perceives as the control, which here is the <label>,
   not the <input> that actually holds DOM focus (user report, 2026-08). The suite's one ring comes
   from a UNIVERSAL `:focus-visible` rule in css/cordis.css, so unaided it landed on the inner input:
   a 151x23 box inside a 206x36 control, with its radius forced to --cordis-radius-sm by that rule —
   measured in the browser, and what the report described as a doubled ring with the wrong corners.
   So: suppress it on the input, re-hang it on the wrapper via :focus-within. The wrapper's own 6px
   radius is what the ring then follows, because a box-shadow ring follows its element's radius.
   Not weakened — the same --cordis-focus-ring, one box further out. */
.cordis-filter:focus-within {
    border-color: var(--cordis-accent);
    box-shadow: var(--cordis-focus-ring);
}
.cordis-filter input {
    flex: 1; border: 0; outline: 0; background: transparent;
    color: var(--cordis-text); font: inherit; font-size: 13px; min-width: 0;
}
/* BOTH properties, because the suite has TWO focus rings and this control must suppress the inner
   copy of whichever one is in play: Shell's box-shadow ring (`css/cordis.css`) and the Studio
   profile's inset `outline: 2px solid` (`css/cordis-studio.css`, D9 — a shadow ring cannot escape a
   26px row, so that profile rings from the inside instead). Suppressing only the shadow left Studio
   drawing a 2px accent rectangle around the <input> INSIDE the wrapper's own ring — the doubled
   highlight a user reported on the explorer's Filter tree field (2026-08), and the reason the profile
   rule's own opt-out list (`.cordis-input`/`.cordis-textarea`/`.cordis-select`) cannot cover this:
   a composite control's focused element carries no such class, it is a bare <input> inside a label. */
.cordis-filter input:focus-visible { box-shadow: none; outline: none; }
/* Clear-filter affordance — only rendered while the box has text. */
.cordis-filter__clear {
    display: grid; place-items: center;
    flex: 0 0 auto; width: 18px; height: 18px; padding: 0;
    border: 0; border-radius: var(--cordis-radius-sm);
    background: transparent; color: var(--cordis-text-dim); cursor: pointer;
}
.cordis-filter__clear:hover { background: var(--cordis-hover); color: var(--cordis-text); }
/* Per-context layout: the explorer/sidebar filter sits below the panel header with a margin. */
.cordis-filter--panel { margin: 0 10px 8px; }
/* The dense size (CordisFilterBox Dense): an editor toolbar's compact height, a half inset, small type, for
   the search toggled above a dashboard panel's list or in a log's filter row. A modifier of this shared
   family rather than a host restating --cordis-control-h, which is how Cordis Server ended up with three
   28px search boxes of its own (#4025). */
.cordis-filter--dense { height: var(--cordis-toolbar-control-h); padding: 0 var(--cordis-space-2); gap: var(--cordis-space-1); }
.cordis-filter--dense input { font-size: var(--cordis-fs-sm); }

/* ---- Rich-text CONTENT (Bucket B — genuinely shared, issue #423) ---------------------------
   The look of authored rich-text content: what a paragraph, heading, list, checklist box, link,
   mention chip, code block or table looks like once it is IN the document. Global, because the same
   stored HTML is rendered by TWO components and neither can style the other's markup:

     · CordisRichTextEditor — inside the contenteditable, where the content is JS-injected
     · CordisRichTextView   — a read-only render of the same HTML (posted comments, docs bodies)

   Both opt in by putting `cordis-richtext-content` on the element that HOLDS the content. These rules
   started life scoped inside the editor, which is precisely why rendered comment bodies drifted: the
   comment pane grew a partial private copy (no mention chip, no checklist rule, no headings/tables), so
   chips rendered as plain links, checklist items showed a bullet AND a box, and boxes took the browser's
   default accent instead of Cordis blue. One home, two consumers — don't re-fork it. Anything that is
   editor CHROME (the frame, the placeholder, the image-resize overlay, the image cursor) stays scoped in
   CordisRichTextEditor.razor.css; anything describing the content itself belongs here. */

.cordis-richtext-content p { margin: 0 0 8px; }
.cordis-richtext-content p:last-child { margin-bottom: 0; }

.cordis-richtext-content h1 {
    margin: 12px 0 6px;
    font-size: 16px;
    font-weight: 600;
}

.cordis-richtext-content h2 {
    margin: 10px 0 5px;
    font-size: 14px;
    font-weight: 600;
}

.cordis-richtext-content h1:first-child,
.cordis-richtext-content h2:first-child { margin-top: 0; }

.cordis-richtext-content ul,
.cordis-richtext-content ol {
    margin: 0 0 8px;
    padding-left: 22px;
}

/* Checklist items (issue #97): a list item whose first child is a checkbox drops its bullet —
   the box IS the marker. The checkbox itself stays crisp on the brand blue. */
.cordis-richtext-content li:has(> input[type="checkbox"]:first-child) { list-style: none; }

.cordis-richtext-content li > input[type="checkbox"] {
    margin: 0 6px 0 -16px;
    vertical-align: -2px;
    accent-color: var(--cordis-blue);
    cursor: pointer;
}

.cordis-richtext-content hr {
    margin: 10px 0;
    border: none;
    border-top: 1px solid var(--cordis-border);
}

.cordis-richtext-content a { color: var(--cordis-blue); }

.cordis-richtext-content img {
    max-width: 100%;
    height: auto;
    border-radius: var(--cordis-radius-sm);
}

.cordis-richtext-content blockquote {
    margin: 0 0 8px;
    padding: 2px 0 2px 10px;
    border-left: 3px solid var(--cordis-border);
    color: var(--cordis-text-dim);
}

/* The token, not a hand-written stack. This rule used to name `Consolas, "Cascadia Mono"` and was the
   fourth of four different monospace answers in the repository (decision D6) — prose in a rich-text
   field is documentation, so it takes the brand's mono rather than the code editor's face. */
.cordis-richtext-content pre,
.cordis-richtext-content code {
    font-family: var(--cordis-font-mono);
    font-size: 12px;
    background: var(--cordis-hover);
    border-radius: var(--cordis-radius-sm);
}

/* A code block preserves its whitespace and line breaks (Tab indents, Enter adds a newline). */
.cordis-richtext-content pre {
    padding: 6px 8px;
    overflow-x: auto;
    white-space: pre-wrap;
    tab-size: 2;
}

/* A FENCE is `<pre><code>`, so the shared rule above painted its grey TWICE. `--cordis-hover` is
   translucent in both themes (0.05 light / 0.07 dark), so the two alphas composited to ~0.0975 wherever the
   inner element sat — and a `<code>` is an inline box that wraps only its own text, so the darker patch
   traced the LINES rather than the block, with its own radius rounding each line box. Reported off a
   `cordis-st` example in the Cordis IDE manual, where a highlighted fence has both.

   The pair rule is right for INLINE code, which does want the fill; only the nested case double-paints. So
   the fill stays on `pre` and on a lone `code`, and the one inside a `pre` gives it up — the block it sits
   in is already drawing it. `font-size: inherit` goes with it: the pair sets 12px on both, which a nested
   element would apply a second time only if the outer size differed, and inheriting says what is meant. */
.cordis-richtext-content pre > code {
    padding: 0;
    background: none;
    border-radius: 0;
    font-size: inherit;
}

/* @-mention chip (issue #99): a reference to a model element, rendered inline in prose. Also the manual's
   `topic:` links, which DocsMarkdown gives the same data-cordis-link treatment.

   The LOOK is element- and context-agnostic on purpose (issue #320): the rich-text editor renders a chip as an
   <a> inside .cordis-richtext-content, and read-only projections of the same prose render it as a <span>
   somewhere else entirely (a requirement's description inside a References row). Both must read identically, so
   the appearance sits on the class alone and only the INTERACTIVE half below is qualified by the anchor. A
   state modifier is COMPOUNDED with the base (`.cordis-mention.cordis-mention--unresolved`, 0,2,0): as a single
   class it tied the base and lost to `.cordis-richtext-content a` (0,1,1), whose blue then won inside every rich
   text, so a broken chip there was blue on amber (cordis-studio#1742 found it). */
.cordis-mention {
    color: var(--cordis-blue);
    text-decoration: none;
    font-weight: 500;
    border-radius: 3px;
    padding: 0 2px;
    background: color-mix(in srgb, var(--cordis-blue) 12%, transparent);
}

/* A reference the model HOLDS but cannot resolve (issue #320) — a requirement's description chunk whose
   referencedElement href didn't bind. Amber + italic rather than blue: it is a real part of the sentence, so
   it has to be visible where it sits (rendering nothing shipped first, and left a hole mid-sentence), but it
   names nothing and navigates nowhere, so it must not read as a live chip. */
.cordis-mention.cordis-mention--unresolved {
    color: var(--cordis-amber);
    background: color-mix(in srgb, var(--cordis-amber) 14%, transparent);
    font-style: italic;
}

/* The interactive half — only the editor's/viewer's anchor is clickable. */
.cordis-richtext-content a.cordis-mention {
    cursor: pointer;
}

.cordis-richtext-content a.cordis-mention:hover {
    background: color-mix(in srgb, var(--cordis-blue) 22%, transparent);
}

.cordis-richtext-content a.cordis-mention--unresolved {
    cursor: default;
}

.cordis-richtext-content a.cordis-mention--unresolved:hover {
    background: color-mix(in srgb, var(--cordis-amber) 14%, transparent);
}

/* A reference to an element that EXISTS but that this reader may not open — a phase the edition or the licence
   leaves out (cordis-studio#1742). Muted rather than amber: nothing is broken, so it must not read as a warning,
   and it must not read as a live chip either. The dotted underline says there is more to it, which the tooltip
   (the reason) and a click (the host explains) then give. */
.cordis-mention.cordis-mention--unavailable {
    color: var(--cordis-text-muted);
    background: color-mix(in srgb, var(--cordis-text-muted) 10%, transparent);
    text-decoration: underline dotted;
    text-underline-offset: 2px;
}

.cordis-richtext-content a.cordis-mention--unavailable {
    cursor: help;
}

.cordis-richtext-content a.cordis-mention--unavailable:hover {
    background: color-mix(in srgb, var(--cordis-text-muted) 10%, transparent);
}

.cordis-richtext-content table {
    border-collapse: collapse;
    margin: 0 0 8px;
}

/* Inserted tables are plain — no styled header row (issue #117). A th only appears in pasted
   content now; it renders like any other cell (bordered + padded), never a grey heading band. */
.cordis-richtext-content th,
.cordis-richtext-content td {
    border: 1px solid var(--cordis-border);
    padding: 3px 8px;
    text-align: left;
}

/* Tree (CordisTreeView `.cordis-tree` container + CordisTreeNode `.cordis-tree-node__*` rows) →
   migrated to Components/Shared/CordisTreeView.razor.css + CordisTreeNode.razor.css (#214). The
   `.cordis-glyph-filled` utility below stays global — it's a CordisIcon fill modifier shared by the
   tree, the SPECIFY pane and the "Add …" menus. */

/* Initial pseudo-state: fill the glyph (Material Symbols "circle" is a hollow ring at
   FILL=0) so it reads as a solid dot rather than another outline circle. Driven from the
   single CordisIcons.GlyphClassFor source; the choice pseudo-state now uses the Material
   Symbols "stat_0" diamond glyph directly (no CSS rhombus), matching the SPECIFY pane and
   the "Add …" menus. */
.cordis-glyph-filled { --cordis-icon-fill: 1; }

/* Quarter turn — the SIDEWAYS reading of a vertical chevron pair. Material Symbols has no horizontal
   unfold glyph: "unfold_more" is a chevron above a chevron pointing apart, and turning it renders
   exactly the `<>` / `><` an affordance that opens sideways needs. A rotation rather than two more
   glyph names on purpose — the subset font is built by sweeping CordisIcons for literals, so a name
   that does not exist upstream would silently render as clipped letters. Single-sourced from
   CordisIcons.SidewaysGlyphClass, like the fill modifier above. */
.cordis-glyph-sideways { transform: rotate(90deg); }

/* Tree diagnostic badges (.cordis-tree-node__diag*) → migrated to CordisTreeNode.razor.css (#214). */

/* Editor area + tabs → migrated (#214): .cordis-editor → MainLayout.razor.css; the tab strip →
   CordisTabStrip.razor.css; the dot-grid empty-state canvas (.cordis-canvas*) → Home.razor.css. */

/* ---- Diagram canvas (Z.Blazor.Diagrams) --------------------------------
   The diagram box widgets (Components/Shared/Diagram) style themselves via scoped CSS, but the
   library wraps each in a `.diagram-node` it owns. These global rules cover what scoped CSS can't
   reach: the grab cursor. (The box selection ring itself is now the scoped CordisDiagramView.razor.css
   `.diagram-node.selected .cdg-class/.cdg-state/.cdg-action` rule — unified with the connect / nest
   emphasis in issue #248 via the shared `--cdg-emph-box` token; this duplicate global outline rule,
   which lacked `.cdg-action` and left the 1px `outline-offset` gap, was removed there.) */
.cdg-host .diagram-node { cursor: pointer; }
/* Right-drag pan in progress: cordis-diagrams.js puts `.cdg-panning` on the host for the duration. Show
   the grabbing "closed hand" across the whole canvas — the descendant selector + !important beat the
   per-box / per-connector `cursor: pointer` rules so the cursor doesn't flicker while panning over them.
   This renders on the Server head (full Chromium). The Desktop head's WebView2 can't render `grabbing`,
   so it ALSO forces a closed-hand cursor host-side while `.cdg-panning` is set (NativeCursorBridge,
   issue #169) — this rule stays as the Server-head path and a harmless no-op fallback there. */
.cdg-host.cdg-panning,
.cdg-host.cdg-panning * { cursor: grabbing !important; }
/* Both rules above were written `.cordis-shell …` while only Studio drew a diagram; they are keyed on
   the canvas host alone since 2026-09, so any head that wears `.cdg-host` (the Cordis Server's HMI
   editor) gets them without a Studio shell around it. */

/* ---- The shared canvas look (Bucket B — several heads render it) ---------------------------------
   The generic half of Studio's CordisDiagramView.razor.css, lifted here so the Cordis Server's HMI
   editor and viewer wear the same surface as Studio's class and state-machine diagrams. Global because
   it is rendered by more than one component in more than one repository, and because the library that
   wraps each box (`.diagram-node`, Z.Blazor.Diagrams) is outside every component's CSS scope. Studio's
   own scoped copies of these rules still win inside Studio (higher specificity), so nothing there moves.

     .cdg-host          the canvas pane: a column of toolbar band + surface, on the canvas background.
     .cdg-surface       what pans and zooms; the positioning context for overlays.
     .cdg-grid          the 20px dot lattice as a CSS background, for a surface the library does not
                        draw a GridWidget into. Size and colour are tokens (--cdg-grid-size / -dot).
     .cdg-node          a box's chrome: node surface, node border, and the shared emphasis ring when
                        the library marks its wrapper `.selected` (or the host marks it `.is-selected`).
     .cdg-resize(--*)   Studio's resize zones: invisible edge strips and corner squares shown only on a
                        selected box; the cursor is the affordance. `--grip` draws a visible corner grip
                        for a surface whose users expect one. */
.cdg-host {
    position: relative;
    display: flex;
    flex-direction: column;
    min-height: 0;
    overflow: hidden;
    /* Studio's canvas ground (CordisDiagramView.razor.css): the band's own --cordis-body-bg, so the
       toolbar and the drawing surface read as one sheet (white in light, --cordis-bg in dark). */
    background-color: var(--cordis-body-bg);
}
.cdg-host:focus,
.cdg-host:focus-visible { outline: none; }   /* the canvas draws its own selection */
.cdg-surface {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}
.cdg-grid {
    background-image: radial-gradient(circle at 1px 1px, var(--cdg-grid-dot) 1px, transparent 1.25px);
    background-size: var(--cdg-grid-size) var(--cdg-grid-size);
}
.cdg-node {
    background-color: var(--cdg-node-bg);
    border: 1px solid var(--cdg-node-border);
    border-radius: var(--cordis-radius-sm);
    transition: border-color var(--cordis-dur-fast) var(--cordis-ease), box-shadow var(--cordis-dur-fast) var(--cordis-ease);
}
.cdg-node:hover { border-color: color-mix(in srgb, var(--cordis-primary) 55%, var(--cdg-node-border)); }
.diagram-node.selected .cdg-node,
.cdg-node.is-selected { box-shadow: var(--cdg-emph-box); }
.cdg-resize {
    position: absolute;
    display: none;
    z-index: 5;
    touch-action: none;   /* pointer capture drives the resize, not the browser's panning */
}
.diagram-node.selected .cdg-resize,
.cdg-node.is-selected .cdg-resize,
.cordis-dashgrid__item.is-selected > .cdg-resize { display: block; }
.cdg-host--locked .diagram-node.selected .cdg-resize { display: none; }
.cdg-resize--n, .cdg-resize--s { left: 12px; right: 12px; height: 7px; cursor: ns-resize; }
.cdg-resize--n { top: 0; }
.cdg-resize--s { bottom: 0; }
.cdg-resize--e, .cdg-resize--w { top: 12px; bottom: 12px; width: 7px; cursor: ew-resize; }
.cdg-resize--e { right: 0; }
.cdg-resize--w { left: 0; }
.cdg-resize--ne, .cdg-resize--nw, .cdg-resize--se, .cdg-resize--sw { width: 12px; height: 12px; z-index: 6; }
.cdg-resize--nw { top: 0; left: 0; cursor: nwse-resize; }
.cdg-resize--ne { top: 0; right: 0; cursor: nesw-resize; }
.cdg-resize--sw { bottom: 0; left: 0; cursor: nesw-resize; }
.cdg-resize--se { bottom: 0; right: 0; cursor: nwse-resize; }
.cdg-resize--grip::after {
    content: "";
    position: absolute;
    right: 1px;
    bottom: 1px;
    width: 8px;
    height: 8px;
    border-radius: 2px;
    background: var(--cdg-node-bg);
    border: 1.5px solid var(--cordis-primary);
}

/* The locked connector-overlay node's wrapper is dropped out of hit-testing (so background presses
   reach the canvas → marquee / pan / clear work) — but that rule lives in CordisDiagramView.razor.css
   (`::deep .diagram-node:has(> svg.cdg-overlay)`), scoped to the diagram host. */

/* ---- Diagram overlay elements (connectors · markers · labels) -----------
   The connector layer is one pointer-events:none SVG, but DiagramOverlaySvg wraps every drawn
   element in a `.cdg-int` group carrying its `data-uuid`, with a transparent fat hit target, so each
   is individually clickable / right-clickable (selection + context menu, see cordis-ui.js). Pointer
   events are re-enabled on just those hit targets (a descendant may re-enable them even though the
   svg + its node wrapper are `none`).

   Colour is driven by CSS variables so selecting (or hovering) an element recolours the *actual*
   line · arrow · marker · label — the shapes draw with inline `style="stroke:var(--cdg-line)"`, which
   picks up the value these rules override — rather than painting a halo around it. No SVG rebuild. */
.cdg-overlay { --cdg-line: var(--cordis-text-dim); --cdg-mark: var(--cordis-text); --cdg-lw: 1; }
.cdg-overlay .cdg-int { cursor: pointer; }
.cdg-overlay .cdg-hit { pointer-events: stroke; stroke: transparent; }  /* fat transparent click target */
.cdg-overlay .cdg-mk  { pointer-events: all;    fill:   transparent; }  /* marker click target */
.cdg-overlay .cdg-lbl { pointer-events: all; }
/* Rounded stroke caps + joins on every VISIBLE connector path — the line, the open arrowheads
   (using / association / dependency), and the filled heads / composition diamond (issue #235) — so the
   whole overlay reads as softly as the rounded bends. Excludes the transparent .cdg-hit target: a round
   cap there would extend the clickable stroke past the endpoints. Markers live in their own .cdg-marker
   svgs, not here, so they're unaffected. */
.cdg-overlay .cdg-int path:not(.cdg-hit) { stroke-linecap: round; stroke-linejoin: round; }
/* Hover: tint the line / marker itself toward Cordis blue (not a surrounding halo). */
.cdg-overlay .cdg-int:hover {
    --cdg-line: color-mix(in srgb, var(--cordis-blue) 50%, var(--cordis-text-dim));
    --cdg-mark: color-mix(in srgb, var(--cordis-blue) 50%, var(--cordis-text));
}
/* Split target (cordis-ide#1269): the connector an armed create tool is hovering, which a placement
   would INSERT a node into — A → B becoming A → new → B. Full Cordis blue and a touch thicker still,
   so it is legible as the drop target next to a merely hovered line (which tints halfway) and next to a
   selected one (which is blue at --cdg-lw: 2 plus a glow). It is stated by JS on pointer move and lasts
   exactly as long as the pointer is on the line, so it is deliberately a variable-only rule: no glow, no
   filter, nothing that would cost a repaint per frame. Placed after :hover — the pointer is by definition
   over the line it describes, so it has to win that tie — and before .cdg-err / .cdg-warn, which keep
   saying what is wrong with a connector while it is being aimed at. */
.cdg-overlay .cdg-int.cdg-split-target {
    --cdg-line: var(--cordis-blue);
    --cdg-mark: var(--cordis-blue);
    --cdg-lw: 3;
}
/* Model-check severity tint: a connector (transition / relation) whose element has problems draws
   red / amber via the same CSS-variable channel. Placed after :hover (the tint holds under the
   cursor) and before .cdg-sel (clicking one still recolours it Cordis blue at equal specificity). */
.cdg-overlay .cdg-int.cdg-err  { --cdg-line: var(--cordis-red);   --cdg-mark: var(--cordis-red); }
.cdg-overlay .cdg-int.cdg-warn { --cdg-line: var(--cordis-amber); --cdg-mark: var(--cordis-amber); }
/* Selected: the line · arrow · marker turn Cordis blue (line a touch thicker). Placed after :hover so
   selection wins at equal specificity. A guard/action label is the exception — it keeps its ST syntax
   colours and only goes bold (below), rather than flattening to blue (issue #183 follow-up). */
.cdg-overlay .cdg-int.cdg-sel {
    --cdg-line: var(--cordis-blue);
    --cdg-mark: var(--cordis-blue);
    --cdg-lw: 2;
}
/* …plus a soft blue glow around the selected connector's visible line + arrowhead, so it reads with the
   SAME glow as a selected box / marker (unified emphasis, issue #248 — shares the --cdg-emph-glow-svg
   token). Scoped to drawn paths: never the transparent .cdg-hit target (a glow there would halo the fat
   invisible click strip), never the <text> label (it keeps its ST colours + bold, below) or the
   <line>/<circle> routing handles. */
.cdg-overlay .cdg-int.cdg-sel path:not(.cdg-hit) {
    filter: var(--cdg-emph-glow-svg);
}
/* A selected transition's guard/action label goes BOLD, keeping its ST syntax colours — it is NOT
   flattened to blue (issue #183 follow-up). The token tspans carry their own font-weight, so bold
   them too, not just the parent <text>. (The connector line / arrow / marker still turn blue above.) */
.cdg-overlay .cdg-int.cdg-sel .cdg-lbl,
.cdg-overlay .cdg-int.cdg-sel .cdg-lbl [class*="st-tok--"] { font-weight: 700; }

/* ---- Structured-Text syntax token classes (issue #183) -------------------
   Shared by every surface that renders highlighted ST OUTSIDE Monaco: the References panel's
   excerpts and the state-machine boxes' entry/do lines (HTML <span>s — the color half), and the
   diagram guard/action labels (SVG <tspan>s — the fill twin). Genuinely cross-component
   (Workbench + Diagrams render them, one look), which is why they live here and not in a scoped
   .razor.css. The colours are the --cordis-st-* tokens above — the same palette cordis-st.js
   feeds the Monaco theme, so the editors and these surfaces can't drift apart. */
.st-tok--kw      { color: var(--cordis-st-keyword); fill: var(--cordis-st-keyword); font-weight: 600; }
.st-tok--call    { color: var(--cordis-st-call);    fill: var(--cordis-st-call); }
.st-tok--type    { color: var(--cordis-st-type);    fill: var(--cordis-st-type); }
.st-tok--true    { color: var(--cordis-st-true);    fill: var(--cordis-st-true); }
.st-tok--false   { color: var(--cordis-st-false);   fill: var(--cordis-st-false); }
.st-tok--var     { color: var(--cordis-st-variable); fill: var(--cordis-st-variable); }
.st-tok--punc    { color: var(--cordis-st-punctuation, var(--cordis-text-muted)); fill: var(--cordis-st-punctuation, var(--cordis-text-muted)); }
.st-tok--num     { color: var(--cordis-st-number);  fill: var(--cordis-st-number); }
.st-tok--str     { color: var(--cordis-st-string);  fill: var(--cordis-st-string); }
.st-tok--comment { color: var(--cordis-st-comment); fill: var(--cordis-st-comment); font-style: italic; }
/* The searched element's name in a References excerpt — bold, inheriting its semantic colour. */
.st-tok--target  { font-weight: 700; }
/* A LIVE READING beside an identifier — what a running controller currently holds for it, drawn on a
   debug canvas beside the code that reads it.

   DELIBERATELY NOT in the .st-tok--* family, and the name says so. Every class above colours a token the
   MODEL contains; this colours text the model does not contain at all, and consumers rightly treat that
   family as a closed set — the SVG exporter mirrors each .st-tok--* rule into a fill so an exported
   diagram highlights identically, and an annotation has no business in an export of the model. A reader
   must never be able to mistake an annotation for something they authored, so it is set apart by weight
   and a warm hue rather than given a colour inside the ST palette.

   It is drawn as a CHIP -- a tinted, rounded ground behind the value -- rather than as differently
   coloured text. Colour alone was not enough: `Var(MyCounter) = 3 := Var(MyCounter) = 3` reads as one
   continuous run of code, and the reader has to work out which halves the controller supplied. A ground
   behind the value says "this is not in your source" before anything is read at all, which is what the
   PLC tools modellers come from do. box-decoration-break keeps the chip intact if a line ever wraps
   between the name and its value.

   In SVG (the diagram's guard/action labels) a tspan takes the `fill` and ignores background, padding and
   radius, so the ground cannot come from this rule there. The overlay draws it instead: one
   `rect.st-live-chip` per reading, placed from the monospace advance the label corridor is already sized
   with, filled inline with the exact three tints below (CSS custom properties do not resolve in SVG
   presentation attributes). So a chip is a chip on both surfaces -- which matters more than the saved
   element, because a reader learning "tinted ground means the controller said it" has to be able to carry
   that from the pane to the canvas. The MODEL exporter is unaffected: it is never handed readings, so the
   per-reading rect it would have cost never arises there.

   The two BOOLEAN modifiers exist because a boolean is the reading a state machine is most often being
   watched for -- whether a guard holds -- and it was the one carrying the least at a glance. TRUE and
   FALSE take the green and red the ST palette already gives those literals, so a live TRUE reads in the
   same green as a written one. They stay outside the .st-tok--* family for the reason above; they borrow
   its two tokens, which is a different thing from joining it. */
.st-live {
    color: var(--cordis-amber);
    fill: var(--cordis-amber);
    font-weight: 700;
    background: color-mix(in srgb, var(--cordis-amber) 16%, transparent);
    border-radius: 4px;
    padding: 0 3px;
    box-decoration-break: clone;
    -webkit-box-decoration-break: clone;
}

.st-live--true {
    color: var(--cordis-st-true);
    fill: var(--cordis-st-true);
    background: color-mix(in srgb, var(--cordis-st-true) 18%, transparent);
}

.st-live--false {
    color: var(--cordis-st-false);
    fill: var(--cordis-st-false);
    background: color-mix(in srgb, var(--cordis-st-false) 18%, transparent);
}

/* Manual routing handles, drawn only for the selected connector (DiagramOverlaySvg.AppendHandlesIfSelected).
   A fat transparent grab-line per segment (drag = move it perpendicular; cursor hints the axis) and, on
   top, a fat transparent grab-circle over a small visible disc per waypoint (drag = move the vertex freely;
   the circle catches a press on OR near the vertex, so a near-corner grab is a free move, not a segment
   move). Pointer events are re-enabled on the handles even though the overlay svg + its wrapper are `none`.
   The drag itself is driven in cordis-ui.js. */
.cdg-overlay .cdg-seg { pointer-events: stroke; stroke: transparent; stroke-width: 12; fill: none; touch-action: none; }
.cdg-overlay .cdg-seg--h { cursor: ns-resize; }   /* horizontal segment → moves vertically */
.cdg-overlay .cdg-seg--v { cursor: ew-resize; }   /* vertical segment → moves horizontally */
.cdg-overlay .cdg-seg--d { cursor: move; }         /* diagonal segment */
.cdg-overlay .cdg-wp {
    pointer-events: all;
    cursor: move;
    touch-action: none;            /* let pointer capture drive the drag, not browser panning */
    fill: transparent;             /* fat grab target; the visible disc is .cdg-wp-dot below it */
}
.cdg-overlay .cdg-wp-dot {
    pointer-events: none;          /* inert — the grab-circle above it carries the pointer */
    fill: var(--cordis-panel-bg);
    stroke: var(--cordis-blue);
    stroke-width: 1.5;
}
.cdg-overlay .cdg-wp:hover + .cdg-wp-dot { fill: var(--cordis-blue); }

/* The live transition drawn while a hover-connect gesture is dragging (DiagramOverlaySvg.AppendPending-
   Connector) — a solid Cordis-blue line + arrowhead over the connectors. Purely visual (inert). */
.cdg-overlay .cdg-pending {
    stroke: var(--cordis-blue);
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
    fill: none;
    pointer-events: none;
}
/* The pending arrowhead is a FILLED triangle (issue #235), matching the committed transition it previews. */
.cdg-overlay .cdg-pending.cdg-pending--fill { fill: var(--cordis-blue); }

/* Smart-alignment guides — thin dashed lines flashed while a box is dragged into alignment with another
   (CordisAlignmentGuidesWidget, drawn in the always-on-top widget layer so they sit over the boxes).
   Cordis blue, dashed, inert. A vertical guide is a 0-width div with a dashed left border; horizontal a
   0-height div with a dashed top border. */
.cdg-guideline {
    position: absolute;
    pointer-events: none;
    z-index: 5;
    opacity: 0.9;
}
.cdg-guideline--v { border-left: var(--cdg-target-px, 1px) dashed var(--cordis-blue); }
.cdg-guideline--h { border-top: var(--cdg-target-px, 1px) dashed var(--cordis-blue); }

/* Equal-spacing mark (cordisUi.canvasGestures, align.spacing): the gap between two boxes the moved box matches, a
   line with an end cap on each side and the size in px above it (data-gap). JS-owned and drawn in the canvas's px
   like .cdg-guideline, so --cdg-target-px keeps its weight and its text at any zoom; the sheet's fallback 1px is an
   unzoomed canvas. */
.cdg-gapmark {
    position: absolute;
    pointer-events: none;
    z-index: 5;
    box-sizing: border-box;
    color: var(--cordis-blue);
    opacity: 0.9;
}
.cdg-gapmark--h {
    height: calc(8 * var(--cdg-target-px, 1px));
    margin-top: calc(-4 * var(--cdg-target-px, 1px));
    border-left: var(--cdg-target-px, 1px) solid currentColor;
    border-right: var(--cdg-target-px, 1px) solid currentColor;
    background: linear-gradient(currentColor, currentColor) center / 100% var(--cdg-target-px, 1px) no-repeat;
}
.cdg-gapmark--v {
    width: calc(8 * var(--cdg-target-px, 1px));
    margin-left: calc(-4 * var(--cdg-target-px, 1px));
    border-top: var(--cdg-target-px, 1px) solid currentColor;
    border-bottom: var(--cdg-target-px, 1px) solid currentColor;
    background: linear-gradient(currentColor, currentColor) center / var(--cdg-target-px, 1px) 100% no-repeat;
}
.cdg-gapmark::after {
    content: attr(data-gap);
    position: absolute;
    left: 50%;
    top: 50%;
    font-size: calc(10 * var(--cdg-target-px, 1px));
    line-height: 1;
    white-space: nowrap;
}
.cdg-gapmark--h::after { transform: translate(-50%, calc(-100% - 3 * var(--cdg-target-px, 1px))); }
.cdg-gapmark--v::after { transform: translate(calc(4 * var(--cdg-target-px, 1px)), -50%); }

/* Marquee multi-select rectangle (CordisSelectionBoxWidget) — drawn over the canvas during a
   left-drag. pointer-events:none so it never interferes with the drag it's tracking. */
.cdg-marquee {
    position: absolute;
    pointer-events: none;
    background: color-mix(in srgb, var(--cordis-blue) 16%, transparent);
    border: 1px solid var(--cordis-blue);
    border-radius: 2px;
}

/* Where a moved or resized item will land (cordisUi.canvasGestures): its cells, a soft slot under an item
   that is moved, a dashed outline over one that is resized (that one is sized to the pointer, so a slot under it
   would hide whenever it shrinks). JS-owned, appended to the canvas for the length of one gesture and placed in
   its px; --cdg-target-px is one on-screen px there, so the outline keeps its weight at any zoom. */
.cdg-target {
    position: absolute;
    pointer-events: none;
    z-index: 1;
    box-sizing: border-box;
    border: var(--cdg-target-px, 1px) dashed var(--cordis-primary);
    border-radius: calc(6 * var(--cdg-target-px, 1px));
    background: var(--cordis-primary-soft);
}
.cdg-target--outline {
    z-index: 1001;
    border-width: calc(2 * var(--cdg-target-px, 1px));
    background: none;
}

/* Create-tool placement ghost (issue #149): a create tool armed, dragging out the new box's size. A
   touch more saturated than the selection marquee so it reads as "the box you're drawing", not a
   selection band. */
.cdg-marquee--create {
    background: color-mix(in srgb, var(--cordis-blue) 22%, transparent);
}

/* Status bar → migrated (#214): the footer (.cordis-statusbar) → CordisStatusBar.razor.css; the cells
   (.cordis-status-item*) → CordisStatusBarItem.razor.css; the shell's own content it renders into the
   footer (.cordis-statusbar__spacer + .cordis-status-git* / .cordis-status-problems*) → MainLayout.razor.css. */

/* Buttons (CordisButton) → migrated to Components/Shared/CordisButton.razor.css (#214). */

/* Context menu (CordisContextMenu) → migrated to Components/Shared/CordisContextMenu.razor.css (#214). */

/* ---- Overlay surfaces in the top layer (Bucket B — JS-owned) --------------
   `cordisUi.raiseOverlay` adds this ATTRIBUTE together with the `popover` attribute when it lifts a
   floating surface into the browser's top layer — the select popup, the context menu, the user menu,
   the @-mention picker. Global rather than scoped for two reasons: several components render it, and
   nothing renders it in MARKUP at all, so there is no owner to scope it to.

   Everything here undoes a UA popover default, and nothing here is a design decision. A shown
   popover gets `margin: auto; inset: 0; width/height: fit-content; border: solid; padding: 0.25em;
   overflow: auto; color: CanvasText; background-color: Canvas` from the UA sheet, which is the
   centred-modal box the attribute was designed for and has nothing to do with an anchored dropdown.
   Each component's own (scoped, therefore more specific) sheet re-declares whatever it does want —
   its surface, its border, its radius, its shadow — so this rule only ever wins where the component
   is silent. Geometry (position/left/top/min-width) is written inline by the placement pass, which
   beats both.

   Do not add anything that LOOKS like something here. If a surface needs it, it belongs in that
   surface's own sheet, or every popover in the suite quietly inherits it.

   KEYED ON A `data-` ATTRIBUTE, and that is load-bearing rather than a style preference. It was
   `.cordis-overlay` until 2026-08, when a reported misplacement traced to Blazor erasing the
   JS-added class on the next render of the surface's `class` attribute -- taking this whole reset
   with it and letting the UA's `inset: 0` + `margin: auto` re-centre an anchored menu. raiseOverlay
   explains it in full. The class stays as a selector so a consumer that puts it in markup keeps
   working, but nothing in the suite emits it any more; the attribute is what the JS sets. */
[data-cordis-overlay],
.cordis-overlay {
    margin: 0;
    inset: auto;
    width: auto;
    height: auto;
    padding: 0;
    border: 0;
    overflow: visible;
    background: none;
    color: inherit;
}

/* Custom tooltip (the shared `title` chip) → moved to css/tokens.css + css/cordis.css by D10
   family merge #6. Nothing about it was ever Blazor-specific: the engine is cordis-ui.js and the
   chip is four declarations, so a plain-HTML consumer loading the asset layer now gets both. This
   file is the RCL's stylesheet and is the wrong layer for a look the whole suite shares. */
/* User menu (CordisUserMenu) → migrated to Components/Shared/CordisUserMenu.razor.css (#214). */

/* Workspace browser (CordisWorkspaceBrowser) → migrated to
   Cordis.IDE.Workbench/Components/Shared/CordisWorkspaceBrowser.razor.css (#214). */

/* ==========================================================================
   Settings view (editor pane) + model-source loaders
   ========================================================================== */
/* The settings page scaffolding (.cordis-settings, __head, __group) is gone: it was the shared home for
   a full-pane page, in a stylesheet rather than in a component, which is why the two other pages that
   needed one could reuse the class names but not the behaviour — the manual took them and then overrode
   half of them back to uncapped. CordisPageView and CordisPageSection are that home now
   (cordis-ide#895). A page still tagged .cordis-settings is tagged for a test locator, not for a look. */

/* Documentation guide cards (CordisDocsView: .cordis-doclink* / .cordis-doclinks) → migrated to
   Cordis.IDE.Manual/Components/CordisDocsView.razor.css (#214). */

/* Model-source loader (CordisModelLoader: .cordis-loader*) → migrated to
   Cordis.IDE.Workbench/Components/Shared/CordisModelLoader.razor.css (#214). */

/* .cordis-spin moved to css/cordis.css, next to the .cordis-spinner ring it shares an animation
   name with. It was declared here at 1s while the ring ran at 0.7s, so one @keyframes was driving
   two speeds and which you got depended on which spinner you reached for. It is asset-layer
   vocabulary anyway: a plain-HTML consumer spinning a Material Symbols glyph needs it, and nothing
   about it is Blazor. The @keyframes was already down there. */

/* ==========================================================================
   Slim scrollbars — APPLICATION-WIDE
   --------------------------------------------------------------------------
   One universal rule so every scroll surface (panels, settings, problems,
   editor canvas, tab strips, the terminal, dialogs / dropdowns, and
   anything added later) gets the same slim scrollbar automatically — no need to
   re-style each new element. The thumb is theme-neutral grey (see the
   --cordis-scrollbar tokens) so it reads on both light and dark surfaces; the
   2px transparent border + content-box clip make the visible thumb ~6px.
   ========================================================================== */
* {
    scrollbar-width: thin;                                    /* Firefox */
    scrollbar-color: var(--cordis-scrollbar) transparent;
}
*::-webkit-scrollbar { width: 10px; height: 10px; }           /* Chromium / WebKit */
*::-webkit-scrollbar-track,
*::-webkit-scrollbar-corner { background: transparent; }
*::-webkit-scrollbar-thumb {
    background: var(--cordis-scrollbar); border-radius: 6px; border: 2px solid transparent; background-clip: content-box;
}
*::-webkit-scrollbar-thumb:hover { background: var(--cordis-scrollbar-hover); background-clip: content-box; }

/* ==========================================================================
   Blazor framework bits (kept from template)
   ========================================================================== */
.valid.modified:not([type=checkbox]) { outline: 1px solid var(--cordis-green); }
.invalid { outline: 1px solid var(--cordis-red); }
.validation-message { color: var(--cordis-red); }

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}
.blazor-error-boundary::after { content: "An error has occurred." }

/* Integrated terminal panel (CordisTerminalPanel) → migrated to
   Cordis.IDE.Workbench/Components/Shared/CordisTerminalPanel.razor.css (#214). */

/* Bottom panel shell (CordisBottomPanel: .cordis-bottompanel* / .cordis-panel-tabs*) → migrated to
   Cordis.IDE.Workbench/Components/Shared/CordisBottomPanel.razor.css (#214). The Problems/Specify
   filter toolbar below (.cordis-ptoolbar* / .pv-*) stays global — CordisClassSpecify's SPECIFY toolbar
   reuses .cordis-ptoolbar too (Bucket B, shared toolbar chrome). */

/* ---- Problems filter toolbar — its OWN row beneath the tab strip (VS Code-style) -----
   A sibling row under .cordis-panel-tabs (only on the Problems tab, see CordisBottomPanel),
   so the tabs never collide with the filter box when the panel is narrow. Class names kept
   as pv-* for continuity with the list's scoped styles, but these are global since the
   markup lives in the panel. */
.cordis-ptoolbar {
    flex: 0 0 auto;                 /* a fixed-height row of its own, under the tabs */
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 8px;
    background: var(--cordis-surface-chrome);
    border-bottom: 1px solid var(--cordis-border);
}
/* Thin vertical rule between the filter · severity · view-tool groups. */
.cordis-ptoolbar__sep {
    flex: 0 0 auto;
    width: 1px;
    height: 18px;
    background: var(--cordis-border);
}
/* On its own row the filter grows to fill the width (VS Code-style), with the severity
   chips and view tools sitting to its right. */
.cordis-ptoolbar .cordis-filter { flex: 1 1 auto; min-width: 120px; }

/* Severity toggle chips — dimmed when off, tinted by severity when on. */
.cordis-ptoolbar .pv-sev { display: inline-flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.cordis-ptoolbar .pv-chip {
    display: inline-flex; align-items: center; gap: 5px;
    height: 24px; padding: 0 9px;
    border: 1px solid transparent; border-radius: 999px;
    background: var(--cordis-hover); color: var(--cordis-text-dim);
    font-size: 12px; font-variant-numeric: tabular-nums; cursor: pointer;
    opacity: .5; transition: opacity var(--cordis-dur-fast) var(--cordis-ease), background var(--cordis-dur-fast) var(--cordis-ease), color var(--cordis-dur-fast) var(--cordis-ease);
}
.cordis-ptoolbar .pv-chip:hover { color: var(--cordis-text); }
.cordis-ptoolbar .pv-chip.is-on { opacity: 1; color: var(--cordis-text); }
.cordis-ptoolbar .pv-chip.is-on.pv-chip--error   { background: rgba(226, 7, 23, .12); }
.cordis-ptoolbar .pv-chip.is-on.pv-chip--warning { background: rgba(216, 148, 31, .14); }
.cordis-ptoolbar .pv-chip.is-on.pv-chip--info    { background: rgba(31, 156, 216, .12); }

/* The TRAILING group of a toolbar row, wherever the row is used — which is why `margin-left: auto` belongs
   here and not at the call sites (cordis-ide#533). The rows that already pushed it right did so by accident
   of their own contents: Problems / References / Comments / SPECIFY grow a filter box beside it, and the SCM
   sidebar sets `justify-content: space-between`. A fourth row with neither — the diff tab's toolbar, whose
   leading item is a revision pair that simply ends where its text does — left the cluster floating in the
   middle of the band. The auto margin is a no-op in the first cases (a grown sibling has already eaten the
   free space) and is the fix in the last, so one declaration covers every row. */
.cordis-ptoolbar .pv-tools {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 0 0 auto;
    margin-left: auto;
}

/* ---- Icon buttons in a toolbar row -----------------------------------------------------------
   The row's buttons are CordisButton Variant="Icon" since D16 (second pass), which retired the
   toolbar-row button (CordisToolButton, `.pv-toolbtn`): it was the same box as `.cordis-btn--icon`
   with a different resting colour and hover, and the look now lives once, in css/cordis.css.

   What stays here is the row's SIZE, and only for Studio's sake: the row's controls follow
   --cordis-control-h (Shell 36, Studio 26), and the asset layer's icon button already does in Shell,
   but cordis-studio.css draws Studio's header icon buttons at a fixed 28px. A toolbar row is not a
   header, so its tools keep the row's control height, as `.pv-toolbtn` did. Two classes so the rule
   outranks the profile's on weight, not on which sheet happened to load last. A menu button (Caret)
   keeps its caret's room, with the square as its floor. */
.cordis-ptoolbar .cordis-btn.cordis-btn--icon { height: var(--cordis-control-h); }
.cordis-ptoolbar .cordis-btn.cordis-btn--icon:not(.cordis-btn--menu) { width: var(--cordis-control-h); }
.cordis-ptoolbar .cordis-btn.cordis-btn--icon.cordis-btn--menu { min-width: var(--cordis-control-h); }

@media (prefers-reduced-motion: reduce) {
    .cordis-ptoolbar .pv-chip { transition: none; }
}

/* Output view (CordisOutputView) → migrated to
   Cordis.IDE.Workbench/Components/Shared/CordisOutputView.razor.css (#214). */

/* Prompt dialog (CordisPromptDialog) → migrated to Components/Shared/CordisPromptDialog.razor.css (#214). */

/* Command palette (CordisSearchBar dropdown: .cordis-search--button/--active/__dropdown/… +
   .cordis-palette*) → migrated to Cordis.IDE.Workbench/Components/Shared/CordisSearchBar.razor.css
   (#214). The palette result rows are authored partly by CordisSearchBar (command mode) and partly by
   the PaletteResultCard child (search mode), both inside .cordis-search__dropdown, so the row-content
   rules use ::deep there. */
