/* ============================================================================
   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-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 sub-page rows sit under their deal */
}

/* 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-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-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);
}

/* 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);
    }

    /* Disabled and read-only controls stay on the theme's own surface with legible text.
       Bootstrap's default (--bs-secondary-bg, opacity 1) is a light grey that is unreadable on
       the dark theme and looks like an empty white box on the light one. */
    .form-control:disabled, .form-select:disabled, .form-control[readonly] {
        background-color: color-mix(in srgb, var(--cn-input-background) 60%, var(--cn-background));
        border-color: var(--cn-border);
        border-style: dashed;
        color: var(--cn-muted-foreground);
        opacity: 1;
        cursor: not-allowed;
    }

    .form-check-input:disabled {
        opacity: 0.6;
    }

    /* Placeholders double as field instructions here (the reason fields say what they are for),
       so they follow the theme's muted foreground instead of Bootstrap's secondary colour, which
       all but vanishes on the dark surface. */
    .form-control::placeholder {
        color: var(--cn-muted-foreground);
        opacity: 0.85;
    }

.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;
    justify-content: center;
    min-height: 12rem;
    padding: 2rem 0;
}

/* 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;
    }
}
