/* Form builder — --cx-* token layer.
 *
 * Two-layer token discipline per design-system §1 + ADR-0007 / ADR-0035:
 *   --bs-* / Threshold substrate (--advant, --marigold-deep, --ink, --line, --paper-*)
 *            owned by threshold-tokens.css + threshold-bootstrap.css.
 *   --cx-*   form-builder-only. References the Threshold substrate where the handoff
 *            binds via --th-* placeholders so the brand cascade reaches the builder
 *            without a markup pass.
 *
 * Ported from docs/design-references/design_handoff_form_builder/tokens.css. The
 * handoff's --th-* placeholder names map to the actual unprefixed Threshold tokens
 * (--ink, --advant, --marigold-deep, --line, --paper-*). Action alpha variants use
 * OKLCH-alpha syntax tied to the same marigold-copper base so the brand swap is atomic.
 *
 * Tweak data attributes (per design-system §16 / component-separation §16):
 *   data-cx-selection="ring|bar|both"     — selection treatment, locked default "bar"
 *   data-cx-cap="striped|dimmed|forbidden" — group depth-cap, locked default "striped"
 *   data-cx-density="linear|notion|slate"  — canvas row spacing, locked default "notion"
 *   data-cx-grid="off|hover|always|drag"   — canvas grid visibility, locked default "hover"
 *
 * No raw hex literals anywhere in form-builder CSS or Razor inline styles. If you need
 * a value that isn't here, add it as a --cx-* token bound to the Threshold substrate.
 */

:root,
[data-bs-theme="light"] {
    /* ── Surfaces ────────────────────────────────────────────────────────────
       Canvas is the darker working surface; cards (fields, palette, properties
       panel) read --cx-surface, which sits one step lighter so they lift off
       the canvas. Threshold's substrate --paper / --paper-2 swap their relative
       lightness across light/dark modes (light: --paper 98.8% lightest, --paper-2
       96.5% zebra darker; dark: --paper 16% mid-dark, --paper-2 20% hover lighter)
       — so each mode pins the canvas/surface tokens explicitly. */
    --cx-canvas: var(--th-paper-2);                       /* 96.5% — working surface */
    --cx-surface: var(--th-paper);                        /* 98.8% — card surface */
    --cx-surface-sunken: oklch(95% 0.003 250);         /* nested group bg, slightly recessed */
    --cx-surface-raised: var(--th-paper-sunk);            /* selected card, lifted further */
    --cx-surface-rail: var(--th-paper);                   /* palette + properties match card surface */
    --cx-surface-disabled: var(--th-paper-3);
    --cx-overlay-scrim: oklch(35% 0.02 250 / 0.32);

    /* ── Ink — Threshold graphite ramp ───────────────────────────────────── */
    --cx-ink: var(--bs-body-color);
    --cx-ink-strong: var(--th-ink);
    --cx-ink-muted: var(--th-ink-2);
    --cx-ink-subtle: var(--th-ink-3);
    --cx-ink-faint: var(--th-ink-4);
    --cx-ink-key: oklch(45% 0.013 255);                /* mono keys */
    --cx-ink-on-action: var(--th-advant-fg);
    --cx-ink-on-dark: oklch(82% 0.008 255);

    /* ── Borders ─────────────────────────────────────────────────────────── */
    --cx-border: var(--th-line);
    --cx-border-strong: var(--th-line-2);
    --cx-border-faint: var(--th-line-soft);
    --cx-border-dashed: var(--th-line-2);

    /* ── Action / semantic — alpha variants bind to substrate via relative-color
       syntax (oklch(from var(--x) l c h / α)) so the L/C/H track the substrate's
       own light/dark retunes. Action family is bound to marigold copper per
       handoff tokens.css §57 — the form builder's editorial accent. Hover binds
       to --marigold-deep, which inverts naturally (light: darker than --marigold;
       dark: lighter than --marigold). */
    --cx-action: var(--th-marigold);
    --cx-action-hover: var(--th-marigold-deep);
    --cx-action-soft: oklch(from var(--th-marigold) l c h / 0.10);
    --cx-action-soft-strong: oklch(from var(--th-marigold) l c h / 0.18);
    --cx-action-border: oklch(from var(--th-marigold) l c h / 0.40);

    --cx-error: var(--bs-danger);
    --cx-error-soft: oklch(from var(--th-risk) l c h / 0.10);
    --cx-error-border: oklch(from var(--th-risk) l c h / 0.40);

    /* Warning — Threshold copper editorial. Foreground is --marigold-deep ink;
       soft / border bind to --warn band for chip fills (lighter copper tone). */
    --cx-warning: var(--th-marigold-deep);
    --cx-warning-soft: oklch(from var(--th-warn) l c h / 0.18);
    --cx-warning-border: oklch(from var(--th-warn) l c h / 0.55);

    /* Info — distinct cyan-blue hue, doesn't bind to substrate. */
    --cx-info: oklch(38% 0.12 230);
    --cx-info-soft: oklch(60% 0.10 230 / 0.10);

    --cx-success: var(--th-ok);
    --cx-success-soft: oklch(from var(--th-ok) l c h / 0.10);
    --cx-success-border: oklch(from var(--th-ok) l c h / 0.40);

    /* ── Status (Draft / Published / Superseded) ─────────────────────────── */
    /* Draft picks up Threshold's copper editorial moment (Draft = authoring).
       Published reads as Threshold's earned ok-green. Superseded sits neutral. */
    --cx-status-draft: var(--th-marigold-deep);
    --cx-status-draft-bg: var(--th-marigold-soft);
    --cx-status-draft-border: oklch(85% 0.06 70);

    --cx-status-published: var(--th-ok);
    --cx-status-published-bg: var(--th-ok-soft);
    --cx-status-published-border: oklch(78% 0.07 155);

    --cx-status-superseded: var(--th-ink-3);
    --cx-status-superseded-bg: var(--th-paper-3);
    --cx-status-superseded-border: var(--th-line-2);

    /* ── Rule references (purple, distinct from action + status) ──────────
       No substrate binding (purple isn't a Threshold brand color). Soft/border
       bind to --cx-rule via relative-color so the dark override on --cx-rule
       below propagates to both alpha variants. */
    --cx-rule: oklch(52% 0.16 290);
    --cx-rule-soft: oklch(from var(--cx-rule) l c h / 0.09);
    --cx-rule-border: oklch(from var(--cx-rule) l c h / 0.32);

    /* ── Gesture (modifier-active drop affordances; design-system §24) ────
       Indigo-violet sibling to rule purple — visually adjacent (both
       purple-family read as "structural / non-default") but its own hue so
       the cell border admins see during an Alt-hover doesn't accidentally
       read as "this cell now has a rule." Hue 275 vs rule's 290 lands
       legibly distinct at every density. Soft/border bind via relative-color
       so the dark override on --cx-gesture below propagates. */
    --cx-gesture: oklch(50% 0.18 275);
    --cx-gesture-soft: oklch(from var(--cx-gesture) l c h / 0.10);
    --cx-gesture-border: oklch(from var(--cx-gesture) l c h / 0.45);

    /* ── Selection ───────────────────────────────────────────────────────── */
    --cx-selection-ring: var(--cx-action);
    --cx-selection-bg: oklch(from var(--th-marigold) l c h / 0.06);
    --cx-selection-bar: var(--cx-action);

    /* ── Drag / drop ─────────────────────────────────────────────────────── */
    --cx-drop-active: oklch(from var(--th-marigold) l c h / 0.12);
    --cx-drop-active-border: oklch(from var(--th-marigold) l c h / 0.55);
    --cx-drop-disabled-ink: var(--th-risk);

    /* ── Grid guides ─────────────────────────────────────────────────────── */
    --cx-grid-guide: oklch(from var(--th-marigold) l c h / 0.16);
    --cx-grid-guide-strong: oklch(from var(--th-marigold) l c h / 0.34);

    /* ── Radii ───────────────────────────────────────────────────────────── */
    --cx-radius-xs: 2px;
    --cx-radius-sm: var(--bs-border-radius);
    --cx-radius-md: var(--bs-border-radius-lg);
    --cx-radius-lg: 8px;
    --cx-radius-pill: 999px;

    /* ── Spacing (in-card detail scale; layout uses Bootstrap p-* / gap-*) ─ */
    --cx-space-1: 2px;
    --cx-space-2: 4px;
    --cx-space-3: 6px;
    --cx-space-4: 8px;
    --cx-space-5: 12px;
    --cx-space-6: 16px;
    --cx-space-7: 24px;

    /* ── Typography ──────────────────────────────────────────────────────── */
    --cx-font: var(--bs-body-font-family);
    --cx-font-serif: var(--th-font-serif);
    --cx-font-mono: var(--th-font-mono);

    /* ── Shadows ─────────────────────────────────────────────────────────── */
    --cx-shadow-card: 0 0 0 1px var(--cx-border);
    --cx-shadow-raised: 0 1px 2px oklch(20% 0.015 255 / 0.05), 0 0 0 1px var(--cx-border);
    --cx-shadow-elevated: 0 6px 16px oklch(20% 0.015 255 / 0.08), 0 0 0 1px var(--cx-border);
    --cx-shadow-popover: 0 12px 28px oklch(20% 0.015 255 / 0.14), 0 0 0 1px var(--cx-border);
    --cx-shadow-selection: 0 0 0 1.5px var(--cx-selection-ring);
}

/* Dark mode — Threshold warm-dark substrate (paper hue 60° at 13-22% L per
 * ADR-0033's sibling decision). Most --cx-* tokens cascade through the substrate
 * automatically (--ink/--ink-2/--ink-3/--ink-4, --line/--line-2/--line-soft,
 * --advant, --marigold-deep, --ok, --risk all retune via [data-theme="dark"],
 * set in sync with [data-bs-theme="dark"] per ADR-0035 HR8). Only the values
 * below need explicit overrides:
 *   - form-builder surface ramp (sunken/raised/rail/disabled) which has no
 *     substrate parallel
 *   - --cx-ink-key warm-tilted to match the dark palette
 *   - overlay scrim + shadows tuned denser for the near-black substrate */
[data-bs-theme="dark"] {
    /* Re-pin canvas + surface for the dark substrate (--paper / --paper-2 hold
       the inverse lightness relationship vs. light mode, so the literal
       references differ but the semantic ordering — canvas darker, surface
       lighter — is preserved). paper-3 (13%) < paper (16%) < paper-2 (20%)
       < paper-sunk (22%). */
    --cx-canvas: var(--th-paper);                         /* 16% — working surface */
    --cx-surface: var(--th-paper-2);                      /* 20% — card surface */
    --cx-surface-sunken: var(--th-paper-3);               /* nested group, darker than canvas */
    --cx-surface-raised: var(--th-paper-sunk);            /* selected card, lifted */
    --cx-surface-rail: var(--th-paper-2);                 /* palette + properties match card */
    --cx-surface-disabled: oklch(14% 0.010 60);        /* between paper-3 and paper */

    /* Mono keys — warm tilt to match the dark palette hue. */
    --cx-ink-key: oklch(62% 0.011 75);

    /* Rule purple — lifted for dark substrate; --cx-rule-soft / -border auto-track
       via the relative-color syntax in the light block above. */
    --cx-rule: oklch(75% 0.13 290);

    /* Gesture purple — same lift-for-dark treatment as --cx-rule; soft/border
       auto-track via the relative-color syntax in the light block above. */
    --cx-gesture: oklch(74% 0.15 275);

    /* Denser scrim so overlay reads against near-black. */
    --cx-overlay-scrim: oklch(0% 0 0 / 0.55);

    /* Shadows — denser, with 1px top-edge highlight per design handoff so
       raised surfaces still feel lifted on near-black. */
    --cx-shadow-raised: 0 1px 0 oklch(100% 0 0 / 0.03) inset, 0 1px 2px oklch(0% 0 0 / 0.40), 0 0 0 1px var(--cx-border);
    --cx-shadow-elevated: 0 1px 0 oklch(100% 0 0 / 0.04) inset, 0 8px 22px oklch(0% 0 0 / 0.55), 0 0 0 1px var(--cx-border);
    --cx-shadow-popover: 0 1px 0 oklch(100% 0 0 / 0.04) inset, 0 16px 36px oklch(0% 0 0 / 0.65), 0 0 0 1px var(--cx-border);
}

/* Drag ghost — the small pill rendered inside DragOverlay's portaled host,
 * shown at the pointer position during a drag. The element is built via
 * __builder.OpenElement in PaletteDropDispatcher.BuildGhost (a RenderFragment
 * built in C#, not a Razor template), so Blazor's scoped-CSS attribute
 * decoration doesn't reach it. The styles live here in the global sheet so
 * the selector matches a plain class. */
.fb-drag-ghost {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px;
    background: var(--cx-surface-raised);
    border: 1px solid var(--cx-action-border);
    border-radius: var(--cx-radius-pill);
    color: var(--cx-action);
    font-size: 12px;
    font-weight: 600;
    box-shadow: var(--cx-shadow-elevated);
    white-space: nowrap;
}

/* Decision C — when Alt is held during a drag, the overlay pins a small
 * modifier badge to the upper-right of the ghost so the gesture state is
 * legible at the pointer (not only on the canvas substrate). Tracks the
 * gesture register: gesture-purple border + ink, gesture-soft fill,
 * mono uppercase "split" label. */
.fb-drag-ghost__modifier {
    position: absolute;
    top: -8px;
    right: -8px;
    padding: 1px 6px;
    background: var(--cx-gesture-soft);
    border: 1px solid var(--cx-gesture-border);
    border-radius: var(--cx-radius-xs);
    color: var(--cx-gesture);
    font-family: var(--th-font-mono);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    line-height: 1.3;
    box-shadow: 0 2px 6px rgba(20, 22, 30, 0.10);
    pointer-events: none;
}


/* Hover-on-cell visibility for the cell-corner affordances (kebab,
 * drag handle). These components used to own these rules via ::deep
 * .fb-cell:hover, but Blazor scoped CSS's ::deep combinator descends from
 * the OWNING component's elements; .fb-cell is the parent of these widgets,
 * not a descendant, so the rule never matched. Owning the rule in the
 * global sheet matches the actual DOM relationship.
 *
 * Why .fb-cell-shell:hover and NOT .fb-cell:hover for kebab + drag handle:
 * groups bypass the cell-shell (LayoutCellView's bypassShell = isGroup ||
 * isSpacer). Hovering a group's outer .fb-cell would propagate the
 * descendant selector to every inner element's kebab. .fb-cell-shell:hover
 * only fires on the immediately-containing cell-shell — groups have none,
 * so hovering a group never opens any inner kebabs. */
.fb-cell-shell:hover .fb-element-kebab__button,
.fb-cell.is-selected .fb-element-kebab__button,
.fb-cell-shell:hover .fb-element-drag-handle,
.fb-cell.is-selected .fb-element-drag-handle {
    opacity: 1;
}

/* Cell-edge resize handle. The edge handle is a SIBLING of cell-shell
 * (rendered before it in LayoutCellView), so .fb-cell-shell:hover doesn't
 * reach it. Must use .fb-cell:hover, but :not(:has(.fb-cell:hover))
 * restricts to the innermost hovered cell — without that, hovering a cell
 * inside a group would also light up the group's edge handle (hover
 * bubbles up the .fb-cell ancestor chain). */
.fb-cell:hover:not(:has(.fb-cell:hover)) .fb-cell-edge-handle,
.fb-cell.is-selected .fb-cell-edge-handle {
    opacity: 1;
    background: var(--cx-action-soft);
}

/* Trailing-empty drop target — applies to both render paths:
 *   - Resting state: <TrailingEmptyCell> Razor component
 *   - Drag state: inline div in LayoutRowView with the --drop modifier
 * Putting the visuals in the global sheet means both paths render the same
 * way without the scoped-CSS attribute mismatch that caused the drag-state
 * div to lose centering / faded color / dashed border / cursor in earlier
 * iterations. TrailingEmptyCell.razor.css keeps a minimal scoped fallback
 * for the resting path (a duplicate but harmless safety net). */
.fb-trailing-empty {
    border: 1px dashed var(--cx-border-dashed);
    border-radius: var(--cx-radius-sm);
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--cx-ink-faint);
    font-size: 11.5px;
    background: transparent;
    transition: opacity 100ms, border-color 100ms;
    opacity: .55;
    /* Decorative label — don't expose an editable text cursor on hover. */
    user-select: none;
    cursor: default;
    /* Fill the wrapping drop-target / slot, which itself stretches to the
     * row's grid track height (set below). Without 100% the inner div
     * collapses to its min-content, leaving the "+ Drop here…" text
     * anchored to top-left of a tall row. */
    width: 100%;
    height: 100%;
    min-height: 36px;
}

.fb-row:hover .fb-trailing-empty {
    opacity: 1;
}

/* Drag state — same shape, accent-colored, taller minimum (the row's
 * grid-track stretch handles the tall-row case; min-height covers
 * a row with only short cells so the drop target still reads as
 * row-sized). */
.fb-trailing-empty--drop {
    opacity: 1;
    color: var(--cx-action);
    border-color: var(--cx-action-border);
    min-height: 64px;
}

/* The wrapping slot is the grid item; align-self: stretch makes it fill
 * the row's implicit grid track height (driven by the tallest sibling
 * cell). The inner drop-target chain is flex with stretch so the inner
 * .fb-trailing-empty fills end-to-end. */
.fb-trailing-drop-slot {
    display: flex;
    align-items: stretch;
    align-self: stretch;
}

.fb-trailing-drop-slot > .fb-drop-target {
    display: flex;
    align-items: stretch;
    width: 100%;
}

/* Cell-edge resize indicator — vertical line that tracks the cursor during
 * a CellEdge drag. Lives inside DragOverlay's fixed-position wrapper (which
 * is anchored at PointerX/PointerY); the line itself uses absolute positioning
 * inside that wrapper to span the full viewport height centered on the
 * pointer X. The cell's right edge snaps to column boundaries on every
 * pointermove, but BETWEEN snaps the cursor moves freely without visible
 * feedback — this line makes the resize feel responsive instead of stuck. */
.fb-cell-edge-resize-indicator {
    position: absolute;
    /* Center 2px line on the wrapper's anchor (pointer X). */
    left: -1px;
    /* Span the viewport height in both directions from the pointer Y. */
    top: -100vh;
    height: 200vh;
    width: 2px;
    background: var(--cx-action);
    border-radius: 1px;
    box-shadow: 0 0 0 1px var(--cx-surface-raised);
    opacity: 0.9;
}

/* Canvas grid visibility — coordinated across BuilderCanvas (data-cx-grid),
 * LayoutRowView (.fb-row + :hover), and GridGuides (.fb-grid-guides). These
 * rules can't live in any single component's scoped CSS because they span
 * the ancestor chain across component boundaries; global stylesheet is the
 * right home.
 *
 * Modes (canvas root's data-cx-grid attribute, set by BuilderCanvas):
 *   off     — never visible
 *   hover   — visible only when the parent .fb-row is hovered (locked default)
 *   always  — unconditionally visible (developer flag, not surfaced in UI)
 *   drag    — unconditionally visible during drag (controller flips the attr)
 * GridGuides.razor.css declares display:none as the resting state; these
 * rules turn display:grid on for the active mode.
 *
 * Hover-mode nuance: a hovered row's :hover state also matches every ancestor
 * row (CSS :hover bubbles), so hovering a row inside a GroupContent would
 * naively light up the outer row's grid too. The :not(:has(.fb-row:hover))
 * gate restricts the visible grid to the innermost hovered row only — when
 * a row contains another hovered row, the outer row's grid stays hidden.
 * Drag / always modes don't carry that gate; both need every row's grid
 * visible so admins can see where every drop target lands.
 */
.fb-canvas[data-cx-grid="hover"] .fb-row:hover:not(:has(.fb-row:hover)) .fb-grid-guides,
.fb-canvas[data-cx-grid="always"] .fb-grid-guides,
.fb-canvas[data-cx-grid="drag"] .fb-grid-guides {
    display: grid;
}
