/**
 * Sales Dashboard v2 — tool stylesheet.
 *
 * Everything is namespaced sd2- 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 and v2 both 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)
 * ------------------------------------------------------------------------ */

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

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

.sd2-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. */
.sd2-refresh-btn .ccm-spinner { display: none; }
.sd2-refresh-btn.is-loading i { display: none; }
.sd2-refresh-btn.is-loading .ccm-spinner { display: inline-block; }

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

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

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

.sd2-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;
}
.sd2-grid--stats { grid-template-columns: repeat(4, 1fr); }
.sd2-grid--donuts { grid-template-columns: repeat(3, 1fr); }
/* One card, full width, at every size — a 24-month axis squeezed into a third
   of the screen is unreadable, and it is the only widget that spans. */
.sd2-grid--trend { grid-template-columns: 1fr; }

@media (max-width: 1200px) {
    .sd2-grid--stats, .sd2-grid--donuts { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 700px) {
    .sd2-grid--stats, .sd2-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.
 * ------------------------------------------------------------------------ */

.sd2-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);
}

.sd2-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));
}

.sd2-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) {
    .sd2-card { transition: none; }
    .sd2-card.fw-anim-drop,
    .sd2-card.fw-anim-wobble { animation: none; }
}

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

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

/* Numbers count up, so proportional figures (the original's deliberate choice) keep the
   headline visually centred instead of locking to a tabular grid. */
.sd2-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.
 * ------------------------------------------------------------------------ */

.sd2-stat { display: block; }
.sd2-stat__label {
    font-size: 1rem;
    color: var(--fw-text-muted);
    margin-bottom: 0.4rem;
    text-transform: none;
    letter-spacing: normal;
}
.sd2-stat__value {
    font-size: 3rem;
    font-weight: 700;
    line-height: 1.05;
    color: var(--fw-text);
}
/* The comparison line under a headline number. Deliberately not coloured green
   or red: on a wall display a red tile reads as a fault in the dashboard from
   across the room, long before anyone is close enough to read the number that
   made it red. The arrow carries the direction. */
.sd2-stat__sub {
    margin-top: 0.45rem;
    font-size: 0.95rem;
    color: var(--fw-text-muted);
}

/* ---------------------------------------------------------------------------
 * Trend chart
 *
 * Two grids sharing a column count and a gap: bars above, month/year ticks
 * below. Deliberately NOT one grid with the labels inside each column — the
 * bars are percentage heights against the plot, and nesting the labels would
 * fold them into that percentage, so the tallest bar would stop short of the
 * top by exactly the height of the axis.
 * ------------------------------------------------------------------------ */

.sd2-trend-card { display: flex; flex-direction: column; min-height: 0; }
.sd2-trend { display: flex; flex-direction: column; flex: 1; min-height: 0; min-width: 0; }

.sd2-trend__peak {
    font-size: 0.85rem;
    color: var(--fw-text-muted);
    opacity: 0.8;
    margin-bottom: 0.5rem;
}

.sd2-trend__plot,
.sd2-trend__axis {
    display: grid;
    grid-template-columns: repeat(var(--sd2-cols, 24), minmax(0, 1fr));
    gap: 0 0.4rem;
}

/* The height the widget is sized by. Fullscreen overrides it to flex into
   whatever is left over, which is the whole reason this is not an SVG. */
.sd2-trend__plot {
    position: relative;
    height: clamp(150px, 24vh, 300px);
    min-height: 0;
    align-items: end;
    border-bottom: 1px solid var(--fw-text-muted);
}

/* The half-way line, drawn once across the plot rather than per column. */
.sd2-trend__plot::before {
    content: "";
    position: absolute;
    left: 0;
    right: 0;
    top: 50%;
    border-top: 1px dashed var(--fw-text-muted);
    opacity: 0.18;
    pointer-events: none;
}

.sd2-trend__col { display: flex; align-items: flex-end; height: 100%; min-width: 0; }

.sd2-trend__bar {
    width: 100%;
    /* Keeps a real but tiny month visible. Safe as a blanket rule because an
       empty month renders no bar element at all. */
    min-height: 3px;
    border-radius: 3px 3px 0 0;
    background: var(--fw-chart-1);
    transform-origin: bottom;
    animation: sd2-bar-rise 520ms var(--fw-ease-settle) both;
}
.sd2-trend__col:hover .sd2-trend__bar { background: var(--fw-brand); }

@keyframes sd2-bar-rise {
    from { transform: scaleY(0); }
    to   { transform: scaleY(1); }
}

.sd2-trend__axis { margin-top: 0.4rem; }
.sd2-trend__tick {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 0;
    font-size: 0.75rem;
    line-height: 1.3;
    color: var(--fw-text-muted);
    white-space: nowrap;
}
.sd2-trend__year { font-weight: 600; opacity: 0.7; }

.sd2-trend__empty {
    padding: 3rem 0;
    text-align: center;
    color: var(--fw-text-muted);
}

@media (prefers-reduced-motion: reduce) {
    .sd2-trend__bar { animation: none; }
}

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

.sd2-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. */
.sd2-donut {
    display: flex;
    align-items: flex-start;
    gap: 2rem;
    flex-shrink: 0;
}

.sd2-donut__ring { position: relative; width: 220px; height: 220px; flex-shrink: 0; }
.sd2-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;
}

.sd2-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;
}
.sd2-arc:hover, .sd2-arc.is-active { stroke-width: 6.2; filter: brightness(1.18); }

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

.sd2-legend {
    display: flex;
    flex-direction: column;
    gap: 0.6rem;
    font-size: 1.05rem;
    min-width: 0;
    flex: 1;
}
.sd2-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;
}
.sd2-legend__row:hover, .sd2-legend__row.is-active { background: var(--fw-bg-hover); }
.sd2-legend__swatch { width: 12px; height: 12px; border-radius: 3px; flex-shrink: 0; }
.sd2-legend__name {
    color: var(--fw-text-2);
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.sd2-legend__val {
    margin-left: auto;
    color: var(--fw-text);
    font-weight: 600;
    padding-left: 0.75rem;
}

@media (prefers-reduced-motion: reduce) {
    .sd2-arc, .sd2-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. */
.sd2-tip { display: flex; flex-direction: column; gap: 0.1rem; }
.sd2-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, SD2_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 SD2_HERO_GROW_MS in dashboard.js.
 * ------------------------------------------------------------------------ */

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

body.sd2-hero-active .sd2-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;
}

.sd2-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. */
.sd2-card.is-hero::before {
    animation:
        fw-border-glow 8s linear infinite,
        sd2-hero-hue 2s linear infinite;
}
@keyframes sd2-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. */
.sd2-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;
}
.sd2-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. */
.sd2-explosion { position: fixed; inset: 0; z-index: 950; pointer-events: none; }
.sd2-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.sd2-hero-active .sd2-card, .sd2-card.is-hero { transition: none; }
    .sd2-card.is-hero::before { animation: fw-border-glow 8s linear infinite; }
    .sd2-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.
 * ------------------------------------------------------------------------ */

.sd2-donut__total b.is-face { display: inline-flex; color: var(--fw-brand-text); }
.sd2-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. */
.sd2-donut__total b.is-face + small { display: none; }

.sd2-card.is-face-wobble {
    animation:
        fw-breathe 7s ease-in-out infinite,
        fw-settle-wobble 2.2s ease-in-out infinite;
}
.sd2-card.is-face-wobble .sd2-donut__total b.is-face svg {
    animation: sd2-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 sd2-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) {
    .sd2-card.is-face-wobble { animation: none; }
    .sd2-card.is-face-wobble .sd2-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; }

/* Fit the whole dashboard on the screen — nothing cut off, no scrollbar.
 *
 * On a wall display there is nobody to scroll, so anything below the fold may
 * as well not have been rendered. app-main becomes exactly the viewport and the
 * three grids divide it: the stats and the donuts take what they need, the
 * trend takes whatever is left.
 *
 * min-height:0 appears on every link in that chain deliberately. A flex item
 * defaults to min-height:auto and refuses to shrink below its content, so
 * without it the chart keeps its full height and pushes the donuts off the
 * bottom — which is the bug this replaced, wearing a different hat.
 *
 * Scoped to body.sd2-page, set by this tool's ccm_page_open() call: app-main is
 * the shared layout's element and every other tool in the suite scrolls
 * normally. */
:root:fullscreen body.sd2-page .app-main {
    height: 100vh;
    height: 100dvh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}
:root:fullscreen body.sd2-page .app-pagehead { flex: 0 0 auto; }
:root:fullscreen body.sd2-page .sd2-grid { margin-bottom: var(--fw-space-md); }
:root:fullscreen body.sd2-page .sd2-grid--stats,
:root:fullscreen body.sd2-page .sd2-grid--donuts { flex: 0 0 auto; }
:root:fullscreen body.sd2-page .sd2-grid--donuts { margin-bottom: 0; }

:root:fullscreen body.sd2-page .sd2-grid--trend { flex: 1 1 auto; min-height: 0; }
:root:fullscreen body.sd2-page .sd2-trend-card { min-height: 0; }
:root:fullscreen body.sd2-page .sd2-trend__plot {
    height: auto;
    flex: 1 1 auto;
    /* Below this the bars stop reading as a chart, so the donuts give up their
       space first — the safety valve below is what absorbs that. */
    min-height: 90px;
}

/* The rings are the other fixed lump in the column. Tied to viewport height so
   a short screen shrinks them rather than losing the bottom of the card. */
:root:fullscreen body.sd2-page .sd2-donut__ring {
    width: clamp(104px, 15vh, 220px);
    height: clamp(104px, 15vh, 220px);
}
:root:fullscreen body.sd2-page .sd2-donut { gap: 1.25rem; }
:root:fullscreen body.sd2-page .sd2-legend__row { padding-block: 0.1rem; }

/* The safety valve.
 *
 * A donut legend is up to eight rows and cannot be shrunk indefinitely, so on a
 * short enough screen something has to give. The page itself must never be what
 * gives — there is nobody at a wall display to scroll it, and the brief was no
 * overflow at all. So the legend scrolls inside its own card instead, which
 * keeps every figure reachable and the layout fixed. On the TV this is aimed
 * at there is enough height that it never engages. */
:root:fullscreen body.sd2-page .sd2-donut-card,
:root:fullscreen body.sd2-page .sd2-donut { min-height: 0; }
:root:fullscreen body.sd2-page .sd2-donut-card { overflow: hidden; }
:root:fullscreen body.sd2-page .sd2-legend { min-height: 0; overflow-y: auto; }

/* Same story one screen down: a laptop in fullscreen has far less height than
   the TV this is aimed at, so hand the chart a smaller share before the donuts
   have to. */
@media (max-height: 900px) {
    :root:fullscreen body.sd2-page .sd2-stat__value { font-size: 2rem; }
    :root:fullscreen body.sd2-page .sd2-stat__sub { font-size: 0.8rem; margin-top: 0.25rem; }
    :root:fullscreen body.sd2-page .fw-stat-card { padding: 1rem 1.2rem; }
    :root:fullscreen body.sd2-page .sd2-legend { font-size: 0.82rem; }
    :root:fullscreen body.sd2-page .sd2-card__title { margin-bottom: 0.5rem; }
    :root:fullscreen body.sd2-page .sd2-grid { margin-bottom: var(--fw-space-sm); }
}
