/* grid-canvas.css
 *
 * Static styles for the Dashboard widget canvas. Per-cell *dynamic* state
 * (live drag transform, FLIP animation transforms) is applied via a JS-owned
 * <style> tag in grid-canvas-interop.js using [data-grid-item-id="..."]
 * selectors with !important — that path is the one that survives Blazor's
 * inline-style diff. Anything that lives here is static / global.
 */

/* Break out of XAF / DevExpress form-layout column widths so the canvas
   spans 100% of the DetailView's available width. The form-layout default
   wraps each LayoutItem in a Bootstrap-style dxbl-col-md-* track which caps
   the canvas to a fraction of the row. Walking up from .grid-canvas with
   :has(), we force every ancestor in the chain (item, row, group, column
   wrapper) to flex-basis 100% with no max-width. */
.dxbl-fl-item:has(.grid-canvas),
.dxbl-fl-group:has(.grid-canvas),
.dxbl-col:has(.grid-canvas),
.dxbl-row:has(.grid-canvas) {
    flex: 1 1 100% !important;
    max-width: 100% !important;
    width: 100% !important;
}

.grid-canvas-cell {
    will-change: transform;
}

/* Responsive stacking — view mode only.
 *
 * The canvas is a fixed 12-column square grid; on a narrow phone each cell
 * collapses to ~33px and widgets shrink into illegible squares in the
 * top-left. Below 600px (the canvas declares container-type:inline-size, so
 * this is a container query against the canvas width, not the viewport) we
 * drop to a single column and let cells stack full-width at a readable
 * height. Per-cell grid-column/grid-row are written as INLINE styles in
 * GridCanvas.razor, so the override needs !important to win the cascade.
 *
 * Scoped to --view: design mode keeps the precise grid because the drag/
 * resize JS interop assumes fixed 12-col coordinates, and editing a
 * dashboard on a phone is not a supported flow. Stacking order follows DOM
 * order, which DashboardCanvas sorts by (RowIndex, ColumnIndex) so cards
 * stack in the same top-to-bottom order the user laid them out.
 */
@container (max-width: 600px) {
    .grid-canvas--view {
        grid-template-columns: 1fr !important;
        grid-auto-rows: minmax(120px, auto) !important;
        min-height: 0 !important;
    }

    .grid-canvas--view .grid-canvas-cell {
        grid-column: 1 / -1 !important;
        grid-row: auto !important;
    }
}

/* Design-mode slot guides — painted as a CSS background gradient on the
   canvas root using the --grid-cols / --grid-row-h / --grid-gap / --grid-pad
   CSS variables set by GridCanvas.razor. Replaces the SlotRowCount × ColumnCount
   inline-styled <div>s we used to emit (up to ~480 DOM nodes at 40×12) — the
   browser now composites the guides directly. The column step is
   (100% - 2 * pad + gap) / cols, which is the algebraic identity of
   (col-width + gap) once gaps and padding are accounted for. */
.grid-canvas--design {
    background-image:
        linear-gradient(to right, #eee 1px, transparent 1px),
        linear-gradient(to bottom, #eee 1px, transparent 1px);
    background-size:
        calc((100% - var(--grid-pad) * 2 + var(--grid-gap)) / var(--grid-cols)) 100%,
        100% calc(var(--grid-row-h) + var(--grid-gap));
    background-position: var(--grid-pad) 0, 0 var(--grid-pad);
    background-origin: padding-box;
    background-clip: padding-box;
    background-repeat: repeat;
}

.grid-canvas--design .grid-canvas-cell {
    user-select: none;
    cursor: move;
}

/* Edit / Delete overlay — rendered as a sibling of the cells (NOT a child),
   so it never sits under a data-grid-role ancestor and the JS interop's
   closest('[data-grid-role]') returns null on a button click. The overlay
   shares the cell's grid placement and pins to the top-right corner via
   align-self:start + justify-self:end (set inline). */
.grid-canvas-cell-controls {
    cursor: default;
    user-select: none;
}

.grid-canvas-cell-controls button {
    transition: background-color 80ms ease;
}

.grid-canvas-cell-controls button:hover {
    background-color: rgba(0, 0, 0, 0.08) !important;
}

.grid-canvas__resize-handle {
    touch-action: none;
}

.grid-canvas__drop-preview {
    position: fixed;
    pointer-events: none;
    border: 2px dashed #1ba1ff;
    background: rgba(27, 161, 255, 0.10);
    border-radius: 4px;
    z-index: 9999;
    box-sizing: border-box;
    transition: left 60ms linear,
                top 60ms linear,
                width 60ms linear,
                height 60ms linear,
                opacity 80ms ease,
                border-color 80ms ease,
                background-color 80ms ease;
}

.grid-canvas__drop-preview--invalid {
    border-color: #d33;
    background: rgba(211, 51, 51, 0.10);
}

/* Widget shell state cells — WidgetShell<TData> renders one of these
   instead of the concrete widget body while loading, empty, or errored.
   The loading state hosts <DxLoadingIndicator>; the flex centering here
   positions the DevExpress indicator inside the grid cell. */
.widget-state {
    /* Size container so the error/empty content can scale with the cell using
       cqmin units (same responsive technique as the KPI badge). */
    container-type: size;
    container-name: wstate;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 100%;
    color: #868e96;
    font-size: clamp(0.7rem, 7cqmin, 0.875rem);
    overflow: hidden;
    box-sizing: border-box;
    user-select: none;
}

.widget-state--error {
    color: #c92a2a;
    flex-direction: column;
    gap: clamp(2px, 1.5cqmin, 6px);
    text-align: center;
    padding: clamp(4px, 4cqmin, 12px);
}

/* More specific than .dash-icon-mask (declared later) so the size wins. */
.widget-state--error .widget-state__error-icon {
    width: clamp(16px, 20cqmin, 34px);
    height: clamp(16px, 20cqmin, 34px);
}

.widget-state__error-title {
    font-weight: 600;
    color: #495057;
    font-size: clamp(0.72rem, 8cqmin, 1rem);
    line-height: 1.15;
    overflow-wrap: anywhere;
}

.widget-state__error-subtext {
    font-size: clamp(0.62rem, 5.5cqmin, 0.8125rem);
    color: #868e96;
    line-height: 1.2;
    overflow-wrap: anywhere;
}

/* Progressive disclosure: in short cells drop the subtext so the icon, title,
   and Try again button always remain. The title stays visible at every size
   (it scales down via clamp and wraps), so the user always sees what happened. */
@container wstate (max-height: 150px) {
    .widget-state__error-subtext { display: none; }
}

/* Error-state "Try again" DxButton — re-runs the widget loader in place
   (WidgetShell.Retry). DevExpress supplies the button chrome (outline/danger);
   we only add responsive sizing and keep it from overflowing a small cell. */
.widget-state__retry {
    max-width: 100%;
    font-size: clamp(0.68rem, 5.5cqmin, 0.8125rem);
    white-space: nowrap;
}

/* Refresh glyph for the Try again button, rendered through DxButton's
   IconCssClass slot. Masked black source SVG tinted to the button's text
   colour (matches the KPI widget's recolour). Absolute IImageService path so
   it resolves against the site root, not this stylesheet's /css/ folder. */
.widget-state__retry-ico {
    display: inline-block;
    width: 1em;
    height: 1em;
    background-color: currentColor;
    -webkit-mask: url(/IImageService/refresh-cw) no-repeat center / contain;
    mask: url(/IImageService/refresh-cw) no-repeat center / contain;
}

/* Shared icon glyph for dashboard buttons (header refresh, widget retry).
   A black source SVG from IImageService is shown through a CSS mask, so the
   glyph is painted with `currentColor` and follows its button's text colour —
   the same recolour technique the KPI widget uses for its icon. The mask url
   itself is set inline on each element so the relative IImageService path
   resolves against the page base (not this stylesheet's /css/ folder). */
.dash-icon-mask {
    display: inline-block;
    width: 15px;
    height: 15px;
    background-color: currentColor;
    flex: 0 0 auto;
}

/* No-data state stacks the placeholder image above the caption. */
.widget-state--empty {
    flex-direction: column;
    gap: clamp(2px, 1.5cqmin, 6px);
    text-align: center;
    padding: clamp(4px, 4cqmin, 12px);
}

/* No-data inbox glyph: larger than the inline button icons, scales with the
   cell, tinted with the empty state's muted-grey `currentColor` (.dash-icon-mask).
   More specific than .dash-icon-mask (declared earlier) so the size wins. */
.widget-state--empty .widget-state__no-data-img {
    width: clamp(28px, 32cqmin, 56px);
    height: clamp(28px, 32cqmin, 56px);
    opacity: 0.85;
}

.widget-state__no-data-text {
    color: #adb5bd;
    font-size: clamp(0.68rem, 7cqmin, 0.875rem);
    overflow-wrap: anywhere;
}

/* Very short cells: drop the caption, keep just the inbox glyph. */
@container wstate (max-height: 92px) {
    .widget-state__no-data-text { display: none; }
}

/* Dashboard header — title + subtitle (reuses DashboardDefinition.Description)
   on the left, relative last-refresh label and a manual refresh button on the
   right. Sits above the GridCanvas; the dashboard auto-refreshes every 5 min. */
.Dashboard-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
    padding: 12px 8px 8px;
}

.Dashboard-header__title {
    font-size: 1.25rem;
    font-weight: 600;
    color: #212529;
    line-height: 1.3;
}

.Dashboard-header__subtitle {
    font-size: 0.875rem;
    color: #868e96;
    margin-top: 2px;
}

.Dashboard-header__meta {
    display: flex;
    align-items: center;
    gap: 8px;
    flex-shrink: 0;
}

.Dashboard-header__refresh-label {
    font-size: 0.8125rem;
    color: #adb5bd;
    white-space: nowrap;
}

/* KPI widget icons are monochrome black source images. Adding `.theicon`
   flips them to white via the global --icon-filter-white variable (defined
   in site.css). Used by the KPI badge when the user picks a light font
   colour — the icon needs to follow the text. */
.theicon {
    filter: var(--icon-filter-white);
}

/* Widget editor popup (KPI / List / chart definitions). Their DetailViews use
   a dense multi-column layout that overflows XAF's default popup width, so the
   field labels collide. WidgetEditorPopupController tags the popup with this
   class; widen it (clamped to the viewport) while keeping the centered modal. */
.dxbl-modal > .dxbl-modal-root > .dxbl-popup.widget-editor-popup {
    width: min(1150px, 95vw) !important;
    max-width: 95vw !important;
}

/* Lookup/dropdown editors (e.g. Source) don't always stretch to their column
   like the enum combo boxes do, so they render short. Force the editors in the
   widget popup to fill their layout cell (already-full editors are unaffected). */
.dxbl-modal > .dxbl-modal-root > .dxbl-popup.widget-editor-popup .dxbl-combobox,
.dxbl-modal > .dxbl-modal-root > .dxbl-popup.widget-editor-popup .dxbl-text-edit,
.dxbl-modal > .dxbl-modal-root > .dxbl-popup.widget-editor-popup .dxbl-editor {
    width: 100% !important;
    max-width: 100% !important;
}

