/* PSC.Blazor.Components.Dashboards — shared styles.
   Host loads via:
   <link rel="stylesheet" href="_content/PSC.Blazor.Components.Dashboards/css/dashboards.css" />
   (Shared, not scoped, because the read-only grid and the editor are separate
   components that must render with identical positioning.) */

.dbx-grid {
    display: grid;
    grid-template-columns: repeat(var(--dbx-cols, 12), minmax(0, 1fr));
    grid-auto-rows: var(--dbx-row-h, 80px);
    gap: 12px;
    width: 100%;
}

.dbx-cell {
    min-width: 0;
    min-height: 0;
    overflow: hidden;
    /* Consistent card chrome in the public render so every widget looks the
       same whether or not it brings its own styling — mirrors the bordered
       cells in the editor. Widgets stay "naked" (no border of their own) and
       sit inside this card. */
    border: 1px solid var(--bs-border-color, #e5e7eb);
    border-radius: .75rem;
    background: var(--bs-body-bg, #fff);
    box-shadow: 0 1px 3px rgba(15, 23, 42, .05);
    box-sizing: border-box;
}

/* The Spacer is intentional empty space — it must NOT look like a card. */
.dbx-cell[data-dbx-widget="core.spacer"] {
    border: 0;
    background: transparent;
    box-shadow: none;
}

.dbx-grid-empty {
    grid-column: 1 / -1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    padding: 3rem 1rem;
    text-align: center;
    color: var(--bs-secondary-color, #6c757d);
    border: 2px dashed var(--bs-border-color, #ced4da);
    border-radius: .75rem;
}

.dbx-grid-empty i { font-size: 2rem; opacity: .6; }
.dbx-grid-empty p { margin: 0; max-width: 28rem; }

.dbx-widget-unknown {
    display: flex;
    align-items: center;
    gap: .5rem;
    height: 100%;
    padding: .75rem;
    border: 1px dashed var(--bs-border-color, #ced4da);
    border-radius: .5rem;
    color: var(--bs-secondary-color, #6c757d);
    background: var(--bs-tertiary-bg, #f8f9fa);
    font-size: .875rem;
}

.dbx-widget-unknown code { font-size: .8125rem; }

/* ---- Editor ---- */

.dbx-grid-editing .dbx-cell-edit {
    position: relative;
    border: 1px solid var(--bs-border-color, #ced4da);
    border-radius: .5rem;
    overflow: hidden;
    background: var(--bs-body-bg, #fff);
    /* Flex column so the toolbar takes its own vertical slice at the top
       and the body grows into whatever's left — no more toolbar-overlaps-
       widget-content (previously the toolbar was position:absolute and
       the body was height:100%, so labels like "Visitors today" sat
       underneath the gear/hide/reset icons). */
    display: flex;
    flex-direction: column;
}

.dbx-cell-toolbar {
    /* In-flow at the top of the cell so the widget body lives BELOW it. */
    flex: 0 0 auto;
    height: 24px;
    display: flex;
    align-items: stretch;
    color: var(--bs-secondary-color, #6c757d);
    /* Fully opaque pale indigo — the previous translucent tint vanished
       over coloured widgets (the purple Clock cell made it disappear
       completely). Solid colour keeps the toolbar visible regardless of
       whatever the widget paints below. */
    background: #eef2ff;
    border-bottom: 1px solid #c7d2fe;
    z-index: 2;
}

.dbx-cell-settings-btn {
    border: 0;
    background: transparent;
    padding: 0 .4rem;
    color: inherit;
    cursor: pointer;
    display: flex;
    align-items: center;
    font: inherit;
    border-right: 1px solid var(--bs-border-color, #e9ecef);
}
.dbx-cell-settings-btn:hover { background: var(--bs-secondary-bg, #e2e8f0); }
.dbx-cell-selected .dbx-cell-settings-btn {
    color: var(--bs-primary, #6366f1);
}

.dbx-cell-handle-area {
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: grab;
    touch-action: none;
}
.dbx-cell-handle-area:active { cursor: grabbing; }

.dbx-cell-selected {
    outline: 2px solid var(--bs-primary, #6366f1);
    outline-offset: -2px;
}

.dbx-cell-body {
    flex: 1;
    min-height: 0;
    /* Scroll vertically when the widget grows taller than the cell. Lives on
       the cell body so every widget benefits — text was the obvious case
       but stat/heading/etc. can all overflow if the cell is too small.
       Horizontal stays hidden because cell content is meant to wrap, not
       scroll sideways (the editor canvas in Desktop tier already handles
       horizontal scroll for the WHOLE grid). */
    overflow-y: auto;
    overflow-x: hidden;
}

.dbx-cell-resize {
    position: absolute;
    right: 0;
    bottom: 0;
    width: 18px;
    height: 18px;
    cursor: nwse-resize;
    touch-action: none;
    /* Always visible in edit mode so users can find it without hover. */
    opacity: .85;
    transition: opacity .12s ease;
    z-index: 3;
    /* Three diagonal lines as a corner grip (no extra markup/images). */
    background:
        linear-gradient(135deg, transparent 40%, var(--bs-secondary-color, #6c757d) 40%, var(--bs-secondary-color, #6c757d) 52%, transparent 52%, transparent 64%, var(--bs-secondary-color, #6c757d) 64%, var(--bs-secondary-color, #6c757d) 76%, transparent 76%, transparent 86%, var(--bs-secondary-color, #6c757d) 86%, var(--bs-secondary-color, #6c757d) 96%, transparent 96%);
}

.dbx-cell-edit:hover .dbx-cell-resize,
.dbx-cell-selected .dbx-cell-resize { opacity: 1; }

/* ---- Palette (DSB-198) ---- */

.dbx-palette {
    display: flex;
    flex-direction: column;
    gap: .75rem;
    padding: .75rem;
    background: var(--bs-body-bg, #fff);
    border: 1px solid var(--bs-border-color, #e5e7eb);
    border-radius: .75rem;
    min-width: 0;
}

.dbx-palette-header {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-weight: 600;
    color: var(--bs-emphasis-color, #0f172a);
    padding-bottom: .5rem;
    border-bottom: 1px solid var(--bs-border-color, #e5e7eb);
}

.dbx-palette-group { display: flex; flex-direction: column; gap: .35rem; }

.dbx-palette-category {
    text-transform: uppercase;
    letter-spacing: .04em;
    font-size: .7rem;
    color: var(--bs-secondary-color, #64748b);
    margin: .25rem 0 .15rem;
}

.dbx-palette-item {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: .65rem;
    padding: .6rem .7rem;
    background: var(--bs-body-bg, #fff);
    border: 1px solid var(--bs-border-color, #e5e7eb);
    border-radius: .55rem;
    text-align: left;
    color: var(--bs-body-color, #0f172a);
    cursor: grab;
    transition: background-color .14s ease, border-color .14s ease, transform .12s ease, box-shadow .14s ease;
    font: inherit;
    line-height: 1.25;
    box-shadow: 0 1px 2px rgba(15, 23, 42, .03);
    /* Prevent text-selection so the drag feels clean. HTML5 DnD owns the
       cursor + drag image; touch-action stays default so touch drag works. */
    user-select: none;
    -webkit-user-select: none;
}
.dbx-palette-item:hover {
    background: var(--bs-body-bg, #fff);
    border-color: color-mix(in srgb, var(--bs-primary, #6366f1) 40%, var(--bs-border-color, #e5e7eb));
    transform: translateY(-1px);
    box-shadow: 0 4px 12px rgba(15, 23, 42, .08);
}
.dbx-palette-item:active {
    cursor: grabbing;
    transform: translateY(0);
    box-shadow: 0 1px 2px rgba(15, 23, 42, .04);
}
.dbx-palette-item:focus-visible { outline: 2px solid var(--bs-primary, #6366f1); outline-offset: 2px; }
.dbx-palette-item-busy { opacity: .55; cursor: progress; pointer-events: none; }

/* Square icon tile so each palette card has a consistent visual anchor.
   The tile gets a light indigo tint by default and pops to a stronger
   tint on hover, matching the card's overall hover treatment. */
.dbx-palette-icon {
    width: 32px;
    height: 32px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-size: 1.05rem;
    color: var(--bs-primary, #6366f1);
    background: color-mix(in srgb, var(--bs-primary, #6366f1) 10%, transparent);
    border-radius: .45rem;
    transition: background-color .14s ease;
}
.dbx-palette-item:hover .dbx-palette-icon {
    background: color-mix(in srgb, var(--bs-primary, #6366f1) 18%, transparent);
}

.dbx-palette-text { display: flex; flex-direction: column; min-width: 0; }
.dbx-palette-name { font-weight: 600; font-size: .9rem; }
.dbx-palette-desc {
    font-size: .75rem;
    color: var(--bs-secondary-color, #64748b);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dbx-palette-add { color: var(--bs-secondary-color, #94a3b8); font-size: .9rem; }
.dbx-palette-item:hover .dbx-palette-add { color: var(--bs-primary, #6366f1); }

.dbx-palette-empty { color: var(--bs-secondary-color, #64748b); font-size: .85rem; margin: 0; }

/* ---- Core widgets (DSB-199) ----
   Minimal defaults; hosts can theme via Bootstrap vars or override. */

.dbx-w-core {
    width: 100%;
    height: 100%;
    padding: .75rem;
    box-sizing: border-box;
    overflow: hidden;
    color: var(--bs-body-color, #0f172a);
}

.dbx-w-core-text {
    padding: 1rem;
    /* Release the widget from the parent `.dbx-w-core { height: 100% }`
       so that long markdown bodies can grow taller than the cell and let
       the cell body scroll catch the overflow. Without these overrides
       the widget element stays clamped to body height and the long
       content disappears silently. */
    height: auto;
    min-height: 100%;
    overflow: visible;
}
.dbx-w-core-text-title { margin: 0 0 .5rem; font-size: 1.1rem; }
.dbx-w-core-text-body { line-height: 1.5; font-size: .95rem; }
.dbx-w-core-text-body p:first-child { margin-top: 0; }
.dbx-w-core-text-body p:last-child { margin-bottom: 0; }

.dbx-w-core-heading {
    display: flex;
    align-items: center;
    justify-content: flex-start;
}
.dbx-w-core-heading h1,
.dbx-w-core-heading h2,
.dbx-w-core-heading h3,
.dbx-w-core-heading h4,
.dbx-w-core-heading h5,
.dbx-w-core-heading h6 { margin: 0; width: 100%; }

.dbx-w-core-image {
    padding: 0;
    display: block;
    background: var(--bs-tertiary-bg, #f1f5f9);
}
.dbx-w-core-image img {
    width: 100%;
    height: 100%;
    display: block;
}
.dbx-w-core-image-empty {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .35rem;
    color: var(--bs-secondary-color, #64748b);
    border: 1px dashed var(--bs-border-color, #cbd5e1);
    border-radius: .5rem;
}
.dbx-w-core-image-empty i { font-size: 1.6rem; opacity: .6; }

.dbx-w-core-link {
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
}
.dbx-w-core-link-btn {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    padding: .55rem 1rem;
    border-radius: .5rem;
    text-decoration: none;
    font-weight: 600;
    font-size: .95rem;
    line-height: 1.2;
    transition: filter .12s ease, background-color .12s ease;
}
.dbx-w-core-link-primary {
    background: var(--bs-primary, #6366f1);
    color: #fff;
}
.dbx-w-core-link-primary:hover { filter: brightness(.93); color: #fff; }
.dbx-w-core-link-secondary {
    background: var(--bs-secondary, #64748b);
    color: #fff;
}
.dbx-w-core-link-secondary:hover { filter: brightness(.93); color: #fff; }
.dbx-w-core-link-outline {
    background: transparent;
    color: var(--bs-primary, #6366f1);
    border: 1px solid var(--bs-primary, #6366f1);
}
.dbx-w-core-link-outline:hover { background: var(--bs-primary, #6366f1); color: #fff; }
.dbx-w-core-link-ghost {
    background: transparent;
    color: var(--bs-body-color, #0f172a);
}
.dbx-w-core-link-ghost:hover { background: var(--bs-tertiary-bg, #f1f5f9); }
.dbx-w-core-link-btn[aria-disabled="true"] {
    opacity: .45;
    pointer-events: none;
}

.dbx-w-core-divider {
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: stretch;
}
.dbx-w-core-divider hr {
    width: 100%;
    margin: 0;
    color: inherit;
}

.dbx-w-core-spacer { padding: 0; }

/* ---- Editor viewport toolbar (DSB-222) ---- */

.dbx-editor { display: flex; flex-direction: column; gap: .75rem; min-width: 0; }

.dbx-editor-toolbar {
    display: flex;
    align-items: center;
    gap: .75rem;
    flex-wrap: wrap;
}

.dbx-viewport-buttons {
    display: inline-flex;
    background: var(--bs-tertiary-bg, #f8fafc);
    border: 1px solid var(--bs-border-color, #e5e7eb);
    border-radius: .5rem;
    padding: .15rem;
    gap: .15rem;
}

.dbx-vp-btn {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .35rem .65rem;
    background: transparent;
    border: 0;
    border-radius: .35rem;
    color: var(--bs-secondary-color, #64748b);
    font: inherit;
    font-size: .85rem;
    cursor: pointer;
    transition: background-color .12s ease, color .12s ease;
}
.dbx-vp-btn:hover { background: var(--bs-secondary-bg, #e2e8f0); color: var(--bs-emphasis-color, #0f172a); }
.dbx-vp-btn.active {
    background: var(--bs-body-bg, #fff);
    color: var(--bs-primary, #6366f1);
    box-shadow: 0 1px 2px rgba(15, 23, 42, .08);
}
.dbx-vp-btn i { font-size: 1rem; }

/* Small vertical separator between groups inside the viewport-buttons row
   (e.g. between the three viewport tabs and the Grid toggle). */
.dbx-vp-divider {
    width: 1px;
    margin: .3rem .25rem;
    background: var(--bs-border-color, #e5e7eb);
}

.dbx-editor-toolbar-hint {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    font-size: .8rem;
    color: var(--bs-secondary-color, #64748b);
}

/* The canvas constrains the grid to the active viewport's max width. */
.dbx-editor-canvas {
    max-width: 100%;
    margin: 0 auto;
    transition: max-width .18s ease;
    width: 100%;
    /* In Desktop tier on a narrow browser the 12-col grid can be wider than
       the available room — let the canvas scroll horizontally rather than
       silently collapsing (which would desync the visual layout from the
       data-dbx-cols the JS shim uses for drag math). */
    overflow-x: auto;
}
.dbx-canvas-desktop { max-width: none; }
.dbx-canvas-tablet  { max-width: 768px; }
.dbx-canvas-mobile  { max-width: 380px; }

/* Defensive: kill any inherited horizontal padding/margin on the canvas so
   cells sit symmetrically inside whatever host container wraps the editor.
   Without this, hosts that drop the editor inside a Bootstrap row/col can
   inherit a half-gutter margin that visibly shifts the grid right. */
.dbx-editor-canvas { padding-inline: 0; }

/* In the narrowest editor viewport the default 18×18 grip starts to feel
   chunky relative to the 380px-wide cells — drop it to 14×14 there. The
   diagonal pattern stays readable, and it's still well above the 11px
   discoverability floor for non-touch input. */
.dbx-canvas-mobile .dbx-cell-resize { width: 14px; height: 14px; }

/* Inherited cell — dashed outline so it reads as "auto" placement. */
.dbx-cell-inherited {
    outline: 1px dashed var(--bs-border-color, #cbd5e1);
    outline-offset: -1px;
    opacity: .82;
}

.dbx-cell-inherit-badge {
    position: absolute;
    bottom: 6px;
    left: 6px;
    display: inline-flex;
    align-items: center;
    gap: .25rem;
    padding: .15rem .45rem;
    /* Opaque enough to read over any widget content (image / coloured stat / etc.) */
    background: rgba(255, 255, 255, .92);
    color: #475569;
    font-size: .7rem;
    border-radius: .35rem;
    border: 1px solid rgba(15, 23, 42, .08);
    box-shadow: 0 1px 3px rgba(15, 23, 42, .12);
    backdrop-filter: blur(4px);
    -webkit-backdrop-filter: blur(4px);
    z-index: 2;
    pointer-events: none;
}

/* Reset-to-inherit button in the cell toolbar. Same skin as the gear button. */
.dbx-cell-reset-btn {
    border: 0;
    background: transparent;
    padding: 0 .4rem;
    color: inherit;
    cursor: pointer;
    display: flex;
    align-items: center;
    font: inherit;
    border-right: 1px solid var(--bs-border-color, #e9ecef);
}
.dbx-cell-reset-btn:hover { background: var(--bs-secondary-bg, #e2e8f0); color: var(--bs-emphasis-color, #0f172a); }

/* ---- Polish: column-count settings + hide-on-device + copy-from-desktop (DSB-224) ---- */

.dbx-editor-toolbar-action {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .35rem .65rem;
    background: var(--bs-tertiary-bg, #f8fafc);
    border: 1px solid var(--bs-border-color, #e5e7eb);
    border-radius: .35rem;
    color: var(--bs-emphasis-color, #0f172a);
    font: inherit;
    font-size: .85rem;
    cursor: pointer;
}
.dbx-editor-toolbar-action:hover { background: var(--bs-secondary-bg, #e2e8f0); }
.dbx-editor-toolbar-action.dbx-toolbar-toggle-on {
    background: var(--bs-body-bg, #fff);
    color: var(--bs-primary, #6366f1);
    border-color: var(--bs-primary, #6366f1);
}

/* Grid-lines guide. Drawn as the grid container's background-image so cells
   (which have opaque backgrounds) sit ON TOP of the lines — you only see
   them in the empty space between widgets, which is exactly where you want
   the visual help when placing/resizing. The two repeating linear-gradients
   are sized to one cell + one gap so the lines align with the grid tracks;
   the gap value (12px) matches data-dbx-gap. */
.dbx-grid-show-lines {
    background-image:
        linear-gradient(to right, rgba(99, 102, 241, .45) 1px, transparent 1px),
        linear-gradient(to bottom, rgba(99, 102, 241, .45) 1px, transparent 1px);
    background-size:
        calc((100% + 12px) / var(--dbx-cols, 12)) 100%,
        100% calc(var(--dbx-row-h, 80px) + 12px);
    background-position: 0 0;
    background-repeat: repeat;
}

.dbx-editor-settings { position: relative; margin-left: auto; }
.dbx-editor-settings > summary {
    list-style: none;
    cursor: pointer;
    padding: .35rem .55rem;
    border-radius: .35rem;
    color: var(--bs-secondary-color, #64748b);
    border: 1px solid transparent;
}
.dbx-editor-settings > summary::-webkit-details-marker { display: none; }
.dbx-editor-settings[open] > summary,
.dbx-editor-settings > summary:hover {
    background: var(--bs-tertiary-bg, #f8fafc);
    border-color: var(--bs-border-color, #e5e7eb);
}

.dbx-editor-settings-body {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: .35rem;
    z-index: 10;
    min-width: 16rem;
    background: var(--bs-body-bg, #fff);
    border: 1px solid var(--bs-border-color, #e5e7eb);
    border-radius: .5rem;
    padding: .85rem;
    box-shadow: 0 6px 20px rgba(15, 23, 42, .08);
    display: flex;
    flex-direction: column;
    gap: .35rem;
}

.dbx-cell-hide-btn {
    border: 0;
    background: transparent;
    padding: 0 .4rem;
    color: inherit;
    cursor: pointer;
    display: flex;
    align-items: center;
    font: inherit;
    border-right: 1px solid var(--bs-border-color, #e9ecef);
}
.dbx-cell-hide-btn:hover { background: var(--bs-secondary-bg, #e2e8f0); color: var(--bs-emphasis-color, #0f172a); }

/* Remove (trash) button in the cell toolbar — desktop only. */
.dbx-cell-remove-btn {
    border: 0;
    background: transparent;
    padding: 0 .4rem;
    color: var(--bs-danger, #dc3545);
    cursor: pointer;
    display: flex;
    align-items: center;
    font: inherit;
    border-left: 1px solid var(--bs-border-color, #e9ecef);
}
.dbx-cell-remove-btn:hover {
    background: var(--bs-danger, #dc3545);
    color: #fff;
}

/* Editor-only treatment for hidden-here cells: fade + diagonal hatch so the
   user can still find + un-hide them (the public render uses display:none). */
.dbx-cell-hidden-here {
    opacity: .35;
    background: repeating-linear-gradient(
        45deg,
        transparent,
        transparent 8px,
        var(--bs-tertiary-bg, #f1f5f9) 8px,
        var(--bs-tertiary-bg, #f1f5f9) 16px
    );
}

/* ---- Settings panel (DSB-200) ---- */

.dbx-settings {
    display: flex;
    flex-direction: column;
    gap: 0;
    background: var(--bs-body-bg, #fff);
    border: 1px solid var(--bs-border-color, #e5e7eb);
    border-radius: .75rem;
    overflow: hidden;
    min-width: 0;
}

.dbx-settings-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: .5rem;
    padding: .65rem .75rem;
    background: var(--bs-tertiary-bg, #f8fafc);
    border-bottom: 1px solid var(--bs-border-color, #e5e7eb);
}

.dbx-settings-title {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-weight: 600;
    color: var(--bs-emphasis-color, #0f172a);
}

.dbx-settings-close {
    border: 0;
    background: transparent;
    color: var(--bs-secondary-color, #64748b);
    padding: .15rem .35rem;
    border-radius: .35rem;
    cursor: pointer;
    font: inherit;
}
.dbx-settings-close:hover { background: var(--bs-secondary-bg, #e2e8f0); color: var(--bs-emphasis-color, #0f172a); }

.dbx-settings-body {
    padding: .85rem;
    display: flex;
    flex-direction: column;
    gap: .35rem;
}

.dbx-settings-empty { color: var(--bs-secondary-color, #64748b); font-size: .85rem; margin: 0 0 .5rem; }

.dbx-settings-label {
    display: block;
    font-size: .75rem;
    color: var(--bs-secondary-color, #64748b);
    font-weight: 600;
    margin-top: .35rem;
}

.dbx-settings-input,
.dbx-settings-textarea {
    width: 100%;
    box-sizing: border-box;
    padding: .45rem .55rem;
    border: 1px solid var(--bs-border-color, #cbd5e1);
    border-radius: .35rem;
    background: var(--bs-body-bg, #fff);
    color: var(--bs-body-color, #0f172a);
    font: inherit;
    font-size: .9rem;
}
.dbx-settings-input:focus,
.dbx-settings-textarea:focus {
    outline: 2px solid var(--bs-primary, #6366f1);
    outline-offset: -1px;
    border-color: var(--bs-primary, #6366f1);
}
.dbx-settings-textarea { resize: vertical; min-height: 4rem; font-family: monospace; font-size: .82rem; }

.dbx-settings-check {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-top: .5rem;
    font-size: .9rem;
    cursor: pointer;
}
.dbx-settings-check input { margin: 0; }

.dbx-w-core-settings { display: flex; flex-direction: column; gap: .35rem; }

/* ---- Mobile reflow (DSB-196) ----
   Below 768px (Bootstrap md) collapse to a single column ordered by SortOrder
   so a public dashboard is readable on a phone OR a narrow desktop window
   without sideways scrolling. Tested at ~650px: the 12-col grid creates
   ~50px columns where 3-col stat cards clip their values (the trigger for
   raising the breakpoint from the initial 640px). CSS source order =
   persisted SortOrder. Drag + resize are desktop-only in v1 — the editor
   chrome is hidden on mobile and the user views layouts there. */
@media (max-width: 768px) {
    /* Auto single-column stack — applied ONLY to grids that don't have explicit
       per-device overrides (those use their own DSB-223 @media rules) AND aren't
       the editor canvas (the editor's column count is driven by the tier tab,
       NOT the browser width — collapsing the editor to one column on a narrow
       browser hides the active tier's actual layout and makes drag/drop fire
       blind. The editor canvas's max-width already constrains the mobile/tablet
       canvases to their device widths, so the layout fits without help). */
    .dbx-grid:not(.dbx-grid-responsive):not(.dbx-grid-editing) {
        grid-template-columns: 1fr;
        grid-auto-rows: auto;
        gap: .75rem;
    }
    .dbx-grid:not(.dbx-grid-responsive):not(.dbx-grid-editing) > .dbx-cell,
    .dbx-grid:not(.dbx-grid-responsive):not(.dbx-grid-editing) > .dbx-cell-edit {
        grid-column: 1 / -1 !important;
        grid-row: auto !important;
        min-height: 8rem;
    }
}

/* ---- Touch-friendly editing ----
   On devices where the primary input is touch (phones / tablets with no
   mouse), CSS :hover never fires, so the toolbar's hover-to-reveal pattern
   leaves users staring at an empty cell. Force the toolbar visible, bump
   tap targets to ~44px-ish (Apple HIG / Material guidance), enlarge the
   resize handle, and grow the icon glyphs so they're readable on a small
   screen. Mouse + touch hybrids (Surface, etc.) match `hover: hover` so
   they keep the desktop experience. */
@media (hover: none) and (pointer: coarse) {
    /* Toolbar is now in-flow (steals real vertical space from the widget
       body) so the 40px height previously used to compensate for the
       hover-only reveal would now visibly shrink content too much.
       32px is a comfortable thumb target without dominating the cell. */
    .dbx-cell-edit .dbx-cell-toolbar { height: 32px; }
    .dbx-cell-edit .dbx-cell-toolbar i { font-size: 1.1rem; }
    .dbx-cell-edit .dbx-cell-settings-btn,
    .dbx-cell-edit .dbx-cell-hide-btn,
    .dbx-cell-edit .dbx-cell-reset-btn,
    .dbx-cell-edit .dbx-cell-remove-btn { padding: 0 .65rem; }

    .dbx-cell-edit .dbx-cell-resize {
        width: 26px;
        height: 26px;
        opacity: 1;
    }

    /* Mobile canvas on touch gets a slightly smaller grip too — still big
       enough to tap reliably but proportional to the narrow cell. */
    .dbx-canvas-mobile .dbx-cell-edit .dbx-cell-resize { width: 20px; height: 20px; }

    /* Palette items: larger touch targets + remove the grab cursor (meaningless
       on touch). Tap-to-add still works via the click handler. */
    .dbx-palette-item { padding: .75rem .8rem; cursor: pointer; }
    .dbx-palette-item:active { cursor: pointer; }

    /* Viewport / Grid pills bigger for thumb taps. */
    .dbx-vp-btn { padding: .55rem .85rem; }
}

.dbx-cell-edit.dbx-dragging {
    opacity: .35;
    outline: 2px dashed var(--bs-primary, #6366f1);
}

/* Drop-target preview while dragging. */
.dbx-drop-ghost {
    border: 2px dashed var(--bs-primary, #6366f1);
    border-radius: .5rem;
    background: color-mix(in srgb, var(--bs-primary, #6366f1) 12%, transparent);
    pointer-events: none;
    z-index: 1;
}

/* ---- Core API widget ---- */

.dbx-w-core-api {
    padding: .75rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    /* The body-level scroll pattern works for plain block widgets (Text),
       but for the API widget — a flex column with a tall <pre> child whose
       intrinsic size flexbox computes weirdly — browsers don't reliably
       trigger the cell-body scrollbar against `min-height: 100%`. Owning
       the scrollbar here is bulletproof: tall JSON / template responses
       always get a vertical scrollbar at the widget's right edge. */
    height: 100%;
    box-sizing: border-box;
    overflow-y: auto;
    overflow-x: hidden;
    /* Containing block for the refresh loading overlay. */
    position: relative;
}

.dbx-w-core-api-header {
    display: flex;
    align-items: center;
    gap: .35rem;
    padding-bottom: .35rem;
    border-bottom: 1px solid color-mix(in srgb, var(--dbx-w-api-accent, var(--bs-primary, #6366f1)) 15%, transparent);
}

.dbx-w-core-api-title {
    margin: 0;
    font-size: .82rem;
    font-weight: 600;
    color: var(--bs-secondary-color, #64748b);
    text-transform: uppercase;
    letter-spacing: .06em;
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    /* Tiny coloured dot before the title to tie the header to the accent stripe.
       Uses currentColor when no accent so it stays subtle for unaccented modes. */
    display: inline-flex;
    align-items: center;
    gap: .4rem;
}
.dbx-w-core-api-title::before {
    content: "";
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--dbx-w-api-accent, var(--bs-primary, #6366f1));
    flex-shrink: 0;
}

.dbx-w-core-api-title-faded { opacity: .55; font-weight: 500; }
.dbx-w-core-api-title-faded::before { background: var(--bs-secondary-color, #94a3b8); }

.dbx-w-core-api-refresh {
    appearance: none;
    border: 1px solid transparent;
    background: transparent;
    color: var(--bs-secondary-color, #64748b);
    cursor: pointer;
    width: 28px;
    height: 28px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font: inherit;
    border-radius: .4rem;
    transition: background-color .12s ease, color .12s ease, border-color .12s ease, transform .08s ease;
}
.dbx-w-core-api-refresh:hover {
    background: color-mix(in srgb, var(--bs-primary, #6366f1) 10%, transparent);
    border-color: color-mix(in srgb, var(--bs-primary, #6366f1) 25%, transparent);
    color: var(--bs-primary, #6366f1);
}
.dbx-w-core-api-refresh:active { transform: scale(.94); }
.dbx-w-core-api-refresh:focus-visible {
    outline: 2px solid var(--bs-primary, #6366f1);
    outline-offset: 1px;
}
.dbx-w-core-api-refresh[disabled] { opacity: .55; cursor: progress; }
.dbx-w-core-api-refresh i { font-size: .95rem; }

.dbx-w-core-api-refresh .spin,
.dbx-w-core-api-status .spin {
    display: inline-block;
    animation: dbx-spin 1s linear infinite;
}
@keyframes dbx-spin {
    from { transform: rotate(0deg); }
    to { transform: rotate(360deg); }
}

/* ---- Shared refresh loading overlay ----
   A subtle scrim + spinner laid over a widget whose data is refreshing. The
   stale content stays readable underneath (semi-transparent background), so
   the viewer sees "this is updating" without losing the current values. Used
   by the API widget on its own refresh and by bound consumers (Map, host
   widgets via the data bus loading signal) while their source refetches. */
.dbx-w-loading-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--bs-body-bg, #ffffff) 55%, transparent);
    border-radius: inherit;
    z-index: 5;
    /* Let clicks fall through to the content underneath — the overlay is
       purely an indicator, never an interaction blocker. */
    pointer-events: none;
}
.dbx-w-loading-spinner {
    width: 1.5rem;
    height: 1.5rem;
    box-sizing: border-box;
    border: 2.5px solid color-mix(in srgb, var(--bs-primary, #6366f1) 25%, transparent);
    border-top-color: var(--bs-primary, #6366f1);
    border-radius: 50%;
    animation: dbx-spin .8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
    .dbx-w-loading-spinner { animation-duration: 2s; }
}

.dbx-w-core-api-status {
    color: var(--bs-secondary-color, #64748b);
    font-size: .85rem;
    display: flex;
    align-items: center;
    gap: .35rem;
    flex-wrap: wrap;
}

.dbx-w-core-api-empty .dbx-w-core-api-hint {
    width: 100%;
    color: var(--bs-secondary-color, #94a3b8);
    font-size: .75rem;
    margin-top: .15rem;
}

.dbx-w-core-api-error {
    color: var(--bs-danger, #dc2626);
}

/* Stat mode: label row + big value. Accent stripe on the left matches the
   demo stat widget convention. */
.dbx-w-core-api-mode-stat {
    border-left: 4px solid var(--dbx-w-api-accent, var(--bs-primary, #6366f1));
}
.dbx-w-core-api-stat { display: flex; flex-direction: column; gap: .25rem; }
.dbx-w-core-api-stat-row {
    display: flex;
    align-items: center;
    gap: .35rem;
    color: var(--bs-secondary-color, #64748b);
    font-size: .85rem;
}
.dbx-w-core-api-stat-icon { color: var(--dbx-w-api-accent, var(--bs-primary, #6366f1)); }
.dbx-w-core-api-stat-label { font-weight: 500; }
.dbx-w-core-api-stat-value {
    font-size: 1.5rem;
    font-weight: 700;
    color: var(--bs-emphasis-color, #0f172a);
    line-height: 1.1;
    word-break: break-word;
}

/* JSON mode: monospace pre, scrolls inside the cell via cell-body's overflow. */
.dbx-w-core-api-json {
    margin: 0;
    font-family: ui-monospace, "Cascadia Code", "Consolas", monospace;
    font-size: .78rem;
    color: var(--bs-body-color, #0f172a);
    white-space: pre;
    max-width: 100%;
}

/* Template mode: rendered markdown body. */
.dbx-w-core-api-template { font-size: .9rem; line-height: 1.5; }
.dbx-w-core-api-template p:first-child { margin-top: 0; }
.dbx-w-core-api-template p:last-child { margin-bottom: 0; }

/* Table mode. The widget itself already provides vertical overflow, so the
   wrap only handles horizontal — wide tables scroll sideways within the
   widget instead of escaping the cell. */
.dbx-w-core-api-table-wrap {
    width: 100%;
    overflow-x: auto;
}
.dbx-w-core-api-table {
    width: 100%;
    border-collapse: collapse;
    font-size: .82rem;
    color: var(--bs-body-color, #0f172a);
}
.dbx-w-core-api-table thead th {
    text-align: left;
    font-weight: 600;
    font-size: .72rem;
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--bs-secondary-color, #64748b);
    padding: .35rem .55rem;
    border-bottom: 1px solid var(--bs-border-color, #e5e7eb);
    background: var(--bs-tertiary-bg, #f8fafc);
    position: sticky;
    top: 0;
    /* Sticky thead lifts above scrolled tbody rows — useful for tall tables
       inside tall cells. The widget's own padding sits ABOVE the sticky
       header, so a tiny background tint avoids the rows showing through. */
    z-index: 1;
}
.dbx-w-core-api-table tbody td {
    padding: .35rem .55rem;
    border-bottom: 1px solid color-mix(in srgb, var(--bs-border-color, #e5e7eb) 50%, transparent);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    max-width: 240px;
}
.dbx-w-core-api-table tbody tr:hover { background: color-mix(in srgb, var(--bs-primary, #6366f1) 5%, transparent); }
.dbx-w-core-api-table tbody tr:last-child td { border-bottom: 0; }
.dbx-w-core-api-table-more {
    margin-top: .35rem;
    font-size: .72rem;
    color: var(--bs-secondary-color, #94a3b8);
    font-style: italic;
}

.dbx-w-core-api-meta {
    font-size: .7rem;
    color: var(--bs-secondary-color, #94a3b8);
    display: flex;
    align-items: center;
    gap: .25rem;
    flex-shrink: 0;
    white-space: nowrap;
    /* Match the refresh button's box height and kill the text line-box leading
       so the timestamp sits on the button's optical centre, not a few px high. */
    height: 28px;
    line-height: 1;
}
.dbx-w-core-api-meta i {
    display: flex;
    align-items: center;
    line-height: 1;
}

/* ---- API widget settings editor ---- */
/* Every button below sets `appearance: none` + explicit border/background/font
   to defeat the browser's default <button> chrome (Chromium / Firefox render
   raw <button> with a 2px buttonface border that would otherwise punch
   through the styled look). */

.dbx-w-core-api-settings .dbx-api-headers {
    display: flex;
    flex-direction: column;
    gap: .4rem;
    margin-bottom: .25rem;
}
.dbx-api-header-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.2fr) auto auto;
    gap: .35rem;
    align-items: center;
    padding: .25rem;
    background: var(--bs-tertiary-bg, #f8fafc);
    border: 1px solid var(--bs-border-color, #e5e7eb);
    border-radius: .4rem;
}
.dbx-api-header-row .dbx-settings-input {
    padding: .3rem .45rem;
    font-size: .82rem;
    background: var(--bs-body-bg, #fff);
}

.dbx-api-header-secret {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    width: 30px;
    height: 30px;
    border-radius: .35rem;
    color: var(--bs-secondary-color, #94a3b8);
    border: 1px solid transparent;
    transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.dbx-api-header-secret input { position: absolute; opacity: 0; pointer-events: none; }
.dbx-api-header-secret i { font-size: 1rem; }
.dbx-api-header-secret:hover { background: var(--bs-body-bg, #fff); color: var(--bs-emphasis-color, #0f172a); }
.dbx-api-header-secret:has(input:checked) {
    color: var(--bs-primary, #6366f1);
    background: color-mix(in srgb, var(--bs-primary, #6366f1) 12%, transparent);
    border-color: color-mix(in srgb, var(--bs-primary, #6366f1) 35%, transparent);
}

.dbx-api-header-remove {
    appearance: none;
    border: 1px solid transparent;
    background: transparent;
    color: var(--bs-secondary-color, #94a3b8);
    cursor: pointer;
    width: 30px;
    height: 30px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border-radius: .35rem;
    font: inherit;
    transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.dbx-api-header-remove i { font-size: 1rem; }
.dbx-api-header-remove:hover {
    color: #fff;
    background: var(--bs-danger, #dc2626);
    border-color: var(--bs-danger, #dc2626);
}

.dbx-api-header-add {
    appearance: none;
    align-self: flex-start;
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    border: 1px dashed var(--bs-border-color, #cbd5e1);
    background: transparent;
    color: var(--bs-secondary-color, #64748b);
    padding: .4rem .75rem;
    border-radius: .4rem;
    font: inherit;
    font-size: .82rem;
    font-weight: 500;
    cursor: pointer;
    transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.dbx-api-header-add:hover {
    border-style: solid;
    border-color: var(--bs-primary, #6366f1);
    color: var(--bs-primary, #6366f1);
    background: color-mix(in srgb, var(--bs-primary, #6366f1) 8%, transparent);
}

/* Radio group as styled cards rather than naked radios. */
.dbx-api-radiogroup {
    display: flex;
    flex-direction: column;
    gap: .35rem;
    margin-bottom: .35rem;
}
.dbx-api-radiogroup label {
    display: flex;
    align-items: flex-start;
    gap: .5rem;
    font-size: .85rem;
    cursor: pointer;
    padding: .5rem .65rem;
    border: 1px solid var(--bs-border-color, #e5e7eb);
    border-radius: .4rem;
    background: var(--bs-body-bg, #fff);
    transition: border-color .12s ease, background-color .12s ease;
}
.dbx-api-radiogroup label:hover { border-color: var(--bs-primary, #6366f1); }
.dbx-api-radiogroup label:has(input:checked) {
    border-color: var(--bs-primary, #6366f1);
    background: color-mix(in srgb, var(--bs-primary, #6366f1) 8%, transparent);
}
.dbx-api-radiogroup input { margin: .15rem 0 0 0; accent-color: var(--bs-primary, #6366f1); }
.dbx-api-hint {
    display: block;
    color: var(--bs-secondary-color, #94a3b8);
    font-size: .72rem;
    font-style: normal;
    margin-top: .1rem;
}

/* Segmented control for display mode. Full-width flex so 4 tabs fit the
   settings sidebar without overflowing; each tab gets an equal share of the
   row. appearance:none on every tab so the browser's default button border
   can't punch through. */
.dbx-api-tabs {
    display: flex;
    width: 100%;
    background: var(--bs-tertiary-bg, #f1f5f9);
    border: 1px solid var(--bs-border-color, #e5e7eb);
    border-radius: .5rem;
    padding: .2rem;
    gap: .15rem;
    margin-bottom: .35rem;
    box-sizing: border-box;
}
.dbx-api-tab {
    appearance: none;
    flex: 1 1 0;
    min-width: 0;
    border: 0;
    background: transparent;
    color: var(--bs-secondary-color, #64748b);
    padding: .4rem .35rem;
    font: inherit;
    font-size: .82rem;
    font-weight: 500;
    border-radius: .35rem;
    cursor: pointer;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: background-color .12s ease, color .12s ease, box-shadow .12s ease;
}
.dbx-api-tab:hover { color: var(--bs-emphasis-color, #0f172a); }
.dbx-api-tab.active {
    background: var(--bs-body-bg, #fff);
    color: var(--bs-primary, #6366f1);
    box-shadow: 0 1px 3px rgba(15, 23, 42, .1);
}
.dbx-api-tab:focus-visible {
    outline: 2px solid var(--bs-primary, #6366f1);
    outline-offset: 1px;
}

/* ---- Core map widget ---- */

.dbx-w-core-map {
    padding: .75rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    /* Map fills the cell. Release the height clamp so a tall settings editor
       can co-exist if the user ever uses the same component shape elsewhere. */
    height: 100%;
    min-height: 0;
    overflow: hidden;
    /* Containing block for the source-refresh loading overlay. */
    position: relative;
}

.dbx-w-core-map-header {
    display: flex;
    align-items: baseline;
    gap: .5rem;
}

.dbx-w-core-map-title {
    margin: 0;
    font-size: .9rem;
    font-weight: 600;
    color: var(--bs-secondary-color, #64748b);
    text-transform: uppercase;
    letter-spacing: .04em;
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dbx-w-core-map-meta {
    font-size: .72rem;
    color: var(--bs-secondary-color, #94a3b8);
}

.dbx-w-core-map-host {
    flex: 1;
    min-height: 120px;
    width: 100%;
    /* jsVectorMap injects its SVG inside this node — width: 100%; height: 100%
       on its own .jvm-container makes the map scale to whatever room we give. */
}

/* Editor-side parse feedback. */
.dbx-w-core-map-error,
.dbx-w-core-map-okay,
.dbx-w-core-map-hint {
    display: flex;
    align-items: center;
    gap: .35rem;
    font-size: .78rem;
    margin: .15rem 0 .25rem;
}
.dbx-w-core-map-error { color: var(--bs-danger, #dc2626); }
.dbx-w-core-map-okay { color: #16a34a; }
.dbx-w-core-map-hint { color: var(--bs-secondary-color, #64748b); }

/* Live-source indicator in the map widget header. */
.dbx-w-core-map-meta .bi-link-45deg {
    color: var(--bs-primary, #6366f1);
    margin-right: .1rem;
}

/* ---- Core chart widget (ApexCharts) ---- */

.dbx-w-core-chart {
    padding: .75rem;
    display: flex;
    flex-direction: column;
    gap: .5rem;
    height: 100%;
    min-height: 0;
    overflow: hidden;
    /* Containing block for the source-refresh loading overlay. */
    position: relative;
}

.dbx-w-core-chart-header {
    display: flex;
    align-items: baseline;
    gap: .5rem;
}

.dbx-w-core-chart-title {
    margin: 0;
    font-size: .9rem;
    font-weight: 600;
    color: var(--bs-secondary-color, #64748b);
    text-transform: uppercase;
    letter-spacing: .04em;
    flex: 1;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.dbx-w-core-chart-meta {
    font-size: .72rem;
    color: var(--bs-secondary-color, #94a3b8);
}
.dbx-w-core-chart-meta .bi-link-45deg {
    color: var(--bs-primary, #6366f1);
    margin-right: .1rem;
}

/* The chart host fills the remaining cell height; ApexCharts measures this
   node, so it must have a real (flex-derived) height. The wrap is the
   positioning context for the empty-state + loading overlays. */
.dbx-w-core-chart-host-wrap {
    position: relative;
    flex: 1;
    min-height: 120px;
}
.dbx-w-core-chart-host {
    width: 100%;
    height: 100%;
}

/* Empty-state card shown when there's nothing to plot (no data / source not
   yet published / unmapped fields). Centred over the host. */
.dbx-w-core-chart-empty {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .25rem;
    text-align: center;
    color: var(--bs-secondary-color, #94a3b8);
    pointer-events: none;
    padding: .5rem;
}
.dbx-w-core-chart-empty .bi {
    font-size: 1.5rem;
    opacity: .6;
}
.dbx-w-core-chart-empty span { font-size: .82rem; }
.dbx-w-core-chart-empty-hint { font-size: .72rem; opacity: .8; max-width: 24ch; }

/* Editor-side parse feedback (mirrors the map editor's). */
.dbx-w-core-chart-error,
.dbx-w-core-chart-okay,
.dbx-w-core-chart-hint {
    display: flex;
    align-items: center;
    gap: .35rem;
    font-size: .78rem;
    margin: .15rem 0 .25rem;
}
.dbx-w-core-chart-error { color: var(--bs-danger, #dc2626); }
.dbx-w-core-chart-okay { color: #16a34a; }
.dbx-w-core-chart-hint { color: var(--bs-secondary-color, #64748b); }

/* Grouped checkboxes row in the chart editor. */
.dbx-settings-checks {
    display: flex;
    flex-wrap: wrap;
    gap: .25rem 1rem;
    margin-top: .35rem;
}
