/* THEME TOKENS
   ==================================================
   DARK IS THE DEFAULT. The base :root block is the dark palette; light applies only when
   the user has explicitly chosen it. prefers-color-scheme is deliberately NOT consulted,
   so a first visit is dark regardless of the visitor's OS setting.

   Because "dark" is now simply the absence of a choice, every dark-only rule in this file
   is written as :root:not([data-theme="light"]) - one guard direction throughout, and no
   duplicated declaration blocks.

   To go back to following the OS instead: put the light values on bare :root, wrap the
   dark values in @media (prefers-color-scheme: dark) guarded by
   :root:not([data-theme="light"]), and repeat them under :root[data-theme="dark"].

   --bg-canvas is the same in both palettes on purpose. Node and edge colours come from
   Color.DARK_GREY / Color.LIGHT_GREY in pynode_graphlib.py, so a dark graph
   canvas would destroy their contrast. In dark mode the Output panel is a light rectangle
   inside dark chrome. That is intentional - please don't "fix" it. */

:root {
    /* Tells the browser to render native widgets - inputs, buttons, scrollbars -
       in the matching scheme. Without this, native form controls (such as the field
       the console injects for input()) stay light-mode white on a dark page. */
    color-scheme: dark;

    --bg-page: #0e0e10;
    --bg-body: #141416;
    --bg-app: #1b1b1d;
    --bg-panel: #1e1e1e;
    --bg-inset: #252526;
    --bg-chrome: #2d2d30;
    --bg-button: #3a3a3d;
    --bg-button-live: #4a4a4f;
    --bg-chip: #37373a;
    --bg-canvas: #FFFFFF;

    --border: #3f3f46;
    --rule: #3f3f46;
    --divider-dark: #1a1a1c;
    --divider-light: #4a4a50;

    --text: #e6e6e6;
    --text-invert: #ffffff;
    --text-muted: #8a8a8a;
    --link: #4ec9b0;

    --shadow: rgba(0, 0, 0, 0.6);
    --shadow-soft: rgba(0, 0, 0, 0.5);
    --shadow-strong: rgba(0, 0, 0, 0.85);
    --shadow-mid: rgba(0, 0, 0, 0.7);

    --c-ok: #6dd36d;
    --c-err: #ff6b6b;
    --c-warn: #e0a030;

    /* Header run controls. The glyph colours are shared by both palettes because the
       header they sit in is dark grey in both; only the button face changes. */
    --run-bg: #3a3a3d;
    --run-bg-hover: #4a4a4f;
    --run-play: #4ade80;
    --run-pause: #fbbf24;
    --run-stop: #f87171;
    --run-restart: #60a5fa;
    --run-step: #c4b5fd;

    /* Debugger marks in the editor. Monaco cannot read custom properties, but these
       are used by plain CSS classes on its decorations, which can. */
    --dbg-breakpoint: #e5484d;
    --dbg-arrow: #fbbf24;
    --dbg-line: rgba(251, 191, 36, 0.16);
    --dbg-changed: rgba(251, 191, 36, 0.22);
}

:root[data-theme="light"] {
    color-scheme: light;

    --bg-page: #525252;
    --bg-body: #ECECEC;
    --bg-app: #BBBBBB;
    --bg-panel: #FFFFFF;
    --bg-inset: #F0F0F0;
    --bg-chrome: #525252;
    --bg-button: #ACACAC;
    --bg-button-live: #6E6E6E;
    --bg-chip: #E0E0E0;
    --bg-canvas: #FFFFFF;

    --border: #B1B1B1;
    --rule: #B0B0B0;
    --divider-dark: #2E2E2E;
    --divider-light: #6A6A6A;

    --text: #000000;
    --text-invert: #FFFFFF;
    --text-muted: #CBCBCB;
    --link: teal;

    --shadow: rgba(0, 0, 0, 0.2);
    --shadow-soft: rgba(0, 0, 0, 0.15);
    --shadow-strong: rgba(0, 0, 0, 0.75);
    --shadow-mid: rgba(0, 0, 0, 0.5);

    --c-ok: green;
    --c-err: red;
    --c-warn: #a06000;

    --run-bg: #3d3d3d;
    --run-bg-hover: #2f2f2f;

    --dbg-breakpoint: #d32f2f;
    --dbg-arrow: #b7791f;
    --dbg-line: rgba(255, 196, 0, 0.22);
    --dbg-changed: rgba(255, 196, 0, 0.35);
}

html {
    font-size: 14px;
}

hr {
    display: block;
    height: 1px;
    border: 0;
    border-top: 1px solid var(--rule);
    margin: 1em 0;
    padding: 0;
}

/* PAGE COLORS
   ================================================== */

html, body {
    color: var(--text);
    transition: background-color 0.2s ease, color 0.2s ease;
    font-family: "Arial";
    -webkit-font-smoothing: antialiased;
    font-smoothing: antialiased;
    font-weight: 400;
    font-style: normal;
    text-align: left;
    height: 100%;
    margin: 0;
    padding: 0;
    background-color: var(--bg-chrome);
}

/* GENERAL
   ================================================== */

body .pynodeContainer {
    background-color: var(--bg-body);
}

body .footerText {
    color: var(--text-muted);
}

body .pageInfoWrapper {
    background-color: var(--bg-chrome);
}

/* MAIN TYPE STYLES AND LINKS
   ================================================== */

@font-face {
    font-family: Oswald;
    src: url('Oswald-Regular.ttf');
}

h1 {
    color: var(--text);
    font: 3.2rem/1.1 'Oswald', 'Arial', sans-serif;
    margin: 0;
}

h2 {
    color: var(--text);
    font: 1.8rem/1.1 'Oswald', 'Arial', sans-serif;
    margin: 0;
}

h3 {
    font: 1.4rem/1.1 'Oswald', 'Arial', sans-serif;
    margin: 0;
}

h4 {
    font: 1.2rem/1.1 'Oswald', 'Arial', sans-serif;
    margin: 0;
}

h5 {
    font: 1.0rem/1.1 'Oswald', 'Arial', sans-serif;
    margin: 0;
}

p {
    font: 1.2rem/1.1 Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif;
    font-size: 14px;
    color: var(--text);
    margin: 0;
}

a {
    text-decoration: none;
    color: var(--link);
}

a:hover {
    text-decoration: none;
}

a.anchor {
    display: block;
    position: relative;
    top: -150px;
    visibility: hidden;
}

/* FONT STYLES
   ================================================== */

.pageTextSmall {
    margin: 0;
    font-family: Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif;
    font-size: 11px;
    color: var(--text);
}

.pageTextGap {
    margin: 0;
    font-size: 5px;
}

.footerText {
    margin: 0;
    font-size: 12px;
}

/* CONTAINERS
   ================================================== */

.pynodeContainer {
    min-width: 900px;
    min-height: 100%;
    position: relative;
    overflow: hidden;
}

.pynodeOutputContainer {
    min-width: 430px;
    min-height: 360px;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

.pynodeEditorContainer {
    min-width: 100px;
    min-height: 100px;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

.pynodeConsoleContainer {
    min-width: 100px;
    min-height: 100px;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

/* PYNODE
   ================================================== */

/* The app fills the viewport and the page itself does not scroll: the only scrollbars are
   Monaco's, the console's and the docs drawer's. The header is fixed at --header-h and
   the app band takes exactly the rest.

   Tuned for ~1280x630 CSS px (1080p at 150% scaling), the common laptop default, where
   the output must stay at its full 500x400 AND the console must still fit below it.
   Vertical space is what runs out, so the main page's chrome is compact (see COMPACT
   MAIN PAGE): the run controls live in the page header, the layout toggles in the
   Output title bar, and the panels have no decorative bottom borders. --out-h is the height left over once the fixed
   parts of the right column are paid for:
       header 40 + app padding 16 + output title 32 + gap 8 + console minimum 120 = 216px
   Change any of those and this number must follow. At 630px that leaves exactly 400 for
   the canvas and 134 for the console; on shorter screens the box scales down (keeping
   the 5:4 shape of the 500x400 logical canvas, see the viewBox set in index.html) rather
   than pushing the console off the bottom. */
body.pynode {
    --header-h: 40px;
    --out-h: clamp(240px, calc(100vh - 216px), 400px);
    --out-w: calc(var(--out-h) * 1.25);
    /* Only shows a scrollbar below the minimum height of .appWrapper. */
    overflow: auto;
}

body.pynode .pageWholeWrapper {
    width: 100%;
}

body.pynode .pageBanner {
    width: 100%;
    height: 20px;
    padding-top: 5px;
    padding-bottom: 5px;
    padding-left: 20px;
    background-color: var(--bg-chrome);
    color: var(--text-invert);
}

body.pynode .appWrapper {
    height: calc(100vh - var(--header-h));
    /* 240px output + 216px fixed, less the header. Below this the page scrolls once
       rather than clipping the console. */
    min-height: 416px;
    box-sizing: border-box;
    background-color: var(--bg-app);
    position: relative;
    padding-bottom: 8px;
    padding-top: 8px;
    box-shadow: inset 0 16px 20px -20px var(--shadow-strong), inset 0 -16px 20px -20px var(--shadow-mid);
}

body.pynode .appContainer {
    height: 100%;
    box-sizing: border-box;
    display: flex;
    gap: 8px;
    padding: 0 8px;
}

body.pynode .appLeft {
    flex: 1;
    min-width: 0;
    height: 100%;
}

body.pynode .editorWrapper, body.pynode_editor .editorWrapper {
    width: 100%;
    height: 100%;
    background-color: var(--bg-panel);
    box-shadow: 0 0 20px 0px var(--shadow);
    border-bottom: 10px solid var(--bg-chrome);
    box-sizing: border-box;
}

body.pynode #editorBox {
    width: 100%;
    height: 100%;   /* no title bar: its icons moved up into the page header */
    visibility: hidden;
}

body.pynode_editor #editorBox {
    width: 100%;
    height: calc(100% - 40px);
}

body.pynode .buttonBarLayoutPanel, body.pynode_output .buttonBarLayoutPanel {
    float: right;
    padding-top: 2px;
    padding-left: 20px;
    width: 80px;
}

body.pynode .layoutButtonOn, body.pynode_output .layoutButtonOn {
    width: 22px;
    height: 22px;
    padding-top: 2px;
    padding-left: 2px;
    float: left;
    margin-right: 8px;
    background-color: var(--bg-chip);
    box-shadow: inset 0px 0px 6px 0px var(--shadow);
}

body.pynode .layoutButtonOff, body.pynode_output .layoutButtonOff {
    width: 22px;
    height: 22px;
    padding-top: 2px;
    padding-left: 2px;
    float: left;
    margin-right: 8px;
    cursor: pointer;
}

body.pynode #runPlay, body.pynode_output #runPlay, body.pynode_debug #runPlay {
    display: inherit;
}

body.pynode #runPlayLoad, body.pynode_output #runPlayLoad, body.pynode_debug #runPlayLoad {
    display: none;
}

body.pynode #runPause, body.pynode_output #runPause, body.pynode_debug #runPause {
    display: none;
}

body.pynode #runResume, body.pynode_output #runResume, body.pynode_debug #runResume {
    display: none;
}

body.pynode .editorHeader {
    height: 20px;
    width: 100%;
    background-color: var(--bg-inset);
}

/* Monaco sizes itself to this container via automaticLayout, so it must be a normal
   in-flow box. The old rules were position:absolute with a placeholder 100px width that
   js/resize.js overwrote at runtime; that hack is no longer needed. */
body.pynode #editor {
    width: 100%;
    height: 100%;
}

body.pynode_editor #editor {
    width: 100%;
    height: 100%;   /* was calc(100% - 50px), which left an empty band under the editor */
}

body.pynode .appRight {
    flex: 0 0 var(--out-w);
    /* A flex item will not shrink below its content by default, so one long console line
       (spaces arrive as &nbsp; and never wrap) widened this column and pushed the editor
       off the screen. The console scrolls sideways instead - see #console. */
    min-width: 0;
    display: flex;
    flex-direction: column;
    height: 100%;
}

/* Title 32 + canvas + button bar 40 + border 10. */
body.pynode .outputWrapper {
    width: 100%;
    height: calc(var(--out-h) + 32px);   /* title bar */
    flex: none;
    background-color: var(--bg-panel);
    box-shadow: 0 0 20px 0px var(--shadow);
    border-bottom: 10px solid var(--bg-chrome);
    box-sizing: border-box;
}

body.pynode_output .outputWrapper {
    width: 100%;
    height: 100%;
    background-color: var(--bg-panel);
    border-bottom: 10px solid var(--bg-chrome);
    box-sizing: border-box;
}

body.pynode_output .outputPageWrapper {
    width: 100%;
    height: 100%;
    background-color: var(--bg-app);
}

body.pynode_editor .editorPageWrapper {
    width: 100%;
    height: 100%;
    background-color: var(--bg-app);
}

body.pynode_console .consolePageWrapper {
    width: 100%;
    height: 100%;
    background-color: var(--bg-app);
}

body.pynode #outputBox {
    width: var(--out-w);
    height: var(--out-h);
    background-color: var(--bg-canvas);
}

body.pynode_output #outputBox {
    width: 100%;
    height: calc(100% - 40px);   /* title bar only; the run controls live in it */
    background-color: var(--bg-canvas);
}

body.pynode #output {
    width: 100%;
    height: 100%;
    display: block;
}

/* While the output is popped out the popup draws every run, so the docked canvas is
   replaced by a pointer to it (body.output-popped is set by js/pynode_host.js). */
body.pynode .outputPoppedNote {
    display: none;
}

body.pynode.output-popped #output {
    display: none;
}

body.pynode.output-popped #outputBox {
    background-color: var(--bg-inset);
}

body.pynode.output-popped .outputPoppedNote {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 8px;
    height: 100%;
    text-align: center;
}

body.pynode .outputPoppedNote p {
    color: var(--text-muted);
}

/* Scales the 500x400 viewBox to the box. CSS beats the width/height attributes greuler
   writes on every update. */
body.pynode #output svg {
    width: 100%;
    height: 100%;
    display: block;
}

body.pynode_output .outputClass {
    width: 100%;
    height: 100%;
    display: block;
}

body.pynode .consoleWrapper {
    width: 100%;
    flex: 1;
    min-height: 120px;
    margin-top: 8px;
    background-color: var(--bg-panel);
    box-shadow: 0 0 20px 0px var(--shadow);
    border-bottom: 10px solid var(--bg-chrome);
    box-sizing: border-box;
}

body.pynode_console .consoleWrapper {
    width: 100%;
    height: 100%;
    background-color: var(--bg-panel);
    box-shadow: 0 0 20px 0px var(--shadow);
    border-bottom: 10px solid var(--bg-chrome);
    box-sizing: border-box;
}

body.pynode .consoleBox, body.pynode_console .consoleBox {
    background-color: var(--bg-inset);
    width: calc(100% - 25px);
    height: calc(100% - 62px);
    padding-top: 10px;
    padding-bottom: 12px;
    padding-left: 10px;
    padding-right: 15px;
}

body.pynode #console, body.pynode_console #console {
    width: 100%;
    height: 100%;
    background-color: var(--bg-panel);
    border: 1px solid var(--border);
    padding-left: 2px;
    font-family: monospace;
    /* Both axes: long lines never wrap (spaces are &nbsp;), so they scroll sideways here
       rather than widening the panel. */
    overflow: auto;
}

/* The field input() appends to the console. Styled to vanish into the transcript so the
   line reads like a REPL prompt rather than a form. */
.consoleInput {
    font-family: inherit;
    font-size: inherit;
    color: inherit;
    background: transparent;
    border: none;
    outline: none;
    padding: 0;
    margin: 0;
    min-width: 12ch;
}

body.pynode .appSectionTitle, body.pynode_output .appSectionTitle, body.pynode_editor .appSectionTitle, body.pynode_console .appSectionTitle, body.pynode_docs .appSectionTitle,
body.pynode_debug .appSectionTitle {
    color: var(--text-invert);
    background-color: var(--bg-chrome);
    padding-left: 10px;
    width: calc(100% - 10px);
    height: 30px;
    padding-top: 10px;
}

/* COMPACT MAIN PAGE
   ==================================================
   The main page's chrome is tighter than the popups' so that a full-size 500x400 output
   and a readable console both fit on a ~630px-tall viewport. Every height here feeds the
   216px in --out-h. The popups keep their roomier layout. */

body.pynode .appSectionTitle {
    height: 26px;
    padding-top: 6px;
}

/* The 10px chrome strip under each panel is decoration the main page cannot afford. */
body.pynode .editorWrapper, body.pynode .outputWrapper, body.pynode .consoleWrapper {
    border-bottom: none;
}

body.pynode .consoleBox {
    width: calc(100% - 16px);
    height: calc(100% - 44px);   /* title 32 + padding 12 */
    padding: 6px 8px;
}

/* The Output title bar carries the run controls (index.html moved the button row up
   into it), so it is a flex row: title, buttons, layout toggles, pop-out icon. */
body.pynode .outputWrapper .appSectionTitle {
    display: flex;
    align-items: center;
    gap: 10px;
    height: 32px;
    padding-top: 0;
}

body.pynode .outputWrapper .appSectionTitle .floatLeft,
body.pynode .outputWrapper .appSectionTitle .appSectionEnlarge {
    float: none;
    flex: none;
}

body.pynode .outputWrapper .appSectionTitle .appSectionEnlarge h3 {
    display: flex;
}

body.pynode .buttonBarLayoutPanel {
    float: none;
    flex: none;
    width: auto;
    margin-left: auto;
    padding: 0;
    display: flex;
}

body.pynode .layoutButtonOn, body.pynode .layoutButtonOff {
    margin-right: 4px;
}


body.pynode .appSectionEnlarge, body.pynode_output .appSectionEnlarge {
    float: right;
    padding-right: 10px;
}

body.pynode .appSectionBarIcon, body.pynode_editor .appSectionBarIcon {
    width: 30px;
    height: 20px;
    margin-bottom: 2px;
    float: right;
}

body.pynode .appSectionBarIconFirst, body.pynode_editor .appSectionBarIconFirst {
    width: 20px;
    height: 20px;
    float: right;
    padding-left: 12px;
}

body.pynode .appSectionBarIconDivider, body.pynode_editor .appSectionBarIconDivider {
    width: 0px;
    height: 28px;
    margin-top: -4px;
    float: right;
    border-right: 1px solid var(--divider-dark);
    border-left: 1px solid var(--divider-light);

}

/* HEADER
   ================================================== */

/* One slim bar replaces the old 500px hero. The intro text and credits moved into the
   docs (pynode_docs.html); the example links became the Examples menu. */
/* Three columns, so the run controls sit at the true centre of the page whatever the two
   sides hold. */
body.pynode .pageHeader {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 14px;
    height: var(--header-h);
    box-sizing: border-box;
    padding: 0 20px;
    background-color: var(--bg-chrome);
    color: var(--text-invert);
    position: relative;
    z-index: 50;
}

body.pynode .pynodeLogo {
    width: 28px;
    height: 28px;
}

body.pynode .pynodeLogo img {
    display: block;
}

body.pynode .pynodeTitle {
    font: 1.9rem/1 'Oswald', 'Arial', sans-serif;
}

body.pynode .examplesMenu {
    position: relative;
    margin-left: 12px;
}

/* The header is dark grey (--bg-chrome) in BOTH palettes, so these use the run controls'
   button faces. --bg-button is light grey in the light palette, which left white text on
   it at poor contrast. */
body.pynode .examplesMenu summary,
body.pynode .docsButton {
    font: 1.1rem/1 'Oswald', 'Arial', sans-serif;
    color: var(--text-invert);
    background-color: var(--run-bg);
    border-radius: 4px;
    padding: 6px 12px;
    cursor: pointer;
    list-style: none;
    user-select: none;
}

body.pynode .examplesMenu summary::-webkit-details-marker {
    display: none;
}

body.pynode .examplesMenu summary::after {
    content: " \25BE";
}

body.pynode .examplesMenu[open] summary,
body.pynode .examplesMenu summary:hover,
body.pynode .docsButton:hover,
body.pynode.docs-open .docsButton {
    background-color: var(--run-bg-hover);
}

body.pynode .examplesMenuList {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    min-width: 240px;
    padding: 6px 0;
    background-color: var(--bg-panel);
    border: 1px solid var(--border);
    box-shadow: 0 6px 20px 0px var(--shadow);
}

body.pynode .examplesMenuList a {
    display: block;
    padding: 7px 14px;
    font-size: 14px;
    font-family: Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif;
}

body.pynode .examplesMenuList a:hover {
    background-color: var(--bg-inset);
}

body.pynode .headerLeft, body.pynode .headerRight {
    display: flex;
    align-items: center;
    gap: 14px;
    min-width: 0;
}

body.pynode .headerRight {
    justify-content: flex-end;
}

/* Header icon strips: Open/Save on the left, editor pop-out and theme toggle on the
   right. Same icon boxes as the popups' title-bar strips, so the spacing matches, but a
   flex row in SOURCE order - those strips float right, which reverses their children. */
body.pynode .headerActions, body.pynode_editor .headerActions {
    display: flex;
    /* Top, not centre: the icon boxes carry a 2px bottom margin the toggle does not, so
       centring left the toggle 1px low. The float strips this replaces aligned tops. */
    align-items: flex-start;
    height: 20px;
    padding-right: 0;
}

body.pynode .headerActions > div, body.pynode_editor .headerActions > div {
    float: none;
}

/* In the editor popup the strip sits at the right of the Source title bar, as the old
   floated icons did. */
body.pynode_editor .headerActions {
    float: right;
    padding-right: 10px;
}

.docsSubtitle {
    color: var(--text-muted);
    margin-top: 4px;
}

/* RUN CONTROLS
   ==================================================
   Icon-only and colour-coded: green play/resume, amber pause, red stop, blue restart.
   Shared by the main page's header, the output popup's and the debugger window's title
   bars - same markup, so these rules are deliberately not scoped to one page.
   #runPlay/#runPlayLoad/#runPause/#runResume are swapped by js/pynode_host.js show(),
   which sets display:inherit on the live one - so #run is a flex box and the icon
   inherits that. Room is left in the group for debugger buttons later. */
.runControls {
    display: flex;
    align-items: center;
    gap: 6px;
}

.runControls .runButton {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 28px;
    border-radius: 4px;
    background-color: var(--run-bg);
    cursor: pointer;
    transition: background-color 0.15s ease, opacity 0.15s ease;
}

.runControls .runButton:hover {
    background-color: var(--run-bg-hover);
}

.runControls .runButton:focus-visible {
    outline: 2px solid var(--link);
    outline-offset: 1px;
}

.runIcon svg {
    display: block;
    width: 18px;
    height: 18px;
    fill: currentColor;
}

.iconPlay { color: var(--run-play); }
.iconPause { color: var(--run-pause); }
.iconStop { color: var(--run-stop); }
.iconRestart { color: var(--run-restart); }
.iconLoad { color: var(--text-muted); }

.iconLoad svg {
    animation: runSpin 1s linear infinite;
}

@keyframes runSpin {
    to { transform: rotate(360deg); }
}

@media (prefers-reduced-motion: reduce) {
    .iconLoad svg { animation: none; }
}

/* Nothing to stop or restart while idle or still loading Python; don't let the red and
   blue read as live. data-run-state is set by PyNodeHost.setState. */
body[data-run-state="idle"] #stop,
body[data-run-state="boot"] #stop,
body[data-run-state="boot"] #restart {
    opacity: 0.35;
    cursor: default;
}

/* Debugger buttons: Step over / into / out, and the button that opens the debugger
   window. Stepping only makes sense while stopped - except Step into, which from idle
   starts a run stopped on its first line. */
.runDivider {
    width: 1px;
    height: 20px;
    margin: 0 4px;
    background-color: var(--divider-light);
}

.iconStep, .iconDebug { color: var(--run-step); }

body:not([data-run-state="paused"]) #stepOver,
body:not([data-run-state="paused"]) #stepOut,
body:not([data-run-state="paused"]):not([data-run-state="idle"]) #stepInto {
    opacity: 0.35;
    cursor: default;
}

/* DOCS
   ================================================== */

/* Slides over the app from the right, below the header. Its content is fetched from
   pynode_docs.html on first open - see toggleDocs() in index.html. */
body.pynode .docsDrawer {
    position: fixed;
    top: var(--header-h);
    right: 0;
    bottom: 0;
    width: min(680px, 92vw);
    overflow-y: auto;
    background-color: var(--bg-panel);
    box-shadow: 0 0 30px 0px var(--shadow-strong);
    transform: translateX(105%);
    visibility: hidden;
    transition: transform 0.2s ease, visibility 0s linear 0.2s;
    z-index: 40;
}

body.pynode.docs-open .docsDrawer {
    transform: none;
    visibility: visible;
    transition: transform 0.2s ease, visibility 0s;
}

@media (prefers-reduced-motion: reduce) {
    body.pynode .docsDrawer, body.pynode.docs-open .docsDrawer {
        transition: none;
    }
}

/* The title bar stays put while the docs scroll under it. */
.docsDrawerBar, body.pynode_docs .appSectionTitle {
    position: sticky;
    top: 0;
    z-index: 1;
}

.docsClose {
    display: block;
    font: 2rem/20px 'Arial', sans-serif;
    text-align: center;
    color: var(--text-invert);
}

.docsContent .pageWhole {
    padding: 24px 28px;
    border-bottom: 1px solid var(--rule);
}

.docsContent h1 {
    font-size: 2.4rem;
}

body.pynode_docs .docsPageWrapper {
    min-height: 100%;
    background-color: var(--bg-panel);
}

body.pynode code, body.pynode_docs code {
    font-family: monospace;
    font: 1.0rem/1.5 monospace;
    border: 1px solid var(--border);
    background-color: var(--bg-inset);
    padding-left: 2px;
    padding-right: 2px;
    padding-bottom: 1px;
}

body.pynode .codeInline, body.pynode_docs .codeInline {
    border: none;
}

body.pynode li, body.pynode_docs li {
    margin: 5px 0px;
}

body .floatLeft {
    float: left;
}

body .floatRight {
    float: right;
}

/* FOOTER
   ================================================== */

.pageInfoWrapper {
    width: 100%;
    height: 12px;
    text-align: center;
    padding-top: 5px;
    padding-bottom: 5px;
    position: absolute;
    bottom: 0;
}

.pageInfoWrapperHome {
    width: 100%;
    height: 10px;
    text-align: center;
    padding-top: 5px;
    padding-bottom: 5px;
}
/* THEME CONTROLS AND DARK-MODE ASSET FIXES
   ================================================== */

/* #run/#stop/#restart used to get background-color and cursor set from JavaScript as
   INLINE styles, which beat any stylesheet rule and so defeated theming entirely. Never
   style them from JS: they are .runButton now, themed under RUN CONTROLS. */

/* Injected by js/pynode_theme.js into the .appSectionTitle icon strip, which is the one
   header structure all four documents share. Children there are float:right. */
.themeToggle {
    float: right;
    width: 20px;
    height: 20px;
    padding-left: 12px;
    cursor: pointer;
    color: var(--text-invert);
}

/* display:block rather than an inline glyph with vertical-align: the inline box depended
   on the 21.56px line-height and landed 1.4px from the box top, while the raster icons in
   .appSectionBarIcon land at 3px. An explicit margin matches them exactly and does not
   drift if the font metrics change. */
.themeToggle svg {
    width: 16px;
    height: 16px;
    display: block;
    margin-top: 3px;
    fill: currentColor;
}

/* Sharing the strip with the other icons, so take their box: 30px wide with the glyph
   flush left, which is what produces the 14px gap they leave between themselves. The
   toggle's own 20px + 12px padding-left left only 4px, so it read as crowded against its
   neighbour. Scoped to .appSectionEnlarge because in the console and output popups the
   toggle is the only icon in the strip and sits against the right edge instead. */
.appSectionEnlarge .themeToggle {
    width: 30px;
    padding-left: 0;
}

/* Which glyph shows is pure CSS. Dark is the default, so the moon is the default glyph
   and the sun appears only under an explicit light choice. */
.themeToggle .iconSun { display: none; }
.themeToggle .iconMoon { display: inline; }

:root[data-theme="light"] .themeToggle .iconSun { display: inline; }
:root[data-theme="light"] .themeToggle .iconMoon { display: none; }

/* Raster fixes for dark chrome. play/pause/stop/restart/resize/import/save are white
   glyphs on transparent and already read correctly, so they are left alone. These do not: */
:root:not([data-theme="light"]) .layoutButtonOn img,
:root:not([data-theme="light"]) .layoutButtonOff img { filter: brightness(1.8); }

/* Which layout mode is active. A chip one shade off the button bar was all that marked it,
   which in the dark palette (#37373a on #252526) was almost invisible. An accent ring on
   the selected mode and a dimmed unselected one read clearly in both themes. */
body.pynode .layoutButtonOn, body.pynode_output .layoutButtonOn {
    box-shadow: inset 0 0 0 2px var(--link);
}

body.pynode .layoutButtonOff, body.pynode_output .layoutButtonOff {
    opacity: 0.5;
}

body.pynode .layoutButtonOff:hover, body.pynode_output .layoutButtonOff:hover {
    opacity: 1;
}

/* logo.png is black artwork on an OPAQUE white square: invert makes it white-on-black,
   then screen against the dark header drops the black away, leaving just the glyph. In
   BOTH themes: the header is dark grey in the light palette too, where the untreated
   white square looked pasted on. */
.pynodeLogo img { filter: invert(1); mix-blend-mode: screen; }


/* OUTPUT POPUP TITLE BAR
   ==================================================
   Mirrors the main page header: title left, run controls centred, layout and theme
   toggles right. The button row that used to sit under the canvas is gone. */
body.pynode_output .outputTitle,
body.pynode_debug .outputTitle {
    display: grid;
    grid-template-columns: 1fr auto 1fr;
    align-items: center;
    gap: 10px;
    height: 40px;
    /* Symmetric padding: the shared title-bar rule pads the left only, which put the
       centre column 5px right of the bar's true centre. */
    box-sizing: border-box;
    width: 100%;
    padding: 0 10px;
}

body.pynode_output .outputTitleRight,
body.pynode_debug .outputTitleRight {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
}

body.pynode_output .outputTitle .buttonBarLayoutPanel,
body.pynode_debug .outputTitle .buttonBarLayoutPanel {
    float: none;
    display: flex;
    width: auto;
    padding: 0;
}

body.pynode_output .outputTitle .themeToggleHost,
body.pynode_debug .outputTitle .themeToggleHost {
    float: none;
    display: flex;
    height: 20px;
    padding-right: 0;
}

/* DEBUGGER
   ==================================================
   Editor decorations (js/monaco_setup.js) and the debugger window (pynode_debug.html). */

/* Breakpoint dot in the glyph margin. */
.pynodeBreakpoint {
    cursor: pointer;
}

.pynodeBreakpoint::before {
    content: "";
    display: block;
    width: 10px;
    height: 10px;
    margin: 5px 0 0 5px;
    border-radius: 50%;
    background-color: var(--dbg-breakpoint);
}

/* The line the debugger is stopped at: a highlight plus an arrow drawn over the dot. */
.pynodeDebugLine {
    background-color: var(--dbg-line);
}

.pynodeDebugArrow::after {
    content: "";
    position: absolute;
    top: 4px;
    left: 4px;
    border-style: solid;
    border-width: 6px 0 6px 10px;
    border-color: transparent transparent transparent var(--dbg-arrow);
}

/* The window itself never scrolls - only the panels do, and only when they overflow -
   so the title bar and its controls cannot be scrolled out of reach. */
body.pynode_debug {
    background-color: var(--bg-app);
    overflow: hidden;
}

body.pynode_debug .debugPage {
    display: flex;
    flex-direction: column;
    height: 100vh;
}

body.pynode_debug .outputTitle,
body.pynode_debug .debugStatus {
    flex: none;
}

body.pynode_debug .debugStatus {
    padding: 6px 12px;
    font-family: Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif;
    font-size: 13px;
    color: var(--text);
    background-color: var(--bg-inset);
}

body.pynode_debug .debugGrid {
    flex: 1;
    min-height: 0;
    display: grid;
    /* minmax(0, ...): a grid track otherwise grows to fit its widest unbreakable line (a
       long trace entry or value), which pushed the whole window into scrolling. */
    grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
    grid-template-rows: minmax(0, 1fr) minmax(0, 1fr);
    gap: 8px;
    padding: 8px;
}

body.pynode_debug .debugPanel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
    background-color: var(--bg-panel);
    box-shadow: 0 0 12px 0 var(--shadow);
}

body.pynode_debug .debugPanel h4 {
    display: flex;
    align-items: center;
    gap: 10px;
    white-space: nowrap;
    overflow: hidden;
    padding: 6px 10px;
    color: var(--text-invert);
    background-color: var(--bg-chrome);
}

body.pynode_debug .debugBody {
    flex: 1;
    min-height: 0;
    overflow: auto;
    margin: 0;
    padding: 4px 0;
    font: 13px/1.5 monospace;
    color: var(--text);
}

body.pynode_debug .debugList {
    list-style: none;
}

/* Values from the last stop stay on show while running or after the run, greyed. */
body.pynode_debug.debugStale .debugBody:not(.traceList) {
    opacity: 0.55;
}

body.pynode_debug .debugFrame {
    display: flex;
    justify-content: space-between;
    padding: 2px 10px;
    cursor: pointer;
}

body.pynode_debug .debugFrame:hover {
    background-color: var(--bg-inset);
}

body.pynode_debug .debugFrame.selected {
    background-color: var(--dbg-line);
    font-weight: bold;
}

body.pynode_debug .debugFrameLine,
body.pynode_debug .debugVarType,
body.pynode_debug .traceLine {
    color: var(--text-muted);
}

body.pynode_debug .debugSectionTitle {
    padding: 4px 10px 2px;
    font-weight: bold;
    color: var(--text-muted);
}

body.pynode_debug .debugVarList,
body.pynode_debug .debugVarChildren {
    list-style: none;
    margin: 0;
    padding-left: 14px;
}

body.pynode_debug .debugVarLine {
    white-space: pre;
}

body.pynode_debug .debugVarLine.changed {
    background-color: var(--dbg-changed);
}

body.pynode_debug .debugVarName {
    color: var(--link);
}

body.pynode_debug .debugVarType {
    margin-left: 10px;
    font-size: 11px;
}

body.pynode_debug .debugVarValue.error {
    color: var(--c-err);
}

body.pynode_debug .debugEmpty,
body.pynode_debug .debugVarMore {
    color: var(--text-muted);
}

body.pynode_debug summary {
    cursor: pointer;
}

body.pynode_debug .watchForm input {
    width: 100%;
    box-sizing: border-box;
    padding: 5px 10px;
    border: none;
    border-bottom: 1px solid var(--border);
    font: 13px monospace;
    color: var(--text);
    background-color: var(--bg-inset);
}

body.pynode_debug .debugWatch {
    padding-left: 4px;
}

body.pynode_debug .watchRemove,
body.pynode_debug .traceClear {
    font: inherit;
    color: var(--text-muted);
    background: none;
    border: none;
    cursor: pointer;
}

body.pynode_debug .watchRemove:hover {
    color: var(--c-err);
}

body.pynode_debug .traceRecord {
    margin-left: auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    font: 12px Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif;
    cursor: pointer;
}

body.pynode_debug .traceClear {
    font-size: 12px;
    color: var(--text-invert);
}

body.pynode_debug .traceEntry {
    display: flex;
    gap: 10px;
    padding: 0 10px;
    white-space: nowrap;
    cursor: pointer;
}

body.pynode_debug .traceEntry:hover {
    background-color: var(--bg-inset);
}

body.pynode_debug .traceFunc {
    color: var(--link);
}

body.pynode_debug .traceChanged {
    color: var(--text);
}
