/*!
 * ZIC components — loading skeletons and empty states
 * ============================================================================
 * Reusable, theme-agnostic building blocks shared by the dashboard and any
 * future async surface (htmx refreshes, portal widgets). Loaded globally from
 * layouts/partials/styles.html.
 *
 * Both halves read from the token layer, so they need no per-theme rules:
 * the skeleton tint is mixed from `--zic-text-primary`, which is dark ink on
 * light and light ink on dark, so one declaration is correct on both.
 */

/* ---------------------------------------------------------------------------
 * 1. Skeleton primitives.
 *
 * The pulse is the only motion; it is switched off under reduced-motion rather
 * than left to flicker at a user who asked for stillness.
 * ------------------------------------------------------------------------- */
@keyframes skeleton-pulse {
  0%,
  100% {
    opacity: 0.4;
  }

  50% {
    opacity: 0.7;
  }
}

.zic-skeleton {
  background-color: color-mix(in srgb, var(--zic-text-primary) 12%, transparent);
  border-radius: var(--zic-radius-sm);
  animation: skeleton-pulse 1.6s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
  .zic-skeleton {
    animation: none;
    opacity: 0.5;
  }
}

/* Shapes. */
.zic-skeleton-label {
  width: 42%;
  height: 0.6875rem;
}

.zic-skeleton-value {
  width: 58%;
  height: 1.5rem;
  margin-top: var(--zic-space-2);
}

.zic-skeleton-text {
  width: 100%;
  height: 0.75rem;
}

.zic-skeleton-text-short {
  width: 64%;
  height: 0.75rem;
}

.zic-skeleton-icon {
  flex: 0 0 auto;
  width: 2.75rem;
  height: 2.75rem;
  border-radius: var(--zic-radius-md);
}

.zic-skeleton-block {
  width: 100%;
  height: 100%;
  border-radius: var(--zic-radius-md);
}

.zic-skeleton-row {
  width: 100%;
  height: 2.25rem;
  border-radius: var(--zic-radius-sm);
}

/* Arrangements. */
.zic-skeleton-split {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--zic-item-gap);
}

.zic-skeleton-split > .zic-skeleton-grow {
  flex: 1 1 auto;
  min-width: 0;
}

.zic-skeleton-stack {
  display: flex;
  flex-direction: column;
  gap: var(--zic-space-2);
}

/* Chart-shaped skeleton: bars on a baseline, with faint rules behind. */
.zic-skeleton-bars {
  display: flex;
  align-items: flex-end;
  gap: var(--zic-space-3);
  height: 100%;
  padding-top: var(--zic-space-4);
}

.zic-skeleton-bars > span {
  flex: 1 1 0;
  min-width: 0;
  border-radius: var(--zic-radius-sm) var(--zic-radius-sm) 0 0;
  background-color: color-mix(in srgb, var(--zic-text-primary) 12%, transparent);
  animation: skeleton-pulse 1.6s ease-in-out infinite;
}

.zic-skeleton-bars > span:nth-child(1) { height: 38%; }
.zic-skeleton-bars > span:nth-child(2) { height: 62%; }
.zic-skeleton-bars > span:nth-child(3) { height: 47%; }
.zic-skeleton-bars > span:nth-child(4) { height: 78%; }
.zic-skeleton-bars > span:nth-child(5) { height: 55%; }
.zic-skeleton-bars > span:nth-child(6) { height: 88%; }
.zic-skeleton-bars > span:nth-child(7) { height: 66%; }
.zic-skeleton-bars > span:nth-child(8) { height: 44%; }

@media (prefers-reduced-motion: reduce) {
  .zic-skeleton-bars > span {
    animation: none;
    opacity: 0.5;
  }
}

/* ---------------------------------------------------------------------------
 * 2. Loadable regions — the skeleton-to-content handoff.
 *
 * A region marked `.zic-loadable` starts showing its skeleton and hides its
 * content. Calling `ZICDashboard.reveal(el)` adds `.is-ready`, which crossfades
 * the two over 200ms. The skeleton keeps its layout box while fading and is
 * taken out of flow once the fade ends, so the swap never shifts the page.
 * ------------------------------------------------------------------------- */
.zic-loadable {
  position: relative;
}

.zic-loadable-skeleton {
  transition: opacity 200ms var(--zic-transition-ease);
}

.zic-loadable-content {
  opacity: 0;
  transition: opacity 200ms var(--zic-transition-ease);
}

.zic-loadable.is-ready .zic-loadable-skeleton {
  opacity: 0;
  pointer-events: none;
}

.zic-loadable.is-ready .zic-loadable-content {
  opacity: 1;
}

/* ---------------------------------------------------------------------------
 * 3. Chart shell.
 *
 * ApexCharts paints after first paint, so the chart's box would otherwise sit
 * empty for a frame or two. The skeleton is absolutely positioned over the
 * canvas so the chart element keeps a real, measurable height while it
 * initialises — hiding it would make ApexCharts measure zero and render
 * nothing.
 * ------------------------------------------------------------------------- */
.zic-chart-shell {
  position: relative;
  min-height: var(--zic-chart-min-h, 200px);
}

.zic-chart-canvas {
  opacity: 0;
  transition: opacity 200ms var(--zic-transition-ease);
}

.zic-chart-skeleton {
  position: absolute;
  inset: 0;
  transition: opacity 200ms var(--zic-transition-ease);
}

.zic-chart-shell.is-ready .zic-chart-canvas {
  opacity: 1;
}

.zic-chart-shell.is-ready .zic-chart-skeleton {
  opacity: 0;
  pointer-events: none;
}

/* Reserved height per shell density, matched to each chart's own height so the
   card does not resize when the chart replaces its placeholder. These lived in
   the dashboard stylesheet until the dashboard was rebuilt on the shell's grid;
   they belong to the component, which other pages also use. */
.zic-chart-shell-primary {
  --zic-chart-min-h: 208px;
}

.zic-chart-shell-compact {
  --zic-chart-min-h: 96px;
}

.zic-chart-shell-wide {
  --zic-chart-min-h: 168px;
}

/* ---------------------------------------------------------------------------
 * 4. Empty states.
 *
 * The illustration is inline SVG drawn with `currentColor` plus the surface
 * tokens, so it re-themes with the rest of the page. That is preferred over
 * the `filter:` approach, which can only ever approximate a target palette and
 * needs a separate rule per theme; filters remain the fallback for a bitmap
 * asset that cannot carry CSS variables.
 * ------------------------------------------------------------------------- */
.zic-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--zic-space-2);
  min-height: var(--zic-empty-min-h, 220px);
  padding: var(--zic-space-5) var(--zic-space-4);
  text-align: center;
}

.zic-empty-art {
  width: 84px;
  height: 84px;
  color: var(--zic-primary);
}

.zic-empty-art svg {
  display: block;
  width: 100%;
  height: 100%;
}

.zic-empty-title {
  margin: var(--zic-space-2) 0 0;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--zic-text-primary);
}

.zic-empty-text {
  max-width: 46ch;
  margin: 0;
  font-size: 14px;
  line-height: 1.5;
  color: var(--zic-text-secondary);
}

.zic-empty-actions {
  margin-top: var(--zic-space-2);
}

/* ---------------------------------------------------------------------------
 * 5. KPI card.
 *
 * A premium financial widget: icon and trend on the top rail, the number at
 * display weight with its caption beneath, and the comparison baseline pinned
 * to the bottom so the footers line up across a row.
 *
 * `kpi-card` is unprefixed on purpose — it is checked against Materio and
 * Bootstrap and collides with neither. The theme tokens still namespace every
 * colour, so nothing here needs a prefix to stay out of the way.
 * ------------------------------------------------------------------------- */
.kpi-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--zic-space-3);
  height: 100%;
  padding: var(--zic-card-pad-compact);
  background-color: var(--zic-surface-primary);
  border: 1px solid var(--zic-border-default);
  border-radius: var(--zic-radius-md);
  box-shadow: var(--zic-shadow-sm);
  animation: kpi-card-enter 420ms var(--zic-transition-ease) backwards;
  animation-delay: calc(min(var(--kpi-index, 0), 8) * 35ms);
}

/* The lift is the only thing that animates, and only while hovered. The card's
   colours are deliberately NOT transitioned: a per-element colour transition
   fires on a theme change and settles on its own frame, which is what made the
   switch look like a skeleton stagger. See theme/variables.css. */
.kpi-card:hover,
.kpi-card:focus-within {
  transition:
    box-shadow var(--zic-transition-fast),
    transform var(--zic-transition-fast);
}

.kpi-card:hover {
  transform: translateY(-2px);
  border-color: var(--zic-border-strong);
  box-shadow: var(--zic-shadow-md);
}

/* `backwards` fill, not `both`: the entrance must release the transform when it
   finishes, or it would pin `transform: none` and cancel the hover lift. */
@keyframes kpi-card-enter {
  from {
    opacity: 0;
    transform: translateY(6px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

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

  .kpi-card:hover {
    transform: none;
  }
}

/* --- Top rail ------------------------------------------------------------- */
.kpi-card__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--zic-space-2);
  min-height: 2.5rem;
}

.kpi-card__icon {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  border-radius: var(--zic-radius-md);
  transition: transform var(--zic-transition-normal);
}

.kpi-card:hover .kpi-card__icon {
  transform: scale(1.06);
}

.kpi-card__trend {
  display: flex;
  justify-content: flex-end;
  min-width: 0;
}

/* --- Trend indicator ------------------------------------------------------
 * Colour comes from the AA `-text` token on its matching `-bg-subtle` tint, so
 * the pill is legible in both themes without a per-theme rule.
 * ------------------------------------------------------------------------ */
.trend-indicator {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.125rem 0.5rem;
  border-radius: var(--zic-radius-pill);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.5;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.trend-up {
  color: var(--zic-status-success-text);
  background-color: var(--zic-status-success-bg-subtle);
}

.trend-down {
  color: var(--zic-status-danger-text);
  background-color: var(--zic-status-danger-bg-subtle);
}

.trend-neutral {
  color: var(--zic-status-neutral-text);
  background-color: var(--zic-status-neutral-bg-subtle);
}

/* When the direction is not the good news — rising overdue commitments, say —
   the caller flips the sentiment and the pill reads as a warning. */
.kpi-card[data-sentiment="negative"] .trend-up {
  color: var(--zic-status-danger-text);
  background-color: var(--zic-status-danger-bg-subtle);
}

.kpi-card[data-sentiment="negative"] .trend-down {
  color: var(--zic-status-success-text);
  background-color: var(--zic-status-success-bg-subtle);
}

/* --- Body ----------------------------------------------------------------- */
.kpi-card__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.kpi-card__value {
  font-size: 1.5rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  color: var(--zic-text-primary);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}

.kpi-card__unit {
  margin-inline-end: 0.15em;
  font-size: 0.62em;
  font-weight: 600;
  letter-spacing: 0;
  color: var(--zic-text-secondary);
}

.kpi-card__label {
  margin-top: 0.25rem;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--zic-text-secondary);
}

.kpi-card__meta {
  margin-top: 0.25rem;
  font-size: 12px;
  color: var(--zic-text-secondary);
  font-variant-numeric: tabular-nums;
}

/* --- Footer ---------------------------------------------------------------
 * `margin-top: auto` pushes the baseline to the bottom of the card, so footers
 * align across a row even when one card's label wraps to a second line.
 * ------------------------------------------------------------------------ */
.kpi-card__footer {
  margin-top: auto;
  padding-top: var(--zic-space-2);
  border-top: 1px solid var(--zic-border-default);
}

.kpi-card__comparison {
  font-size: 12px;
  line-height: 1.4;
  color: var(--zic-text-secondary);
  font-variant-numeric: tabular-nums;
}

/* --- Stretched link -------------------------------------------------------
 * The anchor wraps the label; its ::after covers the card, so the whole tile is
 * clickable and the link's accessible name stays the metric label. The card
 * root therefore remains a plain <div>, which an <a> could not replace without
 * wrapping the block structure above.
 * ------------------------------------------------------------------------ */
.kpi-card__link {
  color: inherit;
  text-decoration: none;
}

.kpi-card__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

.kpi-card:has(.kpi-card__link):hover {
  border-color: var(--zic-border-strong);
}

.kpi-card__link:focus-visible {
  outline: none;
}

.kpi-card:has(.kpi-card__link:focus-visible) {
  outline: var(--zic-focus-ring-width) solid var(--zic-border-focus);
  outline-offset: var(--zic-focus-ring-offset);
}

/* ---------------------------------------------------------------------------
 * 6. Chart card.
 *
 * Header carries the title and the actions; body holds the skeleton/empty
 * state and the chart. Padding lives here rather than in a Materio `.card-body`
 * so the two halves stay in step at either density.
 * ------------------------------------------------------------------------- */
.chart-wrapper {
  display: flex;
  flex-direction: column;
}

.chart-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--zic-item-gap);
  padding: var(--zic-card-pad-spacious) var(--zic-card-pad-spacious) var(--zic-space-3);
}

.chart-heading {
  min-width: 0;
}

.chart-title {
  margin: 0;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--zic-text-primary);
}

.chart-subtitle {
  margin: 2px 0 0;
  font-size: 12px;
  line-height: 1.4;
  color: var(--zic-text-secondary);
}

.chart-actions {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: var(--zic-space-1);
}

.chart-body {
  padding: 0 var(--zic-card-pad-spacious) var(--zic-card-pad-spacious);
}

/* A running action has to look like it is working, or the button reads as
   broken for the ~200ms the PNG takes to render. */
[data-chart-action][data-chart-state="busy"] {
  opacity: 0.6;
  pointer-events: none;
}

[data-chart-action][data-chart-state="busy"] .ri-refresh-line {
  animation: zic-icon-spin 700ms linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  [data-chart-action][data-chart-state="busy"] .ri-refresh-line {
    animation: none;
  }
}

@media (max-width: 767.98px) {
  .chart-header {
    padding: var(--zic-card-pad-compact) var(--zic-card-pad-compact) var(--zic-space-2);
  }

  .chart-body {
    padding: 0 var(--zic-card-pad-compact) var(--zic-card-pad-compact);
  }
}

/* ---------------------------------------------------------------------------
 * Layout utility — a flex/grid child that may shrink below its content.
 *
 * The default `min-width: auto` on a flex item refuses to shrink under the
 * item's intrinsic width, so one long unbreakable token — an error code such as
 * `PROPOSAL_ENRICHMENT_INCOMPLETE`, or a long URL — pushes the whole row wider
 * than its card and the row paints over whatever sits beside it. Paired with
 * Bootstrap's `.text-break`, this is what lets such a row wrap instead.
 * ------------------------------------------------------------------------- */
.zic-min-w-0 {
  min-width: 0;
}

/* ---------------------------------------------------------------------------
 * Plan list — OL quotation wizard, left panel.
 *
 * The panel is `h-100` in a `col-xl-4`, so it stretches to the height of the
 * wizard card beside it. The list used to be capped at `max-height: 62vh`,
 * which meant that on a tall wizard the plans stopped about two-thirds of the
 * way down and the rest of the card sat empty — and on a short one it started
 * scrolling while the card still had room. As a growing flex child it fills the
 * card and scrolls only once the plans genuinely exceed the space. The floor
 * keeps it from collapsing on a short viewport.
 * ------------------------------------------------------------------------- */
.plan-list {
  flex: 1 1 auto;
  min-height: 16rem;
  overflow-y: auto;
}
