﻿:root {
    --bg: #0d1117;
    --bg-raised: #151b24;
    --bg-sunken: #0a0e14;
    --bar: #1b2330;
    --line: #26303d;
    --line-soft: #1d2530;
    --text: #dbe3ec;
    --text-dim: #8b98a9;
    --text-faint: #5d6a7a;
    --ember: #d98324;
    --ember-bright: #f0a04b;
    --danger: #ef476f;
    --mono: ui-monospace, "Cascadia Mono", "SF Mono", Menlo, Consolas, monospace;
    --sans: "Segoe UI", system-ui, -apple-system, sans-serif;
}

* { box-sizing: border-box; }

html, body {
    height: 100%;
    margin: 0;
}

body {
    display: flex;
    flex-direction: column;
    background: var(--bg);
    color: var(--text);
    font-family: var(--sans);
    font-size: 13px;
    overflow: hidden;
}

/* --- Masthead --- */

.masthead {
    display: flex;
    align-items: center;
    gap: 16px;
    padding: 10px 18px;
    background: linear-gradient(180deg, #1a2230 0%, #141b25 100%);
    border-bottom: 1px solid var(--line);
    flex: 0 0 auto;
}

.brand {
    display: flex;
    align-items: center;
    gap: 12px;
    flex: 1 1 0;
    min-width: 0;
}

.brand-mark {
    width: 30px;
    height: 30px;
    color: var(--ember);
    filter: drop-shadow(0 0 6px rgba(217, 131, 36, 0.45));
}

.brand h1 {
    margin: 0;
    font-size: 17px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.brand p {
    margin: 1px 0 0;
    font-size: 11px;
    color: var(--text-dim);
}

.masthead-status {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 14px;
    font-size: 11px;
    flex: 1 1 0;
    min-width: 0;
}

.endpoint {
    font-family: var(--mono);
    color: var(--text-faint);
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.run-status {
    padding: 3px 10px;
    border: 1px solid var(--line);
    border-radius: 999px;
    color: var(--text-dim);
    white-space: nowrap;
}

/* The byte count is a property of the result, not of how the run went, so it is colored apart from the
   status pill's idle/busy/ok/error progression. */
.run-status.is-bytes { color: #2f81f7; border-color: rgba(47, 129, 247, 0.5); }
.run-status.is-cost { color: #c8a2ff; border-color: rgba(200, 162, 255, 0.5); }

.run-status.is-busy { color: var(--ember-bright); border-color: rgba(217, 131, 36, 0.5); }
.run-status.is-ok { color: #06d6a0; border-color: rgba(6, 214, 160, 0.4); }
.run-status.is-error { color: var(--danger); border-color: rgba(239, 71, 111, 0.5); }

/* --- Mode switch --- */

.mode-switch {
    display: flex;
    gap: 2px;
    padding: 2px;
    background: var(--bg-sunken);
    border: 1px solid var(--line);
    border-radius: 5px;
    flex: 0 0 auto;
}

/* --- Explore --- */

.workspace-explore { grid-template-columns: var(--explore-width, 420px) 12px minmax(0, 1fr); }
.workspace[hidden] { display: none; }

.endpoint-list {
    list-style: none;
    margin: 0 0 14px;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 3px;
}

.endpoint-group {
    margin: 12px 0 5px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-dim);
}

.endpoint-group:first-child { margin-top: 0; }

.endpoint-item {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 9px;
    border: 1px solid var(--line-soft);
    border-radius: 5px;
    cursor: pointer;
    user-select: none;
}

.endpoint-item:hover { border-color: var(--line); background: rgba(255, 255, 255, 0.02); }

.endpoint-item.is-active {
    border-color: var(--ember);
    background: rgba(217, 131, 36, 0.1);
}

.endpoint-name { font-size: 12px; }

.endpoint-path {
    font-family: var(--mono);
    font-size: 10px;
    color: var(--text-faint);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.parameter-form {
    display: flex;
    flex-direction: column;
    gap: 11px;
}

.parameter-field { display: flex; flex-direction: column; }
.parameter-field .field-label { margin-bottom: 4px; }
.parameter-field .field-hint { margin: 4px 0 0; }

/* The display above would otherwise win over the user agent's own [hidden] rule — the same fix already
   applied elsewhere in this file, needed here too since the field dialog hides a property row this way
   whenever it means nothing for the mode currently chosen. */
.parameter-field[hidden] { display: none; }

.parameter-select,
.parameter-input {
    width: 100%;
    min-width: 0;
    background: var(--bg-sunken);
    border: 1px solid var(--line);
    border-radius: 5px;
    color: var(--text);
    padding: 7px 9px;
    font-family: var(--mono);
    font-size: 12px;
    outline: none;

    /* Narrower grid columns mean a select's own closed-state text is more likely to be wider than the
       control itself — ellipsis rather than letting it overflow into the next column. */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;

    /* Tells the browser to draw its own chrome for this control — the calendar/clock icon and the picker
       popup a date/datetime-local/time input opens — in a dark-appropriate palette, rather than defaulting
       to a dark icon that all but disappears against this input's own dark background. */
    color-scheme: dark;
}

.parameter-select:focus,
.parameter-input:focus {
    border-color: var(--ember);
    box-shadow: 0 0 0 2px rgba(217, 131, 36, 0.16);
}

.parameter-select:disabled { opacity: 0.5; cursor: default; }
.parameter-select option { background: var(--bg-raised); color: var(--text); }

/* --- Workspace --- */

.workspace {
    /* Owned by the splitter: dragging writes this as an inline style, which is why the column track reads
       it through a variable rather than being set directly. */
    --settings-width: 800px;

    flex: 1 1 auto;
    display: grid;

    /* The splitter occupies its own track, sized to what the column gap used to be, so the visible space
       between the panels is unchanged and the grab area sits inside it. */
    grid-template-columns: var(--settings-width) 12px minmax(0, 1fr);
    gap: 0;
    padding: 12px;
    min-height: 0;
}

.splitter {
    position: relative;
    cursor: col-resize;
    touch-action: none;

    /* A faint track behind the grip, so the divider reads as a control at a glance rather than as the gap
       between two panels. */
    background: var(--bg-raised);
    border-radius: 3px;
}

.splitter:hover,
.splitter:focus-visible,
.splitter.is-dragging { background: var(--bar); }

/* Widens what the pointer can actually grab past the 12px the layout gives the track, without moving
   anything: the panels either side lose a few pixels of their own rounded edge, which has nothing
   clickable on it. */
.splitter::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: -5px;
    right: -5px;
}

/* Always visible so the divider reads as a handle rather than as empty space, and brighter once it is
   hovered, focused or being dragged. */
.splitter::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 4px;
    height: 48px;
    transform: translate(-50%, -50%);
    border-radius: 2px;
    background: var(--text-faint);
    transition: background 120ms ease, height 120ms ease;
}

.splitter:hover::after,
.splitter:focus-visible::after,
.splitter.is-dragging::after {
    background: var(--ember-bright);
    height: 80px;
}

.splitter:focus-visible { outline: none; }

/* Kills text selection everywhere while a drag is in flight, which otherwise highlights the payload as
   the pointer sweeps across it. */
body.is-resizing { user-select: none; cursor: col-resize; }

.output-column {
    display: grid;
    grid-template-rows: minmax(0, 4fr) minmax(0, 6fr);
    gap: 12px;
    min-height: 0;
    min-width: 0;
}

/* --- Windows --- */

.window {
    display: flex;
    flex-direction: column;
    min-height: 0;
    min-width: 0;
    background: var(--bg-raised);
    border: 1px solid var(--line);
    border-radius: 8px;
    overflow: hidden;
}

.window-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 7px 12px;
    background: var(--bar);
    border-bottom: 1px solid var(--line);
    flex: 0 0 auto;

    /* A narrow panel drops the tools onto their own line rather than breaking the title across two. */
    flex-wrap: wrap;
}

.window-title {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-dim);
    white-space: nowrap;
}

.window-tools {
    display: flex;
    align-items: center;
    gap: 10px;
}

.window-meta {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--text-faint);
}

.window-body {
    flex: 1 1 auto;
    min-height: 0;
    overflow: auto;
    padding: 14px;
}

.window-body-flush {
    padding: 0;
    overflow: hidden;
    position: relative;
}

/* --- Settings --- */

.field-label {
    display: block;
    margin: 0 0 5px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-dim);
}

/* The settings window is pinned rather than scrolled: the editor takes whatever height is left over and
   Run stays on the bottom edge, so neither moves as a response fills the legend or the error panel. */
.window-body-settings {
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.settings-head { flex: 0 0 auto; }
.settings-head .text-input { margin-bottom: 16px; }

/* Shrinks before the editor does, and scrolls internally once a long error outgrows its share. */
.settings-aux {
    flex: 0 1 auto;
    min-height: 0;
    overflow: auto;
}

.text-input {
    width: 100%;
    background: var(--bg-sunken);
    border: 1px solid var(--line);
    border-radius: 5px;
    color: var(--text);
    padding: 8px 10px;
    font-family: var(--mono);
    font-size: 12px;
    outline: none;
}

.text-input:focus {
    border-color: var(--ember);
    box-shadow: 0 0 0 2px rgba(217, 131, 36, 0.16);
}

.field-hint {
    margin: 5px 0 16px;
    font-size: 11px;
    color: var(--text-faint);
    line-height: 1.45;
}

.field-hint-tight { margin: 6px 0 0; flex: 0 0 auto; }

.field-hint.is-error { color: var(--danger); }

.field-hint code,
.placeholder code {
    font-family: var(--mono);
    color: var(--text-dim);
}

/* --- JSON editor --- */

.editor {
    /* Widened by the gutter's own digit count, so a payload that runs past 99 lines does not clip its
       numbers. Set from script as the line count changes. */
    --gutter-digits: 2;
    --gutter-width: calc(var(--gutter-digits) * 1ch + 17px);

    position: relative;
    flex: 1 1 auto;
    min-height: 140px;
    background: var(--bg-sunken);
    border: 1px solid var(--line);
    border-radius: 5px;
    overflow: hidden;
}

/* Explicit, because the editor and the tree swap places and a bare [hidden] loses to any display rule
   the box later picks up. */
.editor[hidden] { display: none; }

.editor:focus-within {
    border-color: var(--ember);
    box-shadow: 0 0 0 2px rgba(217, 131, 36, 0.16);
}

/* Every metric here has to match across all three layers or the numbers, the colored text and the caret
   drift apart from each other. */
.editor-gutter,
.editor-highlight,
.editor-input {
    position: absolute;
    inset: 0;
    margin: 0;
    padding: 9px 11px;
    border: 0;
    font-family: var(--mono);
    font-size: 12px;
    line-height: 1.55;
    white-space: pre;
    word-wrap: normal;
    tab-size: 4;
}

/* Scrolled vertically in step with the textarea but never horizontally, so the numbers stay put while a
   long line scrolls under them. */
.editor-gutter {
    right: auto;
    width: var(--gutter-width);
    padding-left: 0;
    padding-right: 8px;
    overflow: hidden;
    text-align: right;
    color: var(--text-faint);
    background: var(--bg-sunken);
    border-right: 1px solid var(--line-soft);
    user-select: none;
    pointer-events: none;
    z-index: 2;
}

/* Both text layers begin where the gutter ends, offset rather than padded. Padding would leave the
   textarea's scrollable box spanning the full width, so its horizontal scrollbar would run underneath the
   gutter and hide the left arrow, and text scrolled left would pass beneath the gutter with its selection
   highlight clipped against the gutter edge. */
.editor-highlight,
.editor-input {
    left: var(--gutter-width);
    padding-left: 9px;
}

.editor-highlight {
    overflow: hidden;
    color: var(--text-dim);
    pointer-events: none;
}

.editor-input {
    background: transparent;
    color: transparent;
    caret-color: var(--ember-bright);
    overflow: auto;
    resize: none;
    outline: none;
}

.editor-input::selection { background: rgba(217, 131, 36, 0.28); }

.tok-key { color: #7fd1f0; }
.tok-string { color: #b5e48c; }
.tok-number { color: #f0a04b; }
.tok-literal { color: #c8a2ff; }
.tok-brace { color: #9aa7b6; }
.tok-punct { color: #6b7888; }

.run-button {
    flex: 0 0 auto;
    margin-top: 12px;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 9px;
    width: 100%;
    padding: 10px;
    background: linear-gradient(180deg, var(--ember-bright), var(--ember));
    border: none;
    border-radius: 6px;
    color: #17110a;
    font-family: var(--sans);
    font-size: 13px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
}

.run-button:hover:not(:disabled) { filter: brightness(1.1); }
.run-button:disabled { opacity: 0.5; cursor: default; }

/* A destructive action's own confirm button — red in place of the ember gradient every other button in
   the app uses for its main action, so a delete does not read as just another "go ahead" button. */
.run-button.is-danger {
    background: linear-gradient(180deg, #f2617f, var(--danger));
    color: #2a0710;
}

.run-button.is-danger .run-button-glyph { background: #2a0710; box-shadow: 0 0 0 3px rgba(42, 7, 16, 0.18); }

.run-button-glyph {
    width: 9px;
    height: 9px;
    border-radius: 50%;
    background: #17110a;
    box-shadow: 0 0 0 3px rgba(23, 17, 10, 0.18);
}

.run-button.is-busy .run-button-glyph { animation: pulse 1.1s ease-in-out infinite; }

@keyframes pulse {
    0%, 100% { opacity: 1; transform: scale(1); }
    50% { opacity: 0.35; transform: scale(0.6); }
}

.ghost-button {
    background: transparent;
    border: 1px solid var(--line);
    border-radius: 4px;
    color: var(--text-dim);
    padding: 3px 9px;
    font-family: var(--sans);
    font-size: 11px;
    cursor: pointer;
}

a.ghost-button { text-decoration: none; white-space: nowrap; }
.ghost-button:hover:not(:disabled) { color: var(--text); border-color: var(--text-faint); }
.ghost-button:disabled { opacity: 0.4; cursor: default; }
.ghost-button.is-done { color: #06d6a0; border-color: rgba(6, 214, 160, 0.5); }

/* --- Legend --- */

.legend {
    margin-top: 18px;
    padding-top: 14px;
    border-top: 1px solid var(--line-soft);
}

.legend-title {
    margin: 0 0 8px;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-dim);
}

.legend-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 5px;
}

.legend-item {
    display: flex;
    align-items: center;
    gap: 9px;
    padding: 5px 7px;
    border: 1px solid var(--line-soft);
    border-radius: 5px;
    cursor: pointer;
    user-select: none;
}

.legend-item:hover { border-color: var(--line); background: rgba(255, 255, 255, 0.02); }
.legend-item.is-off { opacity: 0.4; }

.legend-swatch {
    width: 11px;
    height: 11px;
    border-radius: 3px;
    flex: 0 0 auto;
    box-shadow: 0 0 8px currentColor;
}

.legend-name {
    font-family: var(--mono);
    font-size: 12px;
    flex: 1 1 auto;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.legend-type {
    font-size: 10px;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text-faint);
}

/* --- Errors --- */

.error-panel {
    margin-top: 16px;
    padding: 11px 12px;
    background: rgba(239, 71, 111, 0.07);
    border: 1px solid rgba(239, 71, 111, 0.4);
    border-radius: 6px;
}

.error-title {
    margin: 0 0 6px;
    font-size: 12px;
    font-weight: 600;
    color: var(--danger);
}

.error-detail {
    margin: 0;
    font-family: var(--mono);
    font-size: 11px;
    line-height: 1.5;
    color: var(--text-dim);
    white-space: pre-wrap;
    word-break: break-word;
    max-height: 220px;
    overflow: auto;
}

/* --- Table --- */

.table-scroll,
.schema-scroll {
    height: 100%;
    overflow: auto;
}

.schema-scroll[hidden],
.table-scroll[hidden] { display: none; }

.schema-view {
    margin: 0;
    padding: 14px;
    font-family: var(--mono);
    font-size: 12px;
    line-height: 1.55;
    color: var(--text-dim);
    white-space: pre;
    tab-size: 4;
}

.insert-select {
    background: var(--bg-sunken);
    border: 1px solid var(--line);
    border-radius: 4px;
    color: var(--text-dim);
    padding: 3px 7px;
    font-family: var(--sans);
    font-size: 11px;
    outline: none;
    cursor: pointer;
    max-width: 190px;
}

.insert-select:hover:not(:disabled) { color: var(--text); border-color: var(--text-faint); }
.insert-select:disabled { opacity: 0.4; cursor: default; }
.insert-select:focus { border-color: var(--ember); }
.insert-select option { background: var(--bg-raised); color: var(--text); }

.data-table {
    border-collapse: separate;
    border-spacing: 0;
    font-family: var(--mono);
    font-size: 12px;
    white-space: nowrap;
    min-width: 100%;
}

.data-table th,
.data-table td {
    padding: 5px 12px;
    border-bottom: 1px solid var(--line-soft);
    text-align: left;
}

/* Applied once the columns have been measured and pinned to a colgroup. Fixed layout is what makes a
   column width authoritative instead of a suggestion the browser is free to overrule, and it is also what
   lets a cell clip rather than force its column wider. */
.data-table.is-sized {
    table-layout: fixed;
    min-width: 0;
}

.data-table.is-sized th,
.data-table.is-sized td {
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Sits over the header's right edge, straddling the border so the target covers both sides of it. The
   header cell is already sticky, which positions it, so this anchors to the cell without further help. */
.col-resize {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    width: 11px;
    transform: translateX(5px);
    cursor: col-resize;
    z-index: 3;
    touch-action: none;
}

.col-resize::after {
    content: "";
    position: absolute;
    top: 5px;
    bottom: 5px;
    left: 50%;
    width: 2px;
    transform: translateX(-50%);
    border-radius: 1px;
    background: transparent;
    transition: background 120ms ease;
}

.col-resize:hover::after,
.col-resize.is-dragging::after { background: var(--ember); }

.data-table thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--bar);
    border-bottom: 1px solid var(--line);
    font-weight: 600;
}

.column-name { display: block; }

.column-type {
    display: block;
    font-size: 10px;
    font-weight: 400;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-faint);
}

.data-table th.is-spatial { border-bottom: 2px solid currentColor; }
.data-table th.is-spatial .column-type { color: inherit; }

.row-index {
    color: var(--text-faint);
    text-align: right;
    position: sticky;
    left: 0;
    background: var(--bg-raised);
    z-index: 1;
}

.data-table tbody tr:hover td { background: rgba(217, 131, 36, 0.06); }
.data-table tbody tr:hover .row-index { background: #1d222b; }
.data-table tbody tr.is-selected td { background: rgba(217, 131, 36, 0.14); }
.data-table tbody tr.is-selected .row-index { background: #2a2318; }

.wkt-cell {
    max-width: 340px;
    overflow: hidden;
    text-overflow: ellipsis;
    cursor: pointer;
}

.wkt-tag {
    display: inline-block;
    margin-right: 7px;
    padding: 0 5px;
    border: 1px solid currentColor;
    border-radius: 3px;
    font-size: 10px;
    letter-spacing: 0.05em;
}

/* Right aligned and tabular, so a column of sizes lines up on its digits. */
.number-value {
    text-align: right;
    font-variant-numeric: tabular-nums;
}

.null-value { color: var(--text-faint); font-style: italic; }

.placeholder {
    margin: 0;
    padding: 28px;
    color: var(--text-faint);
    font-size: 12px;
}

.placeholder-map {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
}

/* The display above would otherwise win over the user agent's own [hidden] rule. */
.placeholder-map[hidden] { display: none; }

/* --- Tabs --- */

.tabs {
    display: flex;
    gap: 2px;
    padding: 2px;
    background: var(--bg-sunken);
    border: 1px solid var(--line);
    border-radius: 5px;
}

.tab {
    background: transparent;
    border: none;
    border-radius: 3px;
    color: var(--text-faint);
    padding: 3px 12px;
    font-family: var(--sans);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    cursor: pointer;
}

.tab:hover:not(:disabled) { color: var(--text); }
.tab:disabled { opacity: 0.35; cursor: default; }
.tab.is-active { background: var(--ember); color: #17110a; }

.tab-panel {
    position: absolute;
    inset: 0;
}

/* --- Coordinate readout --- */

/* Sits between the window title and its tools, and takes the slack so the tools stay pinned right. */
.coord-readout {
    display: flex;
    align-items: center;
    gap: 14px;
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
}

.coord-readout[hidden] { display: none; }

.coord-group {
    display: flex;
    align-items: baseline;
    gap: 6px;
    white-space: nowrap;
}

.coord-label {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-faint);
}

/* Tabular figures so the numbers do not jitter sideways as the pointer moves. */
.coord-value {
    font-family: var(--mono);
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    color: var(--text-dim);
}

/* --- Geometry grid --- */

.geometry-grid {
    position: absolute;
    inset: 0;
    background: var(--bg-sunken);
    cursor: grab;
    touch-action: none;
}

.geometry-grid.is-panning { cursor: grabbing; }
.geometry-grid svg { display: block; width: 100%; height: 100%; }

.grid-line { stroke: #1a222d; stroke-width: 1; }
.grid-line-major { stroke: #232e3c; stroke-width: 1; }
.grid-axis { stroke: #33414f; stroke-width: 1; }
.grid-label { fill: #4c5866; font-family: var(--mono); font-size: 10px; }

.shape { fill: none; stroke-linejoin: round; stroke-linecap: round; }
.shape-hit { fill: none; stroke: transparent; stroke-width: 12; cursor: pointer; }
.shape.is-highlighted { stroke-width: 3.5; }

.grid-reset {
    position: absolute;
    right: 10px;
    bottom: 10px;
    background: rgba(10, 14, 20, 0.85);
}

/* --- Leaflet --- */

.leaflet-host {
    position: absolute;
    inset: 0;
    background: var(--bg-sunken);
}

.leaflet-container { background: var(--bg-sunken); }

.leaflet-tooltip.crucible-tip {
    background: rgba(13, 17, 23, 0.94);
    border: 1px solid var(--line);
    color: var(--text);
    font-family: var(--mono);
    font-size: 11px;
    box-shadow: none;
}

.leaflet-tooltip.crucible-tip::before { display: none; }

/* --- Tooltip --- */

.tooltip {
    position: fixed;
    z-index: 3000;
    max-width: 420px;
    padding: 6px 9px;
    background: rgba(13, 17, 23, 0.96);
    border: 1px solid var(--line);
    border-radius: 4px;
    font-family: var(--mono);
    font-size: 11px;
    line-height: 1.5;
    color: var(--text);
    pointer-events: none;
    word-break: break-all;
}

.tooltip-head {
    display: block;
    margin-bottom: 3px;
    font-weight: 600;
}

/* --- Payload tree --- */

/* Takes the editor's place rather than sitting beside it, so it inherits the same share of the column. */
.tree-view {
    flex: 1 1 auto;
    min-height: 140px;
    overflow: auto;
    padding: 8px 4px 10px;
    background: var(--bg-sunken);
    border: 1px solid var(--line);
    border-radius: 5px;
}

.tree-view[hidden] { display: none; }

.tree-root,
.tree-children {
    list-style: none;
    margin: 0;
    padding: 0;
}

/* Each level steps in by one icon's width, and the guide line sits under the parent's own twisty. */
.tree-children {
    margin-left: 10px;
    padding-left: 10px;
    border-left: 1px solid var(--line-soft);
}

.tree-children[hidden] { display: none; }

.tree-row {
    display: flex;
    align-items: center;
    gap: 6px;
    padding: 2px 6px;
    border-radius: 3px;
    font-family: var(--mono);
    font-size: 12px;
    line-height: 1.6;
    color: var(--text-dim);
    cursor: default;
    user-select: none;
    white-space: nowrap;
}

.tree-row:hover { background: rgba(255, 255, 255, 0.03); }

.tree-row.is-selected {
    background: rgba(217, 131, 36, 0.16);
    color: var(--text);
}

.tree-row:focus-visible {
    outline: 1px solid var(--ember);
    outline-offset: -1px;
}

/* Always present, even on a leaf, so every label on a level starts at the same x. */
.tree-twisty {
    flex: 0 0 auto;
    width: 10px;
    text-align: center;
    color: var(--text-faint);
    transition: transform 0.12s ease;
    cursor: pointer;
}

.tree-row[aria-expanded="true"] > .tree-twisty { transform: rotate(90deg); }
.tree-twisty:hover { color: var(--text); }

.tree-icon {
    flex: 0 0 auto;
    width: 14px;
    height: 14px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.3;
    stroke-linecap: round;
    stroke-linejoin: round;
    color: var(--text-faint);
}

.tree-label { color: var(--text); }

/* A row standing for a generated field, which is what a reader scans the tree for. */
.tree-row.is-field > .tree-icon { color: var(--ember-bright); }
.tree-row.is-field > .tree-label { font-weight: 600; }

.tree-detail {
    color: var(--text-faint);
    font-size: 11px;
    overflow: hidden;
    text-overflow: ellipsis;
}

/* Hidden until the row is hovered or the button itself has focus, so it never crowds a row's own text. */
.tree-row-action {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 18px;
    height: 18px;
    padding: 0;
    margin-left: 4px;
    background: none;
    border: none;
    border-radius: 3px;
    color: #fff;
    cursor: pointer;
    opacity: 0;
    pointer-events: none;
}

/* .tree-icon sets its own color directly on the svg, which otherwise wins over the button's by
   specificity regardless of what color the button itself has — overridden here so the icon actually
   follows the button, including its color turning red on the delete button's own :hover below. */
.tree-row-action .tree-icon { width: 12px; height: 12px; color: inherit; }

.tree-row:hover .tree-row-action,
.tree-row-action:focus-visible {
    opacity: 1;
    pointer-events: auto;
}

.tree-row-action:hover { background: rgba(255, 255, 255, 0.12); }

.tree-delete-button:hover { color: var(--danger); background: rgba(239, 71, 111, 0.14); }

.tree-empty {
    margin: 0;
    padding: 10px 12px;
    color: var(--text-faint);
    font-size: 12px;
}

/* --- Field dialog --- */

.modal-overlay {
    position: fixed;
    inset: 0;
    z-index: 4000;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 16px;
    background: rgba(5, 7, 11, 0.66);
}

.modal-overlay[hidden] { display: none; }

/* The place dialog opens over the field dialog that asked for it, so it takes the layer above. */
.modal-overlay-stacked { z-index: 4100; }

.modal {
    display: flex;
    flex-direction: column;
    width: 100%;
    max-width: 820px;
    max-height: 100%;
    background: var(--bg-raised);
    border: 1px solid var(--line);
    border-radius: 8px;
    box-shadow: 0 24px 64px rgba(0, 0, 0, 0.5);
    overflow: hidden;
}

/* A dialog with nothing but a message and two buttons does not need the width a form does. */
.modal-narrow { max-width: 380px; }

/* The field dialog alone earns this: a type's own settings plus a Map section of however many sub-fields
   its generator writes to, which is 22 for Address. The others are a handful of fields each and would only
   look stranded at this width. */
.modal-wide { max-width: 1600px; }

.confirm-dialog-message {
    margin: 0;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text);
}

.modal-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 11px 14px;
    background: var(--bar);
    border-bottom: 1px solid var(--line);
    flex: 0 0 auto;
}

.modal-title {
    font-size: 12px;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: var(--text);
}

.modal-close {
    padding: 2px 9px;
    font-size: 15px;
    line-height: 1;
}

.modal-body {
    padding: 16px;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.modal-actions {
    display: flex;
    justify-content: flex-end;
    gap: 10px;
    padding: 12px 14px;
    background: var(--bar);
    border-top: 1px solid var(--line);
    flex: 0 0 auto;
}

.modal-actions .run-button {
    width: auto;
    /* The base rule's own margin-top is meant to gap the Settings window's Run button off the editor
       above it. Left in here, it eats into this row's own flex stretch and leaves Insert shorter than
       Cancel and sitting low against the row's bottom edge instead of centered in it. */
    margin-top: 0;
    padding: 0 22px;
}

/* As many columns as the dialog is wide enough for, so it grows outward before it grows tall. A row hidden
   for the mode currently chosen is skipped entirely by grid placement, so the remaining visible rows still
   pack tightly instead of leaving a gap where it was.

   auto-fill rather than auto-fit on purpose: auto-fit collapses tracks nothing is placed in and hands
   their width to the rest, which sounds right until one full-width item (a WKT box, a mode switch) spans
   every track and so leaves none of them collapsible — every other field is then pinned to the track
   minimum with the rest of the dialog empty beside it. auto-fill keeps the track count fixed, so a column
   is the same comfortable width whatever else happens to be on screen, wide enough that a label like
   "Center X (longitude)" sits on one line. */
.field-dialog-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
    gap: 12px 16px;
    align-items: start;
}

/* A control wider than one column takes the few it needs rather than the whole row — a mode switch beside
   the setting it governs, a WKT box beside the switch that asked for it. Below three columns' worth of
   width there is nothing to share a row with, so they take what there is. */
.field-dialog-span-2 { grid-column: span 2; }
.field-dialog-span-3 { grid-column: span 3; }

@media (max-width: 780px) {
    .field-dialog-span-2,
    .field-dialog-span-3 { grid-column: 1 / -1; }
}

/* A cluster that reads as one thing — a polygon and its buffer, a radius and its center — gets a row of
   its own and a grid of its own inside it, so the cluster stays on one line rather than wrapping around
   whatever came before it. */
.field-dialog-section-row { grid-column: 1 / -1; }

/* Names a section across the whole of its own grid, so a long form reads as a few labelled groups rather
   than one run of rows. */
.field-dialog-section-title {
    grid-column: 1 / -1;
    margin: 4px 0 0;
    padding-top: 10px;
    border-top: 1px solid var(--line-soft);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-faint);
}

/* The display above would otherwise win over the user agent's own [hidden] rule, the same fix this file
   already applies to .parameter-field and the tree's own children. */
.field-dialog-grid[hidden] { display: none; }

/* A setting edited in a dialog of its own — a boundingLocation, a bounding polygon: what it currently
   holds, and the button that opens that dialog. */
.field-dialog-chooser {
    display: flex;
    align-items: center;
    gap: 8px;
    min-height: 31px;
}

.field-dialog-chooser-summary {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-family: var(--mono);
    font-size: 12px;
    color: var(--text);
}

/* display:contents on the header's own Common wrapper (see index.html) lifts its children into this grid
   directly, so rebuilding them on a type change never touches the Table/Type rows beside them. */
#field-dialog-common-properties { display: contents; }

.field-dialog-checkbox {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 12px;
    color: var(--text-dim);
    cursor: pointer;
}

.field-dialog-checklist {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 16px;
    padding-top: 4px;
}

.field-dialog-weighted {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.field-dialog-weighted-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 84px auto;
    gap: 6px;
    align-items: center;
}

/* A list whose rows can carry a per-entry extra — which script a Greek, Japanese or Korean name comes out
   in — has one more column for it. The cell is empty on every row whose own value has no extra, which is
   most of them, so the columns still line up down the list. */
.field-dialog-weighted-wide .field-dialog-weighted-row {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 84px auto;
}

.field-dialog-weight { text-align: right; }

/* Names each column once, rather than relying on a placeholder that vanishes the moment a row has
   anything typed into it — a select never even had one to begin with. */
.field-dialog-weighted-header {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-faint);
}

.field-dialog-weighted-remove {
    padding: 4px 9px;
    font-family: var(--mono);
}

/* One IdentifierPart — prefix, id, or postfix. Boxed so its handful of rows read as one setting rather
   than as more top-level properties of the field itself. */
.field-dialog-part {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.field-dialog-part-controls {
    display: flex;
    flex-direction: column;
    gap: 8px;
    padding: 10px;
    background: var(--bg-sunken);
    border: 1px solid var(--line-soft);
    border-radius: 5px;
}

.field-dialog-part-controls[hidden] { display: none; }

.field-dialog-part-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
    gap: 8px;
}

/* Divides the header — table, type, and every field's own null/unique/seed settings, none of which
   change shape from one type to another — from the type's own properties below it, the one part of the
   form that does. */
#field-dialog-properties {
    padding-top: 12px;
    border-top: 1px solid var(--line-soft);
}

/* A titled, divided block of settings inside a dialog — the dataset dialog's own CSV Format group.
   General on purpose: any dialog that grows a second cluster of settings can reach for this. */
.field-dialog-section {
    padding-top: 12px;
    border-top: 1px solid var(--line-soft);
}

.field-dialog-section-grid { margin-top: 8px; }

.field-dialog-map {
    padding-top: 12px;
    border-top: 1px solid var(--line-soft);
}

/* Two CSS columns of rows on a wide dialog, one when there is no room for more — filled top to bottom down
   the left column before starting the right one, the way reading order actually works, rather than a grid
   that wraps left to right along each visual row. Each row avoids splitting across the column break.
   The label and index tracks are a fixed width rather than max-content, in ch units since the label font
   is monospace: a shared grid would size them from the widest row on screen, but a row can no longer share
   one with the rows beside it once a multi-column flow — rather than a two-dimensional grid — is what puts
   it in its own CSS column, so every row sizes those two tracks the same way independently instead.
   22ch comfortably fits the longest sub-field name of any generator without truncating it. */
.field-dialog-map-rows {
    margin-top: 8px;
}

@media (min-width: 1180px) {
    .field-dialog-map-rows {
        columns: 2;
        column-gap: 24px;
    }
}

.field-dialog-map-row {
    display: grid;
    grid-template-columns: 3ch 22ch minmax(90px, 1fr) 112px max-content auto auto auto;
    column-gap: 8px;
    align-items: center;
    break-inside: avoid;
    margin-bottom: 6px;
}

/* Where this column lands in the generated table, and so what the arrows beside it are moving. Reading
   order across a wrapping grid is not obvious enough to leave unnumbered. */
.field-dialog-map-index {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--text-faint);
    text-align: right;
}

.field-dialog-map-label {
    font-family: var(--mono);
    font-size: 12px;
    color: var(--text-dim);
    white-space: nowrap;
}

/* Its own line under the rows, left-aligned: a picker and a button, neither of which wants to be stretched
   across a column group meant for a mapped row. column-span breaks it out of the two CSS columns above
   rather than sitting inside whichever one happened to have room left, the way a plain block would. */
.field-dialog-map-add-row {
    column-span: all;
    display: flex;
    align-items: center;
    gap: 8px;
    margin-top: 2px;
}

.field-dialog-map-add-row .parameter-select { flex: 0 0 auto; width: 200px; }

/* Its own narrow column of the map grid, centered rather than left-edged like the wider cells beside it. */
.field-dialog-map-hidden { justify-self: center; }

/* --- Polygon dialog --- */

/* Tall on purpose: the map is the point of this one, so it takes whatever height the viewport allows
   rather than sizing to its contents. */
.modal-tall { height: 100%; }

.polygon-dialog-body {
    display: flex;
    flex-direction: column;
    gap: 10px;

    /* Takes the height the bar and the actions leave, so the map below can have the rest of it. */
    flex: 1 1 auto;
    min-height: 0;
    overflow: hidden;
}

.polygon-dialog-tools {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
}

.polygon-dialog-readout {
    display: flex;
    align-items: center;
    gap: 14px;
    margin-left: auto;
}

/* The map takes the space the toolbar and the text box below it do not. .leaflet-host fills its nearest
   positioned ancestor, which is what the Forge workspace wants from it but would take the whole viewport
   here — this lays it out in the flex column instead. Leaflet needs a real height on its own container,
   which is what the flex basis and min-height together give it. */
.polygon-dialog-map {
    position: relative;
    inset: auto;
    flex: 1 1 auto;
    min-height: 240px;
    border: 1px solid var(--line);
    border-radius: 5px;
    overflow: hidden;
}

.polygon-dialog-text { flex: 0 0 auto; }
.polygon-dialog-text .editor-input { min-height: 54px; }

/* --- Place dialog --- */

/* One tier per box — country, state, county, city, zip — each boxed so its own switch, value and any
   weighted rows read as one thing rather than as five interleaved lists. */
#place-dialog-tiers {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
    gap: 12px;
}

.place-tier {
    padding: 10px 12px 12px;
    background: var(--bg-sunken);
    border: 1px solid var(--line-soft);
    border-radius: 6px;
}

.place-tier-wide { grid-column: span 2; }

@media (max-width: 800px) {
    .place-tier-wide { grid-column: 1 / -1; }
}

.place-tier-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    margin-bottom: 8px;
}

.place-tier-title {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--text-dim);
}

.place-tier-mode { width: auto; flex: 0 0 auto; }

.place-tier-rows {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* --place-cells is set per tier: its own scoping columns, then the value, the weight and the remove
   button. A county row names a country and a state, a country row names neither. */
.place-row {
    display: grid;
    grid-template-columns: repeat(calc(var(--place-cells) - 2), minmax(0, 1fr)) 72px auto;
    gap: 6px;
    align-items: center;
}

.place-row-header {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--text-faint);
}

.place-row-add { align-self: start; }

/* Sits below the tiers rather than among them: it belongs to the whole filter, not to one level of it. */
.place-overseas {
    margin-top: 12px;
    padding: 10px 12px 12px;
    background: var(--bg-sunken);
    border: 1px solid var(--line-soft);
    border-radius: 6px;
}

.place-overseas .field-hint { margin-top: 6px; }

/* --- Derived id dialog --- */

/* One card per section, stacked in the order they are joined in. A card rather than a row: which settings
   a section shows follows its own source, so no two of them line up into columns anyway. */
.derived-section {
    padding: 10px 12px 12px;
    margin-bottom: 8px;
    background: var(--bg-sunken);
    border: 1px solid var(--line-soft);
    border-radius: 6px;
}

.derived-section-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 10px;
}

.derived-section-head .parameter-select { max-width: 220px; }

.derived-section-index {
    min-width: 20px;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-faint);
    text-align: right;
}

.derived-section-body {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 10px 12px;
}

.derived-field-wide { grid-column: span 2; }

@media (max-width: 800px) {
    .derived-field-wide { grid-column: 1 / -1; }
}

/* --- Calculation dialog --- */

/* One card per node — a column, a literal, or an operation over other nodes — nested to whatever depth
   the calculation itself goes to. A lighter background than the modal behind it, so a deeply nested node
   still reads as sitting inside its parent rather than floating free of it. */
.calc-node {
    padding: 8px 10px 10px;
    background: var(--bg-sunken);
    border: 1px solid var(--line-soft);
    border-radius: 6px;
}

.calc-node-head {
    display: flex;
    align-items: center;
    gap: 8px;
    margin-bottom: 8px;
}

.calc-node-head .parameter-select { max-width: 200px; }

.calc-node-index {
    min-width: 16px;
    font-size: 11px;
    font-weight: 600;
    color: var(--text-faint);
    text-align: right;
}

/* Indents one level of nesting — an operation's own inputs, an If's when/then/else, a Case arm's own
   pair — via the left border alone, so depth reads at a glance without a class per level. */
.calc-children {
    margin-top: 6px;
    padding-left: 12px;
    border-left: 2px solid var(--line-soft);
}

.calc-children > .calc-node { margin-bottom: 6px; }
.calc-children > .calc-node:last-child { margin-bottom: 0; }

.calc-operation, .calc-literal, .calc-slot {
    display: flex;
    flex-direction: column;
    gap: 8px;
}

.calc-slot + .calc-slot { margin-top: 8px; }

.calc-extra-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
    gap: 8px 12px;
}

/* One arm of a Case, or one row of a Map — a small card of its own inside the section, holding its own
   pair of nested nodes rather than a single one. */
.calc-case {
    padding: 8px 10px 10px;
    background: var(--bg-raised);
    border: 1px solid var(--line-soft);
    border-radius: 6px;
}

.calc-case + .calc-case { margin-top: 6px; }

/* --- Forge --- */

.workspace-forge { grid-template-columns: var(--forge-width, 520px) 12px minmax(0, 1fr); }

/* One long WKT string rather than lines of JSON: it wraps, there is no gutter to clear, and it paints its
   own text. The JSON editor's textarea is transparent because a highlight layer shows through from behind,
   and there is no such layer here. */
.editor-plain .editor-input {
    left: 0;
    padding-left: 11px;
    color: var(--text);
    white-space: pre-wrap;
    word-break: break-word;
}

.editor-plain .editor-input::placeholder { color: var(--text-faint); }

.forge-help {
    padding-top: 12px;
    border-top: 1px solid var(--line-soft);
}

.forge-help-list {
    margin: 0;
    padding-left: 18px;
    font-size: 11px;
    line-height: 1.7;
    color: var(--text-faint);
}

.forge-help-list strong { color: var(--text-dim); font-weight: 600; }

.forge-help-list code {
    font-family: var(--mono);
    color: var(--text-dim);
}

/* The button for the mode currently being drawn, which is how the user knows a click on the map places a
   point rather than selecting a shape. */
.ghost-button.is-active {
    color: #17110a;
    background: var(--ember);
    border-color: var(--ember);
}

.leaflet-container.is-drawing { cursor: crosshair; }
.leaflet-container.is-drawing .leaflet-interactive { cursor: crosshair; }

/* Vertex handles. Sized by the icon Leaflet was given, so only the paint is here. */
.forge-vertex,
.forge-midpoint {
    border-radius: 50%;
    box-sizing: border-box;
}

.forge-vertex {
    background: #17110a;
    border: 2px solid var(--ember-bright);
    cursor: move;
}

.forge-vertex:hover { background: var(--ember-bright); }

/* Sits on the midpoint of each edge and adds a vertex there, so it is deliberately quieter than a real
   one until the pointer is over it. */
.forge-midpoint {
    background: rgba(240, 160, 75, 0.35);
    border: 1px solid rgba(240, 160, 75, 0.7);
    cursor: copy;
}

.forge-midpoint:hover { background: var(--ember-bright); }

/* Sits in the middle of each hole of the selected polygon and removes it. */
.forge-hole-remove {
    display: flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    border-radius: 50%;
    background: rgba(13, 17, 23, 0.9);
    border: 1px solid var(--ember-bright);
    color: var(--ember-bright);
    font-family: var(--sans);
    font-size: 13px;
    line-height: 1;
    cursor: pointer;
}

.forge-hole-remove:hover {
    background: var(--ember-bright);
    color: #17110a;
}

/* Only the starting width, so a user who has dragged keeps their own: the inline style the splitter
   writes outranks this. */
@media (max-width: 1500px) {
    .workspace { --settings-width: 460px; }
    .workspace-forge { --forge-width: 400px; }
}

@media (max-width: 1100px) {
    .workspace { grid-template-columns: minmax(0, 1fr); grid-template-rows: auto minmax(0, 1fr); }
    .splitter { display: none; }
    .window-body-settings { overflow: auto; }
    .editor { min-height: 320px; }
}
