/* AL Dev Toolbox — tool page styles.
   Everything specific to the user-facing tools: templates browser and
   read-only detail, New Workspace / New Extension forms with the folder
   tree preview and dependency picker, snippets, Object Explorer (release
   browser, module/object views, source file viewer, diff), generate
   button loading state, and the mustache-variables hint. */

/* ---------- Templates browser ---------- */

.templates-browser { max-width: 1100px; }
.templates-browser h2 { margin-top: 28px; }
.templates-browser h2:first-of-type { margin-top: 16px; }

.data-table {
    width: 100%;
    border-collapse: collapse;
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    overflow: hidden;
    font-size: 13px;
}
.data-table th, .data-table td {
    padding: 8px 12px;
    text-align: left;
    border-bottom: 1px solid var(--color-border);
}
.data-table th {
    background: var(--color-bg);
    font-weight: 500;
    color: var(--color-text-secondary);
    font-size: 12px;
    letter-spacing: 0.02em;
    text-transform: uppercase;
}
.data-table tr:last-child td { border-bottom: 0; }
.data-table code { font-family: var(--font-mono); font-size: 12.5px; }

/* ---------- Workspace form ---------- */

.workspace-form { max-width: 640px; }

/* Two-column shell for the New Workspace / New Extension pages: the form
   scrolls on the left, the preview sticks to the top of the viewport on the
   right so it stays visible while the user works through a long modules list. */
.workspace-page { max-width: 1100px; }


.workspace-layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 360px;
    gap: 32px;
    align-items: start;
}

.workspace-layout__form { min-width: 0; }

.workspace-layout__generate { align-self: flex-start; }

.workspace-layout__preview {
    position: sticky;
    top: 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.preview-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 14px 16px;
    max-height: calc(100vh - 120px);
    overflow: auto;
}

.preview-card .section-label { margin-bottom: 8px; display: block; }

/* Import-config card on the New Workspace / New Extension pages. Lives at
   the top of the right-hand column and reuses .preview-card for chrome. */
.import-card {
    max-height: none;
    overflow: visible;
}

.import-card__row {
    display: flex;
    align-items: center;
    gap: 10px;
    flex-wrap: wrap;
}

.import-card__button,
.logo-upload__button {
    cursor: pointer;
    margin: 0;
    align-self: flex-start;
}

/* Native <input type="file"> is awkward to style cross-browser, so we hide
   it visually and let the surrounding label trigger it. The accessible
   name comes from the label's text content. */
.import-card__input,
.logo-upload__input {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.import-card__filename {
    font-size: 12.5px;
    color: var(--color-text-secondary);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    flex: 1 1 auto;
}

.import-card__hint { margin-top: 8px; }

.preview-stats {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
}

.stat-card {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 10px 12px;
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.stat-card__value {
    font-size: 20px;
    font-weight: 600;
    color: var(--color-text);
}

.stat-card__label {
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
}

/* ---------- Folder tree preview ---------- */

.ftp,
.ftp__children {
    list-style: none;
    margin: 0;
    padding: 0;
}

.ftp.ftp--root {
    font-family: var(--font-mono);
    font-size: 12.5px;
    line-height: 1.7;
    color: var(--color-text-secondary);
}

.ftp__children {
    padding-left: 18px;
    border-left: 1px dashed var(--color-border);
    margin-left: 6px;
}

.ftp__row {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

/* Folder rows render as a button so the whole label is clickable to toggle. */
.ftp__row--toggle {
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    font: inherit;
    color: inherit;
    cursor: pointer;
}

.ftp__row--toggle:hover .ftp__name { color: var(--color-text); }

.ftp__chevron {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 12px;
    color: var(--color-text-secondary);
    flex: 0 0 auto;
}

.ftp__chevron--placeholder { visibility: hidden; }

.ftp__icon {
    display: inline-flex;
    align-items: center;
    color: var(--color-text-secondary);
    flex: 0 0 auto;
}

.ftp__name { white-space: nowrap; }

/* The workspace root and module/Core folders get the accent treatment so
   users can distinguish "things that will be generated" from grouping or
   static folders. */
.ftp__node--workspace > .ftp__row > .ftp__icon,
.ftp__node--workspace > .ftp__row > .ftp__name,
.ftp__node--extension > .ftp__row > .ftp__icon,
.ftp__node--extension > .ftp__row > .ftp__name {
    color: var(--color-info);
}

.ftp__node--workspace > .ftp__row > .ftp__name,
.ftp__node--extension > .ftp__row > .ftp__name {
    font-weight: 500;
}

.ftp__node--file > .ftp__row > .ftp__name { color: var(--color-text-muted); }

/* Marker pinned next to a node that's been added relative to an existing
   workspace — used by New Extension's sibling-extension preview. */
.ftp__badge {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    padding: 1px 6px;
    border-radius: 999px;
    line-height: 1.4;
    margin-left: 6px;
}

.ftp__badge--new {
    background: var(--color-accent-soft);
    color: var(--color-accent);
}

/* ---------- Dependency picker ---------- */

.dep-picker { display: flex; flex-direction: column; gap: 16px; }

.dep-picker__group { display: flex; flex-direction: column; gap: 8px; }

.dep-picker__category { display: flex; flex-direction: column; gap: 4px; }

.dep-picker__category-name {
    font-size: 12px;
    font-weight: 500;
    color: var(--color-text-muted);
    padding: 4px 0 2px;
}

.dep-picker__rows { display: flex; flex-direction: column; gap: 4px; }

.dep-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 8px 10px;
    background: var(--color-surface);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    cursor: pointer;
    transition: background 0.1s ease;
}

.dep-row:hover { background: var(--color-bg); }
.dep-row--selected { background: var(--color-accent-soft); border-color: var(--color-accent); }

.dep-row__body { display: flex; flex-direction: column; gap: 1px; flex: 1; min-width: 0; }
.dep-row__name { font-weight: 500; }
.dep-row__publisher { font-size: 12px; }

.dep-row__version {
    flex: 0 0 110px;
    padding: 5px 8px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    font-family: var(--font-mono);
    font-size: 12px;
    background: var(--color-surface);
}

.dep-picker__manual {
    display: grid;
    grid-template-columns: 2fr 1.5fr 1.5fr 1fr auto;
    gap: 6px;
    align-items: center;
}

.dep-picker__manual input {
    padding: 7px 9px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    font-size: 13px;
    background: var(--color-surface);
    min-width: 0;
}

.dep-picker__error { color: var(--color-danger); margin: 0; }

.dep-picker__manual-list { list-style: none; margin: 4px 0 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }

.dep-chip {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 10px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    font-size: 13px;
}

.dep-chip__body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 1px; }
.dep-chip__id { font-family: var(--font-mono); font-size: 11px; }

.dep-chip__remove {
    border: 0;
    background: transparent;
    color: var(--color-text-muted);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    padding: 0 4px;
}

.dep-chip__remove:hover { color: var(--color-danger); }

/* ---------- Success tip (New Extension) ---------- */

.success-tip {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 14px 16px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.success-tip p { margin: 0; }

.success-tip code {
    font-family: var(--font-mono);
    font-size: 12px;
    background: var(--color-bg);
    padding: 1px 4px;
    border-radius: 3px;
}

.success-tip__snippet {
    margin: 0;
    padding: 8px 10px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    font-family: var(--font-mono);
    font-size: 12px;
    overflow-x: auto;
}

/* ---------- Mustache variables hint ---------- */

.mustache-hint {
    margin: 0 0 16px;
    padding: 10px 12px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface-muted, var(--color-surface));
    font-size: 13px;
}

.mustache-hint > summary {
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-weight: 600;
    list-style: none;
}

.mustache-hint > summary::-webkit-details-marker { display: none; }

.mustache-hint__intro {
    margin: 8px 0 6px;
    font-size: 12px;
}

.mustache-hint__list {
    margin: 0;
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: 16px;
    row-gap: 4px;
}

.mustache-hint__list dt code {
    font-size: 12px;
}

.mustache-hint__list dd {
    margin: 0;
    color: var(--color-text-muted);
    font-size: 12px;
}

/* ---------- Template detail (read-only /templates/{key}) ---------- */

.template-detail { max-width: 1100px; }

.template-detail__header {
    display: flex;
    justify-content: space-between;
    align-items: flex-start;
    gap: 16px;
    margin-bottom: 16px;
}

.template-detail__header h1 { margin: 0 0 6px; }

.template-detail__header p { margin: 0; }

.template-detail__meta { margin-bottom: 24px; }

.template-detail__section { margin-bottom: 24px; }

.template-detail__section h2 {
    font-size: 16px;
    margin: 0 0 8px;
}

.template-detail__section textarea.json-editor {
    font-family: var(--font-mono);
    font-size: 12.5px;
    line-height: 1.5;
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    background: var(--color-surface);
    color: var(--color-text);
    resize: vertical;
}

.template-detail__actions {
    display: flex;
    gap: 12px;
    margin-top: 16px;
}

.kv-grid {
    display: grid;
    grid-template-columns: max-content 1fr;
    gap: 6px 16px;
    margin: 0 0 8px;
    font-size: 13px;
}

.kv-grid dt {
    color: var(--color-text-muted, #57606a);
    font-weight: 500;
}

.kv-grid dd { margin: 0; }

/* Two-column layout: editor on the left, live preview on the right.
   Same shape as the New Workspace page's workspace-layout grid so the
   admin and end-user surfaces feel related. */
.admin-template-edit__layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 32px;
    align-items: start;
    max-width: 1240px;
}

.admin-template-edit__editor { min-width: 0; }

.admin-template-edit__preview {
    position: sticky;
    top: 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

/* Mirrors admin-template-edit__layout for the /admin/templates/files page.
   The mustache catalogue now lists 18 variables (with the per-extension
   app.json inputs); the inline MustacheVarsHint that used to sit above the
   file list was crowding the editor. */
.admin-template-files__layout {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 320px;
    gap: 32px;
    align-items: start;
    max-width: 1240px;
}

.admin-template-files__editor { min-width: 0; }

.admin-template-files__aside {
    position: sticky;
    top: 16px;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

@media (max-width: 900px) {
    .admin-template-files__layout {
        grid-template-columns: 1fr;
    }
    .admin-template-files__aside {
        position: static;
    }
}

.preview-card__hint {
    margin: 0 0 8px;
    font-size: 12px;
}

/* Collapsible variant used on the admin template editor: the <details>
   element gets the card chrome, the <summary> becomes the clickable header,
   and content sits in a padded body div. Keeps the card padding consistent
   whether the panel is open or closed. */
.preview-card--collapsible {
    padding: 0;
    max-height: none;
    overflow: visible;
}

.preview-card__summary {
    list-style: none;
    cursor: pointer;
    user-select: none;
    padding: 12px 16px;
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 11px;
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--color-text-secondary);
}

.preview-card__summary::-webkit-details-marker { display: none; }

.preview-card__summary::before {
    content: "";
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 4px 0 4px 6px;
    border-color: transparent transparent transparent currentColor;
    transition: transform 0.15s ease;
}

.preview-card--collapsible[open] > .preview-card__summary::before {
    transform: rotate(90deg);
}

.preview-card__body {
    padding: 0 16px 14px;
}

.preview-card--scroll[open] {
    max-height: calc(100vh - 200px);
    overflow: auto;
}

.mustache-vars {
    margin: 0;
    display: grid;
    grid-template-columns: max-content 1fr;
    column-gap: 12px;
    row-gap: 6px;
    font-size: 12px;
}

.mustache-vars dt { margin: 0; }
.mustache-vars dt code { font-size: 12px; white-space: nowrap; }
.mustache-vars dd { margin: 0; color: var(--color-text-muted); }
.mustache-vars dd code { font-size: 11px; }

.admin-template-edit__form { max-width: 880px; }

/* Section heading inside the structured form. Gives long pages a scannable
   outline so admins can find Identity / Versioning & ID ranges / Modules /
   Always-included files / Workspace settings overlay / Extensions without
   reading every label. Sits between sections; a thin separator above the
   text echoes the panel rhythm of the surrounding cards. */
.admin-template-edit__group-heading {
    font-size: 13px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--color-text-secondary);
    margin: 24px 0 4px;
    padding-top: 16px;
    border-top: 1px solid var(--color-border);
}

.admin-template-edit__group-heading:first-of-type {
    margin-top: 8px;
    padding-top: 0;
    border-top: 0;
}

/* Read-only reference panel showing the org-wide workspace settings
   above the per-template overlay textarea. <details> so it collapses
   by default and doesn't dominate the section. */
.admin-template-edit__org-base {
    margin-bottom: 8px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-bg);
}

.admin-template-edit__org-base > summary {
    cursor: pointer;
    padding: 6px 10px;
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 12.5px;
    color: var(--color-text-secondary);
    list-style: none;
}

.admin-template-edit__org-base > summary::-webkit-details-marker { display: none; }
.admin-template-edit__org-base > summary::marker { content: ""; }

.admin-template-edit__org-base[open] > summary {
    border-bottom: 1px solid var(--color-border);
}

.admin-template-edit__org-base-body {
    margin: 0;
    padding: 10px 12px;
    font-family: var(--font-mono, monospace);
    font-size: 12.5px;
    color: var(--color-text);
    white-space: pre;
    overflow-x: auto;
    max-height: 220px;
}

/* Pill tabs. Originally introduced for the Form / TOML toggle on the
   template editor; now also used to navigate sub-pages of the per-org
   Administration surface (where each tab is an <a href> to a sibling
   route rather than an in-page button). The container is the same; only
   the inner elements differ. */
.pill-tabs {
    display: inline-flex;
    gap: 0;
    margin-bottom: 16px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    padding: 2px;
    background: var(--color-surface);
}

.pill-tab {
    border: 1px solid transparent;
    background: transparent;
}

.pill-tab--active {
    background: var(--color-bg);
    border-color: var(--color-border);
}

/* Anchor variant is used when pill tabs are route navigation links
   (PillTabs.razor); the button variant covers in-page mode toggles
   like Piper's Text/Table switcher. Both share the same size/spacing so
   the visual language matches across the app. */
a.pill-tab,
button.pill-tab {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 6px 12px;
    border-radius: calc(var(--radius) - 2px);
    color: var(--color-text);
    text-decoration: none;
    font-size: 13px;
    line-height: 1.2;
}

button.pill-tab {
    font: inherit;
    font-size: 13px;
    cursor: pointer;
}

a.pill-tab:hover,
button.pill-tab:hover { text-decoration: none; }
a.pill-tab.pill-tab--active,
button.pill-tab.pill-tab--active { color: var(--color-text); font-weight: 500; }

.admin-template-edit__toml-form { max-width: 880px; }

/* The CodeMirror 6 editor mounts into this div. We give it a fixed height
   so the gutter and content area share a stable scroll context, and let
   the inner .cm-editor fill it. Theme colours come from the CodeMirror
   extension in wwwroot/code-editor.js — we only frame the box. */
.admin-template-edit__toml {
    height: 540px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--color-surface);
}

.admin-template-edit__toml .cm-editor {
    height: 100%;
    font-family: var(--font-mono);
    font-size: 12.5px;
}

.admin-template-edit__toml .cm-scroller {
    line-height: 1.5;
}

.admin-template-edit__toml-issues {
    margin-top: 8px;
}

.admin-template-edit__actions {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 16px;
    flex-wrap: wrap;
}

.folder-editor__header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}

.folder-editor__table input[type="text"] {
    width: 100%;
    padding: 6px 8px;
    font-size: 13px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
    color: var(--color-text);
}

.folder-editor__handle { width: 1%; white-space: nowrap; }

.folder-editor__order { display: flex; gap: 4px; }

.folder-editor__list {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.folder-editor__row {
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
}

.folder-editor__row-head {
    display: flex;
    align-items: flex-start;
    gap: 8px;
    padding: 8px;
}

/* Match the up/down/remove buttons to the height of the path input so the
   row reads as one line rather than three slightly-different mini-controls.
   Scoped to the template editor's extension/folder list — other pages (e.g.
   AdminApplicationVersions) reuse `.folder-editor__*` classes with their own
   button sizing that mustn't be bumped to 30px. */
.extension-editor__list .folder-editor__row-head {
    align-items: center;
}

.extension-editor__list .folder-editor__row-head > .btn--sm,
.extension-editor__list .folder-editor__order .btn--sm {
    min-height: 30px;
}

.folder-editor__path {
    flex: 1 1 auto;
    min-width: 0;
}

.folder-editor__path input[type="text"] {
    width: 100%;
    padding: 6px 8px;
    font-size: 13px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-bg);
    color: var(--color-text);
}

.folder-editor__files {
    border-top: 1px solid var(--color-border);
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 8px;
    background: var(--color-bg);
}

.folder-editor__files-empty { margin: 0; }

.folder-editor__file {
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-surface);
    padding: 8px;
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.folder-editor__file-head {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

.folder-editor__file-content {
    width: 100%;
    min-height: 160px;
}

/* Buttons under a recursive folder's expanded body: "Add file" / "Add subfolder". */
.folder-editor__add-row {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

/* Inline checkbox used inside a file-header row (Example toggle). */
.check-row.check-row--inline {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    margin: 0;
    white-space: nowrap;
    font-size: 13px;
}

/* Visual nesting cue for child folders inside the recursive editor. The
   border-left tracks depth without indenting via padding so reorder buttons
   stay flush with the parent's edge. */
.folder-editor__row--depth-1 { border-left: 2px solid var(--color-border-strong, var(--color-border)); }
.folder-editor__row--depth-2 { border-left: 2px solid var(--color-border-strong, var(--color-border)); margin-left: 4px; }
.folder-editor__row--depth-3 { border-left: 2px solid var(--color-border-strong, var(--color-border)); margin-left: 8px; }

/* Application versions editor: the reorder block has four buttons in a 2x2
   grid (top row moves up, bottom row moves down), and the input box plus
   the destructive Remove button stretch to match its height so the
   row-head reads as a single aligned strip. */
.app-versions-editor__row-head {
    align-items: stretch;
}

.app-versions-editor__order {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.app-versions-editor__order-row {
    display: flex;
    gap: 2px;
}

.app-versions-editor__order-btn {
    width: 28px;
    height: 14px;
    padding: 0;
    justify-content: center;
    gap: 0;
}

.app-versions-editor__path {
    display: flex;
    flex-direction: column;
    justify-content: center;
}

.app-versions-editor__remove {
    align-self: stretch;
    padding: 0 10px;
}

/* Extension list — each extension is a folder-editor-style row with a richer
   body of nested sections (metadata, dependencies, folders). */
.extension-editor__list {
    gap: 16px;
}

.extension-editor__row {
    background: var(--color-bg);
}

.extension-editor__body {
    border-top: 1px solid var(--color-border);
    padding: 12px;
    display: flex;
    flex-direction: column;
    gap: 12px;
    background: var(--color-surface);
}

.extension-editor__body .form-section { margin: 0; }

/* Dependency-row body uses the existing dep-editor__fields grid; this rule
   adds the per-row select+remove header spacing without touching that grid. */
.dep-row .folder-editor__path select {
    width: 100%;
    padding: 6px 8px;
    font-size: 13px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-bg);
    color: var(--color-text);
}

/* Per-row field grid used by the module dependency editor and the catalogue
   editor. Sits inside a .folder-editor__row beneath the GUID/order/remove
   header so the four metadata fields wrap predictably on narrow viewports. */
.dep-editor__fields {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: 8px;
    padding: 0 8px 8px;
}

.dep-editor__fields input[type="text"] {
    width: 100%;
    padding: 6px 8px;
    font-size: 13px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-bg);
    color: var(--color-text);
}

/* Module dependency editor: catalogue picker row above the selected-rows list,
   read-only GUID/name/publisher rendered as labels inside each row, and a
   small badge for legacy rows whose GUID isn't in the current catalogue. */
.mod-dep-add {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}


.mod-dep-add select {
    flex: 1;
    min-width: 0;
    padding: 6px 8px;
    font-size: 13px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-bg);
    color: var(--color-text);
}

.mod-dep-row__heading {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: baseline;
    gap: 4px;
    flex-wrap: wrap;
    padding: 0 8px;
}

.mod-dep-row__name { font-weight: 500; }

.mod-dep-row__legacy {
    font-size: 11px;
    padding: 1px 6px;
    border-radius: 999px;
    background: var(--color-bg);
    color: var(--color-text-muted);
    border: 1px solid var(--color-border);
}

.mod-dep-row__fields {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr);
    gap: 8px;
    padding: 0 8px 8px;
}

.mod-dep-row__id code {
    display: block;
    font-family: var(--font-mono);
    font-size: 12px;
    word-break: break-all;
}

.mod-dep-row__version label {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.mod-dep-row__version input[type="text"] {
    width: 100%;
    padding: 6px 8px;
    font-size: 13px;
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    background: var(--color-bg);
    color: var(--color-text);
}

/* ---------- Generate button loading state ---------- */

/* The default state hides the spinner and the busy label. Toggling the
   modifier class swaps which span is visible; the wrapper's width is
   stable because both labels are display:inline-block of similar length. */
.btn__spinner,
.btn__label-busy { display: none; }

.btn--loading .btn__label { display: none; }
.btn--loading .btn__label-busy { display: inline-flex; align-items: center; gap: 6px; }

.btn--loading .btn__spinner {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    border: 2px solid currentColor;
    border-right-color: transparent;
    animation: spin 0.8s linear infinite;
    margin-right: 6px;
}

.btn--loading {
    cursor: wait;
    opacity: 0.85;
}

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

/* ---------- Snippets ---------- */
/* Browser, detail, admin queue, editor. */
.snippets-browser__toolbar {
    display: flex;
    align-items: center;
    gap: 12px;
    margin: 12px 0 20px;
}

.snippets-browser__search {
    flex: 1;
    max-width: 480px;
}

.snippet-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: grid;
    gap: 12px;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
}

.snippet-card {
    border: 1px solid var(--color-border, #d0d7de);
    border-radius: 6px;
    padding: 14px 16px;
    background: var(--color-surface, #fff);
}

.snippet-card__title {
    display: block;
    font-weight: 600;
    margin-bottom: 4px;
}

.snippet-card__badge {
    display: inline-block;
    margin-left: 8px;
    font-size: 0.75em;
    padding: 1px 6px;
    border-radius: 8px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    color: var(--color-text-muted);
}

.snippet-card__description {
    margin: 8px 0;
    color: var(--color-text-muted);
}

.snippet-card__keywords {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.snippet-card__keyword {
    font-size: 0.75em;
    padding: 2px 8px;
    border-radius: 8px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    color: var(--color-text-muted);
}

.snippet-detail--with-outline {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 240px;
    gap: 24px;
    align-items: start;
}

.snippet-detail__main {
    min-width: 0;
}

.snippet-detail__topbar {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.snippet-detail__header {
    display: flex;
    align-items: baseline;
    gap: 12px;
    margin-top: 16px;
}

.snippet-detail__description {
    white-space: pre-wrap;
    margin: 8px 0 16px;
}

.snippet-detail__instructions {
    margin: 16px 0 20px;
    padding: 12px 16px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: 6px;
}

.snippet-detail__instructions h2 {
    margin-top: 0;
    font-size: 1.1em;
}

.snippet-detail__instructions-body :first-child {
    margin-top: 0;
}

.snippet-detail__instructions-body :last-child {
    margin-bottom: 0;
}

.snippet-detail__instructions-body pre {
    background: var(--color-surface);
    border: 1px solid var(--color-border);
    border-radius: 4px;
    padding: 8px 12px;
    overflow-x: auto;
}

.snippet-detail__instructions-body code {
    font-family: var(--font-mono, ui-monospace, SFMono-Regular, monospace);
    font-size: 0.9em;
}

.snippet-detail .snippet-card__keywords {
    margin-bottom: 20px;
}

.snippet-detail h2 {
    margin-top: 8px;
}

.snippet-detail__files {
    list-style: none;
    padding: 0;
    display: grid;
    gap: 16px;
}

.snippet-file-view {
    border: 1px solid var(--color-border);
    border-radius: 6px;
    overflow: hidden;
    background: var(--color-surface);
}

.snippet-file-view__header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 8px;
    padding: 4px 12px 4px 6px;
    background: var(--color-bg);
    color: var(--color-text);
    border-bottom: 1px solid var(--color-border);
}

.snippet-file-view--collapsed .snippet-file-view__header {
    border-bottom-color: transparent;
}

.snippet-file-view__toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: 1 1 auto;
    min-width: 0;
    background: transparent;
    border: 0;
    padding: 4px 6px;
    border-radius: 4px;
    color: inherit;
    cursor: pointer;
    text-align: left;
    font: inherit;
}

.snippet-file-view__toggle:hover {
    background: var(--color-surface);
}

.snippet-file-view__toggle code {
    color: var(--color-text);
    font-family: var(--font-mono);
    font-size: 12.5px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
}

.snippet-file-view__header code {
    color: var(--color-text);
    font-family: var(--font-mono);
    font-size: 12.5px;
}

.snippet-file-view__content {
    margin: 0;
    padding: 12px;
    overflow-x: auto;
    font-size: 0.85em;
    line-height: 1.45;
}

.snippet-file-view--collapsed .snippet-file-view__content {
    display: none;
}

.snippet-outline {
    position: sticky;
    top: 16px;
    align-self: start;
    max-height: calc(100vh - 32px);
}

.snippet-outline__inner {
    border: 1px solid var(--color-border);
    border-radius: 6px;
    background: var(--color-surface);
    padding: 12px 14px;
    overflow: auto;
    max-height: calc(100vh - 32px);
}

.snippet-outline__title {
    font-size: 0.85em;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    margin: 0 0 8px;
    color: var(--color-text-muted, var(--color-text));
}

.snippet-outline__list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    gap: 4px;
}

.snippet-outline__link {
    display: block;
    padding: 4px 6px;
    border-radius: 4px;
    font-family: var(--font-mono);
    font-size: 12.5px;
    color: var(--color-text);
    text-decoration: none;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.snippet-outline__link:hover {
    background: var(--color-bg);
}

@media (max-width: 960px) {
    .snippet-detail--with-outline {
        grid-template-columns: 1fr;
    }
    .snippet-outline {
        position: static;
        max-height: none;
    }
    .snippet-outline__inner {
        max-height: none;
    }
}

.snippet-files {
    display: flex;
    flex-direction: column;
    gap: 10px;
}

.snippet-file {
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    padding: 10px 12px;
    background: var(--color-surface);
}

.snippet-file--expanded { padding-bottom: 12px; }

.snippet-file__row {
    display: flex;
    gap: 10px;
    align-items: flex-end;
}

.snippet-file--expanded .snippet-file__row { margin-bottom: 10px; }

.snippet-file__toggle {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 32px;
    margin-bottom: 1px;
    border: none;
    border-radius: var(--radius);
    background: transparent;
    color: var(--color-text-secondary);
    cursor: pointer;
}

.snippet-file__toggle:hover {
    background: var(--color-bg);
    color: var(--color-text);
}

.snippet-file__name {
    flex: 1;
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.snippet-file__name input {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    font-family: inherit;
    font-size: 13.5px;
    background: var(--color-surface);
    color: var(--color-text);
}

.snippet-file__actions {
    display: flex;
    gap: 4px;
    flex: 0 0 auto;
}

.snippet-file__actions .btn {
    height: 34px;
    padding: 0 10px;
}

.snippet-file__content {
    display: flex;
    flex-direction: column;
    gap: 4px;
}

.snippet-file__content textarea {
    width: 100%;
    padding: 8px 10px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    background: var(--color-surface);
    color: var(--color-text);
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
    font-size: 0.85em;
    resize: vertical;
}

.snippet-suggestion-list {
    list-style: none;
    padding: 0;
    display: grid;
    gap: 20px;
}

.snippet-suggestion {
    border: 1px solid var(--color-border, #d0d7de);
    border-radius: 6px;
    padding: 16px;
}

/* Applied when /admin/snippets/suggestions?focus={id} matches this row, so
   the admin lands directly on the suggestion the Review link pointed at. */
.snippet-suggestion--focused {
    border-color: var(--color-accent);
    box-shadow: 0 0 0 2px var(--color-accent-soft);
}

.snippet-suggestion__header {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    margin-bottom: 8px;
}

.snippet-suggestion__header h2 { margin: 0; }

.snippet-suggestion__note {
    min-width: 220px;
}

/* ---------- Object Explorer ---------- */
/* Release browser, module/object detail, search filters, results tables. */
.object-explorer__filters {
    display: grid;
    grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
    gap: 16px;
    align-items: end;
    margin: 16px 0;
}

/* Per-object C/AL Version List, shown under the object name in the results
   table (issue #271). Long; truncated with the full value on hover (title). */
.oe-version-list {
    margin-top: 2px;
    max-width: 360px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-mono, "SFMono-Regular", Consolas, monospace);
    font-size: 11px;
}

/* With the scope selector added, the Objects-mode row needs five
   columns (scope + search + three filters); procedures / content modes
   need just the two leading columns. `--with-scope` puts the narrow
   scope select first, hands the rest of the width to the search box,
   and lets the three filter columns (when present) sit at their
   natural fraction. */
.object-explorer__filters--with-scope {
    grid-template-columns: minmax(160px, 0.8fr) minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr) minmax(0, 1fr);
}

/* Wrap onto a second row on narrower viewports so the Extension column
   doesn't squeeze the search input down to nothing. The grid stays
   filter-shaped; one row gives way to two. */
@media (max-width: 1400px) {
    .object-explorer__filters--with-scope {
        grid-template-columns: minmax(160px, 0.8fr) minmax(0, 2fr) repeat(2, minmax(0, 1fr));
    }
}

/* Sortable column headers in the version browser. Keeps the <th>'s
   default look but turns the inner button into a hover-able click target
   with a small arrow on the active sort. */
.data-table__sort-button {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0;
    margin: 0;
    color: inherit;
    cursor: pointer;
    font: inherit;
    font-weight: inherit;
    text-align: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
}

.data-table__sort-button:hover {
    color: var(--text-1, #d8d8e0);
}

.data-table__sort-button.is-active {
    color: var(--text-1, #d8d8e0);
}

.data-table__sort-indicator {
    font-size: 10px;
    margin-left: 2px;
}

/* `.field` is referenced from a handful of forms but never defined globally
   until now. Stack label + control vertically with consistent spacing so
   the Object Explorer filter row lines up evenly across search inputs and
   select boxes. */
.object-explorer__filters .field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.object-explorer__filters .field__label {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-2, #b0b0c2);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

/* Visual styling for the Object Explorer's select boxes — the filter
   row on the version browser and the "Compare with" picker on the file
   header both fall outside the .form-section / .admin-form scopes that
   own the global input styling, so without this they render with
   browser defaults (white background, system font) and clash with the
   dark UI. The .admin-search-input on the filter row already brings
   its own block. */
.object-explorer__filters .field__input,
.object-explorer__compare .field__input,
.source-viewer__compare-picker .field__input {
    width: 100%;
    box-sizing: border-box;
    padding: 8px 10px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    font-family: inherit;
    font-size: 13.5px;
    background: var(--color-surface);
    color: var(--color-text);
}

.object-explorer__filters .field__input:focus,
.object-explorer__compare .field__input:focus,
.source-viewer__compare-picker .field__input:focus {
    outline: 2px solid var(--color-accent);
    outline-offset: -1px;
    border-color: var(--color-accent);
}

/* Custom chevron on the <select> variants so they read as intentional
   dropdowns rather than carrying the browser-default arrow (which
   ignores our dark/light tokens and looks bolted on). Kept slim: the
   padding-right just makes room for the chevron, the rest of the
   sizing matches the inputs alongside. */
.object-explorer__filters select.field__input,
.object-explorer__compare select.field__input,
.source-viewer__compare-picker select.field__input {
    appearance: none;
    -webkit-appearance: none;
    padding-right: 28px;
    background-image:
        linear-gradient(45deg, transparent 50%, var(--color-text-muted) 50%),
        linear-gradient(135deg, var(--color-text-muted) 50%, transparent 50%);
    background-position:
        calc(100% - 14px) 50%,
        calc(100% - 9px) 50%;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
    cursor: pointer;
}
.object-explorer__filters select.field__input:focus,
.object-explorer__compare select.field__input:focus,
.source-viewer__compare-picker select.field__input:focus {
    background-image:
        linear-gradient(45deg, transparent 50%, var(--color-accent) 50%),
        linear-gradient(135deg, var(--color-accent) 50%, transparent 50%);
}

.object-explorer__filters .admin-search-input {
    width: 100%;
    box-sizing: border-box;
}

.object-explorer__row {
    cursor: pointer;
}

/* Multi-select "Object type" filter: a <details> disclosure styled as a
   dropdown. The <summary> reuses .field__input chrome; the panel floats over
   the rows below with a scrollable checkbox list. */
.kind-filter {
    position: relative;
}

.kind-filter__summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
}

.kind-filter__summary::-webkit-details-marker {
    display: none;
}

.kind-filter__summary::after {
    content: "";
    width: 0;
    height: 0;
    border-left: 4px solid transparent;
    border-right: 4px solid transparent;
    border-top: 5px solid var(--color-text-muted);
    flex: 0 0 auto;
}

.kind-filter[open] .kind-filter__summary {
    outline: 2px solid var(--color-accent);
    outline-offset: -1px;
    border-color: var(--color-accent);
}

.kind-filter__panel {
    position: absolute;
    z-index: 20;
    top: calc(100% + 4px);
    left: 0;
    min-width: 100%;
    max-height: 280px;
    overflow-y: auto;
    padding: 6px;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    background: var(--color-surface);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}

.kind-filter__option {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 5px 8px;
    border-radius: var(--radius);
    font-size: 13px;
    white-space: nowrap;
    cursor: pointer;
}

.kind-filter__option:hover {
    background: var(--color-bg);
}

/* Fixed column widths on the objects grid so sorting / lazy-loading new rows
   can't reflow the table as the content per page changes. Name + Namespace
   (the two width-less <col>s) share the remaining space; long values wrap
   rather than widen their column. */
.data-table--objects {
    table-layout: fixed;
    width: 100%;
}

.data-table--objects td {
    overflow-wrap: anywhere;
}

/* Type (kind keywords like "reportextension") and ID (negative interface ids
   like -818523667) must never wrap — give them room and keep them single-line. */
.data-table--objects td:first-child,
.data-table--objects td:nth-child(2) {
    white-space: nowrap;
    overflow-wrap: normal;
}

.data-table--objects .col-type { width: 10rem; }
.data-table--objects .col-id { width: 7rem; }
.data-table--objects .col-extension { width: 12rem; }
.data-table--objects .col-lines { width: 5rem; }

/* Bottom-of-list sentinel that triggers the next lazy-load page. */
.oe-scroll-sentinel {
    padding: 12px 0;
    text-align: center;
    font-size: 12.5px;
}

/* Version-compare page sections — each kind of change gets its own
   block under a small heading. Spaced apart so the eye doesn't bleed
   between added / removed / changed rows. */
/* Header trailing block on the version browser — pulls the
   "Compare with" picker and the "All versions" link together so they
   sit on the right side of the page header without fighting the title
   block on narrow widths. */
.object-explorer__header-actions {
    display: flex;
    align-items: end;
    gap: 12px;
    flex-wrap: wrap;
}

.version-compare__section {
    margin: 18px 0;
}
.version-compare__section h2 {
    font-size: 13px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-2, #b0b0c2);
    margin: 0 0 8px;
    display: flex;
    align-items: baseline;
    gap: 8px;
}

/* Status pill on the right end of each compare row. Colours intentionally
   muted — these aren't action buttons, they're metadata. */
.version-compare__badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 3px;
    font-size: 11.5px;
    font-weight: 600;
    text-transform: lowercase;
    letter-spacing: 0.02em;
}
.version-compare__badge--added {
    background: color-mix(in srgb, #2ea043 22%, transparent);
    color: #4cd067;
}
.version-compare__badge--removed {
    background: color-mix(in srgb, #f85149 22%, transparent);
    color: #ff8a82;
}
.version-compare__badge--changed {
    background: color-mix(in srgb, #d29922 22%, transparent);
    color: #ecbe4b;
}

.object-explorer__row:hover {
    background: var(--surface-hover, rgba(124, 124, 144, 0.08));
}

.object-explorer__type {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 3px;
    background: var(--surface-2, rgba(124, 124, 144, 0.12));
    color: var(--text-2, #b0b0c2);
    font-size: 11.5px;
    font-weight: 600;
    text-transform: lowercase;
    letter-spacing: 0.02em;
}

.object-explorer__crumbs {
    display: flex;
    gap: 6px;
    align-items: center;
    font-size: 13px;
    color: var(--text-2, #b0b0c2);
    margin-bottom: 12px;
}

.object-explorer__crumbs a {
    color: var(--text-2, #b0b0c2);
    text-decoration: none;
}

.object-explorer__crumbs a:hover {
    text-decoration: underline;
}

.object-explorer__file-header {
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    gap: 16px;
    margin-bottom: 12px;
}

.object-explorer__file-header h1 {
    display: flex;
    gap: 10px;
    align-items: baseline;
    margin: 0;
}

.object-explorer__compare {
    min-width: 240px;
}

.object-explorer__row-actions {
    display: flex;
    gap: 6px;
    align-items: center;
    flex-wrap: wrap;
}

/* When the row-actions live in their own <td>, keep the cell tight so
   the column doesn't claim a third of the table width. width: 1%
   collapses the cell to its content; nowrap keeps the buttons on one
   line even when there's room. */
.object-explorer__row-actions--cell {
    width: 1%;
    white-space: nowrap;
}

/* Suffix shown after the symbol name on the procedure-search rows —
   reads as a muted "(signature)" hint that doesn't compete with the
   name. Truncated by the parent <td>'s overflow on narrow viewports. */
.object-explorer__row-sig {
    margin-left: 6px;
    font-family: var(--mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
}

/* Snippet column on the content-search results table. Trimmed to a
   single line + ellipsis so a 200-char line doesn't blow up the row
   height; the row click + the link both navigate to the matching
   line in the file viewer, so seeing the start is enough. */
.object-explorer__row-snippet {
    display: block;
    max-width: 70ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12.5px;
}

/* Upload form: native file inputs are ugly and inconsistent across browsers.
   Hide the input visually and let the <label for=...> button stand in.
   `.file-picker__status` gets the filename(s) written by the page script. */
.object-explorer-upload .file-picker {
    display: inline-flex;
    align-items: center;
    gap: 12px;
    flex-wrap: wrap;
}

.object-explorer-upload .file-picker input[type="file"] {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    border: 0;
}

.object-explorer-upload .file-picker__button {
    cursor: pointer;
}

.object-explorer-upload .file-picker__status {
    font-size: 13px;
}

.object-explorer-upload .field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 16px;
}

/* Generic `.field` layout — admin forms that pair a label, an input, and a
   short caption use this triplet of classes. The Object Explorer filter
   bars and the legacy upload form each defined their own scoped versions
   above; this block makes the same layout available to any admin page
   (specifically the Release import form) without a parent-class
   gymnastics step. Input styling (border, padding, focus ring) still
   comes from the `.form-section input[type="…"]` rules earlier in this
   file, which already match by descendant. */
.field {
    display: flex;
    flex-direction: column;
    gap: 4px;
    margin-bottom: 16px;
    min-width: 0;
}

/* Respect the `hidden` attribute — the display:flex above would otherwise beat
   the UA `[hidden] { display: none }`, leaving JS-toggled fields visible. */
.field[hidden] {
    display: none;
}

.field__label {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-2, #b0b0c2);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.field__caption {
    font-size: 12.5px;
    line-height: 1.4;
}

/* Search-mode tabs on the Release detail page. Mirrors the legacy
   VersionBrowser's segmented control — three options, single-select, the
   active one carries a brighter background. */
.oe-search-tabs {
    display: inline-flex;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    overflow: hidden;
    margin-bottom: 16px;
}

.oe-search-tab {
    background: transparent;
    border: 0;
    padding: 6px 14px;
    color: var(--text-2, #b0b0c2);
    font: inherit;
    font-size: 13px;
    cursor: pointer;
    border-right: 1px solid var(--color-border-strong);
}

.oe-search-tab:last-child {
    border-right: 0;
}

.oe-search-tab:hover {
    background: rgba(124, 124, 144, 0.08);
}

.oe-search-tab--active {
    background: var(--color-accent, #ffd54f);
    color: var(--color-bg, #1a1a26);
    font-weight: 600;
}

/* ---------- Source-file viewer ---------- */
/* SourceFileViewer.razor — IDE-style layout: page header pinned at top,
   code area scrolls inside the cm-editor, the outline is always next to
   it. The page itself doesn't scroll (height: 100% inside .content's
   bounded scrollport) — that's the pattern users expect from a code
   viewer. Narrow viewports drop
   back to the page-scroll behaviour because internal scrolling
   feels cramped on small screens. */
.source-viewer {
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}

.source-viewer > .admin-page__header {
    flex: 0 0 auto;
}

.source-viewer__layout {
    display: flex;
    align-items: stretch;
    flex: 1 1 0;
    min-height: 0;
    gap: 0;
}

/* Drag handle between the editor column and the outline. Stretches the
   full height of the layout; the JS handler updates a CSS custom
   property (`--source-viewer-outline-width`) on the layout root, which
   the outline reads from `.source-viewer__outline-inner` so the resize
   stays cheap (no React-style relayout, just the two columns flexing).
   The hover hairline matches the toolbar accent colour so the handle
   feels discoverable without dominating the layout. */
.source-viewer__resizer {
    flex: 0 0 12px;
    cursor: col-resize;
    position: relative;
    user-select: none;
    /* Layout breathing room — visually equivalent to the previous gap
       of 12px so existing screenshots still line up. */
    margin: 0 4px;
}

.source-viewer__resizer::before {
    content: "";
    position: absolute;
    inset: 0 5px;
    border-radius: 2px;
    background: transparent;
    transition: background 80ms ease-out;
}

.source-viewer__resizer:hover::before,
.source-viewer__resizer.is-dragging::before {
    background: var(--color-accent);
    opacity: 0.55;
}

@media (max-width: 1100px) {
    .source-viewer {
        height: auto;
        min-height: 100%;
    }
    .source-viewer__layout {
        flex: 0 0 auto;
        flex-direction: column;
    }
    .source-viewer__code .cm-editor {
        height: auto;
    }
    .source-viewer__outline-inner {
        height: auto;
        max-height: calc(100vh - var(--top-bar-height, 52px) - 36px);
    }
    /* The drag handle only makes sense in the side-by-side layout. */
    .source-viewer__resizer {
        display: none;
    }
}

.source-viewer__code {
    flex: 1 1 0;
    min-width: 0;
    min-height: 0;
    /* Match the outline column's framed look — same border, same
       corner radius, same surface. overflow:hidden also clips the
       cm-editor's rounded corners so the editor body, search panel
       and bottom status bar all sit inside the rounded box.
       CodeMirror's own cm-scroller handles horizontal scrolling. */
    overflow: hidden;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    background: var(--color-surface);
}

/* Bound cm-editor to the code column's height so cm-scroller actually
   scrolls instead of letting the file extend off the page. The
   editor's own border is suppressed — the wrapper carries the frame
   so the inner gutter/scroller live edge-to-edge inside the radius. */
.source-viewer__code .cm-editor {
    height: 100%;
    border: 0;
    border-radius: inherit;
    background: transparent;
}

.source-viewer__outline {
    /* Width is driven by --source-viewer-outline-width on the layout
       root; the drag handler updates that and the column flexes here.
       The clamp matches what the JS handler enforces, duplicated here
       so a stale localStorage value still renders a usable column. */
    flex: 0 0 auto;
    align-self: stretch;
    margin-right: 16px;
    display: flex;
    flex-direction: column;
    width: clamp(220px,
                 var(--source-viewer-outline-width, 340px),
                 720px);
}

.source-viewer__outline-inner {
    /* Width is driven by the layout's --source-viewer-outline-width
       custom property, set by the drag-handle JS handler. The fallback
       (340px) matches the previous default when JS hasn't loaded or
       the user hasn't dragged yet. The two boundaries keep the column
       readable while still letting the editor have space; the drag
       handler clamps to the same range. */
    width: 100%;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    padding: 12px 14px;
    background: var(--color-surface);
}


/* ── Tab strip ───────────────────────────────────────────────── */

.source-viewer__tabs {
    display: flex;
    gap: 4px;
    margin: 0 0 12px 0;
    padding: 0 0 4px 0;
    border-bottom: 1px solid var(--color-border-strong);
}

.source-viewer__tab {
    flex: 0 0 auto;
    padding: 6px 10px;
    background: transparent;
    border: none;
    border-bottom: 2px solid transparent;
    color: var(--color-text-muted);
    font: inherit;
    font-size: 12.5px;
    font-weight: 500;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.source-viewer__tab:hover {
    color: var(--color-text);
}

.source-viewer__tab.is-active {
    color: var(--color-text);
    border-bottom-color: var(--color-accent);
}

.source-viewer__tab[hidden] {
    display: none;
}

/* Right-anchor the Help tab so it sits opposite the navigational tabs. */
.source-viewer__tab--end {
    margin-left: auto;
}

.source-viewer__help {
    margin: 4px 0 0 0;
    font-size: 13px;
    line-height: 1.5;
}

.source-viewer__help dt {
    margin-top: 10px;
    font-weight: 600;
    color: var(--color-text);
}

.source-viewer__help dt:first-of-type {
    margin-top: 0;
}

.source-viewer__help dd {
    margin: 2px 0 0 0;
    color: var(--color-text-secondary);
}

.source-viewer__help kbd {
    display: inline-block;
    padding: 1px 6px;
    border-radius: 4px;
    border: 1px solid var(--color-border-strong);
    background: var(--color-surface-2, rgba(124, 124, 144, 0.12));
    font-family: var(--font-mono);
    font-size: 11.5px;
    line-height: 1.4;
}

.source-viewer__tab-count {
    display: inline-block;
    min-width: 18px;
    padding: 0 5px;
    background: var(--color-surface-2);
    border-radius: 999px;
    font-size: 10.5px;
    text-align: center;
    color: var(--color-text-muted);
}

.source-viewer__panel {
    display: none;
}

.source-viewer__panel.is-active {
    display: block;
}

.source-viewer__outline h2 {
    margin: 0 0 8px 0;
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-2, #b0b0c2);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

/* Matches the global form-input styling so the filter doesn't feel
   foreign next to the rest of the app's text inputs. Same padding,
   border, radius and background as <c>.admin-form input[type="text"]</c>;
   only the bottom margin is panel-specific. */
.source-viewer__outline-filter,
.source-viewer__refs-filter {
    width: 100%;
    box-sizing: border-box;
    padding: 8px 10px;
    margin: 0 0 10px 0;
    background: var(--color-surface);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    color: var(--color-text);
    font-family: inherit;
    font-size: 13.5px;
}

.source-viewer__outline-filter::placeholder,
.source-viewer__refs-filter::placeholder {
    color: var(--color-text-secondary);
}

.source-viewer__outline-filter:focus,
.source-viewer__refs-filter:focus {
    outline: 2px solid var(--color-accent);
    outline-offset: -1px;
    border-color: var(--color-accent);
}

.source-viewer__outline-section + .source-viewer__outline-section {
    margin-top: 8px;
}

.source-viewer__outline-section-toggle {
    display: flex;
    align-items: baseline;
    gap: 6px;
    width: 100%;
    margin: 0;
    padding: 4px 0;
    background: none;
    border: 0;
    color: var(--text-2, #b0b0c2);
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    cursor: pointer;
    text-align: left;
}

.source-viewer__outline-section-toggle:hover {
    color: var(--text-1, #e6e6f0);
}

.source-viewer__outline-section-chevron {
    display: inline-block;
    width: 10px;
    transition: transform 80ms ease-out;
    transform: rotate(0deg);
    font-size: 13px;
    line-height: 1;
}

.source-viewer__outline-section-chevron.is-open {
    transform: rotate(90deg);
}

.source-viewer__outline-section-title {
    flex: 0 0 auto;
}

.source-viewer__outline-section-count {
    color: var(--text-3, #7a7a8a);
    font-weight: 500;
}

.source-viewer__outline-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

.source-viewer__outline-item {
    line-height: 1.5;
    font-size: 14px;
}

.source-viewer__outline-item--child {
    padding-left: 16px;
}

.source-viewer__outline-item a,
.source-viewer__outline-item .source-viewer__outline-link {
    text-decoration: none;
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 1px 0;
    /* Rows fit the panel width — long names truncate with ellipsis on
       the .source-viewer__outline-sig span. The user can widen the
       panel via the resize handle on the outline-inner to see more. */
    width: 100%;
    min-width: 0;
    box-sizing: border-box;
    overflow: hidden;
}

/* Same-file outline rows render as <button> instead of <a> so the
   click handler can bypass Blazor's enhanced-nav pipeline and
   update the URL via history.replaceState — Nav.NavigateTo's
   streaming diff strips CodeMirror's runtime-injected style tags.
   Strip the default button chrome so the row stays visually
   identical to the anchor variant used for cross-page object jumps. */
.source-viewer__outline-link {
    background: none;
    border: 0;
    cursor: pointer;
    color: inherit;
    font: inherit;
    text-align: left;
}

.source-viewer__outline-link:hover .source-viewer__outline-name {
    text-decoration: underline;
}

.source-viewer__outline-name {
    /* AL identifiers in monospace — matches the editor's font and keeps
       parameter signatures visually aligned. Names truncate with
       ellipsis when the panel isn't wide enough; the row's title
       attribute carries the full text on hover, and the resize handle
       on .source-viewer__outline-inner lets the user widen the panel
       to see more. */
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-mono);
}

.source-viewer__outline-sig {
    /* The signature is the one column allowed to elide. flex: 1 1 0
       + min-width: 0 lets it shrink to nothing so the line number
       and (when needed) the next row's name stay aligned. The full
       text always sits on the row's title attribute. */
    flex: 1 1 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--font-mono);
    font-size: 14px;
    min-width: 0;
}

.source-viewer__outline-line {
    flex: 0 0 auto;
    margin-left: auto;
    padding-left: 8px;
    font-size: 13px;
    font-variant-numeric: tabular-nums;
    color: var(--text-3, #7a7a8a);
}

/* Kind badge — IDE-style pill next to each outline row. The label
   indicates what kind of symbol the row represents (field, trigger,
   procedure, etc.); the colour tint groups related kinds so the eye
   can scan a long list quickly. Full kind name lives on the row's
   title attribute for hover. */
.source-viewer__outline-badge {
    flex: 0 0 auto;
    display: inline-block;
    padding: 1px 6px;
    border-radius: 3px;
    font-family: var(--font-mono);
    font-size: 10.5px;
    font-weight: 500;
    letter-spacing: 0.02em;
    text-transform: lowercase;
    line-height: 1.4;
    color: var(--color-text);
    background: var(--color-surface-2, rgba(124, 124, 144, 0.12));
    border: 1px solid var(--color-border, rgba(124, 124, 144, 0.2));
    white-space: nowrap;
}

.source-viewer__outline-badge--field {
    background: rgba(75, 139, 255, 0.14);
    border-color: rgba(75, 139, 255, 0.35);
    color: #6ba3ff;
}

.source-viewer__outline-badge--action {
    background: rgba(255, 184, 64, 0.16);
    border-color: rgba(255, 184, 64, 0.4);
    color: #f0b85a;
}

.source-viewer__outline-badge--trigger {
    background: rgba(76, 201, 114, 0.14);
    border-color: rgba(76, 201, 114, 0.35);
    color: #4cc972;
}

.source-viewer__outline-badge--event {
    background: rgba(229, 159, 64, 0.16);
    border-color: rgba(229, 159, 64, 0.4);
    color: #f0a85a;
}

.source-viewer__outline-badge--label {
    /* Cyan family — distinct from fields (blue), procedures (purple),
       and triggers/events (green/orange). Labels show up frequently in
       large codeunits and benefit from being visually separable so a
       developer triangulating a customer-reported error string can
       spot them at a glance. */
    background: rgba(80, 190, 200, 0.16);
    border-color: rgba(80, 190, 200, 0.4);
    color: #6ec9d2;
}

.source-viewer__outline-badge--proc {
    background: rgba(186, 134, 255, 0.16);
    border-color: rgba(186, 134, 255, 0.4);
    color: #c39bff;
}

.source-viewer__outline-badge--object {
    background: rgba(124, 124, 144, 0.18);
    border-color: rgba(124, 124, 144, 0.4);
    color: var(--color-text-muted);
}

.source-viewer__outline-find-heading {
    margin-top: 16px;
    padding-top: 12px;
    border-top: 1px solid var(--color-border-strong);
}

.source-viewer__find-list {
    list-style: none;
    margin: 0 0 12px 0;
    padding: 0;
}

.source-viewer__find-list li {
    line-height: 1.4;
    padding: 2px 0;
}

.source-viewer__find-list a,
.source-viewer__find-list .source-viewer__outline-link {
    text-decoration: none;
    display: flex;
    gap: 6px;
    align-items: baseline;
}

.source-viewer__find-snippet {
    font-size: 11.5px;
    color: var(--text-2, #b0b0c2);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.source-viewer__notice {
    margin-top: 12px;
    padding: 8px 10px;
    border: 1px dashed var(--color-border-strong);
    border-radius: var(--radius);
    font-size: 12.5px;
}

/* Floating notice toast (`showFloatingToast` in source-viewer.js). Sits
   under the user's mouse so feedback for in-editor gestures ("No
   definition found", "Couldn't reach the server") doesn't slip below
   the viewport in the way the bottom-of-outline notice did. Fixed
   positioning + a z-index above the editor's gutter so it always
   floats on top. */
.source-viewer__toast {
    position: absolute;
    z-index: 1000;
    max-width: 320px;
    padding: 8px 12px;
    background: var(--color-surface-2, #262a30);
    color: var(--color-text, #e6e7ea);
    border: 1px solid var(--color-border-strong, #3a3e47);
    border-radius: var(--radius, 6px);
    box-shadow: 0 4px 16px var(--color-shadow, rgba(0, 0, 0, 0.4));
    font-size: 12.5px;
    line-height: 1.4;
    pointer-events: none;
}

.source-viewer__refs-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    margin-bottom: 8px;
}

.source-viewer__refs-target {
    margin: 0;
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.source-viewer__refs-close {
    flex: 0 0 auto;
}

.source-viewer__refs-list {
    list-style: none;
    margin: 8px 0 0 0;
    padding: 0;
}

.source-viewer__refs-item {
    padding: 2px 0;
    line-height: 1.4;
}

/* Whole row is wrapped in <a> for keyboard / middle-click navigation.
   Kill the default accent colour on the link itself — the kind badge
   carries the "what is this" signal now, and the row's text should sit
   as body copy, not as a wall of blue. The hover background keeps the
   row feeling clickable. */
.source-viewer__refs-item a {
    text-decoration: none;
    display: block;
    padding: 6px 8px;
    border-radius: var(--radius);
    color: var(--color-text);
}

.source-viewer__refs-item a:hover {
    background: var(--color-surface-strong);
}

.source-viewer__refs-item a:hover .source-viewer__refs-name {
    text-decoration: underline;
}

/* Refs groups borrow the outline-section chevron + heading styles for
   collapsibility. The vertical rhythm between groups matches the
   outline panel so the two read as a paired pattern. */
.source-viewer__refs-group + .source-viewer__refs-group {
    margin-top: 8px;
}

.source-viewer__refs-group:not(.is-open) .source-viewer__refs-list {
    display: none;
}

/* Member sub-row: indented under the object-row so the visual hierarchy
   reads object → member → call-site snippet. Badge keeps the row in sync
   with the outline pane's chip vocabulary (proc / local / trigger / …). */
.source-viewer__refs-member-row {
    display: flex;
    gap: 6px;
    align-items: baseline;
    margin-top: 3px;
    margin-left: 4px;
    min-width: 0;
}

.source-viewer__refs-member-name {
    font-family: var(--font-mono);
    font-size: 12.5px;
    color: var(--color-text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    flex: 1 1 auto;
}

/* Outline right-click menu — one-item popover for Find references on a
   procedure / field / trigger / object row. Positioned by JS at the
   click point. */
.source-viewer__outline-menu {
    position: fixed;
    z-index: 100;
    background: var(--color-surface);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    box-shadow: 0 4px 12px var(--color-shadow, rgba(0, 0, 0, 0.4));
    padding: 4px 0;
    min-width: 180px;
}

.source-viewer__outline-menu-item {
    display: block;
    width: 100%;
    padding: 6px 12px;
    background: transparent;
    border: 0;
    color: var(--color-text);
    font: inherit;
    font-size: 12.5px;
    text-align: left;
    cursor: pointer;
}

.source-viewer__outline-menu-item:hover {
    background: var(--color-surface-2, rgba(124, 124, 144, 0.12));
}

/* Object-row: badge + name on the left, module/line meta pinned right.
   Tabular row layout means the eye scans the badge column first, then the
   name, then drifts right for the location — same rhythm as the outline. */
.source-viewer__refs-row-top {
    display: flex;
    gap: 6px;
    align-items: baseline;
}

.source-viewer__refs-line {
    flex: 0 0 auto;
    font-size: 11px;
    font-family: var(--font-mono, "SFMono-Regular", Consolas, monospace);
}

.source-viewer__refs-snippet {
    display: block;
    margin-top: 4px;
    margin-left: 4px;
    font-family: var(--font-mono, "SFMono-Regular", Consolas, monospace);
    font-size: 12px;
    color: var(--color-text-muted);
    background: var(--color-surface-2);
    padding: 3px 8px;
    border-left: 2px solid var(--color-border-strong);
    border-radius: 0 3px 3px 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.source-viewer__refs-item.is-current a {
    background: var(--color-accent-soft, rgba(99, 102, 241, 0.15));
    font-weight: 500;
}

.source-viewer__refs-name {
    font-family: var(--font-mono, "SFMono-Regular", Consolas, monospace);
    font-size: 13px;
    font-weight: 500;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    min-width: 0;
    flex: 1 1 auto;
}

/* Hover-only tooltip for a refs row. Stays out of the document flow —
   appended to <body> by source-viewer.js so the row can stay visually
   tight while the extension / file path / kind details surface on
   demand. */
.source-viewer__refs-tooltip {
    position: absolute;
    z-index: 200;
    background: var(--color-surface);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    box-shadow: 0 6px 18px var(--color-shadow);
    padding: 8px 10px;
    font-size: 12px;
    line-height: 1.45;
    color: var(--color-text);
    pointer-events: none;
}

.source-viewer__refs-tooltip[hidden] {
    display: none;
}

.source-viewer__refs-tooltip-where {
    font-weight: 600;
    margin-bottom: 2px;
}

.source-viewer__refs-tooltip-loc {
    font-family: var(--font-mono);
    font-size: 11.5px;
    color: var(--color-text-muted);
    word-break: break-all;
}

.source-viewer__refs-tooltip-member {
    margin-top: 6px;
    font-family: var(--font-mono);
    font-size: 11.5px;
}

.source-viewer__refs-tooltip-kind {
    margin-top: 6px;
    font-size: 11px;
}
.source-viewer__body {
    padding: 0;
    margin: 0;
    background: var(--color-surface);
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius);
    overflow-x: auto;
    max-height: calc(100vh - 220px);
    overflow-y: auto;
}

.source-viewer__body > code {
    display: block;
    font-family: var(--font-mono, "SFMono-Regular", Consolas, monospace);
    font-size: 12.5px;
    line-height: 1.5;
}

.source-viewer__line {
    display: grid;
    grid-template-columns: 4rem 1fr;
    gap: 12px;
    padding: 0 12px;
    white-space: pre;
}

.source-viewer__line:hover {
    background: rgba(124, 124, 144, 0.08);
}

.source-viewer__line--hit {
    background: rgba(255, 213, 79, 0.14);
    border-left: 3px solid var(--color-accent, #ffd54f);
    padding-left: 9px;
}

.source-viewer__lineno {
    text-align: right;
    color: var(--text-2, #b0b0c2);
    user-select: none;
}

.source-viewer__text {
    white-space: pre;
}

.object-explorer-upload .field__label {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-2, #b0b0c2);
    text-transform: uppercase;
    letter-spacing: 0.03em;
}

.object-explorer-upload .form-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
}

/* The default `display: none` on `[hidden]` would normally hide this
   panel, but specifying `display: flex` unconditionally would beat
   that. Scope the flex layout to the visible state instead so the
   `hidden` attribute keeps working as a plain on/off toggle. */
.upload-progress:not([hidden]) {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 16px;
    padding: 12px 16px;
    background: var(--surface-2, rgba(124, 124, 144, 0.08));
    border: 1px solid var(--border, #2a2a35);
    border-radius: 6px;
}

.upload-progress p {
    margin: 0;
}

.upload-progress__spinner {
    width: 18px;
    height: 18px;
    border: 2px solid var(--border, #2a2a35);
    border-top-color: var(--text-1, #d8d8e0);
    border-radius: 50%;
    animation: upload-spin 0.8s linear infinite;
    flex-shrink: 0;
}

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

/* When the upload is in flight: visually lock down the form so the user
   knows input is captured and that re-clicking is a no-op. Pointer
   events are blocked on the inputs section (everything inside
   .form-section), while the progress panel and its parent form-actions
   stay interactive enough to read but not re-submit. The submit
   button itself carries .btn--loading + the disabled attribute. */
.object-explorer-upload.is-uploading .form-section {
    opacity: 0.55;
    pointer-events: none;
    user-select: none;
}

/* Disabled buttons get a visual cue too — the .btn class doesn't have a
   :disabled rule anywhere else and the browser default barely dims it,
   so the user thinks the click didn't take. Applies globally; nothing
   else relies on disabled buttons looking active. */
.btn:disabled,
.btn[disabled],
.btn[aria-disabled="true"] {
    opacity: 0.55;
    cursor: not-allowed;
}

/* Diff line decorations applied by CodeMirror's Decoration.line(). Selectors
   live in the global stylesheet (not the scoped CodeDiffViewer.razor.css)
   because CodeMirror renders .cm-line outside the Blazor component's scope-id
   wrapper, so ::deep can't reach it. */
.cm-line--diff-added { background: rgba(46, 160, 67, 0.18); }
.cm-line--diff-removed { background: rgba(248, 81, 73, 0.18); }
.cm-line--diff-modified { background: rgba(210, 153, 34, 0.18); }

/* Click affordances on Object Explorer tokens.
   - cm-symbol-decl (procedure / field / object-header *declarations*):
     solid underline at half opacity. The name is "the source" — hover
     keeps the same line but brightens for an explicit "yes, this is
     clickable" signal.
   - cm-symbol-ref (call / field-access *reference sites*): dotted
     underline. Reads as a hyperlink — the user expects to follow it
     somewhere. Hover brightens to mirror the link affordance.
   Both get cursor: pointer so the hand confirms interactivity before
   the user even needs the underline to read. */
.cm-symbol-decl,
.cm-symbol-ref {
    cursor: pointer;
    text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
    text-underline-offset: 3px;
    transition: text-decoration-color 80ms ease-out;
}
.cm-symbol-decl {
    text-decoration: underline solid currentColor;
}
.cm-symbol-ref {
    text-decoration: underline dotted currentColor;
}
.cm-symbol-decl:hover,
.cm-symbol-ref:hover {
    text-decoration-color: currentColor;
}

/* When the user holds Cmd/Ctrl over the editor, hint that identifier-
   shaped tokens are clickable for "Go to definition". We can't pre-
   resolve which tokens have a target without running a parser, so this is
   an honest "anything could be navigable" affordance — the click itself
   does the resolution. The selectors hit CodeMirror's syntax-highlight
   tok-* classes plus our declaration marks. */
.cm-modifier-down,
.cm-modifier-down .cm-line {
    cursor: pointer;
}
.cm-modifier-down .tok-variableName,
.cm-modifier-down .tok-typeName,
.cm-modifier-down .tok-propertyName,
.cm-modifier-down .tok-className,
.cm-modifier-down .tok-keyword,
.cm-modifier-down .cm-symbol-decl,
.cm-modifier-down .cm-symbol-ref {
    text-decoration: underline;
    text-decoration-style: solid;
    text-decoration-color: currentColor;
    text-underline-offset: 2px;
}

.cm-symbol-menu {
    position: fixed;
    z-index: 1000;
    background: var(--surface-1, #1a1c22);
    border: 1px solid var(--border, #2a2a35);
    border-radius: 6px;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.25);
    padding: 4px;
    min-width: 160px;
    font-size: 13px;
}

.cm-symbol-menu__item {
    appearance: none;
    background: transparent;
    border: 0;
    width: 100%;
    text-align: left;
    padding: 6px 10px;
    border-radius: 4px;
    color: var(--text-1, #d8d8e0);
    cursor: pointer;
    font: inherit;
}

.cm-symbol-menu__item:hover:not(.cm-symbol-menu__item--disabled) {
    background: var(--surface-hover, rgba(124, 124, 144, 0.16));
}

.cm-symbol-menu__item--disabled {
    color: var(--text-muted, rgba(216, 216, 224, 0.45));
    cursor: not-allowed;
}

/* CodeMirror @codemirror/search panel (Ctrl/Cmd-F). The default
   styling renders bare inputs/buttons; bring them in line with the
   app's button + input look. */
.cm-panels.cm-panels-top {
    border-bottom: 1px solid var(--color-border-strong);
    background: var(--color-surface);
}

/* Bottom-docked status bar (built by buildStatusBarExtension in
   code-editor.js). Sits below cm-scroller and inherits theme colours
   from the editor's container. Left/right split mirrors VS Code's
   layout — cursor position on the left, document totals on the right. */
.cm-panels.cm-panels-bottom {
    border-top: 1px solid var(--color-border-strong);
    background: var(--color-surface);
}

.cm-status-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 3px 10px;
    font-size: 11.5px;
    font-variant-numeric: tabular-nums;
    color: var(--color-text-muted);
    font-family: var(--font-mono);
}

.cm-status-bar__left,
.cm-status-bar__right {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.cm-search.cm-panel {
    padding: 8px 10px;
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    background: var(--color-surface);
    color: var(--color-text);
    font-family: inherit;
}

.cm-search .cm-textfield {
    font-family: inherit;
    font-size: 13px;
    padding: 4px 8px;
    color: var(--color-text);
    background: var(--color-surface-2, var(--color-bg));
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm, 4px);
    outline: none;
}

.cm-search .cm-textfield:focus {
    border-color: var(--color-accent);
}

.cm-search .cm-button {
    font-family: inherit;
    font-size: 12.5px;
    padding: 4px 10px;
    color: var(--color-text);
    background: transparent;
    background-image: none;
    border: 1px solid var(--color-border-strong);
    border-radius: var(--radius-sm, 4px);
    cursor: pointer;
}

.cm-search .cm-button:hover {
    background: var(--color-surface-2, rgba(124, 124, 144, 0.12));
}

.cm-search label {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    font-size: 12.5px;
    color: var(--color-text-muted);
    cursor: pointer;
}

.cm-search label input[type="checkbox"] {
    accent-color: var(--color-accent);
    margin: 0;
}

.cm-panel button[name="close"] {
    margin-left: auto;
    padding: 0 8px;
    font-size: 16px;
    line-height: 1;
    color: var(--color-text-muted);
    background: transparent;
    border: 0;
    cursor: pointer;
}

.cm-panel button[name="close"]:hover {
    color: var(--color-text);
}

/* Sticky highlight on the line the user navigated to via a deep link
   or a References-panel click. Backing colour comes from
   --color-accent-soft so it tracks the active theme. The actual
   line.background rule lives in CM6's baseTheme (currentLineTheme in
   code-editor.js) so it survives CodeMirror's row virtualisation. */

/* File viewer layout: code editor + right-rail inspector panel. The
   editor fills the remaining width and grows to its content height —
   long files extend the page rather than scrolling internally. The
   panel stays in view via `position: sticky` so the outline / refs
   are always one glance away. Stacks vertically on narrow viewports. */
.object-explorer__file-layout {
    display: flex;
    gap: 16px;
    align-items: flex-start;
}

.object-explorer__file-layout > .code-viewer-host {
    flex: 1 1 auto;
    min-width: 0;
}

@media (max-width: 1100px) {
    .object-explorer__file-layout {
        flex-direction: column;
    }
}

.inspector-panel {
    /* Scale with viewport — narrow browser still gets the minimum, wide
       monitors get more room for reference snippets. */
    flex: 0 0 clamp(280px, 28vw, 520px);
    display: flex;
    flex-direction: column;
    /* Sticky-follow on scroll. The nearest scrolling ancestor is
       .content (`overflow-y: auto`), so the panel pins to the top of
       the work column once it would otherwise scroll out of view.
       max-height caps it to the viewport so long outlines get their
       own internal scrollbar instead of overflowing the screen. */
    position: sticky;
    top: 16px;
    max-height: calc(100vh - var(--top-bar-height) - 32px);
    border: 1px solid var(--border, #2a2a35);
    border-radius: 6px;
    background: var(--surface-1, #1a1c22);
    overflow: hidden;
    font-size: 13px;
}

@media (max-width: 1100px) {
    /* In the stacked layout sticky would pin the panel to the top of
       the column, hiding the editor as the user scrolls — unhelpful.
       Drop back to its natural flow position. */
    .inspector-panel {
        position: static;
        max-height: 60vh;
    }
}

.inspector-panel__crumbs {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 4px;
    padding: 8px 10px;
    border-bottom: 1px solid var(--border, #2a2a35);
    background: var(--surface-2, rgba(124, 124, 144, 0.08));
    font-size: 12.5px;
}

.inspector-panel__crumb {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 2px 6px;
    border-radius: 4px;
    color: inherit;
    font: inherit;
    cursor: default;
    max-width: 160px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.inspector-panel__crumb--link {
    color: var(--text-2, #b0b0c2);
    cursor: pointer;
}

.inspector-panel__crumb--link:hover {
    background: var(--surface-hover, rgba(124, 124, 144, 0.16));
    color: var(--text-1, #d8d8e0);
}

.inspector-panel__crumb--current {
    color: var(--text-1, #d8d8e0);
    font-weight: 600;
}

.inspector-panel__crumb-sep {
    color: var(--text-3, #6b6b80);
}

.inspector-panel__body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: 8px 10px 12px;
}

.inspector-panel__empty {
    margin: 12px 4px;
}

.inspector-panel__filter {
    margin-bottom: 8px;
}

.inspector-panel__filter-input {
    width: 100%;
    padding: 6px 8px;
    border: 1px solid var(--border, #2a2a35);
    border-radius: 4px;
    background: var(--surface-2, rgba(124, 124, 144, 0.08));
    color: var(--text-1, #d8d8e0);
    font: inherit;
    font-size: 12.5px;
}

.inspector-panel__filter-input::placeholder {
    color: var(--text-3, #6b6b80);
}

.inspector-panel__sections,
.inspector-panel__items,
.inspector-panel__refs-files,
.inspector-panel__refs-hits {
    list-style: none;
    margin: 0;
    padding: 0;
}

.inspector-panel__section + .inspector-panel__section {
    margin-top: 12px;
}

.inspector-panel__section-title {
    margin: 0 0 4px 0;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-2, #b0b0c2);
    display: flex;
    gap: 6px;
    align-items: baseline;
}

/* Collapsible section header — same typography as the static title, plus
   a leading chevron and a button cursor so it's discoverable. The chevron
   itself is a vendored Lucide icon, so no rotation animation; we swap
   chevron-right / chevron-down between collapsed and expanded states. */
.inspector-panel__section-toggle {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 4px 0;
    margin: 0 0 4px 0;
    width: 100%;
    font: inherit;
    font-size: 11px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--text-2, #b0b0c2);
    display: flex;
    gap: 6px;
    align-items: center;
    cursor: pointer;
    text-align: left;
}
.inspector-panel__section-toggle:hover {
    color: var(--text-1, #d8d8e0);
}
.inspector-panel__section-title-text {
    flex: 0 0 auto;
}

/* Full-viewport invisible backdrop used to dismiss the outline row's
   right-click menu on outside-click. Sits below the menu (z-index 999
   vs the menu's 1000). Pointer-events: auto so the click is caught. */
.cm-symbol-menu-backdrop {
    position: fixed;
    inset: 0;
    z-index: 999;
    background: transparent;
}

.inspector-panel__item {
    display: flex;
    align-items: center;
    gap: 2px;
    border-radius: 4px;
}

.inspector-panel__item:hover {
    background: var(--surface-hover, rgba(124, 124, 144, 0.10));
}

.inspector-panel__item-link {
    flex: 1 1 auto;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 8px;
    padding: 4px 6px;
    color: inherit;
    text-decoration: none;
    font-family: var(--mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
    font-size: 12.5px;
    min-width: 0;
}

/* Override the global `a:hover { text-decoration: underline }` — the row
   already changes background on hover, the redundant underline reads as
   noise. */
.inspector-panel__item-link:hover {
    text-decoration: none;
}

.inspector-panel__item-name {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 0 1 auto;
    min-width: 0;
}

/* Field type / procedure signature, rendered between the name and the
   line-number badge. Truncated with ellipsis so a 200-character method
   signature doesn't push the line off-row; full text is on the title
   attribute so hovering reveals it. */
.inspector-panel__item-sig {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    flex: 1 1 auto;
    min-width: 0;
    font-family: var(--mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
}

.inspector-panel__item-line {
    flex-shrink: 0;
}

.inspector-panel__item-action {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 4px 6px;
    border-radius: 4px;
    color: var(--text-3, #6b6b80);
    cursor: pointer;
    /* Only reveal the find-references button while the row is hovered or
       the button itself is focused — keeps the row visually quiet at rest. */
    opacity: 0;
    transition: opacity 80ms ease-out;
}

.inspector-panel__item:hover .inspector-panel__item-action,
.inspector-panel__item-action:focus {
    opacity: 1;
}

.inspector-panel__item-action:hover {
    background: var(--surface-hover, rgba(124, 124, 144, 0.16));
    color: var(--text-1, #d8d8e0);
}

.inspector-panel__refs-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px;
    margin: 4px 0 12px 0;
}

.inspector-panel__refs-section + .inspector-panel__refs-section {
    margin-top: 14px;
}

.inspector-panel__refs-file {
    border: 1px solid var(--border, #2a2a35);
    border-radius: 4px;
    overflow: hidden;
    margin-top: 8px;
}

.inspector-panel__refs-file-header {
    display: flex;
    align-items: baseline;
    gap: 6px;
    padding: 4px 8px;
    background: var(--surface-2, rgba(124, 124, 144, 0.08));
    font-size: 12px;
}

.inspector-panel__refs-hit {
    display: grid;
    grid-template-columns: 40px 1fr;
    gap: 8px;
    padding: 4px 8px;
    border-top: 1px solid var(--border, #2a2a35);
    text-decoration: none;
    color: inherit;
    font-family: var(--mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
    font-size: 12px;
}

.inspector-panel__refs-hit:hover {
    background: var(--surface-hover, rgba(124, 124, 144, 0.10));
    /* Suppress the global `a:hover` underline — background change is enough. */
    text-decoration: none;
}

.inspector-panel__refs-hit-line {
    color: var(--text-3, #6b6b80);
    text-align: right;
}

.inspector-panel__refs-hit-snippet {
    color: var(--text-1, #d8d8e0);
    white-space: pre;
    overflow-x: auto;
}

/* ---------- References page ---------- */
.object-explorer__references .references__section {
    margin-top: 24px;
}

.references__section h2 {
    font-size: 15px;
    margin: 0 0 12px 0;
}

.references__toggle {
    appearance: none;
    background: transparent;
    border: 0;
    padding: 0;
    color: inherit;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font: inherit;
    font-size: 15px;
    font-weight: 600;
}

.references__possibly-related-explainer {
    margin: 4px 0 12px 22px;
}

.references__file {
    border: 1px solid var(--border, #2a2a35);
    border-radius: 6px;
    overflow: hidden;
    margin-bottom: 12px;
}

.references__file-header {
    padding: 8px 12px;
    background: var(--surface-2, rgba(124, 124, 144, 0.08));
    display: flex;
    align-items: baseline;
    gap: 8px;
}

.references__hits {
    list-style: none;
    margin: 0;
    padding: 0;
}

.references__hit {
    border-top: 1px solid var(--border, #2a2a35);
}

.references__hit-link {
    display: grid;
    grid-template-columns: 48px 1fr;
    gap: 12px;
    padding: 6px 12px;
    text-decoration: none;
    color: inherit;
    font-family: var(--mono, ui-monospace, "SF Mono", Menlo, Consolas, monospace);
    font-size: 12.5px;
}

.references__hit-link:hover {
    background: var(--surface-hover, rgba(124, 124, 144, 0.12));
}

.references__hit-line {
    color: var(--text-3, #6b6b80);
    text-align: right;
    user-select: none;
}

.references__hit-snippet {
    color: var(--text-1, #d8d8e0);
    white-space: pre;
    overflow-x: auto;
}



/* Account security & admin user-create additions (M-AccountSecurity) */
.invite-link-box {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 8px;
    padding: 8px 10px;
    background: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius);
    font-family: var(--font-mono);
    font-size: 12.5px;
    overflow-x: auto;
}
.invite-link-box code {
    white-space: nowrap;
    flex: 1;
}

.inline-checkbox {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 13px;
}
.inline-checkbox input {
    width: auto;
}

.email-change-row td {
    background: var(--color-bg);
    padding: 12px 16px;
}

/* ---------- Release & file compare ---------- */
/* Side-by-side diff viewer (/object-explorer/compare/file): two CodeMirror
   panes laid out as a 2-column grid. Each pane is a full `.source-viewer`
   shell minus the outline DOM, so the existing CodeMirror styling carries
   through unchanged. Below 1100px we stack vertically.

   Line-level diff colouring uses CodeMirror's line decoration mechanism
   (see code-editor.js → buildLineDecorationExtensions). Lines flagged
   inserted/deleted/modified get a full-row tint; imaginary rows pad the
   shorter side so both panes line up visually.

   Colours pulled from the existing theme palette to stay dark-mode safe.
*/

/* Page shell: mirror the normal source-viewer's pattern — flex column
   that fills .content's grid-row height, so the panes can take the
   remaining space below the header without a hard-coded calc(). */
.oe-compare-file {
    padding: 16px;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
    height: 100%;
    min-height: 0;
}
.oe-compare-file__header {
    flex: 0 0 auto;
    align-items: flex-start;
    gap: 16px;
}
/* Override the 72ch prose cap inherited from `.admin-page__header p`
   (admin.css) so the full "Release · Module · Path" labels show instead of
   wrapping early. The extra ancestor class wins the specificity tie against
   admin.css, which loads after tools.css. */
.oe-compare-file__header .oe-compare-file__breadcrumbs p {
    margin: 0 0 4px;
    max-width: none;
}
.oe-compare-file__panes {
    /* Fills the rest of .oe-compare-file. grid-template-rows: 1fr is what
       lets the inner .source-viewer { height: 100% } resolve correctly —
       without it, the row would be auto-sized and the cm-editor would
       collapse to zero height. */
    flex: 1 1 0;
    min-height: 0;
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr;
    gap: 12px;
    margin-top: 12px;
}
.oe-compare-file__panes > .source-viewer--compare {
    border: 1px solid var(--color-border);
    border-radius: 6px;
    overflow: hidden;
    background: var(--color-surface);
    /* Row so the editor and the KDiff3-style overview ruler sit side by side
       (the base .source-viewer is a column). */
    flex-direction: row;
    /* min-height: 0 keeps the grid item from forcing the cm-scroller's
       intrinsic content height up the cascade — without it, long files
       blow the viewport on browsers that compute grid items' min size
       as min-content. */
    min-height: 0;
}
@media (max-width: 1100px) {
    .oe-compare-file__panes {
        grid-template-columns: 1fr;
        /* Stack vertically and give each pane half the available space. */
        grid-template-rows: 1fr 1fr;
    }
}

/* Diff line tints — applied to <div class="cm-line"> rows. The colour
   variables fall back to the named lights so each pane reads the same in
   light and dark themes. */
.cm-line.cm-diff-inserted   { background-color: rgba(38, 176, 80, 0.16); }
.cm-line.cm-diff-deleted    { background-color: rgba(179, 33, 33, 0.18); }
.cm-line.cm-diff-modified   { background-color: rgba(234, 179, 8, 0.20); }
.cm-line.cm-diff-imaginary  { background-color: var(--color-surface-strong); color: var(--color-text-muted); }

/* Diff change-bar gutter — a thin column (built in code-editor.js's
   buildDiffGutterExtensions) that paints one coloured cell per changed line so
   the run of changes is visible at a glance. Colours mirror the line tints but
   fully opaque to read as a solid bar. */
.cm-diff-gutter { width: 4px; }
.cm-diff-gutter .cm-gutterElement { padding: 0; }
.cm-diff-gutter-mark { width: 4px; }
.cm-diff-gutter-inserted { background-color: rgb(38, 176, 80); }
.cm-diff-gutter-deleted  { background-color: rgb(179, 33, 33); }
.cm-diff-gutter-modified { background-color: rgb(234, 179, 8); }

/* KDiff3-style overview ruler (compare panes, built in source-viewer.js's
   buildDiffOverview). A thin full-height strip on the right edge of each pane
   that maps every changed line in the WHOLE file to a coloured mark, so the
   spread of changes is visible without scrolling. Each mark is a button that
   jumps the editor to that change. */
.oe-diff-overview {
    position: relative;
    flex: 0 0 14px;
    align-self: stretch;
    background: var(--color-surface-strong);
    border-left: 1px solid var(--color-border);
}
.oe-diff-overview__mark {
    position: absolute;
    left: 2px;
    right: 2px;
    padding: 0;
    border: 0;
    border-radius: 1px;
    min-height: 3px;
    cursor: pointer;
    opacity: 0.85;
}
.oe-diff-overview__mark:hover {
    left: 0;
    right: 0;
    opacity: 1;
    outline: 1px solid var(--color-text-muted);
}
.oe-diff-overview__mark--inserted  { background-color: rgb(38, 176, 80); }
.oe-diff-overview__mark--deleted   { background-color: rgb(179, 33, 33); }
.oe-diff-overview__mark--modified  { background-color: rgb(234, 179, 8); }
.oe-diff-overview__mark--imaginary { background-color: var(--color-border); }

/* Change summary line in the compare header. */
.oe-compare-file__summary { margin: 6px 0 0; font-weight: 500; }

/* Compare-with-release picker on the single-file viewer header.
   Sits stacked under the Download button on the right edge. The
   picker collapses to one row inside its container (the field label
   stays above the select for accessibility, but no min-width pushes
   it wider than the outline column). */
.source-viewer__compare-picker {
    display: flex;
    align-items: stretch;
    width: 100%;
}
.source-viewer__compare-picker .field {
    width: 100%;
    min-width: 0;
}

/* Header actions wrapper — sits to the right of the breadcrumb and
   stacks the Download button + Compare-with-release picker
   vertically. The column width tracks the outline panel's width so
   the two controls always read as a visual pair with the outline
   below them (rather than the picker stretching wider than its
   own outline column did). */
.source-viewer__header-actions {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 6px;
    margin-left: auto;
    width: var(--source-viewer-outline-width, 340px);
    max-width: 100%;
}
.source-viewer__header-actions > .btn {
    width: 100%;
    justify-content: center;
    text-align: center;
}
.source-viewer__header-actions .field__label {
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--color-text-muted);
}

/* Status-row badges in the Compare-with-release results table. Reuse the
   same flat-pill shape as the existing kind badges but with semantic colour. */
.badge--success { background-color: rgba(38, 176, 80, 0.18); color: var(--color-valid); padding: 2px 8px; border-radius: 999px; font-size: 12px; }
.badge--danger  { background-color: rgba(179, 33, 33, 0.18); color: var(--color-danger); padding: 2px 8px; border-radius: 999px; font-size: 12px; }
.badge--warning { background-color: rgba(234, 179, 8, 0.20); color: #8a6d00; padding: 2px 8px; border-radius: 999px; font-size: 12px; }
@media (prefers-color-scheme: dark) {
    .badge--warning { color: #ffd05a; }
}
