/* ==========================================================================
   Framework base styles.

   The design system is token-driven: static styling is written inline in
   Python from `theme` tokens. This file holds only what inline styles cannot
   express — interaction states (:hover / :active / :focus) and a few global
   resets. Everything here is keyed to the same CSS variables the tokens compile
   to (--color-primary, --border-radius, ...), so it re-tints with the theme and
   with a per-client re-skin automatically. Don't hard-code colours here.
   ========================================================================== */

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
  /* The theme background reaches the document root, not just the page div, so the
     blank moment of a full reload (a language switch) paints in the theme colour
     instead of white. data-theme is set in <head> before first paint. */
  background: var(--color-background);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  scrollbar-width: thin;
  scrollbar-color: var(--color-border) transparent;
}

/* --------------------------------------------------------------------------
   Buttons — the `Button` widget renders `.app-btn .app-btn--<variant>`.
   Filled variants use the card colour as their text colour: it is the natural
   contrast to the accent in every theme (white text in light, dark text in
   dark) without needing a separate on-accent token.
   -------------------------------------------------------------------------- */

/* -----------------------------------------
TEMPORARY COLOR DEFINITION
TODO: remove !! */

:root {
  --color-primary: #004679;
  --color-secondary: #009EAB;
  --color-background-card: #FFFFFF;
  --color-text: #151515;
  --color-text-muted: #888888;
  --color-border: #004679;
  --border-style:  solid;
  --border-width:  1px;
  --border-radius:  5px;
  --shadow: 1px 4px 8px rgba(0, 0, 0, 0.1);
}
/* ----------------------------------------- */
  
.app-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  font: inherit;
  font-weight: 600;
  line-height: 1;
  padding: 8px 14px;
  border: var(--border-width) solid transparent;
  border-radius: var(--border-radius);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
  transition: background-color .15s ease, border-color .15s ease,
              color .15s ease, box-shadow .15s ease, filter .15s ease,
              transform .04s ease;
}

.app-btn:focus-visible { outline: 2px solid var(--color-secondary); outline-offset: 2px; }
.app-btn:active { transform: translateY(1px); }
.app-btn:disabled { opacity: .5; cursor: not-allowed; }

.app-btn--primary   { background: var(--color-primary);   color: var(--color-background-card); }
.app-btn--secondary { background: var(--color-secondary); color: var(--color-background-card); }
.app-btn--danger    { background: var(--color-danger);    color: var(--color-background-card); }

.app-btn--primary:hover:not(:disabled),
.app-btn--secondary:hover:not(:disabled),
.app-btn--danger:hover:not(:disabled) {
  filter: brightness(1.08);
  box-shadow: var(--shadow);
}

.app-btn--ghost {
  background: transparent;
  color: var(--color-text);
  border-color: var(--color-border);
}
.app-btn--ghost:hover:not(:disabled) {
  border-color: var(--color-primary);
  color: var(--color-primary);
}

/* --------------------------------------------------------------------------
   Navbar items — the `nav_item` helper renders `.app-nav-item`; page links
   also carry `.app-nav-link` and get `.is-active` for the current route.
   -------------------------------------------------------------------------- */

.app-nav-item {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  cursor: pointer;
  user-select: none;
  color: var(--color-text-muted);
  border-radius: var(--border-radius);
  transition: background-color .15s ease, color .15s ease;
}
.app-nav-item:hover { background: var(--color-background); color: var(--color-text); }
.app-nav-item.is-active { color: var(--color-primary); font-weight: 600; }
.app-nav-link { text-decoration: none; }

/* --------------------------------------------------------------------------
   Upload — `.app-upload` on the `dcc.Upload` drop zone (content sits inside).
   dcc.Upload *appends* `--active` (drag-over) / `--reject` (type not in `accept`),
   so each modifier carries only its delta. The browser hides a dragged file's type
   until drop, so `--active` is a neutral "drop here" cue, not "valid".
   -------------------------------------------------------------------------- */

.app-upload {
  display: inline-flex;
  align-items: center;
  cursor: pointer;
  box-sizing: border-box;
  border: 1px dashed var(--color-divider);
  border-radius: var(--border-radius);
  transition: border-color .15s ease, background-color .15s ease;
}
.app-upload:hover { border-color: var(--color-primary); }
.app-upload--active {
  border-style: solid;
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 10%, transparent);
}
.app-upload--reject {
  border-style: solid;
  border-color: var(--color-danger);
  background: color-mix(in srgb, var(--color-danger) 10%, transparent);
}

/* --------------------------------------------------------------------------
   Select — the `Select` widget renders `dcc.Dropdown` with `.app-select`.

   Dash's dropdown draws its own trigger and menu, which a Python `style` cannot
   reach. It reads its colours from `--Dash-*` custom properties that Dash defines
   at `:root`, so re-pointing those at our tokens on the widget's root re-themes the
   whole control — including dark mode — without restyling internals. Only the radius
   is set directly: Dash derives it from `--Dash-Spacing`, which also drives its
   paddings and gaps.

   The widget renders its own root div for this: Dash puts `className` on the trigger
   button, and the menu is a sibling of that button, so a class there would inherit
   nothing to the open menu.
   -------------------------------------------------------------------------- */

.app-select {
  --Dash-Fill-Inverse-Strong: var(--color-background-card);
  --Dash-Fill-Interactive-Strong: var(--color-primary);
  --Dash-Fill-Interactive-Weak: var(--color-background);
  --Dash-Fill-Disabled: var(--color-border);
  --Dash-Stroke-Strong: var(--color-border);
  --Dash-Stroke-Weak: var(--color-border);
  --Dash-Text-Strong: var(--color-text);
  --Dash-Text-Weak: var(--color-text-muted);
  --Dash-Text-Disabled: var(--color-text-muted);
}

.app-select .dash-dropdown,
.app-select .dash-dropdown-content {
  border-radius: var(--border-radius);
}

/* --------------------------------------------------------------------------
   Checkbox — the `Checkbox` widget renders `dcc.Checklist` with `.app-checkbox`.

   Same mechanism as `.app-select` above: the tick and its focus ring come from
   Dash's own `--Dash-*` custom properties, whose default accent is a purple that
   belongs to no theme of ours. Re-pointing them here tints the control in both
   modes without touching its internals.
   -------------------------------------------------------------------------- */

.app-checkbox {
  --Dash-Fill-Inverse-Strong: var(--color-background-card);
  --Dash-Fill-Interactive-Strong: var(--color-primary);
  --Dash-Fill-Interactive-Weak: var(--color-background);
  --Dash-Fill-Disabled: var(--color-border);
  --Dash-Stroke-Strong: var(--color-border);
  --Dash-Stroke-Weak: var(--color-border);
  --Dash-Text-Strong: var(--color-text);
  --Dash-Text-Weak: var(--color-text-muted);
}

.app-checkbox input { cursor: pointer; }

/* --------------------------------------------------------------------------
   DatePicker — the `DatePicker` widget renders `dcc.DatePickerSingle` with
   `.app-datepicker`. Same mechanism as `.app-select`: the field and its calendar
   read Dash's `--Dash-*` custom properties (whose default accent is a purple that
   belongs to no theme of ours), so re-pointing them here themes the control and
   its popup in both modes. The calendar is a child of the control (not portalled),
   so this scoping reaches it. Only the radius is set directly.
   -------------------------------------------------------------------------- */

.app-datepicker {
  --Dash-Fill-Inverse-Strong: var(--color-background-card);
  --Dash-Fill-Interactive-Strong: var(--color-primary);
  --Dash-Fill-Interactive-Weak: var(--color-background);
  --Dash-Fill-Disabled: var(--color-border);
  --Dash-Stroke-Strong: var(--color-border);
  --Dash-Stroke-Weak: var(--color-border);
  --Dash-Text-Strong: var(--color-text);
  --Dash-Text-Weak: var(--color-text-muted);
  --Dash-Text-Disabled: var(--color-text-muted);
}

.app-datepicker .dash-datepicker-input-wrapper,
.app-datepicker .dash-datepicker-calendar-container {
  border-radius: var(--border-radius);
}

/* --------------------------------------------------------------------------
   Filter / Radio — both render their checkboxes/radios inside `.app-selection-tree`.
   Same mechanism as `.app-select`: re-pointing Dash's `--Dash-*` custom properties at
   our tokens themes every control in the tree (default accent is a purple that belongs
   to no theme of ours), in both light and dark.
   -------------------------------------------------------------------------- */

.app-selection-tree {
  --Dash-Fill-Inverse-Strong: var(--color-background-card);
  --Dash-Fill-Interactive-Strong: var(--color-primary);
  --Dash-Fill-Interactive-Weak: var(--color-background);
  --Dash-Fill-Disabled: var(--color-border);
  --Dash-Stroke-Strong: var(--color-border);
  --Dash-Stroke-Weak: var(--color-border);
  --Dash-Text-Strong: var(--color-text);
  --Dash-Text-Weak: var(--color-text-muted);
  --Dash-Text-Disabled: var(--color-text-muted);
}

.app-selection-tree input { cursor: pointer; }

/* --------------------------------------------------------------------------
   Tabs — the `Tabs` widget renders `.app-tab` per tab in the bar and one
   `.app-tab-body` per tab below it. Its behaviour (assets/js/widgets/tabs.js)
   marks all but the active body `.is-hidden`; bodies stay mounted, so a callback
   can write into a closed tab.

   Why CSS and not inline style from Python: the bar needs a :hover state, which
   inline styles cannot express — the same reason `.app-btn` lives here.

   Why `!important`: Dash ships its own `.tab` / `.tab--selected` rules at the same
   specificity as ours, and asset load order decides the winner. Without it the
   selected tab picks up the unselected fill (verified). The hidden state needs it
   too, to beat the helper's inline `display: flex`. Kept to exactly the properties
   that are actually contested.
   -------------------------------------------------------------------------- */

.app-tab-body.is-hidden { display: none !important; }

.app-tab {
  flex: 1;
  text-align: center;
  padding: var(--padding) 0;
  font: inherit;
  cursor: pointer;
  user-select: none;
  border: var(--border-width) solid var(--color-border) !important;
  border-radius: var(--border-radius) var(--border-radius) 0 0;
  background-color: var(--color-background) !important;
  color: var(--color-text-muted) !important;
  transition: background-color .15s ease, color .15s ease;
}

.app-tab:hover { color: var(--color-text) !important; }

.app-tab--selected {
  font-weight: 600;
  border-bottom: none !important;
  border-top: var(--border-width) solid var(--color-border) !important;
  background-color: var(--color-background-card) !important;
  color: var(--color-primary) !important;
}

/* --------------------------------------------------------------------------
   Notifications — the `Toast` widget stacks `.app-toast` items in a fixed
   `.app-toast-stack` overlay (top-right, under the navbar). CSS here because a
   toast fades in and out — an interaction inline styles can't express. The level
   tints only the left border; a `.app-badge` marks an unread count on a nav item.
   -------------------------------------------------------------------------- */

.app-toast-stack {
  position: fixed;
  top: calc(var(--navbar-height) + 12px);
  right: 16px;
  z-index: 3000;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 360px;
  pointer-events: none;  /* clicks pass through the gaps; toasts re-enable it */
}

.app-toast {
  pointer-events: auto;
  padding: 10px 14px;
  background: var(--color-background-card);
  color: var(--color-text);
  border: var(--border-width) solid var(--color-border);
  border-left: 4px solid var(--color-text-muted);
  border-radius: var(--border-radius);
  box-shadow: var(--shadow);
  cursor: pointer;
  opacity: 0;
  transform: translateX(12px);
  transition: opacity .2s ease, transform .2s ease;
}
.app-toast.is-in  { opacity: 1; transform: none; }
.app-toast.is-out { opacity: 0; transform: translateX(12px); }

.app-toast--info    { border-left-color: var(--color-secondary); }
.app-toast--success { border-left-color: var(--color-success); }
.app-toast--warning { border-left-color: var(--color-warning); }
.app-toast--error   { border-left-color: var(--color-danger); }

.app-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 8px;
  background: var(--color-danger);
  color: var(--color-background-card);
  font-size: 10px;
  font-weight: 700;
  line-height: 1;
}
.app-badge:empty { display: none; }  /* no unread → no badge, no callback needed */

/* Overlay a badge on the top-right corner of its anchor (a whole item, an icon, …) so
   showing/hiding it never reflows its neighbours. Put `.app-badge-anchor` on the parent
   (it only establishes the positioning context) and `.app-badge--corner` on the badge.
   Reusable — not tied to notifications. */
.app-badge-anchor { position: relative; }
.app-badge--corner {
  position: absolute;
  top: 0;
  right: 0;
  transform: translate(45%, -45%);
  pointer-events: none;
}

/* Dropdown panel: always mounted (so callback-driven content stays addressable),
   shown by `.is-open` on toggle click. Owned by the Dropdown widget. */
.app-dropdown-panel {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 2000;
  width: 320px;
  max-height: 500px;
  overflow: auto;
  padding: var(--padding);
  background: var(--color-background-card);
  border: var(--border-width) solid var(--color-border);
  border-radius: var(--border-radius);
  box-shadow: var(--shadow);
}
.app-dropdown-panel.is-open { display: block; }
/* Left-align the panel under the toggle (for dropdowns near the left edge). */
.app-dropdown-panel--start { right: auto; left: 0; }

/* --------------------------------------------------------------------------
   Plotly graphs — Plotly bakes concrete colours into the SVG and ignores our
   CSS-var tokens, so theme its text here instead (the base figure's backgrounds
   are transparent, so the themed card shows through). `!important` overrides
   Plotly's inline fill; scoped to axis/legend/title text (`.cartesianlayer`,
   `.infolayer`) so hover tooltips (`.hoverlayer`) keep their own colours.
   -------------------------------------------------------------------------- */

.js-plotly-plot .cartesianlayer text,
.js-plotly-plot .infolayer text {
  fill: var(--color-text) !important;
}
