* { box-sizing: border-box; }

body {
    margin: 0;
    font-family: -apple-system, Segoe UI, Arial, sans-serif;
    color: #1c2530;
    background: #f4f6f8;
}

a { color: #2563eb; text-decoration: none; }
a:hover { text-decoration: underline; }

.app {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

/* --- Top bar --- */

.topbar {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 0 16px;
    height: 52px;
    flex-shrink: 0;
    background: #1c2530;
    color: #fff;
    border-bottom: 1px solid #2f3a4a;
}

.menu-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 4px;
    width: 32px;
    height: 32px;
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
}

.menu-toggle span {
    display: block;
    height: 2px;
    background: #cbd5e1;
    border-radius: 1px;
}

.menu-toggle:hover span { background: #fff; }

.topbar-title {
    font-weight: 600;
}

.topbar-user {
    margin-left: auto;
    display: flex;
    align-items: center;
    gap: 12px;
    font-size: 0.9em;
    color: #cbd5e1;
}

.topbar-user .link-button {
    color: #cbd5e1;
}

.topbar-user .link-button:hover { color: #fff; }

.body-row {
    flex: 1;
    display: flex;
    min-height: 0;
    position: relative;
}

/* --- Left menu --- */

.menu {
    position: absolute;
    top: 0;
    left: 0;
    width: 240px;
    max-height: 100%;
    background: #1c2530;
    color: #cbd5e1;
    display: none;
    flex-direction: column;
    overflow-y: auto;
    box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3);
    z-index: 20;
}

.menu.open { display: flex; }

.menu > ul {
    list-style: none;
    margin: 0;
    padding: 8px 0 0;
}

.menu li { margin: 2px 0; }

:is(.menu, .menu-group-children) a,
:is(.menu, .menu-group-children) .menu-group,
:is(.menu, .menu-group-children) .menu-group-toggle {
    display: block;
    width: 100%;
    text-align: left;
    padding: 6px 16px;
    color: #cbd5e1;
    background: none;
    border: none;
    font: inherit;
    cursor: pointer;
}

:is(.menu, .menu-group-children) .menu-group {
    color: #8fa3bf;
    font-size: 0.85em;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding-top: 12px;
}

:is(.menu, .menu-group-children) .menu-group-toggle {
    color: #8fa3bf;
    font-size: 0.85em;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    padding-top: 12px;
    padding-right: 28px;
    position: relative;
}

:is(.menu, .menu-group-children) .menu-group-toggle::after {
    content: "\25B8"; /* ▸ - points toward the flyout, which always opens to that side */
    position: absolute;
    right: 16px;
}

:is(.menu, .menu-group-children) li.has-children.expanded > .menu-group-toggle {
    background: #2f3a4a;
    color: #fff;
}

:is(.menu, .menu-group-children) a:hover,
:is(.menu, .menu-group-children) .menu-group-toggle:hover {
    background: #2f3a4a;
    color: #fff;
    text-decoration: none;
}

/*
 * Submenus fly out to the side as their own floating, independently
 * scrollable panel instead of an inline accordion - both the live
 * `LIST MENU;` tree and the admin-only "Space data" group (one child
 * per space) can get arbitrarily large, and an inline accordion would
 * either push the rest of the menu far off-screen or need its own
 * nested scroll region.
 *
 * Positioned in JS (app.js positionMenuFlyout()) since it has to react to
 * the trigger's on-screen position and the viewport size, and is
 * reparented to <body> on open so it isn't clipped by an ancestor's
 * overflow:auto (.menu above; overflow-x implicitly becomes 'auto' -
 * i.e. clipping - as soon as overflow-y is anything but 'visible').
 */
.menu-group-children {
    display: none;
    position: fixed;
    min-width: 200px;
    max-width: 320px;
    background: #1c2530;
    box-shadow: 2px 0 8px rgba(0, 0, 0, 0.3);
    overflow-y: auto;
    z-index: 30;
}

.menu-group-children.open { display: block; }

.menu-group-children ul {
    list-style: none;
    margin: 0;
    padding: 6px 0;
}

/* --- Content area --- */

.content {
    flex: 1;
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.breadcrumb {
    padding: 12px 24px;
    background: #fff;
    border-bottom: 1px solid #e2e8f0;
    font-size: 0.9em;
    color: #64748b;
}

.breadcrumb-admin { border-bottom: 3px solid #dc2626; }

.suspend-banner {
    flex-shrink: 0;
    padding: 8px 24px;
    background: #dc2626;
    color: #fff;
    font-size: 0.9em;
    font-weight: 600;
    text-align: center;
}

.suspend-banner.allow-banner { background: #b45309; }

.breadcrumb .sep { margin: 0 6px; }

.page-body {
    padding: 24px;
    flex: 1;
}

/* --- Common elements --- */

.link-button {
    background: none;
    border: none;
    padding: 0;
    color: #2563eb;
    cursor: pointer;
    font: inherit;
    text-decoration: none;
}
.link-button:hover { text-decoration: underline; }

table {
    border-collapse: collapse;
    width: 100%;
    background: #fff;
}
th, td {
    text-align: left;
    padding: 8px 12px;
    border-bottom: 1px solid #e2e8f0;
}
th { background: #f8fafc; font-weight: 600; }

.card {
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    padding: 24px;
    max-width: 480px;
}

/* Record-detail page (admin/data/<space>/<id>), Data tab
   (src/views/admin/space_records/edit.php's "record-data-tab" wrapper):
   the metadata/main-fields/relation-detail cards read better spanning
   the full page width here instead of .card's narrow default, with a
   visible gap between each stacked card. */
.record-data-tab .card {
    max-width: none;
    margin-bottom: 16px;
}

/* A .card that reads better spanning the full page width than .card's
   narrow 480px default, which is sized for simple forms - e.g.
   Administration -> Info's License/Configuration cards (the License
   card's Installations-ID is a long unbroken hash, and the
   Configuration card is a ~50-row dump) and Admin-Kommandos' three
   action cards. */
.card-wide {
    max-width: none;
    margin-bottom: 16px;
}

form.inline { display: inline; }

.view-rename-form { display: flex; gap: 8px; align-items: center; }
.view-rename-form input[type=text] { width: auto; flex: 0 1 360px; }

.view-header-row { display: flex; flex-wrap: wrap; gap: 24px; align-items: stretch; margin-bottom: 24px; }
.view-header-row .card { flex: 1 1 0; max-width: none; min-width: 280px; }
.view-header-col h2 { margin: 24px 0 8px; }
.view-header-col h2:first-child { margin-top: 0; }

.field-row { display: flex; flex-wrap: wrap; gap: 24px; align-items: flex-start; }
.field-row > div { flex: 1 1 180px; min-width: 160px; }
.field-row > .field-row-narrow { flex: 0 1 auto; min-width: 0; }

.button-outline {
    display: inline-block;
    padding: 8px 16px;
    border: 1px solid #2563eb;
    border-radius: 4px;
    color: #2563eb;
    background: transparent;
}
.button-outline:hover { background: #eff6ff; text-decoration: none; }

label { display: block; margin: 12px 0 4px; font-weight: 600; }
input[type=text], input[type=email], input[type=password], input[type=number] {
    width: 100%;
    padding: 8px;
    border: 1px solid #cbd5e1;
    border-radius: 4px;
    font: inherit;
}

button, .button {
    display: inline-block;
    padding: 8px 16px;
    background: #2563eb;
    color: #fff;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font: inherit;
}
button.secondary, .button.secondary { background: #64748b; }
button.danger, .button.danger { background: #dc2626; }
button.success, .button.success { background: #16a34a; }
button.warning, .button.warning { background: #ea580c; }

/* Disabled keeps its own color (success/warning/danger/...) rather
   than going flat gray, so the currently-active choice in e.g.
   .script-mode-options below still reads as "the green one"/"the red
   one" - just visibly muted and non-interactive. */
button:disabled, .button:disabled {
    opacity: 0.5;
    cursor: not-allowed;
}

/* Admin-Kommandos -> Skript-Ausführung: the three mutually exclusive
   script-mode actions (GUARD/ALLOW/SUSPEND SCRIPTS), stacked as one
   button + its own explanation per choice rather than a single
   current-status line above a couple of conditionally-shown buttons.
   All three buttons are forced to the same (full) width regardless of
   their own label length - the form wrapper defaults to
   `display: inline` (form.inline above), which would otherwise size
   to its content instead of filling `.script-mode-option`. */
.script-mode-options {
    display: flex;
    flex-direction: column;
    gap: 16px;
    margin-top: 12px;
}
.script-mode-option form.inline {
    display: block;
    width: 100%;
}
.script-mode-option button {
    width: 100%;
}
/* The currently-active choice is already muted (button:disabled
   above); a solid border on top of that is the actual "this one is
   selected" marker, since a merely-faded button on its own reads as
   just "temporarily unavailable" rather than "the current setting". */
.script-mode-option button:disabled {
    border: 3px solid #0f172a;
}
.script-mode-description { margin: 6px 0 0; }

.badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 0.8em;
    font-weight: 600;
}
.badge.ok { background: #dcfce7; color: #166534; }
.badge.off { background: #fee2e2; color: #991b1b; }
.badge.pending { background: #fef9c3; color: #854d0e; }

.flash {
    padding: 10px 16px;
    border-radius: 4px;
    margin-bottom: 16px;
}
.flash.success { background: #dcfce7; color: #166534; }
.flash.error { background: #fee2e2; color: #991b1b; }
.flash.info { background: #dbeafe; color: #1e40af; }

/* --- Spaces / dynamic field rows --- */

select {
    padding: 8px;
    border: 1px solid #cbd5e1;
    border-radius: 4px;
    font: inherit;
    background: #fff;
}

.space-form { max-width: 720px; }

/* --- Settings popup (language + timezone, src/views/layout.php) --- */

.settings-dialog {
    border: none;
    border-radius: 6px;
    padding: 24px;
    width: 320px;
    max-width: calc(100vw - 32px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}

.settings-dialog::backdrop {
    background: rgba(0, 0, 0, 0.4);
}

.settings-dialog h2 {
    margin-top: 0;
}

.settings-dialog select {
    width: 100%;
}

.dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: 8px;
    margin-top: 20px;
}

/* --- "Show commands" popup (src/views/partials/_commands_dialog.php) --- */

.definition-dialog {
    width: 720px;
}

.definition-dialog-text {
    min-height: 360px;
    resize: vertical;
    white-space: pre;
}

.page-heading-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 16px;
    flex-wrap: wrap;
}

.page-heading-row h1 {
    margin: 0;
}

/* --- Scripts / Libraries source editing --- */

.code-textarea {
    width: 100%;
    min-height: 320px;
    font-family: "SF Mono", Consolas, monospace;
    padding: 8px;
    border: 1px solid #cbd5e1;
    border-radius: 4px;
    resize: vertical;
}

/* The "add field"/"create space" field-definition fieldset
   (src/views/admin/spaces/_field_row.php, mirrored by app.js's
   addFieldRow). Named .field-def rather than .field-row so it can't
   collide with the unrelated, differently-purposed generic ".field-row"
   flex-row utility above (Group-Builder etc.) - the two used to share a
   name, which made this fieldset a flex container of its own and blew
   apart the row1/row2 layout below into a jumbled single row
   (CODE_REVIEW_FRONTEND.md-style rename, not a behavior change for
   anything that used the generic utility). */
.field-def {
    position: relative;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    padding: 16px;
    margin: 12px 0;
}

/* Row 2: the type-dependent rest of the fieldset (only one of these
   three shown at a time, toggled by app.js's toggleRefFields() per the
   selected Type) - laid out as one horizontal row (wrapping if needed)
   the same way .field-row-line1 lays out row 1, instead of each label
   stacking on its own line. */
.field-notnull-only,
.field-autonumber-only,
.field-ref-only {
    display: flex;
    flex-wrap: wrap;
    gap: 16px;
    align-items: flex-start;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px dashed #e2e8f0;
}
.field-notnull-only > label,
.field-autonumber-only > label,
.field-ref-only > label {
    flex: 1 1 200px;
    min-width: 160px;
    margin-top: 0;
}

/* A fixed label height so every label in a row reserves the same
   vertical space regardless of its own text length, keeping the actual
   input/select directly under it at the same Y position as its row
   neighbors - instead of each one starting wherever its own label text
   happened to end. Every label in this fieldset (row 1 and row 2) is
   now short enough to fit on one line (space_field_row.* in lang/*.php
   was shortened for exactly this - a longer label wraps this fixed
   single-line height, giving up only alignment for that one label, not
   a whole extra reserved line for every label in the fieldset). */
.field-label-text {
    display: block;
    min-height: 1.3em;
    line-height: 1.3;
}

/* A checkbox's own label has no separate input below it to align - the
   checkbox+text sits right where the input would, so it still gets the
   same reserved .field-label-text height above it (left empty) as
   every real label+input pair in the same row. */
.field-checkbox-label { display: block; }
.field-checkbox-line { display: flex; align-items: center; gap: 6px; }

.field-remove {
    position: absolute;
    top: 8px;
    right: 8px;
    width: 28px;
    height: 28px;
    padding: 0;
    line-height: 1;
    border-radius: 999px;
}

.field-actions {
    display: flex;
    gap: 8px;
    align-items: center;
}

/* --- Space-data "Fields" section: one card per field, an internal
   table (fixed equal-width columns, 3 rows) plus a drag-handle column
   beside it (reordering is drag-and-drop only, initSpaceFieldDrag() in
   app.js - no more move-up/move-down buttons, and no more leading "#"
   position cell either - the drag handle already conveys order).
   Dropping that "#" cell let row 1 shift left and pick up Description
   in its now-free last cell; Save then moved into row 3's now-free
   first cell, and Delete (.field-card-delete-cell) sits directly below
   it in row 3's own last cell, one column over from where it used to
   be in row 2 (src/views/admin/spaces/show.php) - row 2 keeps an empty
   trailing 5th cell purely so its bottom border still reaches the full
   row width like row 1's, and the table's own last-row bottom border is
   suppressed since it would otherwise sit right above the card's own
   (now slightly shorter) bottom edge.
   Delete lives outside the <form> it submits (a `form=""` attribute
   points back at it) since it can't be a descendant of the field-update
   form without illegally nesting its own separate <form>; Save is a
   genuine descendant of the field-update form (it still carries the
   same `form="<id>"` attribute, redundantly). The field-update form
   submits via AJAX (bindAjaxForms() in app.js, generic - any
   form[data-ajax] on any admin page, not just this one) so saving one
   field's card never reloads the page and discards in-progress edits in
   another field's card.
   .field-card-table itself (fixed equal-width columns, label above its
   input in each cell) is reused as-is by the "Script" section's attach
   form below for its own 2x2 layout. --- */

.fields-list { display: flex; flex-direction: column; gap: 12px; }

.field-card {
    display: flex;
    flex-direction: column;
    gap: 12px;
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    padding: 16px 16px 8px;
}

.field-card-body { display: flex; gap: 16px; align-items: flex-start; }

/* This invisible form (just a CSRF field - see show.php's comment on it)
   is .field-card's second flex child purely so it can be a real,
   separately-submittable <form> without nesting inside the field-update
   one. display: contents drops its own box from the flex layout
   entirely, so it no longer eats a share of .field-card's own row gap
   as empty space below the last visible row. */
.field-card-delete-form { display: contents; }

.field-card-form { flex: 1 1 auto; min-width: 0; }

.field-card-table { width: 100%; table-layout: fixed; border-collapse: collapse; }
.field-card-table td { padding: 6px 8px; vertical-align: top; }
.field-card-table td:first-child { padding-left: 0; }
/* Undo the global "table td { border-bottom }" rule for the table's
   own last row only - the line separating row from row within the
   card is useful, but the bottommost one just adds a redundant line
   right above the card's own bottom edge. */
.field-card-table tr:last-child td { border-bottom: none; }
/* Save and Delete sit bottom-aligned in their own cells (rather than
   the row's default top alignment, .field-card-table td above - needs
   the extra `td` in the selector to outrank that rule's own class+type
   specificity, since a bare class selector alone loses to it) so they
   read as a single action row regardless of how tall their row's other
   cells (Unique/Autonumber/Tags) happen to be; Delete is additionally
   right-aligned within its own cell. */
.field-card-table td.field-card-save-cell,
.field-card-table td.field-card-delete-cell { vertical-align: bottom; }
.field-card-delete-cell { text-align: right; }
.field-card-table label { margin: 0 0 4px; font-weight: 600; font-size: 0.85em; }
.field-card-table input[type=text],
.field-card-table input[type=number],
.field-card-table select { width: 100%; }

.field-card-field-label { display: block; font-weight: 600; font-size: 0.85em; margin-bottom: 4px; }

.field-card-inline-inputs { display: flex; gap: 8px; }
.field-card-inline-inputs input { width: auto; flex: 1 1 auto; }

/* Shared status indicator for any form[data-ajax] (bindAjaxForms() in app.js), not just the field-card. */
.ajax-status { font-size: 0.85em; }
.ajax-status.success { color: #166534; }
.ajax-status.error { color: #991b1b; }

.field-card-actions {
    display: flex;
    flex-direction: column;
    gap: 8px;
    flex: 0 0 auto;
}

/* Drag-and-drop field reordering (app.js's initSpaceFieldDrag(), jQuery
   UI sortable on .fields-list) - the handle sits above the existing
   move-up/move-down buttons in the same column. */
.field-drag-handle {
    text-align: center;
    color: #94a3b8;
    cursor: grab;
    user-select: none;
}
.field-drag-handle:hover { color: #475569; }
.field-card.ui-sortable-helper {
    box-shadow: 0 4px 10px rgba(15, 23, 42, 0.18);
    cursor: grabbing;
}
.field-drag-placeholder {
    background: #dbeafe;
    border: 2px dashed #93c5fd;
    border-radius: 6px;
}

/* --- Two cards side by side, e.g. a space's Workflow/Label-script or
   Script/Global-condition-scripts sections
   (src/views/admin/spaces/show.php) --- */

.section-columns { display: flex; flex-wrap: wrap; gap: 24px; align-items: flex-start; }
.section-columns > div { flex: 1 1 340px; min-width: 280px; }

/* --- A form's own labelled fields laid out side by side instead of
   stacked, e.g. the space "Add field" form's row 1 --- */

.field-row-line1 { display: flex; flex-wrap: wrap; gap: 16px; }
.field-row-line1 > label { flex: 1 1 200px; min-width: 160px; margin-top: 0; }

/* Overrides .space-form's own max-width for a form/card that should
   span the full content width instead of the narrow default every
   other .space-form card uses. */
.space-form-wide { max-width: none; }

/* A GROUP's own divider row inside the merged Fields table
   (admin/views/edit.php) - the row itself carries the group's label/path/
   order-by/anchor info plus its Edit/Delete actions; nested inside its
   wide cell is a second table listing the group's own fields, styled to
   read as its members (view-layout-reorder.js drags each independently,
   so the nesting also doubles as the drag boundary between the two). */
.view-group-row td { background: #eef2ff; }
.view-group-row-inner {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    margin-bottom: 8px;
}
.view-group-fields-table {
    margin-left: 20px;
    width: calc(100% - 20px);
    border-left: 3px solid #c7d2fe;
}
.view-group-fields-table td { background: #f5f7ff; }

/* Drag-and-drop reordering (view-layout-reorder.js, jQuery UI sortable
   on each <tbody class="view-layout-zone">). */
.view-drag-handle-col { width: 28px; }
.view-drag-handle {
    width: 28px;
    text-align: center;
    color: #94a3b8;
    cursor: grab;
    user-select: none;
}
.view-drag-handle:hover { color: #475569; }
.view-layout-zone tr.ui-sortable-helper {
    box-shadow: 0 4px 10px rgba(15, 23, 42, 0.18);
    background: #fff;
    cursor: grabbing;
}
.view-drag-placeholder {
    display: table-row;
    background: #dbeafe !important;
    outline: 2px dashed #93c5fd;
    outline-offset: -2px;
}

/* Query-Builder's root-space/WHERE/LIMIT row (query_builder.php) - three
   labelled fields side by side instead of stacked. */
.qb-root-row { display: flex; flex-wrap: wrap; gap: 24px; }
.qb-root-row > .qb-root-field { flex: 1 1 220px; min-width: 200px; }
.qb-root-field label { margin-top: 0; }
.qb-root-field select { width: 100%; }

.login-page {
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    background: #f4f6f8;
}

/* --- CLI --- */

.field-log-value {
    white-space: pre-wrap;
}

.cli-output {
    background: #0f172a;
    color: #e2e8f0;
    font-family: "SF Mono", Consolas, monospace;
    padding: 16px;
    border-radius: 6px;
    height: 420px;
    overflow-y: auto;
    white-space: pre-wrap;
}
.cli-output .line-error { color: #f87171; }
.cli-output .line-message { color: #fbbf24; }
.cli-output .line-comment { color: #64748b; }
.cli-output .line-meta { color: #38bdf8; }
.cli-output .line-echo { color: #94a3b8; }

.cli-input {
    width: 100%;
    font-family: "SF Mono", Consolas, monospace;
    padding: 8px;
    border: 1px solid #cbd5e1;
    border-radius: 4px;
    margin-top: 12px;
}

/* --- Workflow editor/viewer --- */

.workflow-toolbar {
    display: flex;
    gap: 8px;
    margin-bottom: 8px;
}

.workflow-toolbar button.active {
    background: #1d4ed8;
}

.workflow-status {
    min-height: 1.4em;
    margin-bottom: 8px;
    font-size: 0.9em;
}

.workflow-status.success { color: #166534; }
.workflow-status.error { color: #991b1b; }

.workflow-body {
    display: flex;
    gap: 16px;
    align-items: flex-start;
}

.workflow-canvas-wrap {
    flex: 1;
    max-width: 100%;
    height: 600px;
    overflow: auto;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    background: #fff;
}

.workflow-canvas { display: block; }

.workflow-state-rect {
    fill: #eff6ff;
    stroke: #2563eb;
    stroke-width: 1.5;
    cursor: grab;
}

.workflow-state.selected .workflow-state-rect {
    stroke-width: 3;
}

.workflow-state.active .workflow-state-rect {
    fill: #fef9c3;
    stroke: #ca8a04;
}

.workflow-state.connect-source .workflow-state-rect {
    stroke-dasharray: 4 3;
}

.workflow-state-label {
    font-size: 13px;
    fill: #1c2530;
    pointer-events: none;
}

.workflow-state-badge {
    font-size: 9px;
    letter-spacing: 0.04em;
    fill: #64748b;
    pointer-events: none;
}

.workflow-flow-line {
    stroke: #64748b;
    stroke-width: 1.5;
    fill: none;
}

.workflow-flow.selected .workflow-flow-line { stroke: #2563eb; stroke-width: 2.5; }
.workflow-flow.allowed .workflow-flow-line { stroke: #ca8a04; stroke-width: 3.5; }

.workflow-flow-hit {
    stroke: transparent;
    stroke-width: 14;
    fill: none;
}

/* Only actually clickable: every flow in editor mode (selectable), but
   only a currently-allowed flow in viewer mode (triggerable) - see
   workflow-editor.js's renderFlows(), which only attaches a mousedown
   handler in those same two cases. An unconditional cursor:pointer here
   would mislead a viewer-mode user into thinking every flow line is
   clickable. */
.workflow-mode-editor .workflow-flow-hit { cursor: pointer; }
.workflow-mode-viewer .workflow-flow.allowed .workflow-flow-hit { cursor: pointer; }

.workflow-flow-handle {
    fill: #fff;
    stroke: #2563eb;
    stroke-width: 2;
    cursor: grab;
}

.workflow-arrowhead { fill: #64748b; }
.workflow-flow.selected .workflow-arrowhead { fill: #2563eb; }
.workflow-flow.allowed .workflow-arrowhead { fill: #ca8a04; }

.workflow-flow-label {
    font-size: 11px;
    fill: #475569;
}

.workflow-panel {
    width: 280px;
    flex-shrink: 0;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    padding: 16px;
    background: #fff;
}

.workflow-panel h3 { margin-top: 0; }

/* A <select> sizes itself to its longest option (a long script name ran out
   of the panel and got cut off) - pin selects and inputs to the panel width;
   a too-long option text is then clipped inside the control instead. */
.workflow-panel select,
.workflow-panel input[type="text"],
.workflow-panel input[type="number"] {
    width: 100%;
    max-width: 100%;
    box-sizing: border-box;
}

.workflow-panel-hint { color: #64748b; font-size: 0.9em; }

.workflow-field-error {
    color: #991b1b;
    font-size: 0.85em;
    min-height: 1.2em;
}

.workflow-panel h4 {
    margin: 16px 0 4px;
    font-size: 0.95em;
}

.workflow-script-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 6px 0;
    border-bottom: 1px solid #e2e8f0;
    flex-wrap: wrap;
}

.workflow-script-name {
    flex: 1;
    font-size: 0.9em;
}

.workflow-script-row button {
    padding: 4px 8px;
    font-size: 0.85em;
}

/* --- Record-detail tabs (Data/Workflow/Log) - admin/space_records/edit.php --- */

.tab-strip {
    display: flex;
    gap: 4px;
    border-bottom: 1px solid #e2e8f0;
    margin: 16px 0;
}

.tab-button {
    background: none;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: 6px 6px 0 0;
    padding: 8px 16px;
    cursor: pointer;
    color: #64748b;
    font-size: 0.95em;
}

.tab-button:hover { color: #1e293b; }

.tab-button.active {
    color: #1e293b;
    font-weight: bold;
    border-color: #e2e8f0;
    background: #f8fafc;
}

[data-tab-panel]:not([data-tab-panel="data"]) { display: none; }

/* --- Run-scripts button/dropdown (record-detail page, /view/{name}) --- */

.run-scripts-bar {
    display: inline-block;
    margin-bottom: 16px;
}

.run-scripts-menu {
    position: relative;
}

.run-scripts-menu > summary {
    list-style: none;
    cursor: pointer;
}

.run-scripts-menu > summary::-webkit-details-marker { display: none; }

.run-scripts-menu > summary::after { content: " \25BE"; }

.run-scripts-menu ul {
    position: absolute;
    top: 100%;
    left: 0;
    margin: 4px 0 0;
    padding: 4px;
    list-style: none;
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    box-shadow: 0 4px 10px rgba(15, 23, 42, 0.18);
    min-width: 160px;
    z-index: 10;
}

.run-scripts-menu li + li { margin-top: 2px; }

.run-scripts-menu li form { margin: 0; }

.run-scripts-menu li button {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    color: #1e293b;
}

.run-scripts-menu li button:hover { background: #f1f5f9; }

/* Field caption the RENDER VIEW fragment emits per field (SINGLE view),
   from DATA.fields[i].label - server markup, host-styled here. */
.noplm-view-field-label {
    display: block;
    margin-top: 12px;
    font-weight: bold;
    font-size: 0.9em;
    color: #334155;
}

/* A GROUP's own caption (view_render.go's <div class="noplm-view-group-
   label">, text = g.label || g.path) - matches .noplm-view-field-label
   above so a group's heading reads the same as a plain field's. */
.noplm-view-group-label {
    display: block;
    margin-top: 12px;
    font-weight: bold;
    font-size: 0.9em;
    color: #334155;
}

/* Breathing room between the Edit/Save toolbar and the fields. */
.noplm-view-toolbar {
    margin-bottom: 20px;
    display: flex;
    gap: 10px;
}

/* SINGLE view: label left, value right, every value aligned on one
   shared column edge. The fragment emits each row as a flat run of
   alternating <label class="noplm-view-field-label"> / <div
   class="noplm-view-field"> pairs, so a 2-column grid with auto
   row-by-row placement drops labels in column 1 and values in
   column 2. LIST views (data-kind="list") keep their table layout. */
.noplm-view[data-kind="single"] .noplm-view-row {
    display: grid;
    grid-template-columns: max-content minmax(0, 1fr);
    align-items: stretch;
    border-top: 1px solid #e2e8f0;
}

/* Each logical field = a label cell + a value cell sharing one grid
   track; a bottom border + matching vertical padding on both cells
   gives the same row separation the LIST view's table rows have. The
   column gap is padding on the label (not grid column-gap) so the two
   cells' bottom borders meet in one unbroken line. */
.noplm-view[data-kind="single"] .noplm-view-field-label,
.noplm-view[data-kind="single"] .noplm-view-field {
    border-bottom: 1px solid #e2e8f0;
    padding: 12px 0;
    min-width: 0;
}

.noplm-view[data-kind="single"] .noplm-view-field-label {
    margin-top: 0;
    padding-right: 24px;
    text-align: left;
}

.noplm-view[data-kind="single"] .noplm-view-field input[type="text"],
.noplm-view[data-kind="single"] .noplm-view-field textarea,
.noplm-view[data-kind="single"] .noplm-view-field .noplm-picker {
    max-width: 480px;
    width: 100%;
    box-sizing: border-box;
}

/* The "search" variant relation picker's results list
   (noplm-server's view_render.go, searchPickerScript - see CLAUDE.md's
   "Edit-mode relation picker") - a popup overlay, not an inline block.
   It only ever appears while its own .noplm-picker input is focused or
   being typed into (opens on focus, closes on selection/outside
   click/Escape), so positioning it relative to its own .noplm-picker
   keeps it from pushing the rest of the page down - a GROUP table's
   other rows in particular - the way a plain block element would every
   time a field is focused. Same dropdown treatment (border/shadow/
   z-index) as .menu-group-children ul / .run-scripts-menu li button
   above. */
.noplm-picker {
    position: relative;
}

/* The picker's current value(s) (view_render.go's searchPickerScript:
   <span class="noplm-picker-chip">label<button class="noplm-picker-remove">).
   Same pill look and spacing as .entity-picker-current li in the
   Space-data area, so the "x" no longer sits flush against the label. */
.noplm-picker-selected {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-bottom: 6px;
}

.noplm-picker-chip {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: #eef2ff;
    border: 1px solid #c7d2fe;
    border-radius: 999px;
    padding: 2px 4px 2px 10px;
    font-size: 0.9em;
}

.noplm-picker-remove {
    line-height: 1;
    padding: 2px 7px;
    border-radius: 999px;
    font-size: 0.85em;
}

.noplm-picker-results {
    position: absolute;
    top: 100%;
    left: 0;
    width: 100%;
    box-sizing: border-box;
    margin-top: 2px;
    max-height: 240px;
    overflow-y: auto;
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    box-shadow: 0 4px 10px rgba(15, 23, 42, 0.18);
    z-index: 10;
}

/* Flipped upward by the server's own positionPickerResults() (in
   searchPickerScript) when there isn't enough room below the input but
   more room above - found live in a GROUP's "Add row" row, where the
   results list otherwise landed squarely on top of the "Add row" button
   sitting right below the table, making it genuinely unclickable, not
   just visually covered. */
.noplm-picker.noplm-picker-open-up .noplm-picker-results {
    top: auto;
    bottom: 100%;
    margin-top: 0;
    margin-bottom: 2px;
}

.noplm-picker-option,
.noplm-picker-message {
    padding: 6px 10px;
}

.noplm-picker-option {
    cursor: pointer;
}

.noplm-picker-option:hover,
.noplm-picker-option-selected {
    background: #eff6ff;
}

.noplm-picker-message {
    color: #64748b;
    font-style: italic;
}

/* A GROUP's own <td class="noplm-view-field"> cells (view_render.go's
   buildGroupRow) sit inside their own <table class="noplm-view-group-
   table">, not the SINGLE view's label/value grid above - but they share
   the "noplm-view-field" class, so the padding:12px 0 rule above (which
   zeroes horizontal padding on purpose, for the grid layout) otherwise
   left the group table's columns touching with no gap between them.
   Restore normal cell padding for just those. */
.noplm-view[data-kind="single"] .noplm-view-group-table td.noplm-view-field {
    padding: 8px 12px;
}

/* Indent the group's table a bit from the group label/the view's left
   edge, so the table reads as nested under its caption. Width is
   trimmed to match (the global "table { width: 100% }" rule would
   otherwise push the indented table's right edge past its container). */
.noplm-view-group-table {
    margin-left: 16px;
    width: calc(100% - 16px);
}

/* Navigable fields/references inside a RENDER VIEW fragment - server
   markup, host-styled here. A relation reference renders as
   <a class="noplm-view-nav">; a record/row/static navigate puts
   class "noplm-view-navigable" on the whole field cell. Both must
   read as clickable, and highlight on hover. */
.noplm-view-nav,
.noplm-view-navigable {
    color: #2563eb;
    text-decoration: none;
    cursor: pointer;
}

.noplm-view-nav {
    text-decoration: underline;
    text-decoration-color: rgba(37, 99, 235, 0.4);
}

.noplm-view-nav:hover,
.noplm-view-navigable:hover {
    color: #1d4ed8;
    text-decoration: underline;
    text-decoration-color: currentColor;
    background: rgba(37, 99, 235, 0.08);
    border-radius: 3px;
}

/* Relation-field value inside a RENDER VIEW fragment - server markup
   (noplm-server's __default_relation__/__default_relation_list__
   renderView), host-styled here. Each value is now its own
   "noplm-view-chip" pill (a MANY-side field wraps its chips in a
   "noplm-view-chip-list" flex container instead of joining labels into
   one comma-separated text span); a navigable chip is the <a
   class="noplm-view-nav noplm-view-chip"> from above, a non-navigable
   one a plain <span class="noplm-view-chip">. Matches the look of the
   entity-picker chips used for relation/USER/ROLE fields in the
   Space-data area (admin/data/<space>/<id>, .entity-picker-current li
   above) so the same value reads the same way in both places. */
.noplm-view-chip-list {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.noplm-view-chip {
    display: inline-flex;
    align-items: center;
    background: #eef2ff;
    border: 1px solid #c7d2fe;
    border-radius: 999px;
    padding: 2px 10px;
    font-size: 0.9em;
}

a.noplm-view-chip {
    color: #3730a3;
    text-decoration: none;
}

a.noplm-view-chip:hover {
    background: #e0e7ff;
    border-color: #a5b4fc;
    color: #312e81;
    text-decoration: none;
}

/* --- LIST view client-side table (view-list.js, /view/{name} for a
   LIST kind view - STRUCTURE/DATA split, PLAN_VIEW_LIST_DATA_API.md).
   The table itself reuses .noplm-view-table/-nav/-chip* (server markup
   classes) with no new rule needed - only the parts view-list.js adds
   on top (sortable headers, the columns-visibility dropdown, the
   truncated-rows notice) need new CSS. .run-scripts-bar/-menu (above)
   is reused as-is for the columns dropdown, same look, no duplication. */
.noplm-view-list-toolbar {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 12px;
}

/* Left group: Spalten/Filter (affect what's shown); right group: Saved
   views/Export (act on the current state) - kept apart so the two
   groups can be pushed to opposite ends of the toolbar regardless of
   how many buttons either one holds. */
.noplm-view-list-toolbar-left,
.noplm-view-list-toolbar-right {
    display: flex;
    align-items: flex-start;
    gap: 8px;
}

/* Small-caption filter summary next to the view's <h1> (list.php's
   [data-view-list-filter-summary] span) - refreshFilterIndicators()
   fills it in with the active filter's plain-language text, flattened
   left-to-right with no parentheses (matches how the filter itself is
   folded server-side, buildWhereClause()'s own doc comment). Empty
   (and so invisible) when no filter is active. */
.noplm-view-list-filter-summary {
    font-size: 0.5em;
    font-weight: normal;
    color: #64748b;
    vertical-align: middle;
}

/* A Spalten/Filter toggle button whose panel represents non-default
   state (a column hidden / a filter currently applied) - reuses the
   existing button.warning color rather than inventing a new accent, so
   it reads the same "this isn't the default view" way similar warning
   buttons already do elsewhere. Toggled by updateColumnsButtonState()/
   refreshFilterIndicators() in view-list.js. */
.run-scripts-menu > summary.warning {
    background: #ea580c;
    color: #fff;
}

.noplm-view-table th[data-view-field] {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}

.noplm-view-table th[data-view-field]:hover {
    background: #f1f5f9;
}

.noplm-view-table th.is-sorted::after {
    content: " \25B2";
}

.noplm-view-table th.is-sorted.is-sorted-desc::after {
    content: " \25BC";
}

.noplm-view-truncated-notice {
    color: #64748b;
    font-size: 0.9em;
    margin-bottom: 8px;
}

/* Column filter panel (view-list.js, PLAN_VIEW_LIST_FILTER.md). The
   <details>/<summary> toggle itself reuses .run-scripts-bar/-menu as-is
   (same look as the columns dropdown, sits right next to it) - spacing
   between the two now comes from .noplm-view-list-toolbar-left's own
   gap, not a margin here. */

/* Export menu (view-list.js) - a <details>/<summary> "Export" main
   button with an "ODS"/"CSV" dropdown, same .run-scripts-bar/-menu
   shape as the columns/filter toggles (not plain side-by-side buttons
   any more). Sits in the right-hand toolbar group, so its dropdown
   opens leftward (right: 0) instead of overflowing past the page edge
   the way the left-anchored menus' left: 0 default would here. */
.noplm-view-list-export ul {
    left: auto;
    right: 0;
}

.noplm-view-list-filter-panel {
    position: absolute;
    top: 100%;
    left: 0;
    margin: 4px 0 0;
    padding: 12px;
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    box-shadow: 0 4px 10px rgba(15, 23, 42, 0.18);
    min-width: 480px;
    z-index: 10;
}

/* Grid, not flex: a row's controls must line up under the same row's
   siblings above/below it (connector / column / operator / value), so
   the four columns are sized once on the shared rows container and each
   row's cells (display: contents) drop straight into that grid rather
   than each row sizing its own cells independently. */
.noplm-view-list-filter-rows {
    display: grid;
    grid-template-columns: 56px minmax(150px, 1fr) minmax(110px, auto) minmax(140px, 1.4fr) auto;
    column-gap: 6px;
    row-gap: 6px;
    align-items: center;
}

.noplm-view-list-filter-row {
    display: contents;
}

.noplm-view-list-filter-row .nvlf-connector,
.noplm-view-list-filter-row .nvlf-column,
.noplm-view-list-filter-row .nvlf-operator {
    width: 100%;
    box-sizing: border-box;
}

.noplm-view-list-filter-row .nvlf-value-holder {
    min-width: 0;
}

.noplm-view-list-filter-row .nvlf-value {
    width: 100%;
    box-sizing: border-box;
}

.noplm-view-list-filter-row .nvlf-remove {
    background: none;
    border: none;
    color: #64748b;
    cursor: pointer;
    font-size: 1.1em;
    line-height: 1;
    padding: 0 4px;
}

.noplm-view-list-filter-row .nvlf-remove:hover {
    color: #dc2626;
}

.noplm-view-list-filter-actions {
    display: flex;
    gap: 8px;
    margin-top: 8px;
}

.noplm-view-list-filter-actions button {
    white-space: nowrap;
}

/* Saved-views menu (view-list.js, LIST view state persistence). Its own
   panel, not a reused .run-scripts-menu <ul> - it mixes saved-view rows
   (name + delete "×") with a trailing name input + save button, which
   that generic "<li><button></li>" styling isn't built for. Sits in the
   right-hand toolbar group, so - like the export menu above - its panel
   opens leftward (right: 0) rather than overflowing the page edge. */
.noplm-view-list-saved-views-panel {
    position: absolute;
    top: 100%;
    right: 0;
    margin: 4px 0 0;
    padding: 8px;
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 6px;
    box-shadow: 0 4px 10px rgba(15, 23, 42, 0.18);
    min-width: 240px;
    z-index: 10;
}

.noplm-view-list-saved-views-list {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* The list lives inside a .run-scripts-menu <details>, whose generic
   `ul` rule above (specificity 0,1,1) would otherwise turn it into a
   second absolutely positioned dropdown floating below the panel, and
   its `li button` rule stretches every button to full width. Neutralise
   both here (one class more specific). */
.run-scripts-menu .noplm-view-list-saved-views-list {
    position: static;
    min-width: 0;
    padding: 0;
    margin: 0;
    background: none;
    border: none;
    border-radius: 0;
    box-shadow: none;
}

.run-scripts-menu .noplm-view-list-saved-view-row button {
    width: auto;
}

.run-scripts-menu .noplm-view-list-saved-view-row button:first-child {
    flex: 1;
}

.noplm-view-list-saved-views-list li + li {
    margin-top: 2px;
}

.noplm-view-list-saved-view-row {
    display: flex;
    align-items: center;
    gap: 4px;
}

.noplm-view-list-saved-view-row button:first-child {
    flex: 1;
    text-align: left;
    background: none;
    border: none;
    padding: 4px;
    color: #1e293b;
    cursor: pointer;
}

.noplm-view-list-saved-view-row button:first-child:hover {
    background: #f1f5f9;
}

/* Stacked, not side-by-side: the panel's own min-width (240px) is too
   narrow to fit a text input next to "Aktuelle Ansicht speichern"/"Save
   current view" without one or the other being squeezed to near-zero
   (found live - the input collapsed to a sliver, the button wrapped to
   two lines) - a vertical layout has no such width contest regardless
   of locale/button-text length. */
.noplm-view-list-saved-views-save {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 8px;
    padding-top: 8px;
    border-top: 1px solid #e2e8f0;
}

.noplm-view-list-saved-views-save input {
    width: 100%;
    box-sizing: border-box;
}

.noplm-view-list-saved-views-save button {
    width: 100%;
}

/* --- Mehrere Backend-Benutzer pro Login --- */

.muted { color: #64748b; font-weight: normal; font-size: 0.85em; }

.card.user-group { max-width: none; margin-bottom: 20px; }

.user-group-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: 12px;
    margin-bottom: 12px;
}
.user-group-header h2 { margin: 0; }

.user-group-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

.account-switch select {
    padding: 4px 8px;
    font-size: 0.9em;
}

.account-option {
    display: flex;
    align-items: center;
    gap: 8px;
    font-weight: normal;
    padding: 8px 0;
    border-bottom: 1px solid #e2e8f0;
}

/* --- Relation-detail table: a relation field's DETAIL SPACE, editable
       inline (src/views/partials/_relation_detail_table.php,
       App\Controllers\Admin\SpaceRecordsController::
       buildRelationDetailTable()) - its own card already spans full
       width via .record-data-tab .card above (a table with one column
       per DETAIL SPACE field needs the room), so only its own internal
       spacing is left to set here. --- */

.relation-detail-table td { vertical-align: top; }
.relation-detail-table .entity-picker { margin-bottom: 0; }
.relation-detail-actions { white-space: nowrap; }
.relation-detail-actions button { margin-right: 4px; }

/* --- Entity picker: relation / USER / ROLE fields in the Space-data
       area (src/views/partials/_picker_field.php +
       _entity_picker_dialog.php, public/assets/entity-picker.js) --- */

.entity-picker {
    margin-bottom: 8px;
}

.entity-picker-current {
    list-style: none;
    margin: 0 0 6px;
    padding: 0;
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.entity-picker-current li {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    background: #eef2ff;
    border: 1px solid #c7d2fe;
    border-radius: 999px;
    padding: 2px 4px 2px 10px;
    font-size: 0.9em;
}

.entity-picker-remove {
    line-height: 1;
    padding: 2px 7px;
    border-radius: 999px;
    font-size: 0.85em;
}

.entity-picker-empty {
    margin: 0 0 6px;
}

.entity-picker-dialog {
    border: none;
    border-radius: 6px;
    padding: 24px;
    width: 460px;
    max-width: calc(100vw - 32px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}

.entity-picker-dialog::backdrop {
    background: rgba(0, 0, 0, 0.4);
}

.entity-picker-dialog h2 {
    margin-top: 0;
}

.entity-picker-dialog input[type=search] {
    width: 100%;
    padding: 8px;
    border: 1px solid #cbd5e1;
    border-radius: 4px;
    font: inherit;
}

.entity-picker-results {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
    max-height: 320px;
    overflow-y: auto;
    border: 1px solid #e2e8f0;
    border-radius: 4px;
}

.entity-picker-results:empty {
    display: none;
}

.entity-picker-results li {
    display: flex;
    align-items: baseline;
    gap: 8px;
    padding: 8px 10px;
    cursor: pointer;
    border-bottom: 1px solid #f1f5f9;
}

.entity-picker-results li:last-child {
    border-bottom: none;
}

.entity-picker-results li:hover {
    background: #f8fafc;
}

.entity-picker-results li.is-selected {
    background: #eef2ff;
    font-weight: 600;
}

.entity-picker-results li.is-selected::before {
    content: "\2713";
    color: #4f46e5;
}

.entity-picker-noresults {
    margin: 12px 0 0;
}

/* --- DATETIME field picker (public/assets/datetime-picker.js) --- */

/* jQuery UI appends .ui-datepicker to <body>; its computed z-index can
   land below the sticky topbar (z-index 30) - keep the popup on top,
   and give it an opaque background/border (the cdnjs "base" theme
   leaves it translucent over our layout). */
.ui-datepicker {
    z-index: 100 !important;
    background: #fff;
    border: 1px solid #cbd5e1;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}

input.datetime-input {
    max-width: 22em;
}

/* Query builder (Administration -> Views -> <name> -> Query-Builder). */
.qb-table td, .qb-table th {
    padding: 4px 8px;
    vertical-align: middle;
}
.qb-table tr.qb-selected {
    background: #eff6ff;
}
.qb-table input[type="text"], .qb-table select {
    width: 100%;
    min-width: 6em;
}
.qb-table input[type="text"]:disabled, .qb-table select:disabled {
    background: #f1f5f9;
    color: #94a3b8;
}
.qb-caret, .qb-caret-spacer {
    display: inline-block;
    width: 1.4em;
    text-align: center;
}
.qb-caret {
    border: none;
    background: none;
    color: #475569;
    cursor: pointer;
    padding: 0;
    font-size: 0.9em;
}
.qb-field-name {
    font-weight: 600;
}
.qb-hint {
    font-size: 0.85em;
}
.qb-preview {
    background: #0f172a;
    color: #e2e8f0;
    padding: 12px 16px;
    border-radius: 6px;
    white-space: pre-wrap;
    word-break: break-word;
}

/* Group builder (Administration -> Views -> <name> -> Groups -> Add/Edit). */
.gb-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-bottom: 6px;
}
.gb-row select {
    min-width: 10em;
}

/* Script Testen tab (Administration -> Scripts -> <name> -> Testen),
   SIMULATE SCRIPT - src/views/admin/scripts/edit.php,
   src/views/admin/scripts/partials/_simulate_context.php,
   public/assets/script-simulate.js. */
.sim-field-row {
    margin-bottom: 12px;
}
.simulate-record-box {
    background: #f1f5f9;
    border-radius: 6px;
    padding: 16px;
    margin-bottom: 16px;
}
.simulate-record-box h3:first-child {
    margin-top: 0;
}
.simulate-command {
    background: #0f172a;
    color: #e2e8f0;
    padding: 12px 16px;
    border-radius: 6px;
    white-space: pre-wrap;
    word-break: break-word;
}
.simulate-success {
    padding: 10px 16px;
    border-radius: 4px;
    margin-bottom: 12px;
    background: #dcfce7;
    color: #166534;
}
.simulate-error {
    padding: 10px 16px;
    border-radius: 4px;
    margin-bottom: 12px;
    background: #fee2e2;
    color: #991b1b;
}
.simulate-log {
    background: #f1f5f9;
    padding: 10px 16px;
    border-radius: 6px;
    white-space: pre-wrap;
    word-break: break-word;
}
.simulate-diff {
    width: 100%;
}
.simulate-return {
    background: #f1f5f9;
    padding: 10px 16px;
    border-radius: 6px;
    white-space: pre-wrap;
    word-break: break-word;
}
/* Warnings mark a real, non-rollback-able side effect (ctx.httpQuery(),
   an AUTONUMBER draw) - deliberately not reusing .flash.error's
   red/danger palette, since it isn't an error: it's a "this really
   happened" notice that needs to stand out on its own terms (see
   HANDOFF_SIMULATE_FRONTEND.md §6). */
.simulate-warning {
    background: #fef9c3;
    color: #854d0e;
    padding: 10px 16px;
    border-radius: 6px;
    list-style: none;
    margin: 0;
}
.simulate-warning li::before {
    content: "\26A0 ";
}
.simulate-exec {
    color: #475569;
}

/* Host-level save confirmation (view-bridge.js noplmToast). */
.noplm-toast {
    position: fixed;
    left: 50%;
    bottom: 24px;
    transform: translateX(-50%);
    padding: 10px 18px;
    background: #166534;
    color: #fff;
    border-radius: 6px;
    box-shadow: 0 4px 10px rgba(15, 23, 42, 0.25);
    z-index: 100;
    transition: opacity 0.4s;
}

.noplm-toast-error { background: #991b1b; max-width: 80vw; }

.noplm-toast-hide { opacity: 0; }

/* Account edit page: tiles two per row across the full content width;
   the wide table and the destructive sections span both columns. */
.account-tiles {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 0 24px;
    align-items: start;
}
.account-tiles .card { max-width: none; margin-bottom: 8px; }
.account-tile-full { grid-column: 1 / -1; }
@media (max-width: 800px) {
    .account-tiles { grid-template-columns: minmax(0, 1fr); }
}

/* --- Renderer documentation (renderer-doc.js popover, renderers.js lint) --- */

.renderer-select-wrap {
    display: flex;
    align-items: center;
    gap: 4px;
}

.renderer-select-wrap select {
    flex: 1;
}

.renderer-doc-button {
    flex: none;
    width: 1.8em;
    height: 1.8em;
    padding: 0;
    background: #f1f5f9;
    color: #1e293b;
    border: 1px solid #94a3b8;
    border-radius: 50%;
    font-weight: bold;
    line-height: 1;
    cursor: pointer;
}

.renderer-doc-button:hover {
    background: #e2e8f0;
    border-color: #64748b;
}

.renderer-doc-dialog {
    border: none;
    border-radius: 6px;
    padding: 24px;
    width: 640px;
    max-width: calc(100vw - 32px);
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
}

.renderer-doc-dialog::backdrop {
    background: rgba(0, 0, 0, 0.4);
}

.renderer-doc-dialog h2 {
    margin-top: 0;
}

.renderer-doc-params {
    width: 100%;
    border-collapse: collapse;
}

.renderer-doc-params th,
.renderer-doc-params td {
    text-align: left;
    padding: 4px 8px;
    border-bottom: 1px solid #e2e8f0;
    vertical-align: top;
}

.renderer-doc-code,
.renderer-doc-example code {
    font-family: "SF Mono", Consolas, monospace;
}

.renderer-doc-text {
    white-space: pre-wrap;
}

.renderer-lint ul {
    margin: 4px 0;
    padding-left: 20px;
}

.renderer-lint .renderer-lint-error {
    color: #b91c1c;
}

.renderer-lint .renderer-lint-warning {
    color: #92400e;
}

.renderer-doc-preview {
    margin-bottom: 16px;
}

/* Script list: a label may be up to 255 chars */
td.truncate {
    max-width: 28em;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

textarea.monospace { font-family: ui-monospace, monospace; width: 100%; }

/* --- Multilingual text editor (public/assets/i18n-text.js) --- */

.i18n-text-plain { display: flex; gap: 6px; align-items: center; }
.i18n-text-plain input, .i18n-text-panel input { flex: 1; min-width: 0; width: 100%; box-sizing: border-box; }
.i18n-text-more { white-space: nowrap; font-size: 0.85em; padding: 4px 8px; }
.i18n-text-tabs { display: flex; gap: 2px; margin-bottom: -1px; position: relative; }
.i18n-text-tab {
    background: none;
    border: 1px solid transparent;
    border-bottom: none;
    border-radius: 6px 6px 0 0;
    padding: 3px 10px;
    cursor: pointer;
    color: #64748b;
    font-size: 0.8em;
}
.i18n-text-tab:hover { color: #1e293b; }
.i18n-text-tab.active { color: #1e293b; font-weight: bold; border-color: #e2e8f0; background: #f8fafc; }
.i18n-text-tab.is-default::after { content: " \2605"; font-size: 0.85em; }
.i18n-text-panel { border: 1px solid #e2e8f0; border-radius: 0 6px 6px 6px; padding: 6px; background: #f8fafc; }
.i18n-text-actions { display: flex; gap: 8px; align-items: center; margin-top: 4px; font-size: 0.8em; }
.i18n-text-action { font-size: 0.95em; padding: 2px 8px; }
.i18n-text-counter { margin-left: auto; }
.i18n-text-counter.over { color: #b91c1c; font-weight: bold; }
.i18n-text.is-disabled { opacity: 0.6; }
