/* ---------------------------------------------------------------------------
   Payrolla design tokens — the single source of truth for colour, radius,
   shadow and type. Loaded by BOTH shells:

     base.html        (Payrolla operator app — Bootstrap + styles.css)
     client/base.html (tenant company portal — portal.css, no Bootstrap)

   Colour is derived from the Payrolla brand board (Deep Teal / Bright Teal /
   Soft Teal). Nothing in this file may reference a layout class. Tokens only.
   If a value appears twice in a stylesheet, it belongs here instead.
   --------------------------------------------------------------------------- */

:root {
    /* Ink + neutrals */
    --ink: #183b36;
    --ink-soft: #3c5550;
    --muted: #586963;
    --line: #dedfd7;          /* quiet architectural grey */
    --line-strong: #c3d9d3;
    /* The boundary of an INTERACTIVE element, as distinct from the two above.
       WCAG 1.4.11 requires 3:1 for "visual information required to identify
       user interface components" — a button whose only affordance is its
       outline is exactly that, and --line-strong is 1.48:1 on white, so an
       outlined control drawn with it is a control a low-vision user cannot see
       is a control. --line/--line-strong remain correct for card and table
       hairlines, which 1.4.11 does not govern (see the exemption note at the
       foot of this file) and which would draw the product as a wireframe at
       3:1. Measured: 3.17:1 on --shell, 3.43:1 on --surface, 3.29:1 on
       --surface-alt. */
    --line-control: #7a8f8a;
    --surface: #ffffff;
    --surface-alt: #f8f8f4;
    --shell: #f3f3ee;

    /* Brand — Payrolla teal system (brand board: Deep Teal / Bright Teal /
       Soft Teal). --brand carries white text at AA (~5.4:1); --brand-deep is
       the dark surface tone; --brand-bright is the energetic accent. */
    --brand: #0c6e62;          /* primary — buttons, links, focus */
    --brand-dark: #0a544b;     /* hover / pressed */
    --brand-deep: #0d4d4d;     /* Deep Teal — dark surfaces, headers, sidebar */
    --brand-bright: #17c3b2;   /* Bright Teal — accents, glows, gradient end */
    --brand-soft: #d6f3f1;     /* Soft Teal — soft fills, badges */
    --brand-tint: #ecfbfa;     /* lightest wash — info surfaces */

    /* Accent = Bright Teal, replacing the former gold. --gold/--gold-deep stay
       as aliases so existing references recolour to the Payrolla accent without
       a template sweep. */
    --accent: #17c3b2;
    --accent-deep: #0fa697;
    --gold: var(--accent);
    --gold-deep: var(--accent-deep);

    /* Primary brand gradient (Deep Teal → Bright Teal) — logo tile, marks. */
    --brand-gradient: var(--brand-deep);

    /* Status */
    --green: #237348;
    --green-soft: #e8f5ee;
    --warn: #b45309;
    --warn-soft: #fff4e0;
    --danger: #dc2626;
    --danger-soft: #fdecee;

    /* Elevation */
    --shadow: 0 12px 32px rgba(24, 59, 54, 0.07);
    --shadow-sm: 0 2px 8px rgba(24, 59, 54, 0.035);

    /* Geometry */
    --radius-sm: 8px;
    --radius: 12px;
    --radius-lg: 18px;
    --radius-pill: 999px;

    /* Type */
    --font-sans: Satoshi, sans-serif;
    --font-display: Newsreader, serif;
    --DESIGN_VARIANCE: 1;
    --space-editorial: calc(28px + 3px * var(--DESIGN_VARIANCE));

    /* Motion — respected by both shells */
    --ease: 180ms ease;
    --ease-out: cubic-bezier(0.23, 1, 0.32, 1);   /* entrances, exits — starts fast */
    --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1); /* on-screen movement */
}

/* The shell gradient is brand identity, not layout — both portals share it. */
.brand-shell-bg {
    background: var(--shell);
}

/* The Payrolla brand mark — a white app-icon tile that holds the real logo SVG
   (or, as a fallback, the "P" glyph in deep teal). White reads cleanly on the
   deep-teal sidebar, the dark login/error cards, and light surfaces alike, and
   matches the brand board's white-background lockup. One definition, one look:
   sidebar, portal header, public payslip, error pages. */
.brand-mark {
    align-items: center;
    background: #ffffff;
    border: 1px solid var(--line);
    border-radius: 13px;
    box-shadow: 0 8px 22px rgba(13, 77, 77, 0.22);
    color: var(--brand-deep);
    display: inline-flex;
    flex: none;
    font-size: 1.4rem;
    font-weight: 800;
    height: 46px;
    justify-content: center;
    width: 46px;
}
/* Height drives, width follows: the mark is taller than it is wide (99 x 117),
   so a square box would letterbox it. `width: auto` keeps the aspect exact at
   any tile size and survives a future change to the artwork's proportions. */
.brand-mark img {
    display: block;
    height: 34px;
    width: auto;
}

/* --- Contrast: the two accepted exemptions -------------------------------
   Every foreground/background pair these two shells put on screen was measured
   against WCAG 2.1 (4.5:1 for text, 3:1 for non-text). Two do not reach the
   threshold and are accepted deliberately rather than silently:

   1. `.xpanel` / `.kpi` / `.dist-job` hairline borders — `--line` on
      `--surface` is 1.26:1. SC 1.4.11 governs the boundary of a user-interface
      COMPONENT where that boundary is what identifies it. A content panel is
      not a control, and it is identified by its surface and elevation, not its
      hairline; the interactive things inside it (links, tiles, buttons, focus
      rings) are all measured separately and all pass. Taking every card edge to
      3:1 would draw the whole product as a wireframe — a real regression in
      clarity to satisfy a clause that does not apply.

      The distribution monitor's in-flight cards are covered by this entry and
      by nothing weaker: each also carries a coloured left edge that IS
      measured (`--brand` at 6.14:1 for running, `--muted` at 6.38:1 for
      scheduled) and a status pill in words, so the state is never the
      hairline's job.

   2. `.donut-slice--accent` — `--brand-bright` on `--surface` is 2.21:1.
      --brand-bright is a fixed brand colour and cannot be darkened. The slice
      is therefore never the only carrier of its information: every donut ships
      a legend with a swatch, the label, the value AND the percentage; the
      <figure> carries an aria-label naming every slice and value; and slices
      are now separated by a hairline gap so two pale neighbours cannot merge
      into one. Under SC 1.4.11 the information is available without relying on
      the graphic's own contrast.

   Everything else measures at or above threshold. When a token or a pairing
   changes, re-measure — the two entries above are the only ones allowed to
   fail, and each states why.
   ------------------------------------------------------------------------- */

/* --- Shared chart primitives ---------------------------------------------
   Styling for templates/macros/charts.html. These live here, beside
   .brand-mark, for the same reason: the macros are rendered by BOTH shells
   (operator dashboard via styles.css, tenant portal via portal.css), so one
   definition means one look. Colour is tokens only, so a brand change
   recolours every chart. No animation — a dashboard of cards redrawing on
   every load is noise, not polish. */

.chart {
    margin: 0;
    min-width: 0;   /* let a chart shrink inside a grid cell instead of overflowing */
}

.chart svg {
    display: block;
    height: auto;
    width: 100%;
}

.chart-grid {
    stroke: var(--line);
    stroke-width: 1;
}

.chart-line {
    fill: none;
    stroke: var(--brand);
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 2.25;
}

.chart-dot {
    fill: var(--surface);
    stroke: var(--brand);
    stroke-width: 2;
}

.chart-dot--last {
    fill: var(--brand);
    stroke: var(--surface);
}

/* --- Truncated-axis bounds -----------------------------------------------
   A line chart whose series is range-scaled (macros/charts.html::line_chart,
   fed by analytics._as_points(baseline="range")) does not start at zero, and
   says so by labelling both bounds ON the plot. `.chart-plot` is the
   positioning context; the labels sit against the top and bottom gridlines the
   SVG already draws, so each one names the line it sits on.

   They are aria-hidden because the figure's own aria-label already states the
   axis range in a sentence — announcing the bounds twice makes the chart's
   summary harder to follow, not more complete. */
.chart-plot { position: relative; }

.chart-bound {
    color: var(--muted);
    font-size: 10.5px;
    font-variant-numeric: tabular-nums;
    letter-spacing: 0.01em;
    line-height: 1;
    pointer-events: none;
    position: absolute;
    right: 0;
    /* A chip on the surface, not floating text: the series can pass directly
       under either label, and unbacked text on top of the line is unreadable. */
    background: var(--surface);
    border-radius: var(--radius-sm);
    padding: 2px 4px;
}

.chart-bound--max { top: 0; }
.chart-bound--min { bottom: 0; }

/* Period labels under a line/bar chart, on the same track as the data. */
.chart-axis {
    color: var(--muted);
    display: flex;
    font-size: 11px;
    font-variant-numeric: tabular-nums;
    justify-content: space-between;
    margin-top: 8px;
}

.chart-axis span {
    flex: 1 1 0;
    text-align: center;
}

/* Bars are CSS boxes, not SVG: they reflow at any width with no distortion. */
.chart-bars {
    align-items: flex-end;
    display: flex;
    gap: 8px;
}

.chart-bar {
    align-items: flex-end;
    background: var(--surface-alt);
    border-radius: var(--radius-sm);
    display: flex;
    flex: 1 1 0;
    height: 100%;
    max-width: 88px;
    overflow: hidden;
}

/* A bar's WIDTH carries no meaning — only its height encodes the value — so an
   uncapped `flex: 1 1 0` let a one-period series grow into a full-card slab
   that read as a filled area chart rather than a bar. The cap only bites when
   there are few periods; at twelve months the bars shrink below it and pack
   the row exactly as before. The axis is pinned to the same track and the same
   8px gap so a label stays under its own bar — scoped to .chart--bar because
   the line chart shares .chart-axis and does span the full width. */
.chart--bar .chart-axis {
    gap: 8px;
    justify-content: flex-start;
}

.chart--bar .chart-axis span {
    max-width: 88px;
}

/* The gradient's light end is --brand-bright, which measures 2.21:1 against the
   card — under the 3:1 a data mark owes. --brand-bright is a locked brand
   colour and cannot be darkened, so the mark's BOUNDARY carries the contrast
   instead: a 1px inset ring in --brand (7.5:1) means the top of every bar is
   unambiguous even where its fill is pale. The fill is untouched, so the brand
   reads exactly as before. */
.chart-bar-fill {
    background: var(--brand);
    border-radius: var(--radius-sm);
    box-shadow: inset 0 0 0 1px var(--brand);
    display: block;
    min-height: 3px;   /* a small-but-real period stays visible */
    width: 100%;
}

.chart-bar-fill.is-empty {
    background: var(--line);
    min-height: 2px;
}

/* --- Horizontal bar chart (a ranking) ---
   Rows rather than columns, because the categories are company names. Laid out
   with CSS grid, not SVG, so the labels stay real text: selectable, truncatable
   with an ellipsis, and readable by a screen reader without a <title> hack. */
.hbars {
    display: flex;
    flex-direction: column;
    gap: 10px;
    list-style: none;
    margin: 0;
    padding: 0;
}

.hbar {
    align-items: center;
    display: grid;
    gap: 10px;
    /* Label and value columns are content-sized within a ceiling; the track
       takes whatever is left, so the chart works from phone to wide desktop
       with no media query. */
    grid-template-columns: minmax(56px, 22%) 1fr auto;
}

.hbar-label {
    color: var(--ink-soft);
    font-size: 12.5px;
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.hbar-track {
    background: var(--surface-alt);
    border-radius: var(--radius-pill);
    display: block;
    height: 12px;
    overflow: hidden;
}

/* Same treatment as .chart-bar-fill, and needed more here: this gradient ENDS
   on --brand-bright, so the bar's tip — the exact pixel a reader compares one
   company against another with — was its palest point, at 2.12:1 against the
   track behind it. */
.hbar-fill {
    background: var(--brand);
    border-radius: var(--radius-pill);
    box-shadow: inset 0 0 0 1px var(--brand);
    display: block;
    height: 100%;
    min-width: 3px;   /* a small-but-real company stays visible */
}

.hbar-fill.is-empty { background: var(--line); min-width: 2px; }

.hbar-value {
    color: var(--ink);
    font-size: 12.5px;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
}

/* --- Donut --- */
/* The donut sits beside its legend once there is room for both. Shared by both
   shells, so it lives here rather than in either shell's stylesheet. */
.chart--donut { align-items: center; display: flex; flex-wrap: wrap; gap: 18px; }
/* The legend sizes to its CONTENT, and stops there. It used to be `flex: 1 1
   130px`, so it grew to fill every pixel the card had left; combined with the
   `margin-left: auto` on .chart-legend strong below — which is what aligns the
   values into a column when there are several rows — a single-category
   breakdown put the swatch and label beside the donut and threw its value to
   the far edge of the card, reading as two unrelated things with a void
   between them. Shrink-to-fit keeps the value with the label it belongs to and
   still right-aligns the column across multiple rows, because every row is now
   as wide as the widest. The card's leftover width stays neutral background. */
.chart--donut .chart-legend { flex: 0 1 auto; margin-top: 0; max-width: 100%; }

.donut-wrap {
    display: inline-grid;
    place-items: center;
    position: relative;
}

.donut-wrap svg {
    height: auto;
    max-width: 100%;
}

/* Slice tones. brand/accent are the two-way splits (payroll vs expenses); the
   three below extend the same ramp for multi-category breakdowns (expenses by
   category, payroll status distribution) — still teal-family, still tokens, so
   a brand change recolours every slice. app/analytics.py cycles these names. */
.donut-track { stroke: var(--surface-alt); }
.donut-slice--brand { stroke: var(--brand-deep); }
.donut-slice--accent { stroke: var(--brand-bright); }
.donut-slice--deep { stroke: var(--brand); }
.donut-slice--soft { stroke: var(--accent-deep); }
/* The `muted` tone is the neutral --muted, not --brand-soft. --brand-soft is a
   FILL tone (badge backgrounds, icon tiles) and measures 1.17:1 against the
   card — as a data slice it was invisible, which on the operator dashboard
   meant every Draft run silently disappeared from the status ring while still
   being counted in the total underneath it. --muted is 6.24:1, and neutral
   grey is the semantically right colour for the one status that means "not in
   the pipeline yet". No brand value changed: this is a role picking a
   different existing token. */
.donut-slice--muted { stroke: var(--muted); }
.donut-slice--warn { stroke: var(--warn); }
.donut-slice--danger { stroke: var(--danger); }
.donut-slice--green { stroke: var(--green); }

.donut-center {
    line-height: 1.15;
    position: absolute;
    text-align: center;
}

.donut-center strong {
    display: block;
    font-size: 14px;
    font-weight: 800;
}

.donut-center small {
    color: var(--muted);
    font-size: 11px;
}

.chart-legend {
    display: flex;
    flex-direction: column;
    font-size: 13px;
    gap: 7px;
    list-style: none;
    margin: 14px 0 0;
    padding: 0;
}

.chart-legend li {
    align-items: center;
    display: flex;
    gap: 8px;
}

.chart-legend strong {
    font-variant-numeric: tabular-nums;
    margin-left: auto;
}

.chart-legend small {
    color: var(--muted);
    min-width: 3.2em;
    text-align: right;
}

.legend-swatch {
    border-radius: 3px;
    flex: none;
    height: 10px;
    width: 10px;
}

.legend-swatch--brand { background: var(--brand-deep); }
.legend-swatch--accent { background: var(--brand-bright); }
.legend-swatch--deep { background: var(--brand); }
.legend-swatch--soft { background: var(--accent-deep); }
/* Matches .donut-slice--muted — a legend swatch that does not match its slice
   is worse than no legend. */
.legend-swatch--muted { background: var(--muted); }
.legend-swatch--warn { background: var(--warn); }
.legend-swatch--danger { background: var(--danger); }
.legend-swatch--green { background: var(--green); }

/* --- Sparkline (table-cell trend) --- */
.sparkline {
    display: inline-block;
    line-height: 0;
    vertical-align: middle;
}

.sparkline svg { height: auto; max-width: 100%; }

.sparkline-path {
    fill: none;
    stroke: var(--brand);
    stroke-linecap: round;
    stroke-linejoin: round;
    stroke-width: 1.75;
}

.sparkline-head { fill: var(--brand); }

/* A falling trend is drawn in the warn tone — but direction is always ALSO
   carried by the delta pill's arrow and text, never by colour alone. */
.sparkline--down .sparkline-path { stroke: var(--warn); }
.sparkline--down .sparkline-head { fill: var(--warn); }

/* --- Delta pill --- */
.delta {
    align-items: center;
    background: var(--surface-alt);
    border-radius: var(--radius-pill);
    color: var(--ink-soft);
    display: inline-flex;
    font-size: 12px;
    font-variant-numeric: tabular-nums;
    font-weight: 700;
    gap: 4px;
    padding: 2px 9px;
    white-space: nowrap;
}

.delta small { color: var(--muted); font-weight: 600; }
.delta--up { background: var(--green-soft); color: #14532d; }
.delta--down { background: var(--warn-soft); color: var(--warn); }
/* "No change" and "no baseline to compare against" are different statements and
   must not look identical: flat is a real measurement and keeps the neutral
   surface a measured value gets; none is an absence and is muted throughout. */
.delta--flat { background: var(--surface-alt); color: var(--ink-soft); }
.delta--none { color: var(--muted); }

@media (prefers-reduced-motion: reduce) {
    :root { --ease: 1ms linear; }
}
