/* ============================================================================
   Convergence brand theme — tokens imported from ConvergencePortalUser
   (src/index.css @theme). Keep the *-token names aligned with the portal
   frontend so values stay copy-comparable across projects.

   Dark mode: follows the OS via prefers-color-scheme by default; a future
   toggle sets data-theme="dark" | "light" on <html> to override (the inline
   script in App.razor applies the stored preference before first paint).
   ========================================================================== */

@import url('https://fonts.googleapis.com/css2?family=Lato:ital,wght@0,300;0,400;0,700;0,900;1,400;1,700&display=swap');

:root {
    /* ── Brand palette (theme-invariant) ── */
    --cn-light-azure: #f5f8fa;
    --cn-light-blue: #eaf0f6;
    --cn-mid-blue: #d4dde7;
    --cn-dark-azure: #213343;
    --cn-vivid-azure: #1398b2;
    --cn-vivid-azure-card-bg: #eefcff;
    --cn-original-brand: #41748d;
    --cn-bold-orange: #f37021;
    --cn-bold-orange-hover: #dc6b2f;
    --cn-soft-orange: #ff914d;

    /* Menu palette (the sidebar is dark-azure in BOTH themes — it is the brand) */
    --cn-menu-background: var(--cn-dark-azure);
    --cn-menu-separator: #3f5264;
    --cn-menu-active: #516f90;
    --cn-menu-hover: #425b76;
    --cn-gradient-start: var(--cn-dark-azure);
    --cn-gradient-end: var(--cn-vivid-azure);

    /* ── Semantic tokens: LIGHT ── */
    color-scheme: light;
    --cn-background: var(--cn-light-azure);
    --cn-foreground: var(--cn-dark-azure);
    --cn-heading: #1b5f7a;
    --cn-clickable: var(--cn-vivid-azure);
    --cn-clickable-hover-bg: var(--cn-light-blue);
    --cn-card-background: #ffffff;
    --cn-border: #dde5ec;
    --cn-input-background: #ffffff;
    --cn-input-disabled-background: #e9ecef;
    --cn-muted-foreground: #5b7183;
    --cn-topbar-background: #ffffff;
    /* Ribbon sits ABOVE the page: a shade off the white cards so the panel reads as raised. */
    --cn-ribbon-background: #f6f8fa;
    --cn-focus-ring: rgba(19, 152, 178, 0.35);

    /* One source of truth for the page's horizontal inset. The ribbon aligns to it and the sheet
       tab strip bleeds out through it, so a literal in three places would drift. */
    --cn-content-inset: 1.25rem;
    /* Large ribbon button height — also the fixed height of every group's control row, so tabs
       of tall buttons and tabs of compact controls produce the same panel height. Sized to fit a
       TWO-LINE label (icon + gap + 2 lines + padding); at 3.5rem a wrapping caption like
       "Previous versions" overflowed the row and made that tab 8px taller than the others. */
    --cn-ribbon-btn-h: 4rem;

    /* ── Sidebar rail ──
       Every collapsed-vs-expanded difference is a value here rather than a duplicated ruleset,
       because the rail collapses for TWO independent reasons — the user asked, or the viewport is
       narrow — and a second copy of the rules is a second thing to forget to change. NavMenu and
       MainLayout only ever read these vars; they never test the state themselves. */
    --cn-nav-w: 250px;
    --cn-nav-label: inline; /* wordmark + link captions */
    --cn-nav-mark: none; /* the compact logo shown only when collapsed */
    --cn-nav-meta: flex; /* the user card's name/email stack */
    --cn-nav-caret: block; /* the active item's trailing chevron */
    --cn-nav-icon-gap: 1rem;
    --cn-nav-justify: flex-start;
    --cn-nav-link-pad: 0.5rem 0.5rem 0.5rem 1rem;
    --cn-nav-chevron: 0deg;
    --cn-nav-sub-indent: 1.5rem; /* the rail's step rows sit under their company */
}

/* The user's explicit choice. */
:root[data-nav='collapsed'] {
    --cn-nav-w: 3.75rem;
    --cn-nav-label: none;
    --cn-nav-mark: block;
    --cn-nav-meta: none;
    --cn-nav-caret: none;
    --cn-nav-icon-gap: 0;
    --cn-nav-justify: center;
    --cn-nav-link-pad: 0.5rem 0;
    --cn-nav-chevron: 180deg;
    --cn-nav-sub-indent: 0;
}

/* Automatic collapse. Scoped to :not([data-nav='expanded']) so someone who deliberately expanded
   the rail keeps it — and it matches the specificity of the explicit rule above while coming
   later, so at narrow widths the automatic collapse wins for everyone who has not chosen. */
@media (max-width: 1000px) {
    :root:not([data-nav='expanded']) {
        --cn-nav-w: 3.75rem;
        --cn-nav-label: none;
        --cn-nav-mark: block;
        --cn-nav-meta: none;
        --cn-nav-caret: none;
        --cn-nav-icon-gap: 0;
        --cn-nav-justify: center;
        --cn-nav-link-pad: 0.5rem 0;
        --cn-nav-chevron: 180deg;
        --cn-nav-sub-indent: 0;
    }
}

/* Dark tokens live in one place: applied by media query (unless the user forced
   light) and by the explicit data-theme="dark" override. Values derived from the
   portal frontend's .dark scheme, tuned so the azure/orange accents keep contrast. */
:root[data-theme='dark'] {
    color-scheme: dark;
    --cn-background: #0d1721;
    --cn-foreground: #dbe6ee;
    --cn-heading: #7cc6d8;
    --cn-clickable: #3fb6cd;
    --cn-clickable-hover-bg: #16283a;
    --cn-card-background: #16222e;
    --cn-border: #2c3e50;
    --cn-input-background: #1b2938;
    --cn-input-disabled-background: #111c27;
    --cn-muted-foreground: #8ba1b3;
    --cn-topbar-background: #16222e;
    --cn-ribbon-background: #1b2938;
    --cn-focus-ring: rgba(63, 182, 205, 0.4);
}

/* ── Theme toggle label: pure CSS off html[data-theme] (absent = auto). Lives here
   (unscoped) because scoped CSS cannot target :root, and because the state must survive
   circuit re-renders and enhanced-nav merges without JS re-stamping. ── */
.theme-toggle .mode {
    display: none;
}

:root:not([data-theme]) .theme-toggle .mode-auto,
:root[data-theme='light'] .theme-toggle .mode-light,
:root[data-theme='dark'] .theme-toggle .mode-dark {
    display: inline;
}

/* ── Enhanced-navigation progress bar (shown by the inline script in App.razor) ── */
#nav-progress {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: 3px;
    z-index: 2000;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.15s ease-in;
    overflow: hidden;
}

#nav-progress.active {
    opacity: 1;
}

#nav-progress::after {
    content: "";
    position: absolute;
    inset: 0;
    width: 40%;
    border-radius: 2px;
    background: var(--cn-bold-orange);
    animation: nav-progress-slide 1.1s ease-in-out infinite;
}

@keyframes nav-progress-slide {
    from { transform: translateX(-100%); }
    to   { transform: translateX(350%); }
}

/* While an enhanced navigation is in flight, dim the outgoing content so the stale page
   reads as "on its way out" instead of unresponsive. Sidebar/topbar stay full-strength. */
main {
    transition: opacity 0.15s ease-in;
}

body.navigating main {
    opacity: 0.5;
    pointer-events: none;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme='light']) {
        color-scheme: dark;
        --cn-background: #0d1721;
        --cn-foreground: #dbe6ee;
        --cn-heading: #7cc6d8;
        --cn-clickable: #3fb6cd;
        --cn-clickable-hover-bg: #16283a;
        --cn-card-background: #16222e;
        --cn-border: #2c3e50;
        --cn-input-background: #1b2938;
        --cn-input-disabled-background: #111c27;
        --cn-muted-foreground: #8ba1b3;
        --cn-topbar-background: #16222e;
        --cn-ribbon-background: #1b2938;
        --cn-focus-ring: rgba(63, 182, 205, 0.4);
    }
}

/* ── Base ─────────────────────────────────────────────────────────────────── */

html, body {
    font-family: 'Lato', 'Helvetica Neue', Helvetica, Arial, sans-serif;
    background-color: var(--cn-background);
    color: var(--cn-foreground);
}

h1, h2, h3, h4, h5, h6 {
    color: var(--cn-heading);
    font-weight: 700;
}

a, .btn-link {
    color: var(--cn-clickable);
}

    a:hover, .btn-link:hover {
        color: var(--cn-original-brand);
    }

/* ── Bootstrap bridge: map brand tokens onto the components the template uses ── */

.btn-primary {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--cn-vivid-azure);
    --bs-btn-border-color: var(--cn-vivid-azure);
    --bs-btn-hover-bg: #107f95;
    --bs-btn-hover-border-color: #107f95;
    --bs-btn-active-bg: #0d6b7e;
    --bs-btn-active-border-color: #0d6b7e;
    --bs-btn-disabled-bg: var(--cn-mid-blue);
    --bs-btn-disabled-border-color: var(--cn-mid-blue);
}

/* Outline primary buttons take the brand teal, like the solid ones (Chris, 2026-09-16: Bootstrap's own deep blue
   "clashes" in dark mode). The tokens are themed, so one rule serves both modes. */
.btn-outline-primary {
    --bs-btn-color: var(--cn-clickable);
    --bs-btn-border-color: var(--cn-clickable);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--cn-vivid-azure);
    --bs-btn-hover-border-color: var(--cn-vivid-azure);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #0d6b7e;
    --bs-btn-active-border-color: #0d6b7e;
    --bs-btn-disabled-color: var(--cn-clickable);
    --bs-btn-disabled-border-color: var(--cn-clickable);
}

/* Accent CTA — the brand orange, for the one action per screen that matters */
.btn-accent {
    --bs-btn-color: #fff;
    --bs-btn-bg: var(--cn-bold-orange);
    --bs-btn-border-color: var(--cn-bold-orange);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: var(--cn-bold-orange-hover);
    --bs-btn-hover-border-color: var(--cn-bold-orange-hover);
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: var(--cn-bold-orange-hover);
    --bs-btn-active-border-color: var(--cn-bold-orange-hover);
}

.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus {
    box-shadow: 0 0 0 0.25rem var(--cn-focus-ring);
}

.card {
    --bs-card-bg: var(--cn-card-background);
    --bs-card-border-color: var(--cn-border);
    --bs-card-color: var(--cn-foreground);
}

.form-control, .form-select {
    background-color: var(--cn-input-background);
    border-color: var(--cn-border);
    color: var(--cn-foreground);
}

    .form-control:focus, .form-select:focus {
        background-color: var(--cn-input-background);
        border-color: var(--cn-clickable);
        color: var(--cn-foreground);
    }

/* Bootstrap paints disabled fields with its own --bs-secondary-bg, which this theme never overrides, so in dark
   mode a disabled field turned light gray under the light text (2026-09-14). */
.form-control:disabled, .form-select:disabled {
    background-color: var(--cn-input-disabled-background);
    color: var(--cn-muted-foreground);
    opacity: 1;
}

/* Bootstrap's select arrow is dark gray; on a dark field it disappears. */
:root[data-theme='dark'] .form-select {
    --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dbe6ee' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme='light']) .form-select {
        --bs-form-select-bg-img: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23dbe6ee' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e");
    }
}

/* Secondary buttons in dark mode (Chris, 2026-09-16: "the dark gray buttons appear disabled"). Bootstrap draws them in
   #6c757d, which on this background is the colour of a disabled control; they get the page's own text colour and a
   border that shows, and a disabled button fades further than Bootstrap's 0.65 so the two cannot be confused. */
:root[data-theme='dark'] .btn-outline-secondary {
    --bs-btn-color: var(--cn-foreground);
    --bs-btn-border-color: #4b6378;
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #26394b;
    --bs-btn-hover-border-color: #6a8499;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #2f465b;
    --bs-btn-active-border-color: #6a8499;
    --bs-btn-disabled-color: #7f93a4;
    --bs-btn-disabled-border-color: #2c3e50;
}

:root[data-theme='dark'] .btn-secondary {
    --bs-btn-color: #fff;
    --bs-btn-bg: #3d566d;
    --bs-btn-border-color: #3d566d;
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #4a6782;
    --bs-btn-hover-border-color: #4a6782;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #54728e;
    --bs-btn-active-border-color: #54728e;
    --bs-btn-disabled-color: #9fb2c2;
    --bs-btn-disabled-bg: #25384a;
    --bs-btn-disabled-border-color: #25384a;
}

:root[data-theme='dark'] .btn {
    --bs-btn-disabled-opacity: .45;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme='light']) .btn-outline-secondary {
        --bs-btn-color: var(--cn-foreground);
        --bs-btn-border-color: #4b6378;
        --bs-btn-hover-color: #fff;
        --bs-btn-hover-bg: #26394b;
        --bs-btn-hover-border-color: #6a8499;
        --bs-btn-active-color: #fff;
        --bs-btn-active-bg: #2f465b;
        --bs-btn-active-border-color: #6a8499;
        --bs-btn-disabled-color: #7f93a4;
        --bs-btn-disabled-border-color: #2c3e50;
    }

    :root:not([data-theme='light']) .btn-secondary {
        --bs-btn-color: #fff;
        --bs-btn-bg: #3d566d;
        --bs-btn-border-color: #3d566d;
        --bs-btn-hover-color: #fff;
        --bs-btn-hover-bg: #4a6782;
        --bs-btn-hover-border-color: #4a6782;
        --bs-btn-active-color: #fff;
        --bs-btn-active-bg: #54728e;
        --bs-btn-active-border-color: #54728e;
        --bs-btn-disabled-color: #9fb2c2;
        --bs-btn-disabled-bg: #25384a;
        --bs-btn-disabled-border-color: #25384a;
    }

    :root:not([data-theme='light']) .btn {
        --bs-btn-disabled-opacity: .45;
    }
}

.table {
    --bs-table-bg: var(--cn-card-background);
    --bs-table-color: var(--cn-foreground);
    --bs-table-border-color: var(--cn-border);
    --bs-table-striped-bg: var(--cn-clickable-hover-bg);
    --bs-table-striped-color: var(--cn-foreground);
}

/* The horizontal inset used to come from Bootstrap's px-4 (!important, so unreadable from CSS).
   It lives here now because the sheet tab strip bleeds out through EXACTLY this value — guessing
   it wrong overhangs the container and raises a horizontal scrollbar. */
.content {
    padding-top: 1.1rem;
    padding-left: var(--cn-content-inset);
    padding-right: var(--cn-content-inset);
}

h1:focus {
    outline: none;
}

/* ── Staff-surface density (ConvergencePortalTechnicians-inspired) ──────────
   The staff app is a WORK tool: tighter padding, smaller semibold headings,
   denser tables. Scoped to the circuit surface via the body attribute so the
   customer-facing pages keep their presentation styling untouched. */

body[data-render-surface="circuit"] h1 {
    font-size: 1.375rem;
    font-weight: 600;
    margin-bottom: 0.75rem;
}

body[data-render-surface="circuit"] h2 {
    font-size: 1.125rem;
    font-weight: 600;
    margin: 0.75rem 0 0.5rem;
}

body[data-render-surface="circuit"] h3 {
    font-size: 1rem;
    font-weight: 600;
}

body[data-render-surface="circuit"] .content {
    padding-top: 0.6rem;
}

/* ── Worksheet layout (§12 editors: quote detail, template editor) ──────────────────────────
   The page wraps its panes in .sheet-host; the SheetTabBar component (scoped css) rides the
   bottom via sticky + margin-top:auto.

   The sheet host's height is DERIVED, not measured. It used to be calc(100vh - 162px) with the
   162 hand-measured from the top bar + ribbon — which silently broke the moment the ribbon's
   height changed (taller icon buttons did exactly that, and the tab strip slid off the bottom
   of the window). Instead main is a full-height flex column, the chrome above holds its natural
   size, and the content area takes what is left; the host then fills it. Nothing to re-measure
   when the chrome changes. */
body[data-render-surface="circuit"] main {
    height: 100vh;
    display: flex;
    flex-direction: column;
    min-height: 0;
}

/* Chrome keeps its intrinsic height — without this the flex column would shrink the ribbon
   before it shrinks the content. */
body[data-render-surface="circuit"] .top-row,
body[data-render-surface="circuit"] .ribbon-tabs,
body[data-render-surface="circuit"] .ribbon {
    flex-shrink: 0;
}

/* Flex COLUMN, not just a flex item: .sheet-host's flex:1 only fills the content area if its
   parent is itself a flex container. Without this the host keeps its intrinsic height and the
   tab strip stops wherever the content happens to end. */
body[data-render-surface="circuit"] .content {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

body[data-render-surface="circuit"] .sheet-host {
    display: flex;
    flex-direction: column;
    /* FIXED viewport grid, Excel-style: panes never grow past the tab strip — long content
       scrolls INSIDE the pane, and the strip sits flush on the window edge instead of floating
       over overflow. */
    flex: 1 1 auto;
    min-height: 0;
}

body[data-render-surface="circuit"] .sheet-pane {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    padding-bottom: 0.75rem;
    min-height: 0;
    overflow-y: auto;
}

/* ── Embedded SharePoint sheet (quote AND work-plan editors — one class, one look): the frame
   fills the pane down to the tab strip, same flex idiom as the prose host. ── */
.sharepoint-sheet-fill {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    min-height: 0;
}

.sharepoint-sheet-fill iframe {
    /* Office embeds render on paper — the app's dark theme must not bleed through while the
       document loads. */
    background-color: #ffffff;
    border: 1px solid var(--cn-border);
    border-radius: 0.375rem;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
}

/* ── Dialog chrome, GLOBAL: ConfirmDialog/ErrorDialog carry scoped copies of these rules (CSS
   isolation means a page reusing the class names matches nothing of theirs) — pages that inline
   a dialog with fields (work-plan capture, the SharePoint link prompts) match here instead.
   Same values as the components so every dialog reads as one system. ── */
.dialog-backdrop {
    position: fixed;
    inset: 0;
    z-index: 1000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1.5rem;
    background: rgb(0 0 0 / 45%);
}

.dialog {
    background-color: var(--cn-card-background);
    border: 1px solid var(--cn-border);
    border-top: 3px solid var(--cn-bold-orange);
    border-radius: 0.75rem;
    box-shadow: 0 12px 32px rgb(0 0 0 / 35%);
    max-width: 32rem;
    padding: 1.25rem 1.5rem 1.5rem;
}

.dialog h2 {
    font-size: 1.05rem;
    font-weight: 600;
    margin: 0 0 0.5rem;
}

.dialog p {
    margin: 0 0 1.25rem;
    overflow-wrap: anywhere;
}

/* The template-update review dialog (QuoteDetail): the action list IS the decision material,
   so it gets room and its own scroll — a long template diff must not push the buttons off
   screen. */
.doc-upgrade-dialog {
    max-width: 38rem;
}

.doc-upgrade-dialog p {
    margin-bottom: 0.75rem;
}

.doc-upgrade-notes {
    margin: 0 0 0.9rem;
    padding-left: 1.3rem;
    max-height: 45vh;
    overflow-y: auto;
}

.doc-upgrade-notes li {
    font-size: 0.9rem;
    margin-bottom: 0.35rem;
    overflow-wrap: anywhere;
}

/* A labelled input/select inside an inline dialog. */
.dialog-field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    margin-bottom: 0.7rem;
    text-align: left;
}

.dialog-field label {
    color: var(--cn-muted-foreground);
    font-size: 0.8rem;
}

.dialog-field input,
.dialog-field select {
    background-color: var(--cn-input-background);
    border: 1px solid var(--cn-border);
    border-radius: 0.375rem;
    color: var(--cn-foreground);
    padding: 0.3rem 0.55rem;
}

.dialog-field input:focus,
.dialog-field select:focus {
    border-color: var(--cn-clickable);
    box-shadow: 0 0 0 0.25rem var(--cn-focus-ring);
    outline: none;
}

.dialog-actions {
    display: flex;
    gap: 0.6rem;
    justify-content: flex-end;
}

.dialog-cancel,
.dialog-confirm {
    border-radius: 0.375rem;
    cursor: pointer;
    font: inherit;
    font-weight: 600;
    padding: 0.45rem 1.1rem;
}

.dialog-cancel {
    background-color: transparent;
    border: 1px solid var(--cn-border);
    color: var(--cn-foreground);
}

.dialog-cancel:hover {
    background-color: var(--cn-clickable-hover-bg);
}

.dialog-confirm {
    background-color: var(--cn-bold-orange);
    border: 1px solid var(--cn-bold-orange);
    color: #ffffff;
}

.dialog-confirm:hover {
    filter: brightness(1.08);
}

.dialog-confirm:disabled {
    cursor: default;
    filter: none;
    opacity: 0.55;
}

.dialog-cancel:focus-visible,
.dialog-confirm:focus-visible {
    outline: 2px solid var(--cn-vivid-azure);
    outline-offset: 2px;
}

/* ── Prose editor (Toast UI, vendored) — rendered CHROMELESS: the ribbon is the one toolbar
   (Format/View groups drive it through interop), so the library's own toolbar hides entirely.
   prose-editor.js picks the theme at mount AND retoggles it live on html[data-theme] changes
   (MutationObserver). ── */
/* Fill the sheet pane: FLEX at every level, never percentage heights — the pane's height comes
   from flex-grow against the host's min-height, which is indefinite, so 100% chains resolve to
   auto and collapse. Flex stretching doesn't care. */
body[data-render-surface="circuit"] .doc-prose-host {
    display: flex;
    flex-direction: column;
}

body[data-render-surface="circuit"] .doc-prose-host .toastui-editor-defaultUI {
    border: 0; /* full-bleed sheet — the pane IS the editor, no inner frame */
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
}

body[data-render-surface="circuit"] .doc-prose-host .toastui-editor-main,
body[data-render-surface="circuit"] .doc-prose-host .toastui-editor-main-container,
body[data-render-surface="circuit"] .doc-prose-host .toastui-editor-ww-container,
body[data-render-surface="circuit"] .doc-prose-host .toastui-editor-md-container,
body[data-render-surface="circuit"] .doc-prose-host .toastui-editor-ww-container > .toastui-editor,
body[data-render-surface="circuit"] .doc-prose-host .toastui-editor-md-container > .toastui-editor {
    display: flex;
    flex-direction: column;
    flex: 1 1 auto;
    min-height: 0;
}

body[data-render-surface="circuit"] .doc-prose-host .ProseMirror {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
}

/* The flex rules above must not resurrect the INACTIVE mode's container — the library hides it
   per the mode class on .toastui-editor-main. */
body[data-render-surface="circuit"] .doc-prose-host .toastui-editor-ww-mode .toastui-editor-md-container,
body[data-render-surface="circuit"] .doc-prose-host .toastui-editor-md-mode .toastui-editor-ww-container {
    display: none;
}

/* Hide the WHOLE toolbar strip — the icons bar AND the md-mode Write/Preview tabs live inside
   .toastui-editor-toolbar, and the library re-shows it when entering markdown mode. The ribbon
   owns formatting and view switching. */
body[data-render-surface="circuit"] .doc-prose-host .toastui-editor-toolbar {
    display: none;
}

/* Jodit matches: no inner frame on the HTML editor either. NOTE Jodit.make REPLACES the host
   element — .doc-prose-host never survives as an ancestor; scope on .doc-prose-fill. */
body[data-render-surface="circuit"] .doc-prose-fill .jodit-container,
body[data-render-surface="circuit"] .doc-prose-fill .jodit-container:not(.jodit_inline) .jodit-workplace {
    border: 0;
}

/* The HTML Visual surface follows the APP theme, not the document's. It used to be forced to
   paper on the reasoning that §12 documents are authored light — true, but it meant opening an
   HTML sheet in dark mode fired a full-height white rectangle at the author. Editing comfort
   wins here; fidelity is what Preview and the customer view are for, and the sheet carries a
   standing warning that the quote itself renders light.

   DEFAULTS only: these set the inherited color and the surface behind it, so any color or
   background the markup actually specifies still wins — authored styling is never overridden,
   it is only what the author did NOT specify that follows the theme.

   Full-height so the sheet fills to the tab strip like the prose editor. Selector shape mirrors
   Jodit's own (.jodit-container:not(.jodit_inline) …) so these outrank the library's theme
   rules. */
.doc-prose-fill .jodit-container {
    background-color: var(--cn-card-background);
    height: 100% !important;
}

.doc-prose-fill .jodit-container:not(.jodit_inline) .jodit-workplace {
    background-color: var(--cn-card-background);
    height: 100% !important;
}

.doc-prose-fill .jodit-container:not(.jodit_inline) .jodit-wysiwyg {
    background-color: var(--cn-card-background);
    color: var(--cn-foreground);
    padding: 1rem 1.25rem;
}

.doc-prose-fill .jodit-container:not(.jodit_inline) .jodit-placeholder {
    color: var(--cn-muted-foreground);
}

/* Minimal editing-surface padding (library default is a roomy 18px 25px). */
body[data-render-surface="circuit"] .doc-prose-host .ProseMirror {
    padding: 8px 12px;
}

/* Ribbon toggle state (the View group's Editor/Markdown/Preview buttons). */
.ribbon-btn.toggled {
    box-shadow: inset 0 -2px 0 var(--cn-vivid-azure);
    color: var(--cn-heading);
    font-weight: 600;
}

body[data-render-surface="circuit"] .table {
    font-size: 0.875rem;
}

    body[data-render-surface="circuit"] .table > :not(caption) > * > * {
        padding: 0.4rem 0.5rem;
    }

body[data-render-surface="circuit"] .btn {
    font-size: 0.875rem;
    padding: 0.3rem 0.75rem;
}

body[data-render-surface="circuit"] .form-control,
body[data-render-surface="circuit"] .form-select {
    font-size: 0.875rem;
    padding: 0.3rem 0.5rem;
}

/* ── Ribbon: the per-module command surface (Excel-style tabs + groups) ───── */

/* Tabs sit on the page, not welded to the panel below — the ribbon is now a raised card with a
   margin, so a shared border would leave a line running past its rounded corners. */
.ribbon-tabs {
    display: flex;
    align-items: flex-end;
    gap: 0.1rem;
    background-color: transparent;
    /* Same inset as the panel below, so the tab row and the ribbon share a left edge. */
    padding: 0.35rem var(--cn-content-inset) 0;
}

.ribbon-tab {
    font-size: 0.8rem;
    padding: 0.25rem 0.9rem;
    border: 1px solid transparent;
    /* The selection underline's space is reserved on EVERY tab, transparent until active — give
       it only to the active one and that tab grows 2px, shifting its label out of line. */
    border-bottom: 2px solid transparent;
    border-radius: 0.375rem 0.375rem 0 0;
    background: transparent;
    color: var(--cn-muted-foreground);
    cursor: pointer;
}

    .ribbon-tab:hover {
        color: var(--cn-foreground);
    }

    /* Modern-Office selection: an accent underline rather than a connected folder tab, which
       cannot work against a detached panel. COLOUR only — the 2px is already reserved above. */
    .ribbon-tab.active {
        color: var(--cn-heading);
        font-weight: 600;
        border-bottom-color: var(--cn-bold-orange);
    }

.ribbon-customize {
    margin-left: auto;
}

.ribbon-group-toggle {
    font-size: 0.7rem;
    color: var(--cn-muted-foreground);
    display: flex;
    align-items: center;
    gap: 0.25rem;
    margin-bottom: 0.15rem;
    user-select: none;
}

.ribbon-group-off .ribbon-group-content,
.ribbon-group-off .ribbon-group-caption {
    opacity: 0.35;
}

/* The ribbon proper: a raised panel rather than a band welded to the chrome, so the command
   surface reads as a distinct object sitting above the sheet — Excel's treatment. */
.ribbon {
    display: flex;
    align-items: stretch;
    gap: 0.15rem;
    background-color: var(--cn-ribbon-background);
    border: 1px solid var(--cn-border);
    border-radius: 0.55rem;
    box-shadow: 0 1px 4px rgb(0 0 0 / 10%);
    /* Left/right match .content's inset so the panel's edge lines up with the page's cards and
       tables rather than floating in from them. */
    margin: 0.4rem var(--cn-content-inset) 0.5rem;
    padding: 0.45rem 0.5rem 0.3rem;
    /* ONE row at every width: a strip that doesn't fit SCROLLS, never wraps — rows of ribbon
       stacked above the document read as chrome eating the page. Scrolling makes this a
       clipping box (overflow-y follows overflow-x to auto per spec), which is why the
       drop-down panels are position:fixed with auto insets — the static position keeps them
       under their buttons while fixed positioning lifts them out of the clip. */
    flex-wrap: nowrap;
    overflow-x: auto;
    scrollbar-width: thin;
}

    /* Groups and buttons keep their intrinsic width and line up as one scrollable strip. */
    .ribbon .ribbon-group,
    .ribbon .ribbon-btn {
        flex: none;
    }

.ribbon-group {
    display: flex;
    flex-direction: column;
    padding: 0 0.6rem;
    border-right: 1px solid var(--cn-border);
}

    .ribbon-group:last-child {
        border-right: none;
    }

/* One fixed control-row height for EVERY tab. Without it a tab of tall buttons and a tab of
   compact formatting controls produce panels of different heights, and the whole page jumps as
   you switch tabs. Centred, so a row of small buttons sits in the middle of the same box a
   large button fills. */
.ribbon-group-content {
    display: flex;
    align-items: center;
    gap: 0.25rem;
    flex: 1;
    min-height: var(--cn-ribbon-btn-h);
}

.ribbon-group-caption {
    font-size: 0.65rem;
    color: var(--cn-muted-foreground);
    text-align: center;
    margin-top: 0.3rem;
    user-select: none;
}

/* Tall icon-over-label button. Borderless until hover — the modern Office look, and it keeps a
   dense strip from turning into a grid of boxes. Buttons WITHOUT an icon still work: the label
   simply centres in the same footprint, so a page that has not been given icons yet degrades
   rather than breaking. */
.ribbon-btn {
    font-size: 0.7rem;
    line-height: 1.15;
    min-width: 4rem;
    max-width: 6rem;
    min-height: var(--cn-ribbon-btn-h);
    padding: 0.4rem 0.45rem 0.35rem;
    border: 1px solid transparent;
    border-radius: 0.4rem;
    background: transparent;
    color: var(--cn-foreground);
    cursor: pointer;
    text-align: center;
    text-decoration: none;
    display: inline-flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.3rem;
}

    .ribbon-btn:hover:not(:disabled) {
        background-color: var(--cn-clickable-hover-bg);
        border-color: var(--cn-border);
        color: var(--cn-foreground);
    }

    .ribbon-btn:disabled {
        opacity: 0.4;
        cursor: default;
    }

    /* The primary action of a tab keeps its fill so it reads first. */
    .ribbon-btn.accent {
        background-color: var(--cn-bold-orange);
        border-color: var(--cn-bold-orange);
        color: #fff;
    }

        .ribbon-btn.accent:hover:not(:disabled) {
            background-color: var(--cn-bold-orange-hover);
            border-color: var(--cn-bold-orange-hover);
            color: #fff;
        }

.ribbon-icon {
    flex-shrink: 0;
}

/* ── Ribbon on small layouts ────────────────────────────────────────────────────────────────
   The strip scrolls instead of wrapping at EVERY width (see .ribbon); these tiers only step
   the density down, Office-style. CONDENSE first: tighter buttons, smaller glyphs, slimmer
   groups — the labels stay. At phone width the tab row scrolls too, and the drop-down panels
   trade their under-the-button anchor for a fixed, centered sheet over the backdrop the menu
   already renders — the mobile menu idiom, and a 17rem panel anchored to a right-edge button
   would hang off a 375px screen anyway. */
@media (max-width: 68rem) {
    .ribbon-btn {
        font-size: 0.62rem;
        gap: 0.2rem;
        min-width: 2.9rem;
        max-width: 5rem;
        padding: 0.3rem 0.3rem 0.25rem;
    }

    .ribbon-icon {
        width: 16px;
        height: 16px;
    }

    .ribbon-group {
        padding: 0 0.35rem;
    }

    .ribbon-group-content {
        gap: 0.15rem;
    }

    .ribbon-group-caption {
        font-size: 0.6rem;
        margin-top: 0.2rem;
    }
}

@media (max-width: 48rem) {
    /* The tab row scrolls the same way — nothing hangs below it, so plain overflow is safe. */
    .ribbon-tabs {
        overflow-x: auto;
        scrollbar-width: none;
    }

    .ribbon-tab {
        flex: none;
        white-space: nowrap;
    }

    /* Centered sheet over the backdrop. The parent class in the selector is for CASCADE, not
       structure: the panel's base rule sits later in this file, so this one needs the extra
       specificity to win. */
    .ribbon-menu .ribbon-menu-panel {
        top: 20vh;
        left: 50%;
        transform: translateX(-50%);
        margin-top: 0;
        width: min(24rem, calc(100vw - 1.5rem));
        min-width: 0;
        max-height: 60vh;
    }
}

/* ── Ribbon drop-down (Excel's "Define Name ⌄" form factor) ─────────────────────────────────
   A normal tall button whose label carries a caret, plus a panel anchored beneath it. The
   wrapper is the positioning context; the backdrop below it closes the menu on any outside
   click without needing a document-level listener. */
.ribbon-menu {
    position: relative;
    display: inline-flex;
}

.ribbon-caret {
    font-size: 0.6rem;
    margin-left: 0.2rem;
    opacity: 0.75;
}

.ribbon-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 55;
}

/* FIXED with auto insets, not absolute: the ribbon is a horizontal scroll container (a clip
   box), and an absolute panel inside it gets severed at the strip's edge. Auto insets place a
   fixed box at its STATIC position — inside .ribbon-menu, at the wrapper's top-left — while
   fixed positioning resolves against the viewport, out of every ancestor clip. The margin-top
   is what "below the button" becomes under that scheme: the control row's fixed height pushes
   the panel under the strip. (Anchored at open time; the strip scrolling underneath an open
   menu doesn't drag it along, which the close-on-outside-click backdrop makes a non-issue.) */
.ribbon-menu-panel {
    position: fixed;
    margin-top: calc(var(--cn-ribbon-btn-h) + 0.9rem);
    z-index: 60;
    min-width: 17rem;
    max-height: 20rem;
    overflow-y: auto;
    padding: 0.25rem;
    background-color: var(--cn-card-background);
    border: 1px solid var(--cn-border);
    border-radius: 0.5rem;
    box-shadow: 0 8px 24px rgb(0 0 0 / 28%);
}

.ribbon-menu-item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.05rem;
    width: 100%;
    padding: 0.4rem 0.6rem;
    border: 0;
    border-radius: 0.35rem;
    background: transparent;
    color: var(--cn-foreground);
    font: inherit;
    font-size: 0.85rem;
    text-align: left;
    cursor: pointer;
}

    .ribbon-menu-item:hover {
        background-color: var(--cn-clickable-hover-bg);
    }

.ribbon-menu-detail {
    font-size: 0.75rem;
    color: var(--cn-muted-foreground);
}

.ribbon-menu-empty {
    padding: 0.5rem 0.6rem;
    font-size: 0.8rem;
    color: var(--cn-muted-foreground);
}

/* Non-interactive section header inside a menu panel (the Variable menu's Quote / People /
   Blocks groupings). */
.ribbon-menu-group {
    margin: 0.35rem 0 0.1rem;
    padding: 0 0.6rem;
    font-size: 0.7rem;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--cn-muted-foreground);
}

    .ribbon-menu-group:first-child {
        margin-top: 0.1rem;
    }

/* ── Uploaded-images picker: a tile GRID, not a list — thumbnails read side by side, the
   source rides as a bottom badge, and the remove control sits in the tile's corner. ───────── */
.ribbon-menu-images {
    display: grid;
    grid-template-columns: repeat(3, 5.2rem);
    gap: 0.3rem;
    padding: 0.1rem;
}

.ribbon-menu-image-tile {
    position: relative;
}

.ribbon-menu-image {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 5.2rem;
    height: 5.2rem;
    padding: 0.25rem;
    border: 1px solid var(--cn-border);
    border-radius: 0.4rem;
    background: #fff; /* thumbnails preview on paper, like the document they land in */
    overflow: hidden;
    cursor: pointer;
}

    .ribbon-menu-image:hover {
        border-color: var(--cn-muted-foreground);
    }

    .ribbon-menu-image img {
        max-width: 100%;
        max-height: 100%;
        object-fit: contain;
    }

.ribbon-menu-image-source {
    position: absolute;
    inset: auto 0 0 0;
    padding: 0.1rem 0.3rem;
    font-size: 0.62rem;
    line-height: 1.3;
    text-align: left;
    color: #fff;
    background: rgb(0 0 0 / 55%);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    pointer-events: none;
}

.ribbon-menu-remove {
    position: absolute;
    top: 0.2rem;
    right: 0.2rem;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: rgb(0 0 0 / 55%);
    color: #fff;
    font: inherit;
    font-size: 0.6rem;
    line-height: 1;
    cursor: pointer;
}

    .ribbon-menu-remove:hover {
        background: rgb(0 0 0 / 80%);
    }

/* Help link on the Variable menu — external syntax reference, new tab. Floated so it sits at
   the right end of the first group heading's row and scrolls with the list. */
.ribbon-menu-help {
    float: right;
    margin: 0.15rem 0.1rem 0 0.3rem;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.15rem;
    height: 1.15rem;
    border: 1px solid var(--cn-border);
    border-radius: 50%;
    color: var(--cn-muted-foreground);
    font-size: 0.7rem;
    line-height: 1;
    text-decoration: none;
}

    .ribbon-menu-help:hover {
        background-color: var(--cn-clickable-hover-bg);
        color: var(--cn-foreground);
    }

/* Compact variant for dense formatting rows (B / I / U, headings) where a tall tile per glyph
   would be absurd — Excel does the same for its font controls. */
.ribbon-btn.ribbon-btn-sm {
    min-width: 0;
    min-height: 0;
    max-width: none;
    font-size: 0.85rem;
    flex-direction: row;
    padding: 0.3rem 0.55rem;
}

.ribbon-input,
.ribbon select {
    font-size: 0.85rem;
    padding: 0.28rem 0.5rem;
    border: 1px solid var(--cn-border);
    border-radius: 0.375rem;
    background-color: var(--cn-input-background);
    color: var(--cn-foreground);
}

/* ── Blazor functional styles (template defaults, rebranded where colored) ── */

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.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."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* ── Ribbon file-input button (Insert > Image) ──
   A <label class="ribbon-btn"> wrapping a hidden InputFile: same visual language as every other
   ribbon button, with the browser's file dialog behind it. The input is size-zero rather than
   display:none out of caution for label-activation quirks; it stays in the tree and focusable
   behaviour comes from the label. */
label.ribbon-btn {
    cursor: pointer;
    user-select: none;
}

label.ribbon-btn.disabled {
    cursor: default;
    opacity: 0.45;
    pointer-events: none;
}

.ribbon-file-input {
    height: 0;
    opacity: 0;
    position: absolute;
    width: 0;
}

/* ── Loading rings ────────────────────────────────────────────────────────────────────────────
   The two-circle pulse from the reconnect overlay, defined ONCE and used by both: a page waiting
   for its first load and a circuit waiting to come back are the same promise to the reader, and
   two copies of an animation drift. ReconnectModal keeps its own show/hide rules — those are tied
   to the framework's state classes — but takes its look from here. */
.cn-rings {
    position: relative;
    width: 64px;
    height: 64px;
}

    .cn-rings div {
        position: absolute;
        border: 3px solid var(--cn-vivid-azure, #1398b2);
        opacity: 1;
        border-radius: 50%;
        animation: cn-rings-pulse 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
    }

    .cn-rings div:nth-child(2) {
        border-color: var(--cn-bold-orange, #f37021);
        animation-delay: -0.5s;
    }

@keyframes cn-rings-pulse {
    0%, 4.9% { top: 32px; left: 32px; width: 0; height: 0; opacity: 0; }
    5% { top: 32px; left: 32px; width: 0; height: 0; opacity: 1; }
    100% { top: 0; left: 0; width: 64px; height: 64px; opacity: 0; }
}

/* Centred in whatever pane is still loading — fills its parent rather than sitting at the top
   left, which is what made the old "Loading…" read as a stuck page. */
.cn-loading {
    align-items: center;
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 0.9rem;
    justify-content: center;
    min-height: 12rem;
    padding: 2rem 0;
}

/* What is being waited for, under the rings (2026-09-15). */
.cn-loading .cn-loading-text {
    color: var(--cn-muted-foreground, #5b7183);
    font-size: 0.95rem;
    text-align: center;
}

/* Announced, not shown: an animation on its own tells a screen reader nothing. */
.cn-loading .cn-loading-label {
    clip-path: inset(50%);
    height: 1px;
    overflow: hidden;
    position: absolute;
    white-space: nowrap;
    width: 1px;
}

@media (prefers-reduced-motion: reduce) {
    .cn-rings div {
        animation-duration: 3s;
    }
}
