:root {
  --bg: #ffffff;
  --fg: #1a1a1a;
  --muted: #666666;
  --border: #d8d8d8;
  --editor-bg: #fafafa;
  --shape-fill: #ffffff;
  --shape-stroke: #1a1a1a;
  --accent: #2f6fed;
  --error: #c0392b;
  --line-highlight: rgba(255, 213, 0, 0.35);
  --line-error: rgba(192, 57, 43, 0.28);
  /* Fixed border: palette (docs/file-format.md § Named styles and
     per-shape status) — red/grey deliberately reuse --error/--muted
     rather than mint near-duplicate colors. purple/yellow/teal/pink/
     indigo use Tailwind's 600-weight scale for light mode, matching
     the saturation/legibility of the original 5. */
  --border-green: #2e7d32;
  --border-blue: #1565c0;
  --border-orange: #e67e22;
  --border-purple: #7c3aed;
  --border-yellow: #ca8a04;
  --border-teal: #0d9488;
  --border-pink: #db2777;
  --border-indigo: #4f46e5;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #1c1c1e;
    --fg: #f0f0f0;
    --muted: #9a9a9a;
    --border: #3a3a3c;
    --editor-bg: #141416;
    --shape-fill: #232326;
    --shape-stroke: #e8e8e8;
    --accent: #5b8dfb;
    --error: #ff6b5c;
    --line-highlight: rgba(255, 196, 0, 0.28);
    --line-error: rgba(255, 107, 92, 0.28);
    --border-green: #66bb6a;
    --border-blue: #6ea8fe;
    --border-orange: #f2a65a;
    --border-purple: #a78bfa;
    --border-yellow: #facc15;
    --border-teal: #2dd4bf;
    --border-pink: #f472b6;
    --border-indigo: #818cf8;
  }
}

* {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
}

body {
  margin: 0;
  display: flex;
  flex-direction: column;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  background: var(--bg);
  color: var(--fg);
}

header {
  padding: 0.75rem 1.5rem;
  border-bottom: 1px solid var(--border);
  flex-shrink: 0;
}

h1 {
  margin: 0 0 0.5rem;
  font-size: 1.1rem;
  font-weight: 600;
}

.toolbar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

/* Already the toolbar's first child in index.html, so it renders
   leftmost regardless — this rule is what actually earns it the
   distinct "pinned to the leading edge, set apart by a divider" look
   native macOS apps give their own sidebar toggle (Mail, Notes, Finder),
   the same convention the Mac app's own collapse button now gets via
   SwiftUI's `ToolbarItem(placement: .navigation)` (CanvasView.swift) —
   without this it was visually identical to an ordinary action button
   like Clear/Expand, positioned first only incidentally. */
#toggle-editor-button {
  padding-right: 0.75rem;
  margin-right: 0.1rem;
  border-right: 1px solid var(--border);
}

.field[hidden] {
  display: none;
}

.field {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  color: var(--muted);
}

.field input {
  font-size: 0.85rem;
  padding: 0.3rem 0.5rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--editor-bg);
  color: var(--fg);
}

button,
select {
  font-size: 0.85rem;
  padding: 0.35rem 0.7rem;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg);
  color: var(--fg);
  cursor: pointer;
}

button:hover,
select:hover {
  border-color: var(--accent);
}

.direction-toggle {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow: hidden;
}

/* Same reasoning as .field-checkbox[hidden] below — without this, the
   display: inline-flex above wins over the browser's own
   [hidden] { display: none } UA-stylesheet rule (author styles always
   beat UA styles, regardless of the hidden attribute), needed since
   app.js hides the toolbar's own #layout-direction-toggle in sequence
   mode (a real bug, caught live: the Horizontal/Vertical toggle stayed
   visible — and clickable — for a diagram type it doesn't apply to). */
.direction-toggle[hidden] {
  display: none;
}

.direction-button {
  border: none;
  border-radius: 0;
  background: var(--bg);
}

.direction-button + .direction-button {
  border-left: 1px solid var(--border);
}

.direction-button[aria-pressed="true"] {
  background: var(--accent);
  color: #fff;
}

.status {
  font-size: 0.8rem;
  color: var(--muted);
}

.status-error {
  color: var(--error);
}

.help-link {
  margin-left: auto;
  font-size: 0.85rem;
  color: var(--accent);
  text-decoration: none;
}

.help-link:hover {
  text-decoration: underline;
}

main {
  flex: 1;
  display: flex;
  min-height: 0;
  position: relative; /* anchors .inspector's absolute positioning below */
}

.editor-wrap {
  /* Width is set inline by app.js (the splitter drag handler); this is
     just the starting point before JS runs, and the floor JS clamps to. */
  width: 40%;
  min-width: 200px;
  flex-shrink: 0;
  position: relative; /* anchors #editor-backdrop's absolute positioning below */
}

/* #editor and #editor-backdrop stack exactly on top of each other,
   sharing identical font/padding/wrapping so a position measured
   against one is valid on the other — see line-highlight.js's own
   top-of-file comment for the full history (two rounds of
   backdrop-scroll-sync bugs, then a third bug in the fix that replaced
   them). #editor-backdrop is still used for the *error-token* highlight
   (line-highlight.js's errorRangesByLine) — a plain <textarea> has no
   API of its own for per-character foreground color, so a backdrop
   <div> with invisible text and a <mark> wrapped around just the
   malformed token's own characters is the closest achievable
   equivalent — and, as of the third round, it's *also* what
   line-highlight.js measures (never paints directly) to correctly
   position the *current-line* highlight, which is painted on #editor's
   own background via background-attachment: local instead. */
#editor,
#editor-backdrop {
  position: absolute;
  inset: 0;
  margin: 0;
  padding: 1rem;
  border: none;
  font: 13px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
  white-space: pre-wrap;
  word-wrap: break-word;
  overflow-wrap: break-word;
  /* Without this, a non-overlay scrollbar (some Windows/Linux browser
     configs) would reserve width on whichever of these two actually
     shows one, wrapping its text a few pixels sooner than the other and
     throwing off the highlight's alignment on any wrapped line. */
  scrollbar-gutter: stable;
}

#editor {
  z-index: 1;
  resize: none;
  outline: none;
  /* background-color only — background-image (the current-line
     highlight band) is set/cleared directly by line-highlight.js's
     updateCurrentLineBackground, as an inline style layered on top of
     this shorthand's own initial (none) value. */
  background: transparent;
  color: var(--fg);
}

#editor-backdrop {
  z-index: 0;
  overflow: hidden;
  color: transparent;
  background: var(--editor-bg);
  pointer-events: none;
}

/* A plain <textarea> has no per-character foreground-color API the way
   AppKit's NSTextView does (see LineHighlightingTextEditor.swift's own
   red-text treatment for the same feature, Gantt's unparseable st=/
   end=/dur:/colour: tokens — docs/file-format.md § Gantt charts) — a
   reddish background tint, scoped to just the malformed token's own
   characters (not its whole line — Julian's own explicit ask), on
   #editor-backdrop is the closest achievable equivalent, a deliberate,
   acknowledged platform difference. */
.editor-line-error {
  background: var(--line-error);
  color: transparent;
}

.splitter {
  flex-shrink: 0;
  width: 7px;
  cursor: col-resize;
  background: var(--border);
  position: relative;
}

.splitter:hover,
.splitter:focus-visible,
.splitter.dragging {
  background: var(--accent);
  outline: none;
}

.canvas-scroll {
  flex: 1;
  min-width: 200px;
  overflow: auto;
  padding: 1rem;
}

/* The slide-out panel shown while a shape/connector is selected — an
   overlay on top of the canvas pane, not a fixed third column, so it
   reads as transient (mirrors the Mac app's ZStack-hosted
   SelectionInspectorPanel, not an HSplitView column). */
.inspector {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  width: 260px;
  padding: 1rem;
  background: var(--bg);
  border-left: 1px solid var(--border);
  box-shadow: -2px 0 8px rgba(0, 0, 0, 0.08);
  transform: translateX(100%);
  transition: transform 150ms ease;
  overflow-y: auto;
}

.inspector.open {
  transform: translateX(0);
}

.inspector-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
  font-weight: 600;
}

#inspector-close {
  border: none;
  background: none;
  font-size: 1.1rem;
  line-height: 1;
  color: var(--muted);
  padding: 0.1rem 0.3rem;
}

.inspector .field {
  flex-direction: column;
  align-items: stretch;
  margin-bottom: 0.75rem;
}

.inspector .field input[type="text"] {
  width: 100%;
}

.field-checkbox {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  margin-bottom: 0.75rem;
}

/* Without this, the `display: flex` above would win over the browser's
   own `[hidden] { display: none }` UA-stylesheet rule (author styles
   always beat UA styles, regardless of the hidden attribute) — needed
   since inspector.js toggles `.hidden` on this exact class to show/hide
   the double-border checkbox per shape kind. */
.field-checkbox[hidden] {
  display: none;
}

.delete-button {
  color: var(--error);
  border-color: var(--error);
  width: 100%;
  margin-top: 0.5rem;
}

/* Right-click-on-empty-canvas shape-creation menu — positioned by
   app.js via inline left/top styles at the click point (fixed, so
   .canvas-scroll's own scroll position doesn't need accounting for). */
.context-menu {
  position: fixed;
  z-index: 10;
  min-width: 150px;
  padding: 0.3rem;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.18);
}

.context-menu button {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  border-radius: 4px;
  background: transparent;
  padding: 0.4rem 0.6rem;
}

.context-menu button:hover {
  background: var(--accent);
  color: #fff;
}

#canvas {
  display: block;
  /* Without this, dragging a connector-creation/re-anchor handle across
     shape/connector labels triggers the browser's native text-selection
     (drag-to-select), painting its own blue highlight over every label
     the drag path crosses — visually unrelated to, and much noisier than,
     the intended dashed drop-target highlight (see DiagramCanvas.swift's
     isLiveDropTarget for the Mac equivalent, which has no such problem
     since AppKit doesn't auto-select Text views under a drag). */
  user-select: none;
  -webkit-user-select: none;
}

.shape .outline {
  fill: var(--shape-fill);
  stroke: var(--shape-stroke);
  stroke-width: 1.5;
}

.shape-text .outline {
  display: none;
}

/* Fixed border: palette (docs/file-format.md § Named styles and
   per-shape status) — the extra .outline class raises specificity above
   the plain ".shape .outline" rule above without needing !important, the
   same technique ".shape.selected .outline" below already relies on.
   Placed before that selected-ring rule so, on the rare shape that's
   both selected and styled, the selection ring (same specificity, later
   in source) keeps winning — a selected shape should stay unambiguously
   visible as selected.

   fill: uses color-mix() to wash the shape's own border color into its
   fill, strength controlled by --fill-alpha (0% by default — i.e. pure
   --shape-fill, identical to a plain uncolored shape — set to 12%/28%
   by .fill-tint/.fill-pastel on the SVG root when the diagram's fill:
   directive is set; docs/file-format.md § Fill directive). One rule per
   color handles all three fill: modes uniformly — no separate
   .fill-tint/.fill-pastel compound selector needed, since --fill-alpha
   already carries which mode (if any) is active via the cascade. */
.shape .outline.outline-green {
  stroke: var(--border-green);
  fill: color-mix(in srgb, var(--border-green) var(--fill-alpha, 0%), var(--shape-fill));
}

.shape .outline.outline-blue {
  stroke: var(--border-blue);
  fill: color-mix(in srgb, var(--border-blue) var(--fill-alpha, 0%), var(--shape-fill));
}

.shape .outline.outline-orange {
  stroke: var(--border-orange);
  fill: color-mix(in srgb, var(--border-orange) var(--fill-alpha, 0%), var(--shape-fill));
}

.shape .outline.outline-red {
  stroke: var(--error);
  fill: color-mix(in srgb, var(--error) var(--fill-alpha, 0%), var(--shape-fill));
}

.shape .outline.outline-grey {
  stroke: var(--muted);
  fill: color-mix(in srgb, var(--muted) var(--fill-alpha, 0%), var(--shape-fill));
}

.shape .outline.outline-purple {
  stroke: var(--border-purple);
  fill: color-mix(in srgb, var(--border-purple) var(--fill-alpha, 0%), var(--shape-fill));
}

.shape .outline.outline-yellow {
  stroke: var(--border-yellow);
  fill: color-mix(in srgb, var(--border-yellow) var(--fill-alpha, 0%), var(--shape-fill));
}

.shape .outline.outline-teal {
  stroke: var(--border-teal);
  fill: color-mix(in srgb, var(--border-teal) var(--fill-alpha, 0%), var(--shape-fill));
}

.shape .outline.outline-pink {
  stroke: var(--border-pink);
  fill: color-mix(in srgb, var(--border-pink) var(--fill-alpha, 0%), var(--shape-fill));
}

.shape .outline.outline-indigo {
  stroke: var(--border-indigo);
  fill: color-mix(in srgb, var(--border-indigo) var(--fill-alpha, 0%), var(--shape-fill));
}

/* fill: tint|pastel (docs/file-format.md § Fill directive) — applied to
   the SVG root by diagram-renderer.js when the diagram sets fill:.
   Absent entirely (fill: none, the default) leaves --fill-alpha
   undefined, so every .outline-<color> rule above's own var(--fill-alpha,
   0%) fallback kicks in — resolves to plain --shape-fill, unchanged
   from before this feature existed. The fallback lives in each
   color-mix() call rather than a :root default specifically so this
   doesn't depend on :root correctly matching inside an embedded/detached
   SVG (diagram-export.js's own EXPORT_STYLE mirror of this block reuses
   the exact same var(--fill-alpha, 0%) form for that reason). */
.fill-tint {
  --fill-alpha: 12%;
}

.fill-pastel {
  --fill-alpha: 28%;
}

.shape.selected .outline {
  stroke: var(--accent);
  stroke-width: 2.5;
}

.shape-label {
  fill: var(--fg);
  font-size: 13px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

/* text: trailing-token modifiers — combinable, applied to the label only
   (docs/file-format.md § Named styles and per-shape status). */
.text-strikethrough {
  text-decoration: line-through;
}

.text-bold {
  font-weight: 700;
}

.text-italic {
  font-style: italic;
}

.connector-line {
  stroke: var(--fg);
  stroke-width: 1.5;
  fill: none;
}

.connector.selected .connector-line {
  stroke: var(--accent);
  stroke-width: 2.5;
}

.connector-label {
  fill: var(--muted);
  font-size: 11px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

/* Milestone 3: handles for dragging out a new connector — hidden by
   default, shown while the shape they belong to is caret-highlighted
   (see diagram-renderer.js's renderShape `showHandles` doc comment).
   Deliberately not hover-driven (an earlier design) — hidden state here
   just means "not the current caret target", not "not hovered". */
.connector-handle {
  fill: var(--accent);
  opacity: 0;
  pointer-events: none;
  cursor: crosshair;
}

.shape.handles-visible .connector-handle {
  opacity: 1;
  pointer-events: all;
}

/* Endpoint handles for re-anchoring a caret-highlighted connector. */
.connector-endpoint-handle {
  fill: var(--accent);
  cursor: crosshair;
}

/* Live dashed preview line for an in-progress drag-to-create-connector
   gesture — see connectorHandles in diagram-renderer.js. */
.connector-preview {
  stroke: var(--accent);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
  fill: none;
  pointer-events: none;
}

/* Live drop-target highlight — toggled on whichever shape's <g> is
   currently under an in-progress connector-creation/re-anchor drag's
   point (see createDropTargetTracker in diagram-renderer.js). Mirrors
   DiagramCanvas.swift's isLiveDropTarget dashed ring. */
.shape.drop-target .outline {
  stroke: var(--accent);
  stroke-width: 3;
  stroke-dasharray: 5 3;
}

.arrowhead {
  fill: var(--fg);
}

.group-outline {
  fill: none;
  stroke: var(--muted);
  stroke-width: 1.5;
}

.group-label {
  fill: var(--muted);
  font-size: 12px;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

.diagram-title {
  font: 600 16px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  fill: var(--fg);
}

/* footer: is deliberately export-only (docs/file-format.md § Footer
   directive) — renderDiagram/renderSequenceDiagram/renderGanttDiagram
   always draw it into the live SVG whenever set (so the browser's own
   Print-to-PDF path, which prints that same live SVG as-is, has it
   available), but it stays hidden here on screen; the @media print
   block below is what actually reveals it. diagram-export.js's PNG path
   never goes through app.css at all (a fully detached, self-contained
   SVG — see EXPORT_STYLE), so its own .diagram-footer rule there is
   always visible regardless of this one. */
.diagram-footer,
.sequence-footer,
.gantt-footer,
.mindmap-footer {
  display: none;
  font: 10px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  fill: var(--muted);
}

/* Sequence diagrams (type: sequence) — entirely separate rules from the
   flowchart .shape/.connector classes above, drawn by
   sequence-renderer.js. v1 is view-only, so nothing here needs a
   :hover/.selected state the way the flowchart classes do. */
.sequence-title {
  font: 600 16px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  fill: var(--fg);
}

.sequence-lifeline {
  stroke: var(--border);
  stroke-width: 1.5;
  stroke-dasharray: 4 3;
}

.sequence-participant-box {
  fill: var(--shape-fill);
  stroke: var(--shape-stroke);
  stroke-width: 1.5;
}

.sequence-participant-label,
.sequence-note-label,
.sequence-fragment-keyword,
.sequence-fragment-label,
.sequence-divider-label {
  font: 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  fill: var(--fg);
}

.sequence-message-line {
  stroke: var(--fg);
  stroke-width: 1.5;
}

.sequence-message-line.dashed {
  stroke-dasharray: 6 3;
}

.sequence-message-label {
  font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  fill: var(--muted);
}

.sequence-arrowhead {
  fill: var(--fg);
}

.sequence-arrowhead-open {
  stroke: var(--fg);
  stroke-width: 1.5;
  fill: none;
}

.sequence-failure-mark {
  stroke: var(--error);
  stroke-width: 2;
}

.sequence-activation-bar {
  fill: var(--shape-fill);
  stroke: var(--shape-stroke);
  stroke-width: 1.25;
}

.sequence-note-box {
  fill: #fdf6d8;
  stroke: var(--border);
  stroke-width: 1.25;
}

.sequence-divider-line {
  stroke: var(--border);
  stroke-width: 1.25;
}

.sequence-divider-label-box {
  fill: var(--bg);
  stroke: var(--border);
  stroke-width: 1.25;
}

.sequence-fragment-box {
  fill: none;
  stroke: var(--muted);
  stroke-width: 1.25;
  stroke-dasharray: 3 3;
}

.sequence-fragment-tab {
  fill: #eef1f5;
  stroke: var(--muted);
  stroke-width: 1.25;
}

.sequence-fragment-keyword {
  font-weight: 700;
  font-style: italic;
}

.sequence-fragment-else-line {
  stroke: var(--muted);
  stroke-width: 1;
  stroke-dasharray: 3 3;
}

/* Caret-follow highlighting: whichever element the text caret's current
   line belongs to (see app.js's caretHighlightedSequenceLine and
   sequence-renderer.js's own `highlightedLine` param) gets a
   ".highlighted" class alongside its base one. One shared accent-color
   treatment per element type, mirroring the flowchart .selected rules
   above (var(--accent), a step up in stroke-width) rather than each
   element inventing its own look. */
.sequence-participant-box.highlighted,
.sequence-activation-bar.highlighted,
.sequence-note-box.highlighted,
.sequence-fragment-box.highlighted,
.sequence-fragment-tab.highlighted {
  stroke: var(--accent);
  stroke-width: 2.5;
}

.sequence-message-line.highlighted,
.sequence-divider-line.highlighted,
.sequence-divider-label-box.highlighted,
.sequence-fragment-else-line.highlighted {
  stroke: var(--accent);
  stroke-width: 2;
}

.sequence-message-label.highlighted,
.sequence-fragment-label.highlighted {
  fill: var(--accent);
}

.sequence-arrowhead.highlighted {
  fill: var(--accent);
}

.sequence-arrowhead-open.highlighted {
  stroke: var(--accent);
}

/* Gantt charts (type: gantt) — entirely separate rules from the
   flowchart/sequence classes above, drawn by gantt-renderer.js. Bar/
   diamond fill color comes from literal hex (STYLE_COLOUR_HEX in
   gantt-renderer.js), same reasoning diagram-export.js's own detached-
   SVG palettes already established — not repeated as CSS custom
   properties here since these are drawn as plain `fill` attributes, not
   classed rules, matching how sequence's own arrowhead/note fills work. */
.gantt-title {
  font: 600 16px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  fill: var(--fg);
}

/* Mind maps (type: mindmap) — mindmap-renderer.js. Only a title rule is
   needed here (plus .mindmap-footer, grouped with every other diagram
   type's own footer above) — every other element (node boxes/text,
   connecting lines, the collapse triangle) is drawn with literal hex
   fills directly, the same "no CSS class needed" choice Gantt's own
   segment fills already made, so there's nothing else to keep in sync
   with EXPORT_STYLE_MINDMAP. */
.mindmap-title {
  font: 600 16px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  fill: var(--fg);
}

.gantt-grid-line {
  stroke: var(--border);
  stroke-width: 1;
}

.gantt-column-label {
  font: 11px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  fill: var(--muted);
}

.gantt-row-text {
  font: 12px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  fill: var(--fg);
}

.gantt-row-text.highlighted {
  fill: var(--accent);
}

/* Display-only row numbering (docs/file-format.md § Gantt charts) —
   never written to the file. Smaller and more muted than
   .gantt-row-text so it reads as a reference number, not part of the
   task label. */
.gantt-row-number {
  font: 10px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  fill: var(--muted);
}

.gantt-row-highlight {
  fill: none;
  stroke: var(--accent);
  stroke-width: 1.5;
}

.gantt-collapse-triangle {
  fill: var(--muted);
}

.gantt-segment.highlighted {
  stroke: var(--accent);
  stroke-width: 2;
}

/* progress:n% (docs/file-format.md § Gantt charts) — a thin black core
   line over a slightly thicker white halo, so it stays visible against
   every bar fill color, including the default uncoloured bar's own
   near-black fill. Literal hex, not var(--fg)/var(--bg): this indicator
   must read the same regardless of the page's own light/dark theme,
   the same reasoning diagram-export.js's own detached-SVG palette
   already established for bar/diamond fills just above. */
.gantt-progress-halo {
  stroke: #ffffff;
  stroke-width: 3;
  stroke-linecap: round;
}

.gantt-progress-core {
  stroke: #000000;
  stroke-width: 1.25;
  stroke-linecap: round;
}

/* The toolbar's "Print…" button (see app.js) just calls window.print()
   — printing operates on the *live*, already-styled DOM (unlike PNG
   export's detached-SVG technique, see diagram-export.js), so every CSS
   custom property/class here still resolves normally. Only two things
   need doing: hide everything except the diagram, and neutralize the
   on-screen scroll/flex constraints that would otherwise clip a diagram
   larger than one screen's worth of space instead of letting the
   browser paginate it naturally. Placed after the dark-mode block above
   so its :root override wins on cascade order for any property both
   touch. */
@media print {
  header,
  .editor-wrap,
  #splitter,
  #shape-context-menu,
  .inspector {
    display: none !important;
  }

  body,
  main {
    display: block;
    height: auto;
    overflow: visible;
  }

  .canvas-scroll {
    overflow: visible;
    padding: 0;
  }

  /* Forces the same light-mode values diagram-export.js's PNG path
     hardcodes — belt-and-suspenders against browsers that keep
     evaluating prefers-color-scheme: dark while printing (inconsistent
     across engines), matching the Mac export's "always force light"
     decision rather than trusting cascade order alone everywhere.
     --border added alongside the sequence-diagram renderer: flowchart's
     own .shape/.connector-* classes never reference it (only UI chrome
     like .toolbar/.field does), but sequence-renderer.js's lifelines/
     fragment boxes/notes/dividers do — without forcing it here too,
     printing a sequence diagram while the system is in dark mode would
     print those in dark mode's #3a3a3c instead of light mode's #d8d8d8. */
  :root {
    --bg: #ffffff !important;
    --fg: #1a1a1a !important;
    --muted: #666666 !important;
    --border: #d8d8d8 !important;
    --shape-fill: #ffffff !important;
    --shape-stroke: #1a1a1a !important;
  }

  /* The one place footer: actually becomes visible for the PDF export
     path (Print…) — see .diagram-footer's own comment above for why
     it's always drawn into the live SVG but display:none until now. */
  .diagram-footer,
  .sequence-footer,
  .gantt-footer,
  .mindmap-footer {
    display: inline;
  }
}
