/**
 * Sales Dashboard (v1) — tool stylesheet.
 *
 * Everything is namespaced sd- so nothing here can reach another tool. No
 * colours are declared: surfaces, text and borders come from --fw-* tokens and
 * every chart colour is one of frikwork's categorical --fw-chart-N slots, so
 * both themes work and the palette is shared with every other chart in the
 * suite.
 *
 * The card motion (breathing glow, rotating border ring, drop-in landing,
 * settle wobble) is frikwork's LIVELY tier — the page opts in with
 * motion => 'lively' and the keyframes below are frikwork's own. v1 shipped
 * private copies of all four; they were upstreamed byte-for-byte, so this file
 * only carries what frikwork does not have: the hero takeover, the shatter,
 * and the face easter egg.
 */

/* ---------------------------------------------------------------------------
 * Toolbar (page-heading actions)
 * ------------------------------------------------------------------------ */

.sd-toolbar {
    display: flex;
    align-items: center;
    gap: var(--fw-space-sm);
    flex-wrap: wrap;
}

.sd-syncstate {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    font-size: 0.78rem;
    color: var(--fw-text-muted);
    white-space: nowrap;
}
.sd-syncstate i { color: var(--fw-success); font-size: 0.6em; }
.sd-syncstate.is-error { color: var(--fw-danger); }
.sd-syncstate.is-error i { color: var(--fw-danger); }

.sd-refresh-form { display: inline-flex; margin: 0; }

/* The brand spinner takes the icon's place while a refresh runs. It is a real
   element, not a ::before ring, because a pseudo-element cannot hold an SVG. */
.sd-refresh-btn .ccm-spinner { display: none; }
.sd-refresh-btn.is-loading i { display: none; }
.sd-refresh-btn.is-loading .ccm-spinner { display: inline-block; }

.sd-fullscreen-btn .sd-icon-collapse { display: none; }
.sd-fullscreen-btn.is-fullscreen .sd-icon-expand { display: none; }
.sd-fullscreen-btn.is-fullscreen .sd-icon-collapse { display: inline-block; }

.sd-picker { position: relative; }
.sd-picker__panel { width: 260px; max-height: 70vh; overflow-y: auto; }
.sd-picker__item { cursor: pointer; }

/* ---------------------------------------------------------------------------
 * Grids
 * ------------------------------------------------------------------------ */

.sd-grid {
    display: grid;
    gap: var(--fw-space-md);
    margin-bottom: var(--fw-space-lg);
    /* The drop-in stagger step. Cards set --fw-drop-i inline from their grid
       position, so the two grids read as one continuous cascade. */
    --fw-drop-step: 90ms;
}
.sd-grid--stats { grid-template-columns: repeat(4, 1fr); }
.sd-grid--donuts { grid-template-columns: repeat(3, 1fr); }

@media (max-width: 1200px) {
    .sd-grid--stats, .sd-grid--donuts { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 700px) {
    .sd-grid--stats, .sd-grid--donuts { grid-template-columns: 1fr; }
}

/* ---------------------------------------------------------------------------
 * Cards
 *
 * The animation rules below are (0,2,0) — the same specificity as frikwork's
 * `[data-fw-motion="lively"] .fw-card`, which also sets the `animation`
 * shorthand. This stylesheet loads after frikwork.css, so on the tie these
 * win; a bare `.fw-anim-drop` (0,1,0) would lose and silently never play.
 * Each one therefore has to re-declare the ambient breathe alongside its own
 * animation, or the card stops breathing for the duration.
 * ------------------------------------------------------------------------ */

.sd-card {
    /* opacity/transform are the widget-picker fade and the hero's sibling
       shrink; both are driven by class, not by an animation. */
    transition: opacity 260ms ease, transform 380ms cubic-bezier(0.22, 1, 0.36, 1);
}

.sd-card.fw-anim-drop {
    animation:
        fw-breathe 7s ease-in-out infinite,
        fw-drop 1.05s var(--fw-ease-settle) backwards;
    animation-delay: 0s, calc(var(--fw-drop-i, 0) * var(--fw-drop-step, 90ms));
}

.sd-card.fw-anim-wobble {
    animation:
        fw-breathe 7s ease-in-out infinite,
        fw-settle-wobble 1.7s ease-in-out;
}

@media (prefers-reduced-motion: reduce) {
    .sd-card { transition: none; }
    .sd-card.fw-anim-drop,
    .sd-card.fw-anim-wobble { animation: none; }
}

.sd-card__title {
    font-size: 1.15rem;
    font-weight: 600;
    margin: 0 0 1.25rem;
}

/* Widget picker state. */
.sd-card.is-hidden { display: none; }
.sd-card.is-fading { opacity: 0; transform: scale(0.92); pointer-events: none; }
.sd-card.is-entering { opacity: 0; transform: scale(0.92); }

/* Numbers count up, so proportional figures (v1's deliberate choice) keep the
   headline visually centred instead of locking to a tabular grid. */
.sd-num { font-variant-numeric: proportional-nums; }

/* ---------------------------------------------------------------------------
 * Stat tiles
 *
 * frikwork's .fw-stat-card already supplies the padding and the 3rem value.
 * Only the ordering differs: this dashboard puts the label above the number,
 * and does not shout it in small caps.
 * ------------------------------------------------------------------------ */

.sd-stat { display: block; }
.sd-stat__label {
    font-size: 1rem;
    color: var(--fw-text-muted);
    margin-bottom: 0.4rem;
    text-transform: none;
    letter-spacing: normal;
}
.sd-stat__value {
    font-size: 3rem;
    font-weight: 700;
    line-height: 1.05;
    color: var(--fw-text);
}

/* ---------------------------------------------------------------------------
 * Donuts
 * ------------------------------------------------------------------------ */

.sd-donut-card {
    /* Explicit column flex, rather than default block flow: the grid stretches
       every row to the tallest card, and without this the leftover height gets
       split around the content instead of sitting below it. */
    display: flex;
    flex-direction: column;
}

/* align-items: flex-start, not center — an eight-row legend is taller than the
   fixed 220px ring, and centring dragged that one card's ring ~50px down while
   every shorter card's stayed flush. */
.sd-donut {
    display: flex;
    align-items: flex-start;
    gap: 2rem;
    flex-shrink: 0;
}

.sd-donut__ring { position: relative; width: 220px; height: 220px; flex-shrink: 0; }
.sd-donut__svg {
    width: 100%;
    height: 100%;
    transform: rotate(-90deg);
    /* The hover stroke-width (6.2) pushes the ring past the 36-unit viewBox;
       without this the SVG clips whichever slice is highlighted. */
    overflow: visible;
}

.sd-arc {
    /* No stroke-dasharray here on purpose. The SVG attribute holds the real
       slice size (written server-side) so the ring is correct with JS off, and
       the draw-in animation only ever overrides it via inline style, which
       already outranks a stylesheet rule. */
    transition:
        stroke-dasharray 900ms var(--fw-ease-draw, cubic-bezier(0.16, 1, 0.3, 1)),
        stroke-width 150ms ease,
        filter 150ms ease;
    cursor: pointer;
}
.sd-arc:hover, .sd-arc.is-active { stroke-width: 6.2; filter: brightness(1.18); }

.sd-donut__total {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}
.sd-donut__total b { font-size: 2.1rem; line-height: 1.1; }
.sd-donut__total small { color: var(--fw-text-muted); font-size: 0.85rem; }

.sd-legend {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    font-size: 1.05rem;
    min-width: 0;
    flex: 1;
}
.sd-legend__row {
    display: flex;
    align-items: center;
    gap: 0.6rem;
    border-radius: var(--fw-radius-sm);
    padding: 0.2rem 0.4rem;
    transition: background 150ms ease;
    cursor: pointer;
}
.sd-legend__row:hover, .sd-legend__row.is-active { background: var(--fw-bg-hover); }
.sd-legend__swatch { width: 12px; height: 12px; border-radius: 3px; flex-shrink: 0; }
.sd-legend__name {
    color: var(--fw-text-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sd-legend__val {
    margin-left: auto;
    color: var(--fw-text);
    font-weight: 600;
    padding-left: 0.75rem;
}

@media (prefers-reduced-motion: reduce) {
    .sd-arc, .sd-legend__row { transition: none; }
}

/* Cursor-follow tooltip. Uses frikwork's .fw-tooltip surface so it matches
   every other tooltip in the suite, with a bold value line above the label. */
.sd-tip { display: flex; flex-direction: column; gap: 0.1rem; }
.sd-tip__value { font-size: 0.85rem; font-weight: 700; }

/* ---------------------------------------------------------------------------
 * "Improved value" hero takeover
 *
 * Every other card shrinks to nothing over the SAME duration the hero grows
 * (that half is an inline JS transition, SD_HERO_GROW_MS) so the two motions
 * read as one move: the hero expanding into the space the others vacate. Keep
 * this 1400ms in sync with SD_HERO_GROW_MS in dashboard.js.
 * ------------------------------------------------------------------------ */

body.sd-hero-active { overflow: hidden; }

body.sd-hero-active .sd-card:not(.is-hero) {
    transition: transform 1400ms cubic-bezier(0.22, 1, 0.36, 1), opacity 1400ms ease;
    transform: scale(0);
    opacity: 0;
    pointer-events: none;
}

.sd-card.is-hero { z-index: 900; cursor: pointer; }

/* The border ring keeps travelling, and also walks the hue wheel while the
   hero is held — a moving colour change in place of a spin. */
.sd-card.is-hero::before {
    animation:
        fw-border-glow 8s linear infinite,
        sd-hero-hue 2s linear infinite;
}
@keyframes sd-hero-hue {
    from { filter: hue-rotate(0deg) saturate(1.4); }
    to   { filter: hue-rotate(360deg) saturate(1.4); }
}

/* The "Up $505" badge. Fixed, not appended into the hero — the card is
   transform-scaled, so anything inside it would be scaled too. */
.sd-hero-report {
    position: fixed;
    top: 96px;
    left: 50%;
    z-index: 920;
    transform: translateX(-50%) translateY(-14px);
    background: var(--fw-brand);
    color: var(--fw-on-primary);
    font-size: 1.7rem;
    font-weight: 800;
    letter-spacing: 0.01em;
    padding: 0.55rem 1.5rem;
    border-radius: var(--fw-radius-pill);
    box-shadow: var(--fw-shadow-glow, 0 10px 34px rgba(0, 0, 0, 0.35));
    opacity: 0;
    transition: opacity 400ms ease, transform 400ms cubic-bezier(0.22, 1, 0.36, 1);
    pointer-events: none;
    white-space: nowrap;
}
.sd-hero-report.is-shown { opacity: 1; transform: translateX(-50%) translateY(0); }

/* Shatter pieces for the hero's exit. Each shard is an irregular polygon
   (clip-path set inline per piece from the radial-crack tessellation), not a
   rectangle — real glass, not a grid. drop-shadow rather than box-shadow so
   the edge glow follows the clip silhouette instead of being clipped away. */
.sd-explosion { position: fixed; inset: 0; z-index: 950; pointer-events: none; }
.sd-shard {
    position: fixed;
    background: var(--fw-gradient-secondary);
    filter: drop-shadow(0 0 5px color-mix(in srgb, var(--fw-brand) 55%, transparent));
    opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
    body.sd-hero-active .sd-card, .sd-card.is-hero { transition: none; }
    .sd-card.is-hero::before { animation: fw-border-glow 8s linear infinite; }
    .sd-hero-report { transition: opacity 400ms ease; }
}

/* ---------------------------------------------------------------------------
 * Face easter egg
 *
 * A donut's centre value briefly becomes a face — the ring reads as the head,
 * the inline SVG (eyes + mouth) as the face inside it. The card runs
 * frikwork's settle wobble on a loop while the face is up, and the SVG runs
 * the exact inverse ROTATION at the same duration and keyframe percentages, so
 * the card rocks and the face stays upright. Both start in the same tick, so
 * the two rotations stay perfectly cancelled.
 * ------------------------------------------------------------------------ */

.sd-donut__total b.is-face { display: inline-flex; color: var(--fw-brand-text); }
.sd-donut__total b.is-face svg { display: block; width: 148px; height: 148px; }

/* display:none, not opacity:0 — the "total" caption must leave the flow
   entirely or it reserves vertical space and pushes the face off centre. */
.sd-donut__total b.is-face + small { display: none; }

.sd-card.is-face-wobble {
    animation:
        fw-breathe 7s ease-in-out infinite,
        fw-settle-wobble 2.2s ease-in-out infinite;
}
.sd-card.is-face-wobble .sd-donut__total b.is-face svg {
    animation: sd-settle-counter 2.2s ease-in-out infinite;
}

/* The exact inverse of fw-settle-wobble's rotation, same percentages. Only the
   rotation is countered — the bob and squash still carry the face along. */
@keyframes sd-settle-counter {
    0%        { transform: rotate(0deg); }
    9%        { transform: rotate(2.5deg); }
    19%       { transform: rotate(-2deg); }
    29%       { transform: rotate(1.2deg); }
    37%       { transform: rotate(-0.7deg); }
    45%, 100% { transform: rotate(0deg); }
}

@media (prefers-reduced-motion: reduce) {
    .sd-card.is-face-wobble { animation: none; }
    .sd-card.is-face-wobble .sd-donut__total b.is-face svg { animation: none; }
}

/* ---------------------------------------------------------------------------
 * Fullscreen
 *
 * The kiosk view drops the suite chrome so the cards get the whole screen.
 * :fullscreen matches <html>, which is what the toggle requests.
 * ------------------------------------------------------------------------ */

:root:fullscreen .app-header,
:root:fullscreen .fw-footer,
:root:fullscreen .app-pagehead__text { display: none; }
:root:fullscreen .app-main { padding-top: var(--fw-space-md); }
:root:fullscreen .app-pagehead { margin-bottom: var(--fw-space-md); justify-content: flex-end; }
