/* ============================================================
   Template Editor — global styles
   ============================================================ */

:root {
    --canvas: #e8ecec;
    --surface: #ffffff;
    --surface-2: #f2f4f4;
    --border: #e5e5e5;
    --border-strong: #cfd5d5;
    --text: #24323a;
    --muted: #6b7a80;
    --faint: #9aa7ab;

    /* The sage green the portal uses for table headings, the active segment and primary buttons. */
    --accent: #93ada4;
    --accent-hover: #7f998f;
    --accent-soft: #eef2f0;
    --accent-soft-2: #dde7e2;

    /* The accent: the active view, every dialog heading, and a selected row. */
    --select: #3f616f;
    --navy: #12293b;
    --danger: #c0392b;

    /* The saturated green of a confirm action, distinct from the sage chrome. */
    --confirm: #28a745;
    --confirm-hover: #1f8c39;
    --content-max: 1400px;
    --radius: 8px;
    --radius-sm: 6px;
    --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.06), 0 1px 3px rgba(16, 24, 40, 0.08);
    --shadow: 0 4px 12px rgba(16, 24, 40, 0.10);
    --shadow-lg: 0 12px 32px rgba(16, 24, 40, 0.18);
    /* The portal uses a rounded humanist sans; fall back through whatever the machine actually has. */
    --font: "Nunito", "Segoe UI Variable Text", "Segoe UI", -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif;
}

* {
    box-sizing: border-box;
}

body {
    margin: 0;
    font-family: var(--font);
    color: var(--text);
    background: var(--canvas);
    font-size: 14px;
    -webkit-font-smoothing: antialiased;
}

/* ---- Buttons (te- prefix so they don't clash with Bootstrap's .btn) ---- */

.te-btn {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.85rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: 0.85rem;
    font-weight: 500;
    line-height: 1.2;
    cursor: pointer;
    white-space: nowrap;
    transition: background-color 0.12s ease, border-color 0.12s ease, color 0.12s ease, box-shadow 0.12s ease;
}

.te-btn:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
}

.te-btn-icon {
    font-size: 1rem;
    line-height: 1;
}

.te-btn-ghost {
    color: var(--muted);
}

.te-btn-ghost:hover {
    background: var(--surface-2);
    color: var(--text);
}

.te-btn-default {
    background: var(--surface);
    border-color: var(--border-strong);
}

.te-btn-default:hover {
    background: var(--surface-2);
    border-color: var(--faint);
}

.te-btn-primary {
    background: var(--accent);
    color: #fff;
    box-shadow: var(--shadow-sm);
}

.te-btn-primary:hover {
    background: var(--accent-hover);
}

.te-btn-primary:disabled {
    background: var(--border-strong);
    color: #fff;
    cursor: default;
    box-shadow: none;
}

/* ---- App shell: top bar / scrolling body / bottom bar ---- */

.app-root {
    min-height: 0;
}

.editor {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    background: var(--canvas);
}

.app-bar {
    position: relative;
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 1rem;
    height: 60px;
    padding: 0 max(1rem, (100% - var(--content-max)) / 2);
    background: var(--surface);
    border-bottom: 1px solid var(--border);
    box-shadow: var(--shadow-sm);
    z-index: 3;
}

.app-logo {
    height: 80px;
    width: auto;
    margin-right: 0.6rem;
}

/* Once the gutter either side of the content column (capped at --content-max) is wide enough to hold it, the logo
   leaves the flow and sits in the very left corner rather than pushing the Document and Open menus along. Below
   that width there is nowhere for it to go, so it stays inline. */
@media (min-width: 1760px) {
    .app-logo {
        position: absolute;
        left: 1rem;
        top: 50%;
        transform: translateY( -50% );
        margin-right: 0;
    }
}

.app-bar-group {
    display: flex;
    align-items: center;
    gap: 0.35rem;
    flex: 1 1 0;
}

.app-bar-group-end {
    justify-content: flex-end;
}

.app-bar-divider {
    width: 1px;
    height: 1.4rem;
    background: var(--border);
    margin: 0 0.3rem;
}

.app-bar-title {
    flex: 0 1 auto;
    max-width: 40%;
    font-weight: 600;
    font-size: 0.95rem;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.app-brand {
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #fff;
    background: var(--navy);
    padding: 0.3rem 0.65rem;
    border-radius: 4px;
}

.editor-body {
    position: relative;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    gap: 1rem;
    padding: 1rem max(1rem, (100% - var(--content-max)) / 2);
    overflow: hidden;
}

.panel {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}

.panel-tree {
    flex: 0 0 320px;
}

.panel-props {
    /* Basis 0 rather than auto, and min-width 0 to switch off the automatic minimum size a flex item gets from its
       content. Without that, one long unbreakable value can force this panel wider than its basis and squeeze the
       view beside it - the width would then move about with whatever happens to be selected. */
    flex: 1 1 0;
    min-width: 0;
    /* The category cards are the panels here, so the container itself is just the canvas behind them. */
    background: transparent;
    border: none;
    box-shadow: none;
    overflow: visible;
}

/* ---- View tabs (Outline / Summary), styled as the panel header they replace ---- */

.te-tabs {
    flex: 0 0 auto;
    display: flex;
    padding: 0.55rem 0.7rem;
    background: var(--surface);
    border-bottom: 1px solid var(--border);
}

.te-tab {
    padding: 0.4rem 1.1rem;
    border: 1px solid var(--border-strong);
    background: var(--surface);
    cursor: pointer;
    font: inherit;
    font-size: 0.85rem;
    color: var(--text);
    transition: background-color 0.12s ease, color 0.12s ease;
}

/* A joined segmented control, as the portal uses for All / Enabled / Disabled. */
.te-tab + .te-tab {
    border-left: none;
}

.te-tab:first-of-type {
    border-radius: 4px 0 0 4px;
}

.te-tab:last-of-type {
    border-radius: 0 4px 4px 0;
}

.te-tab:hover:not(.active) {
    background: var(--surface-2);
}

.te-tab.active {
    background: var(--select);
    border-color: var(--select);
    color: #fff;
}

/* The segment either side of the active one must not draw a divider against its fill. */
.te-tab.active + .te-tab {
    border-left-color: var(--select);
}

/* The outline is a fixed sidebar; the summary needs the room for a 210mm sheet. */
.panel-summary {
    flex: 1 1 auto;
    min-width: 0;
}

/* General sibling, not adjacent: the collapse toggle sits between the two panes, so "+" stopped matching and this
   width was silently being ignored. */
.panel-summary ~ .panel-props {
    flex: 0 0 445px;
}

.panel-summary .panel-scroll {
    overflow: auto;
}

/* ---- Summary: the page sheet ---- */

.te-sheet-scroll {
    min-width: fit-content;
}

.te-page-banner {
    margin: 0 0 0.6rem;
    padding: 0.35rem 0.6rem;
    border-radius: var(--radius-sm);
    background: #fff8e1;
    color: #7a5c00;
    font-size: 0.75rem;
}

.te-page {
    /* One sheet per page; the gap separates them in the scroller. */
    margin: 0 auto 0.8rem;
    background: #fff;
    border: 1px solid var(--border-strong);
    box-shadow: var(--shadow);
    overflow: hidden;
}

.te-page-inner {
    display: flex;
    flex-direction: column;
    height: 100%;
    box-sizing: border-box;
}

.te-page-body {
    flex: 1 1 auto;
    min-height: 0;
}

/* Footers print anchored to the bottom of the page. */
.te-page-footer {
    margin-top: auto;
}

/* ---- Preview tab (server-rendered PDF) ---- */

.te-preview {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
    gap: 0.6rem;
}

.te-preview-bar {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.8rem;
    flex-wrap: wrap;
}

.te-preview-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0.35rem;
    font-size: 0.8rem;
    color: var(--muted);
}

.te-preview-stale {
    font-size: 0.8rem;
    color: #7a5c00;
    background: #fff8e1;
    padding: 0.2rem 0.5rem;
    border-radius: var(--radius-sm);
}

.te-preview-frame {
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
}

.te-preview-hint {
    flex: 0 0 auto;
    margin: 0;
    font-size: 0.85rem;
    color: var(--muted);
}

/* ---- Summary: selectable nodes ---- */

.te-pv-node {
    min-width: 0;
    cursor: pointer;
}

.te-pv-node:hover {
    background: var(--accent-soft);
}

.te-pv-node:focus-visible {
    outline: 2px solid var(--select);
    outline-offset: 2px;
}

/* After the hover rules so it wins on equal specificity, as .node.selected does for the outline. Outline
   rather than border: in this view the box model is the layout, and a border would shift it. */
.te-pv-node.selected {
    outline: 2px solid var(--select);
    outline-offset: 2px;
    box-shadow: 0 0 0 4px rgba( 63, 97, 111, 0.15 );
}

.te-pv-pagebreak {
    border-top: 1px dashed var(--faint);
    text-align: center;
    font-size: 0.65rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--faint);
}

/* Details grid (label/value pairs) and data grid, both populated from the sample data. */

.te-pv-grid-label,
.te-pv-grid-value {
    min-width: 0;
    overflow-wrap: anywhere;
}

.te-pv-table {
    width: 100%;
    border-collapse: collapse;
}

.te-pv-table th,
.te-pv-table td {
    padding: 0.4mm 1mm;
    text-align: left;
    vertical-align: top;
    /* No grid colour set means no grid lines, matching an empty GpcColour on the renderer. */
    border: 1px solid var(--te-grid, transparent);
}

/* The grid layout, used only once a column has been given a width - see DataGridPreviewComponent.ColumnTracks. The
   cells repeat the table's own padding and border so the two layouts look the same; align-items matches the table's
   vertical-align:top. */
.te-pv-grid {
    display: grid;
    align-items: start;
}

/* No proportional column to take up the slack, so the columns keep their own widths instead of being stretched to fill
   the row. */
.te-pv-grid-hug {
    justify-content: start;
}

.te-pv-grid-cell {
    padding: 0.4mm 1mm;
    /* An absolute column has to hold its width, so a long word wraps rather than pushing the track wider. */
    min-width: 0;
    overflow-wrap: anywhere;
    border: 1px solid var(--te-grid, transparent);
}

.te-pv-table-rownum {
    color: var(--faint);
    text-align: right;
}

.te-pv-placeholder {
    display: flex;
    gap: 0.5rem;
    align-items: baseline;
    padding: 0.4rem 0.6rem;
    border: 1px dashed var(--border-strong);
    background: var(--surface-2);
}

.te-pv-placeholder-label {
    font-size: 0.7rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
}

.te-pv-placeholder-detail {
    font-size: 0.7rem;
    color: var(--faint);
}

.panel-scroll {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 0.6rem;
}

/* A small handle floating on the canvas between the views and the properties. */
.te-panel-toggle {
    flex: 0 0 auto;
    align-self: center;
    width: 1.4rem;
    padding: 0.9rem 0;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    box-shadow: var(--shadow-sm);
    cursor: pointer;
    font: inherit;
    font-size: 0.85rem;
    color: var(--muted);
    transition: background-color 0.12s ease, color 0.12s ease;
}

.te-panel-toggle:disabled {
    opacity: 0.4;
    cursor: default;
}


.te-panel-toggle:hover:not(:disabled) {
    background: var(--accent);
    border-color: var(--accent);
    color: #fff;
}

/* ---- Loading a different document ---- */

.te-loading {
    position: absolute;
    inset: 0;
    z-index: 5;
    display: flex;
    align-items: center;
    justify-content: center;
    background: rgba( 255, 255, 255, 0.55 );
}

/* Inline companion to .te-spinner, for a button or a line of text. Drawn in currentColor, so it works on a filled
   primary button and on plain text without knowing which. */
.te-spinner-inline {
    display: inline-block;
    width: 0.85em;
    height: 0.85em;
    margin-right: 0.4rem;
    vertical-align: -0.1em;
    border: 2px solid currentColor;
    border-right-color: transparent;
    border-radius: 50%;
    animation: te-spin 0.7s linear infinite;
}

.te-spinner {
    width: 2.2rem;
    height: 2.2rem;
    border: 3px solid var(--border-strong);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: te-spin 0.8s linear infinite;
}

@keyframes te-spin {
    to {
        transform: rotate( 360deg );
    }
}


.app-bar-status {
    font-size: 0.8rem;
    color: var(--muted);
    white-space: nowrap;
}

/* ---- Striped lists ----
   Alternating rows, as the portal uses for its tables. These selectors are more specific than the plain :hover
   and .selected rules declared earlier, so those are restated afterwards to keep winning. */

.property-card-body > .property-row:nth-child(even),
.te-modal-body > .displayed-value:nth-child(even) {
    background: var(--surface-2);
}

.property-card-body > .property-row:hover,
.te-modal-body > .displayed-value:hover {
    background: var(--accent-soft);
}


/* A row being dragged, or the row it will land on, must still read as such on a striped background. */
.te-modal-body > .displayed-value.drop-target {
    background: rgba( 63, 97, 111, 0.10 );
}

.te-modal-body > .displayed-value.dragging {
    opacity: 0.4;
}

/* ---- Command-bar menus ---- */

.te-menu-host {
    position: relative;
}

.te-menu-button {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.4rem 0.7rem;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: none;
    cursor: pointer;
    font: inherit;
    font-size: 0.85rem;
    color: var(--text);
}

.te-menu-button:hover,
.te-menu-button.open {
    background: var(--surface-2);
    border-color: var(--border-strong);
}

.te-caret {
    font-size: 0.7rem;
    color: var(--muted);
}

.te-menu {
    position: absolute;
    top: calc(100% + 0.3rem);
    left: 0;
    z-index: 20;
    min-width: 15rem;
    max-height: 60vh;
    overflow-y: auto;
    padding: 0.25rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-lg);
}

.te-menu-item {
    display: block;
    width: 100%;
    padding: 0.45rem 0.6rem;
    border: none;
    border-radius: 4px;
    background: none;
    cursor: pointer;
    font: inherit;
    font-size: 0.85rem;
    text-align: left;
    color: var(--text);
}

.te-menu-item:hover {
    background: var(--accent-soft);
}

/* An option belonging to the heading above it - the Send to... destinations. */
.te-menu-item.te-menu-sub {
    padding-left: 1.5rem;
}

/* What is already open. */
.te-menu-item.current {
    background: var(--select);
    color: #fff;
}

/* A saved entry plus its delete affordance. */
.te-menu-row {
    display: flex;
    align-items: center;
    gap: 0.2rem;
}

.te-menu-row .te-menu-item {
    flex: 1 1 auto;
    min-width: 0;
}

.te-menu-delete,
.te-menu-danger,
.te-menu-cancel {
    flex: 0 0 auto;
    border: none;
    border-radius: 4px;
    background: none;
    cursor: pointer;
    font: inherit;
    font-size: 0.75rem;
    padding: 0.25rem 0.4rem;
    color: var(--muted);
}

.te-menu-delete {
    visibility: hidden;
}

.te-menu-row:hover .te-menu-delete {
    visibility: visible;
}

.te-menu-delete:hover {
    background: var(--danger);
    color: #fff;
}

.te-menu-danger {
    background: var(--danger);
    color: #fff;
}

.te-menu-cancel {
    border: 1px solid var(--border-strong);
}

.te-menu-note {
    display: block;
    padding: 0.45rem 0.6rem;
    font-size: 0.8rem;
    color: var(--muted);
}

/* Invisible sheet that turns the next click anywhere into "close the menu". It has to stay BELOW the command bar:
   the bar is a flex item with a z-index, so it is its own stacking context and the menu inside it cannot rise above
   a sibling with a higher one - at z-index 10 this sheet covered the menu and swallowed every click. */
.te-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 2;
}

/* ---- Picture catalogue picker ---- */

/* Wider than the standard modal: a list and a preview side by side. */
.te-picture-picker {
    width: 46rem;
    height: 34rem;
}

.te-picture-body {
    display: grid;
    grid-template-columns: 16rem 1fr;
    gap: 1rem;
    min-height: 0;
}

.te-picture-list {
    display: flex;
    flex-direction: column;
    gap: 1px;
    overflow-y: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.25rem;
}

.te-picture-item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 0.1rem;
    padding: 0.35rem 0.5rem;
    border: none;
    border-radius: 4px;
    background: none;
    cursor: pointer;
    font: inherit;
    text-align: left;
    color: var(--text);
}

.te-picture-item:hover {
    background: var(--accent-soft);
}

.te-picture-item.current {
    background: var(--select);
    color: #fff;
}

.te-picture-item-name {
    font-size: 0.85rem;
    overflow-wrap: anywhere;
}

.te-picture-item-note {
    font-size: 0.72rem;
    color: var(--muted);
}

.te-picture-item.current .te-picture-item-note {
    color: rgba( 255, 255, 255, 0.8 );
}

.te-picture-preview {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.6rem;
    min-width: 0;
    overflow: auto;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface-2);
    padding: 0.8rem;
}

/* Contained rather than stretched: this is what the picture IS, not how big it will print. */
.te-picture-image {
    max-width: 100%;
    max-height: 16rem;
    object-fit: contain;
}

.te-picture-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.35rem;
    width: 100%;
    max-width: 20rem;
}

.te-picture-actions button {
    cursor: pointer;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    font: inherit;
    font-size: 0.82rem;
    padding: 0.3rem 0.6rem;
    color: var(--text);
}

.te-picture-actions button:hover:not(:disabled) {
    background: var(--surface-2);
}

.te-picture-actions button:disabled {
    opacity: 0.4;
    cursor: default;
}

.te-picture-usage {
    font-size: 0.78rem;
    color: var(--muted);
    text-align: center;
}

/* The consequence of a rename or a delete, which is the thing most likely to surprise. */
.te-picture-warning {
    font-size: 0.75rem;
    line-height: 1.4;
    color: var(--danger);
}

.te-picture-error {
    flex: 1 1 auto;
    min-width: 0;
    font-size: 0.8rem;
    overflow-wrap: anywhere;
}

.te-modal-body label.te-picture-file {
    margin-top: 0.7rem;
}

/* The chosen picture in the Summary preview. Its node is a flex row so the alignment can position it, and
   align-self stops flexbox stretching a picture that has been given a width but no height - a replaced element with
   an auto cross-size is the one case where the default stretch distorts rather than fills. */
.te-pv-image {
    display: block;
    align-self: flex-start;
}

/* ---- Full-page states (loading / error) ---- */

.state-message {
    flex: 1 1 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem;
    color: var(--muted);
    font-size: 0.95rem;
}

.state-error {
    color: var(--danger);
}

.error-message {
    color: var(--danger);
}

/* ---- Splash / document picker ---- */

.splash-screen {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem;
    overflow-y: auto;
}

.splash-card {
    width: 100%;
    max-width: 30rem;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    box-shadow: var(--shadow);
    padding: 2rem;
    text-align: center;
}

.splash-brand {
    display: inline-block;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: #fff;
    background: var(--navy);
    padding: 0.3rem 0.7rem;
    border-radius: 4px;
    margin-bottom: 1rem;
}

.splash-card h1 {
    margin: 0 0 0.35rem;
    font-size: 1.5rem;
}

.splash-card p {
    margin: 0 0 1.5rem;
    color: var(--muted);
}

.document-type-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
    text-align: left;
}

.document-type-list button {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    width: 100%;
    padding: 0.7rem 0.9rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: 0.9rem;
    font-weight: 500;
    cursor: pointer;
    transition: background-color 0.12s ease, border-color 0.12s ease;
}

.document-type-list button:hover {
    background: var(--accent-soft);
    border-color: var(--accent);
}

.document-type-list button:disabled {
    opacity: 0.5;
    cursor: default;
}

.document-type-go {
    color: var(--accent);
    opacity: 0;
    transform: translateX(-4px);
    transition: opacity 0.12s ease, transform 0.12s ease;
}

.document-type-list button:hover .document-type-go {
    opacity: 1;
    transform: translateX(0);
}

/* ---- Outline tree ---- */

.tree-section-title {
    display: block;
    margin: 0.75rem 0 0.25rem;
    padding-left: 0.5rem;
    font-size: 0.68rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--faint);
}

.tree-section:first-child .tree-section-title {
    margin-top: 0;
}

.node {
    padding: 0.2rem 0.3rem 0.2rem 0.6rem;
    border-left: 1px solid var(--border);
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.1s ease;
}

.node:hover {
    background: var(--accent-soft);
}

/* Nested nodes rest on the surface so an ancestor's hover doesn't bleed through them. */
.node .node {
    background: var(--surface);
}

.node .node:hover {
    background: var(--accent-soft);
}

/* When the cursor is over a descendant, don't leave the ancestor highlighted as well. */
.node:has(.node:hover) {
    background: var(--surface);
}

/* Selected node — after the hover rules so it wins on equal specificity. */
.node.selected,
.node .node.selected {
    background: var(--select);
    border-left-color: var(--select);
    color: #fff;
}

.node.selected .node-sub-title,
.node.selected .node-hidden-label {
    color: #dfe7ec;
}

.node-title {
    font-weight: 600;
    font-size: 0.85rem;
}

.node-sub-title {
    font-size: 0.75rem;
    color: var(--muted);
    margin-left: 0.3rem;
}

.node-hidden-label {
    font-size: 0.65rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--faint);
    margin-left: 0.4rem;
    padding: 0.05rem 0.3rem;
    border: 1px solid var(--border-strong);
    border-radius: 3px;
}

/* ---- Right-hand property grid (the WinForms iPropertyGrid equivalent) ---- */

/* ---- Property groups as cards ---- */

.property-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    overflow: hidden;
}

.property-card + .property-card {
    margin-top: 0.8rem;
}

.property-card-header {
    padding: 0.5rem 0.8rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: #fff;
    background: var(--accent);
}

.property-card-body > .property-row:last-child {
    border-bottom: none;
}

.property-grid-empty {
    color: var(--muted);
    font-style: italic;
    padding: 0.6rem 0.4rem;
}

.property-header {
    font-weight: 600;
    padding: 0.4rem 0.7rem;
    background: var(--accent-soft);
    border-bottom: 1px solid var(--border);
}

.property-row {
    display: flex;
    align-items: center;
    padding: 0.35rem 0.7rem;
    border-bottom: 1px solid var(--border);
}

.property-row:hover {
    background: var(--surface-2);
}

.property-row:last-child {
    border-bottom: none;
}

.property-name {
    flex: 0 0 42%;
    min-width: 0;
    overflow-wrap: anywhere;
    font-size: 0.82rem;
    color: var(--muted);
}

.property-value {
    flex: 1 1 58%;
    min-width: 0;
    /* Long values wrap instead of demanding width from the panel. */
    overflow-wrap: anywhere;
    font-size: 0.82rem;
    color: var(--text);
}

.property-value input[type="text"],
.property-value input[type="number"],
.property-value select {
    width: 100%;
    padding: 0.25rem 0.4rem;
    border: 1px solid var(--border-strong);
    border-radius: 4px;
    font: inherit;
    font-size: 0.82rem;
    background: var(--surface);
    color: var(--text);
}

.property-value input[type="text"]:focus,
.property-value input[type="number"]:focus,
.property-value select:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.property-value.colour-value {
    display: flex;
    gap: 0.4rem;
    align-items: center;
}

.property-value.colour-value input[type="color"] {
    width: 2.4rem;
    height: 1.9rem;
    padding: 0;
    border: 1px solid var(--border-strong);
    border-radius: 4px;
    background: var(--surface);
    cursor: pointer;
}

.property-value.colour-value input.alpha {
    width: 4rem;
    flex: 0 0 auto;
}

.property-value.readonly {
    color: var(--muted);
}

/* ---- Displayed values (list + reorder controls) ---- */

.displayed-value {
    gap: 0.4rem;
}

/* Drag-to-reorder. The grip is the drag source, so dragging can never fight with editing the name field. */

.drag-grip {
    flex: 0 0 auto;
    cursor: grab;
    padding: 0 0.15rem;
    color: var(--faint);
    line-height: 1;
    user-select: none;
}

.drag-grip:active {
    cursor: grabbing;
}

.displayed-value:hover .drag-grip {
    color: var(--muted);
}

.displayed-value.dragging {
    opacity: 0.4;
}

/* Where the dragged row will land. */
.displayed-value.drop-target {
    background: rgba( 63, 97, 111, 0.10 );
    box-shadow: inset 0 0 0 1px var(--select);
}

.displayed-value .display-name {
    flex: 1 1 auto;
    min-width: 0;
}

/* The name reads as a label now - renaming is its own prompt on the context menu - so it needs the ellipsis that an
   input gave for free. */
.displayed-value span.display-name {
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    padding: 0.2rem 0;
    font-size: 0.85rem;
    cursor: default;
}

.displayed-value .reorder {
    flex: 0 0 auto;
    display: flex;
    gap: 2px;
}

.edit-values-button,
.displayed-value .reorder button {
    cursor: pointer;
    border: 1px solid var(--border-strong);
    background: var(--surface);
    border-radius: 4px;
    font: inherit;
    transition: background-color 0.12s ease, border-color 0.12s ease;
}

.edit-values-button {
    padding: 0.25rem 0.7rem;
    font-size: 0.82rem;
}

.edit-values-button:hover {
    background: var(--surface-2);
    border-color: var(--faint);
}

.displayed-value .reorder button {
    line-height: 1;
    padding: 0 0.4rem;
}

.displayed-value .reorder button:hover:not(:disabled) {
    background: var(--surface-2);
}

.displayed-value .reorder button:disabled {
    opacity: 0.4;
    cursor: default;
}

/* ---- Modal (te- prefix so it doesn't clash with Bootstrap's .modal*) ---- */

.te-modal-overlay {
    position: fixed;
    inset: 0;
    background: rgba(15, 23, 42, 0.45);
    display: flex;
    align-items: center;
    justify-content: center;
    z-index: 1000;
}

.te-modal {
    background: var(--surface);
    border-radius: 12px;
    width: 560px;
    height: 600px;
    max-width: 95vw;  /* safety clamp for narrow windows only */
    max-height: 90vh; /* safety clamp for short windows only */
    display: flex;
    flex-direction: column;
    box-shadow: var(--shadow-lg);
    overflow: hidden;
}

/* Compact variant for small prompts (e.g. the debug token prompt) — sizes to content. */
.te-modal.te-modal-auto {
    height: auto;
    width: 420px;
}

.token-prompt-hint {
    margin: 0.6rem 0 0;
    font-size: 0.8rem;
    color: var(--muted);
}

/* A floating, draggable panel rather than a blocking modal: no dimming and no pointer blocking, so what is
   behind stays visible AND usable while the panel is open. The panel itself takes pointers back. */
.te-modal-overlay.te-modal-floating {
    background: transparent;
    pointer-events: none;
}

.te-modal-overlay.te-modal-floating .te-modal {
    pointer-events: auto;
}

.te-modal-draggable > .te-modal-header {
    cursor: move;
    user-select: none;
}

.te-modal-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0.7rem 1rem;
    font-weight: 600;
    color: #fff;
    background: var(--select);
}

.te-modal-close {
    border: none;
    background: none;
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    color: #fff;
    padding: 0.2rem 0.35rem;
    border-radius: 4px;
}

.te-modal-close:hover {
    background: rgba( 255, 255, 255, 0.2 );
}

/* Fills the space between header and footer, with its own scrollbar. */
.te-modal-body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 0.8rem 1rem;
}

.te-modal-body label {
    display: block;
    margin-bottom: 0.35rem;
    font-size: 0.8rem;
    font-weight: 600;
    color: var(--muted);
}

.te-modal-body input[type=text] {
    width: 100%;
    padding: 0.4rem 0.55rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    font: inherit;
    font-size: 0.9rem;
    background: var(--surface);
    color: var(--text);
}

.te-modal-body input[type=text]:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 2px var(--accent-soft);
}

.te-prompt-text {
    margin: 0;
}

/* Straight from the server: keeps its line breaks, wraps long identifiers, and scrolls rather than growing without
   limit. */
.te-problem-text {
    margin: 0;
    max-height: 40vh;
    overflow-y: auto;
    white-space: pre-wrap;
    overflow-wrap: anywhere;
    font-size: 0.85rem;
    line-height: 1.45;
}

/* Resize: the three sizing modes, and the size two of them need. These override .te-modal-body label, which is
   styled as a caption above a field rather than as the text beside a radio. */
.te-modal-body label.te-resize-mode {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-bottom: 0.3rem;
    font-size: 0.85rem;
    font-weight: 400;
    color: var(--text);
}

.te-modal-body label.te-resize-size {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    margin: 0.8rem 0 0;
    font-weight: 400;
    color: var(--text);
}

.te-modal-body label.te-resize-size input {
    width: 6rem;
    padding: 0.3rem 0.4rem;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    font: inherit;
    background: var(--surface);
    color: var(--text);
}

/* Auto-size has no size of its own, so the box stays put and goes quiet. */
.te-modal-body label.te-resize-size input:disabled {
    opacity: 0.5;
    cursor: default;
}


.te-modal-footer {
    padding: 0.8rem 1rem;
    border-top: 1px solid var(--border);
    display: flex;
    justify-content: flex-end;
    gap: 0.5rem;
}

.te-modal-footer button {
    cursor: pointer;
    border: 1px solid var(--border-strong);
    background: var(--surface);
    color: var(--text);
    border-radius: var(--radius-sm);
    font: inherit;
    font-size: 0.85rem;
    padding: 0.35rem 1rem;
    transition: background-color 0.12s ease, border-color 0.12s ease;
}

.te-modal-footer button:hover {
    background: var(--surface-2);
    border-color: var(--faint);
}

.te-modal-footer button.te-modal-primary {
    background: var(--accent);
    color: #fff;
    border-color: var(--accent);
}

.te-modal-footer button.te-modal-primary:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
}

/* ---- Select-fields panel (Displayed values / columns) ---- */

.te-fields-filter {
    flex: 0 0 auto;
    display: flex;
    align-items: stretch;
    border-bottom: 1px solid var(--border);
}

.te-fields-filter input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0.55rem 0.7rem;
    border: none;
    background: none;
    font: inherit;
    font-size: 0.85rem;
    color: var(--text);
}

.te-fields-filter input:focus {
    outline: none;
}

/* How many fields the list is showing. */
.te-fields-count {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    min-width: 3rem;
    padding: 0 0.6rem;
    border-left: 1px solid var(--border);
    font-size: 0.85rem;
    color: var(--text);
}

.te-fields-title {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The header's overflow menu. Sized like the Back button beside it so the two read as a pair. */
.te-fields-more {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.15rem 0.5rem;
    border: none;
    border-radius: 4px;
    background: rgba( 255, 255, 255, 0.15 );
    cursor: pointer;
    font: inherit;
    font-size: 0.8rem;
    line-height: 1.4;
    color: #fff;
}

.te-fields-more:hover {
    background: rgba( 255, 255, 255, 0.3 );
}

/* Keeps the header tick in the same column as the row ticks. The extra margin makes up the difference between
   this and the glyph it stands in for. */
.drag-grip-spacer {
    flex: 0 0 auto;
    width: 1ch;
    margin-left: 0.4rem;
    padding: 0 0.15rem;
}

/* Reads as a control rather than part of the heading: a tinted, hoverable pill whose word is clickable. The left
   padding stays at zero so the tick keeps lining up with the ticks in the rows below. */
.te-fields-all {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    padding: 0.1rem 0.4rem 0.1rem 0;
    border-radius: 4px;
    background: rgba( 255, 255, 255, 0.18 );
    cursor: pointer;
    font-size: 0.75rem;
    font-weight: 400;
}

.te-fields-all:hover {
    background: rgba( 255, 255, 255, 0.32 );
}

.te-fields-all input {
    cursor: pointer;
}

.te-fields-columns-label {
    flex: 1 1 auto;
    /* Set off from the control beside it. */
    padding-left: 0.5rem;
    border-left: 1px solid rgba( 255, 255, 255, 0.35 );
}

.te-fields-shown {
    flex: 0 0 auto;
    font-weight: 400;
    font-size: 0.75rem;
    opacity: 0.85;
}

.te-fields-columns {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    gap: 0.4rem;
    padding: 0.45rem 0.7rem;
    background: var(--accent);
    color: #fff;
    font-size: 0.85rem;
    font-weight: 600;
}

/* The field list as a drill-down filling the properties column, in place of the cards. */
.te-fields-panel {
    display: flex;
    flex-direction: column;
    /* Fills the column so its list scrolls, rather than the whole pane. */
    max-height: 100%;
}

.te-fields-panel-header {
    display: flex;
    align-items: center;
    gap: 0.5rem;
}

.te-fields-back {
    flex: 0 0 auto;
    padding: 0 0.35rem;
    border: none;
    border-radius: 4px;
    background: rgba( 255, 255, 255, 0.15 );
    cursor: pointer;
    font: inherit;
    font-size: 1rem;
    line-height: 1.4;
    color: #fff;
}

.te-fields-back:hover {
    background: rgba( 255, 255, 255, 0.3 );
}

/* Fills the panel and scrolls itself; rows run edge to edge as they do in the portal. */
.te-fields-list {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    padding: 0;
}

/* Right-clicking a field opens the menu at the pointer, so it has to escape the list's own scrolling - hence fixed,
   where the command bar's menus are absolute inside their button. --te-menu-x/y are the click point, arriving as a
   style attribute; min() against 100vw/100vh keeps the whole menu on screen when the click is near an edge, which
   the browser can work out with nothing having to measure it. The two sizes are the menu's own: it is a fixed set
   of four commands, so they do not have to be guessed at. */
.te-fields-menu {
    position: fixed;
    z-index: 30;
    --te-menu-width: 16rem;
    /* A fallback only: the menu sets its own from how many commands it is showing, since Send to... makes that vary. */
    --te-menu-height: 10.5rem;
    left: max( 0.25rem, min( var( --te-menu-x, 0px ), calc( 100vw - var( --te-menu-width ) ) ) );
    top: max( 0.25rem, min( var( --te-menu-y, 0px ), calc( 100vh - var( --te-menu-height ) ) ) );
}

/* The category its fields sit under. Sticky, so it is still clear which category you are looking at once the list
   is long enough to scroll. */
.te-fields-category {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: baseline;
    gap: 0.4rem;
    padding: 0.4rem 0.6rem 0.25rem;
    background: var(--surface-2);
    border-bottom: 1px solid var(--border);
    /* The whole row collapses it, so it has to look like it does something. */
    cursor: pointer;
    user-select: none;
}

.te-fields-category:hover {
    background: var(--accent-soft);
}

.te-fields-category-toggle {
    flex: 0 0 auto;
    padding: 0;
    width: 1ch;
    border: none;
    background: none;
    cursor: pointer;
    font: inherit;
    font-size: 0.7rem;
    line-height: 1;
    color: var(--muted);
}

.te-fields-category-name {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 0.78rem;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--muted);
}

.te-fields-category-count {
    flex: 0 0 auto;
    font-size: 0.72rem;
    color: var(--faint);
}

/* Where a dragged field would land if it were dropped on the heading. */
.te-fields-category.drop-target {
    background: rgba( 63, 97, 111, 0.14 );
    box-shadow: inset 0 0 0 1px var(--select);
}

/* How the column is sized, worded as GhostWin words it. Last on the row, so the names stay readable. */
.te-fields-note {
    flex: 0 0 auto;
    font-size: 0.75rem;
    color: var(--muted);
    white-space: nowrap;
}

/* ---- Font picker ---- */

.font-button {
    width: 100%;
    text-align: left;
    cursor: pointer;
    border: 1px solid var(--border-strong);
    border-radius: 4px;
    background: var(--surface);
    padding: 0.25rem 0.5rem;
    font: inherit;
    font-size: 0.82rem;
}

.font-button:hover {
    border-color: var(--faint);
}

.font-picker-grid {
    display: flex;
    flex-direction: column;
    gap: 0.8rem;
    padding: 0.4rem 0;
}

.font-picker-grid label {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-size: 0.85rem;
    color: var(--muted);
}

.font-picker-grid select,
.font-picker-grid input {
    padding: 0.3rem 0.4rem;
    border: 1px solid var(--border-strong);
    border-radius: 4px;
    font: inherit;
    background: var(--surface);
    color: var(--text);
}

.font-effects {
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 0.6rem 0.8rem;
    margin-top: 0.8rem;
}

.font-effects legend {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--muted);
    padding: 0 0.3rem;
}

.font-effects label {
    display: inline-flex;
    gap: 0.35rem;
    align-items: center;
    margin-right: 1rem;
    font-size: 0.85rem;
}

.font-sample {
    margin-top: 1rem;
}

.font-sample > span {
    font-size: 0.75rem;
    font-weight: 600;
    color: var(--muted);
}

.font-sample-text {
    margin-top: 0.4rem;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    padding: 1rem;
    text-align: center;
    min-height: 3rem;
    background: var(--surface-2);
}

.font-script {
    margin-top: 0.8rem;
}
