/* =============================================================================================
   2Forge CRM - design system
   ---------------------------------------------------------------------------------------------
   Hand-rolled, no framework. Enterprise-sober: high information density, Fluent-like restraint,
   no decoration that does not carry meaning. A salesperson looks at this for seven hours a day.

   HOW TO USE IT
     - Never write a raw colour, spacing or radius in a page. Use a custom property from :root.
     - Never put a max-width on a data component. Tables, boards and lists take the full content
       column; only forms and prose are narrowed, by .form-column and .prose.
     - The component classes below are the shared vocabulary. Add a modifier, do not fork a class.
     - Never write a raw font-size. There are SEVEN steps, named by role, in the token block:
       --text-micro / --text-meta / --text-body / --text-title / --text-head / --text-page /
       --text-metric. There is no eighth. If none fits, the element has the wrong role.
     - There are FOUR weights: 300 / 400 / 500 / 600. 700 and "bold" do not exist here. A
       heading is light and large; 600 is for uppercase micro-labels and nothing else.

   THEMING
     Tokens are declared once on :root for light, then redefined twice for dark: once behind the
     OS preference (guarded so an explicit light choice still wins) and once behind an explicit
     data-theme="dark". The two dark blocks must stay in sync.
   ============================================================================================= */

/* ---------------------------------------------------------------------------------------------
   0. TYPEFACE - Geist, self-hosted. ONE FAMILY, FOUR WEIGHTS, NOTHING ELSE.
   ---------------------------------------------------------------------------------------------
   SELF-HOSTED, NOT GOOGLE FONTS. Embedding the Google CDN sends every visitor's IP address to
   a third country on every page load; a German court has already held that to be a GDPR
   breach, and this product ships to an EU client. 84 KB for eight files, served from our own
   origin, cached for a year.

   ONE FAMILY. IBM Plex Sans and IBM Plex Mono are both gone. The monospace existed for exactly
   one job - making figures line up in a column - and Geist does that job itself the moment
   tabular figures are switched on, which they are, globally, on body. A second family that buys
   nothing still costs a download, a fallback stack, and a decision every time someone writes a
   rule. There is no --font-mono token either: it went with them.

   Two subsets per weight. "latin" carries ASCII, "latin-ext" carries the Croatian letters -
   c with caron, c with acute, z, s, d with stroke. Both are required: latin-ext alone has no
   ASCII, so the unicode-range on each face is what makes a Croatian word render in one font
   instead of half in Geist and half in the fallback. The ranges below are the ones the IBM Plex
   faces used and are already proven against this app's Croatian copy.
   ============================================================================================= */

@font-face {
    font-family: "Geist";
    font-style: normal;
    font-weight: 300;
    font-display: swap;
    src: url("fonts/geist-300-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Geist";
    font-style: normal;
    font-weight: 300;
    font-display: swap;
    src: url("fonts/geist-300-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Geist";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("fonts/geist-400-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Geist";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("fonts/geist-400-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Geist";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("fonts/geist-500-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Geist";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("fonts/geist-500-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: "Geist";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("fonts/geist-600-latin.woff2") format("woff2");
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: "Geist";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("fonts/geist-600-latin-ext.woff2") format("woff2");
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ---------------------------------------------------------------------------------------------
   1. TOKENS - light
   --------------------------------------------------------------------------------------------- */

:root {
    color-scheme: light dark;

    /* Neutral ramp: warm graphite on paper. Warm, because a cool grey next to a pure-black
       accent reads as cheap; paper reads as considered. */
    --c-neutral-0: #ffffff;
    --c-neutral-25: #fdfdfc;
    --c-neutral-50: #fafaf9;
    --c-neutral-100: #f4f4f2;
    --c-neutral-200: #e4e4e1;
    --c-neutral-300: #d3d3cf;
    --c-neutral-400: #a8a8a2;
    --c-neutral-500: #85857e;
    --c-neutral-600: #6b6b66;
    --c-neutral-700: #4a4a46;
    --c-neutral-800: #2c2c29;
    --c-neutral-900: #1a1a18;
    --c-neutral-950: #111111;

    /* THERE IS NO BRAND HUE. Ink is the accent: a primary button is black, an active row is
       black. Colour in this product means something - a pipeline stage, an ageing threshold, a
       status - and chrome that spends colour decoratively spends the signal too. */
    --c-ink-900: #111111;
    --c-ink-800: #1f1f1d;
    --c-ink-700: #2c2c29;
    --c-ink-600: #4a4a46;

    /* Semantic surfaces */
    /* THREE TIERS OF SURFACE, and the order matters. The navigation rail is RECESSED, the page
       sits above it, a card is brightest. A rail lighter than the page it borders reads as flat
       and slightly wrong without the eye being able to say why. The payoff is the active nav item:
       on a recessed rail it can be plain white and it will look lifted, which buys a premium
       active state for no colour and no shadow. */
    --surface-page: #fbfbfa;
    --surface: #ffffff;
    --surface-raised: #ffffff;
    --surface-sunken: #f4f4f2;
    --surface-hover: #f7f7f5;
    --surface-active: #f1f1ee;
    --surface-inverse: #111111;
    --surface-nav: #f6f6f4;
    --surface-nav-hover: #eeeeeb;
    --surface-nav-active: #ffffff;

    --border: #e4e4e1;
    --border-strong: #d3d3cf;
    --border-nav: #e4e4e1;

    --text: #111111;
    --text-muted: #6b6b66;
    --text-subtle: #6e6e68;
    --text-inverse: #ffffff;
    --text-nav: #4a4a46;
    --text-nav-strong: #111111;
    --text-on-accent: #ffffff;

    --accent: #111111;
    --accent-hover: #2c2c29;
    --accent-active: #000000;
    --accent-soft: #f4f4f2;
    --accent-soft-border: #d3d3cf;
    --accent-text: #111111;

    --focus-ring-color: #111111;
    --focus-ring: 0 0 0 2px var(--surface), 0 0 0 4px var(--focus-ring-color);

    /* THE BRAND MARK HAS ITS OWN PAIR, and deliberately does not ride on --accent. --accent means
       "the interactive accent"; it is near-black here but a generic blue in dark, so a mark keyed
       to it changes colour halfway through a session - blue on the sign-in screen, then something
       else once the user is inside - and re-skins itself every time someone retunes the accent.
       A logo is not an accent. It is ink on paper in light and paper on ink in dark: the same
       mark, flipped, which is exactly what a printed one does. The dark chip is the WARM
       --c-neutral-100, not a cool white, because it sits on the deliberately warm --surface-nav. */
    --brand-mark-bg: #111111;
    --brand-mark-fg: #ffffff;

    /* Status - used by .alert, .badge--*, validation messages */
    --info: #1a56db;
    --info-bg: #eef3ff;
    --info-border: #c4d5fb;
    --info-text: #14398c;

    --success: #147a3d;
    --success-bg: #e6f5ec;
    --success-border: #b7e0c7;
    --success-text: #0f5c30;

    --warning: #9a6200;
    --warning-bg: #fdf3e2;
    --warning-border: #f2dcb0;
    --warning-text: #7a4d00;

    --danger: #c22a22;
    --danger-bg: #fdecec;
    --danger-border: #f5c9c6;
    --danger-text: #8f201a;

    /* Pipeline stages - a cool-neutral to green progression, closed-lost a muted red, dropped
       grey. Each pair is a soft fill with a text colour that clears WCAG AA against that fill. */
    --stage-new-inquiry-bg: #eef1f5;
    --stage-new-inquiry-fg: #44506b;
    --stage-awaiting-client-bg: #eaeefb;
    --stage-awaiting-client-fg: #3f4b85;
    --stage-questionnaire-sent-bg: #e9ecfd;
    --stage-questionnaire-sent-fg: #3d45a0;
    --stage-questionnaire-returned-bg: #e7e9ff;
    --stage-questionnaire-returned-fg: #3730a3;
    --stage-quote-sent-bg: #e3eefe;
    --stage-quote-sent-fg: #1c4da6;
    --stage-meeting-scheduled-bg: #ddf0fb;
    --stage-meeting-scheduled-fg: #12537b;
    --stage-meeting-held-bg: #d9f1f2;
    --stage-meeting-held-fg: #0d5d63;
    --stage-negotiation-bg: #d8f2ea;
    --stage-negotiation-fg: #0a614e;
    --stage-contract-signed-bg: #dcf3de;
    --stage-contract-signed-fg: #18682a;
    --stage-won-bg: #cdeed2;
    --stage-won-fg: #115a24;
    --stage-lost-bg: #fae8e7;
    --stage-lost-fg: #94231e;
    --stage-disqualified-bg: #ededef;
    --stage-disqualified-fg: #575c63;

    /* Ageing - amber past 7 days, red past 14. Both clear AA against their own fill. */
    --ageing-fresh-fg: #5a6679;
    --ageing-fresh-bg: transparent;
    --ageing-warn-fg: #8a5503;
    --ageing-warn-bg: #fdf1dc;
    --ageing-late-fg: #9d1c16;
    --ageing-late-bg: #fbe4e3;

    /* ---- LIST WORKBENCH -------------------------------------------------------------------
       The colours the four list screens share. Every one of them is a value a page would
       otherwise invent inline, which is exactly how a design system rots. */

    /* A monogram is an ANCHOR, not a label: one flat neutral tint for every row, never a hue
       derived from the name. Twenty-five differently coloured circles is a fruit salad, and it
       spends the colour budget that the pipeline stages need. */
    --monogram-bg: #efefec;
    --monogram-fg: #2c2c29;

    /* A selected row has to beat the hover tint without becoming a second accent. */
    --row-selected-bg: #eeeeea;
    --row-selected-marker: #111111;

    /* The bulk bar is the one inverted surface in the product. It appears only while a selection
       exists, which is what makes an inversion legible rather than loud. */
    --bulk-bar-bg: #111111;
    --bulk-bar-fg: #ffffff;
    --bulk-bar-border: rgba(255, 255, 255, 0.22);
    --bulk-bar-hover: rgba(255, 255, 255, 0.14);

    --skeleton-bg: #eeeeeb;
    --skeleton-sheen: rgba(255, 255, 255, 0.9);

    /* The stand-in for a value nobody entered. Recessive on purpose, and rendered aria-hidden
       wherever it is used: an empty cell must read as empty to a screen reader, not as a dash. */
    --text-empty: #6e6e69;

    /* Dot marks. A 5px dot plus a plain label, in place of a filled pill: the dot is the only
       colour on the row, so these are the least saturated hues in the product that still
       separate from one another. The label carries the meaning; the dot only speeds the scan. */
    --dot-neutral: #82827c;
    --dot-positive: #2f7d4f;
    --dot-info: #4a5b7a;
    --dot-attention: #b07400;
    --dot-critical: #c22a22;
    --dot-inactive: #878782;

    /* Spacing - 4px base. Nothing in this product is spaced off the scale. */
    --space-0: 0;
    --space-1: 0.25rem;
    --space-2: 0.5rem;
    --space-3: 0.75rem;
    --space-4: 1rem;
    --space-5: 1.25rem;
    --space-6: 1.5rem;
    --space-7: 2rem;
    --space-8: 2.5rem;
    --space-9: 3rem;
    --space-10: 4rem;

    --radius-xs: 3px;
    --radius-sm: 5px;
    --radius-md: 8px;
    --radius-lg: 12px;
    --radius-full: 999px;

    --shadow-1: 0 1px 2px rgba(17, 22, 29, 0.06), 0 1px 1px rgba(17, 22, 29, 0.04);
    --shadow-2: 0 2px 6px rgba(17, 22, 29, 0.08), 0 1px 2px rgba(17, 22, 29, 0.05);
    --shadow-3: 0 12px 32px rgba(17, 22, 29, 0.16), 0 2px 8px rgba(17, 22, 29, 0.08);

    /* TYPOGRAPHY. One family: Geist. The system stack stays as the fallback so a failed font
       load degrades rather than breaks.

       MEASURED, NOT ASSUMED - read out of the real font files:
         Geist          x-height 0.530 em, cap 0.710 em, ascent+descent 1.300 em
         IBM Plex Sans  x-height 0.516 em, cap 0.698 em, ascent+descent 1.300 em
       Geist is 2.7% larger on the x-height at the same pixel size, but the line box is
       IDENTICAL, so nothing that depends on line height had to move. The base also drops
       14px -> 13px, which more than absorbs the difference: perceived x-height goes
       7.22px -> 6.89px. That is why no fixed control height (.btn 32px, .input 34px, the
       40px/32px table rows) needed to grow - at 13px they gained slack, not lost it. */
    --font-sans: "Geist", -apple-system, BlinkMacSystemFont, "Segoe UI Variable Text",
        "Segoe UI", "Noto Sans", Roboto, "Helvetica Neue", Arial, sans-serif;

    /* THERE IS NO --font-mono. There was one, as an alias of --font-sans, only for as long as
       the sweep needed it; every use of it is gone and so is the token itself. A monospace
       existed here for exactly one job, lining figures up in a column, and Geist does that job
       itself now that tabular figures are on globally (see body). Reintroducing a second family
       means re-arguing the decision, not adding a line. */

    /* ------------------------------------------------------------------------------------
       THE SCALE. Seven steps, named by ROLE and not by size, because --text-sm invites
       someone to pick it for how it looks while --text-micro only fits one job. There is no
       eighth step. If something does not fit, it is the wrong role, not a missing size.
       ------------------------------------------------------------------------------------ */
    --text-micro: 0.6875rem;  /* 11px  600, uppercase, 0.1em - column headers, labels, chips */
    --text-meta: 0.75rem;     /* 12px  400 - secondary text, captions, footnotes             */
    --text-body: 0.8125rem;   /* 13px  400 - BASE: body, tables, inputs, buttons, nav        */
    --text-title: 0.9375rem;  /* 15px  500 - card and section titles                         */
    --text-head: 1.125rem;    /* 18px  400 - sub-page headings                               */
    --text-page: 1.625rem;    /* 26px  300 - page titles                                    */
    --text-metric: 1.75rem;   /* 28px  300, tabular - KPI figures                            */

    /* 11px IS THE FLOOR and it is not arbitrary: 10px uppercase at 0.1em tracking is where
       legibility actually fails on a 1080p laptop panel, which is what the client's sales desk
       runs. Every 9px and 10px in the old sheet came up to --text-micro. */

    /* --leading-normal was 1.45 under Plex. Nudged to 1.50 because Geist's larger x-height
       wants the air; at 13px that is a 19.5px line, still shorter than the 20.3px that
       14px/1.45 produced, so density improved rather than regressed. */
    --leading-tight: 1.25;
    --leading-normal: 1.5;
    --leading-relaxed: 1.6;

    /* THE WEIGHTS. Exactly four. 700 and "bold" are BANNED: a heading in this product is light
       and large, not bold. The weight contrast between a 300 page title and the 600 uppercase
       micro-labels under it IS the editorial idea; a bold heading collapses it. */
    --weight-light: 300;      /* display only: page titles, KPI metrics */
    --weight-normal: 400;     /* body                                   */
    --weight-medium: 500;     /* emphasis, active nav, name cells, buttons */
    --weight-semibold: 600;   /* micro-labels ONLY                      */

    /* THE NINE RETIRED SIZE NAMES ARE GONE, and so is --weight-bold. --text-2xs / -xs / -sm /
       -base / -md / -lg / -xl / -2xl / -3xl / -display pointed at the new scale for exactly one
       wave, so the component sweep could not break the app halfway through. The sweep finished,
       the aliases were deleted, and TypographyTests now fails the build if one comes back. Do
       not re-add a name because it reads better in a rule: pick the role. */


    /* Layout */
    --nav-width: 244px;
    --topbar-height: 48px;

    /* Table row heights. A page never writes a height; it sets a density class and the table
       reads it from here, so every list in the product breathes identically. */
    --row-height: 40px;
    --row-height-compact: 32px;
    --content-max: 1800px;
    --form-column: 640px;
    --prose-max: 72ch;
    --gutter: 1.5rem;

    --transition-fast: 120ms cubic-bezier(0.4, 0, 0.2, 1);
    --transition-base: 180ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* ---------------------------------------------------------------------------------------------
   2. TOKENS - dark. Declared twice on purpose: once for the OS preference (unless the user has
   explicitly asked for light) and once for an explicit choice of dark.
   --------------------------------------------------------------------------------------------- */

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --surface-page: #0e1319;
        --surface: #161c24;
        --surface-raised: #1c232c;
        --surface-sunken: #11171e;
        --surface-hover: #1f2731;
        --surface-active: #263039;
        --surface-inverse: #eef1f5;
        --surface-nav: #131312;
        --surface-nav-hover: #1e1e1c;
        --surface-nav-active: #262624;

        --border: #262f3a;
        --border-strong: #35414f;
        --border-nav: #2c2c29;

        --text: #e7ecf3;
        --text-muted: #9aa7b8;
        --text-subtle: #7b8798;
        --text-inverse: #11161d;
        --text-nav: #a8a8a2;
        --text-nav-strong: #ffffff;
        --text-on-accent: #0e1319;

        --accent: #4d87f5;
        --accent-hover: #6b9cf8;
        --accent-active: #85adfa;
        --accent-soft: #172540;
        --accent-soft-border: #26406b;
        --accent-text: #9dbdfb;

        /* Paper on ink, the light mark flipped. See --brand-mark-bg in the light block. */
        --brand-mark-bg: #f4f4f2;
        --brand-mark-fg: #111111;

        --focus-ring-color: #5d8cf5;

        --info: #6b9cf8;
        --info-bg: #17223a;
        --info-border: #2a3d63;
        --info-text: #b3c9fb;

        --success: #56c281;
        --success-bg: #12291d;
        --success-border: #1f4a32;
        --success-text: #93dfb0;

        --warning: #e0a44a;
        --warning-bg: #2e2413;
        --warning-border: #4d3c1c;
        --warning-text: #f0c47c;

        --danger: #f07a72;
        --danger-bg: #331b1a;
        --danger-border: #55302d;
        --danger-text: #f5aba5;

        --stage-new-inquiry-bg: #272e39;
        --stage-new-inquiry-fg: #b2becf;
        --stage-awaiting-client-bg: #242a44;
        --stage-awaiting-client-fg: #b6c0ee;
        --stage-questionnaire-sent-bg: #24284a;
        --stage-questionnaire-sent-fg: #b3b9f2;
        --stage-questionnaire-returned-bg: #26254e;
        --stage-questionnaire-returned-fg: #b8b4f5;
        --stage-quote-sent-bg: #1c2d4b;
        --stage-quote-sent-fg: #a8c8f7;
        --stage-meeting-scheduled-bg: #133146;
        --stage-meeting-scheduled-fg: #92cef0;
        --stage-meeting-held-bg: #0f363a;
        --stage-meeting-held-fg: #86d8de;
        --stage-negotiation-bg: #0e372e;
        --stage-negotiation-fg: #80dcc1;
        --stage-contract-signed-bg: #143b1d;
        --stage-contract-signed-fg: #90dc9d;
        --stage-won-bg: #14411f;
        --stage-won-fg: #8ce49f;
        --stage-lost-bg: #3c1f1d;
        --stage-lost-fg: #f0a8a2;
        --stage-disqualified-bg: #2c3138;
        --stage-disqualified-fg: #aeb5bf;

        --ageing-fresh-fg: #9aa7b8;
        --ageing-warn-fg: #f0c268;
        --ageing-warn-bg: #362a13;
        --ageing-late-fg: #f4a49d;
        --ageing-late-bg: #3d1f1d;

        --monogram-bg: #253040;
        --monogram-fg: #cdd7e4;

        --row-selected-bg: #202b3a;
        --row-selected-marker: #4d87f5;

        --bulk-bar-bg: #eef1f5;
        --bulk-bar-fg: #11161d;
        --bulk-bar-border: rgba(17, 22, 29, 0.24);
        --bulk-bar-hover: rgba(17, 22, 29, 0.10);

        --skeleton-bg: #1f2731;
        --skeleton-sheen: rgba(255, 255, 255, 0.07);

        --text-empty: #8b97a8;

        --dot-neutral: #7b8798;
        --dot-positive: #56c281;
        --dot-info: #8fa8cd;
        --dot-attention: #e0a44a;
        --dot-critical: #f07a72;
        --dot-inactive: #6f7986;

        --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
        --shadow-2: 0 2px 6px rgba(0, 0, 0, 0.45);
        --shadow-3: 0 14px 34px rgba(0, 0, 0, 0.6), 0 2px 8px rgba(0, 0, 0, 0.4);
    }
}

:root[data-theme="dark"] {
    --surface-page: #0e1319;
    --surface: #161c24;
    --surface-raised: #1c232c;
    --surface-sunken: #11171e;
    --surface-hover: #1f2731;
    --surface-active: #263039;
    --surface-inverse: #eef1f5;
    --surface-nav: #131312;
    --surface-nav-hover: #1e1e1c;
    --surface-nav-active: #262624;

    --border: #262f3a;
    --border-strong: #35414f;
    --border-nav: #2c2c29;

    --text: #e7ecf3;
    --text-muted: #9aa7b8;
    --text-subtle: #7b8798;
    --text-inverse: #11161d;
    --text-nav: #a8a8a2;
    --text-nav-strong: #ffffff;
    --text-on-accent: #0e1319;

    --accent: #4d87f5;
    --accent-hover: #6b9cf8;
    --accent-active: #85adfa;
    --accent-soft: #172540;
    --accent-soft-border: #26406b;
    --accent-text: #9dbdfb;

    /* Paper on ink, the light mark flipped. See --brand-mark-bg in the light block. */
    --brand-mark-bg: #f4f4f2;
    --brand-mark-fg: #111111;

    --focus-ring-color: #5d8cf5;

    --info: #6b9cf8;
    --info-bg: #17223a;
    --info-border: #2a3d63;
    --info-text: #b3c9fb;

    --success: #56c281;
    --success-bg: #12291d;
    --success-border: #1f4a32;
    --success-text: #93dfb0;

    --warning: #e0a44a;
    --warning-bg: #2e2413;
    --warning-border: #4d3c1c;
    --warning-text: #f0c47c;

    --danger: #f07a72;
    --danger-bg: #331b1a;
    --danger-border: #55302d;
    --danger-text: #f5aba5;

    --stage-new-inquiry-bg: #272e39;
    --stage-new-inquiry-fg: #b2becf;
    --stage-awaiting-client-bg: #242a44;
    --stage-awaiting-client-fg: #b6c0ee;
    --stage-questionnaire-sent-bg: #24284a;
    --stage-questionnaire-sent-fg: #b3b9f2;
    --stage-questionnaire-returned-bg: #26254e;
    --stage-questionnaire-returned-fg: #b8b4f5;
    --stage-quote-sent-bg: #1c2d4b;
    --stage-quote-sent-fg: #a8c8f7;
    --stage-meeting-scheduled-bg: #133146;
    --stage-meeting-scheduled-fg: #92cef0;
    --stage-meeting-held-bg: #0f363a;
    --stage-meeting-held-fg: #86d8de;
    --stage-negotiation-bg: #0e372e;
    --stage-negotiation-fg: #80dcc1;
    --stage-contract-signed-bg: #143b1d;
    --stage-contract-signed-fg: #90dc9d;
    --stage-won-bg: #14411f;
    --stage-won-fg: #8ce49f;
    --stage-lost-bg: #3c1f1d;
    --stage-lost-fg: #f0a8a2;
    --stage-disqualified-bg: #2c3138;
    --stage-disqualified-fg: #aeb5bf;

    --ageing-fresh-fg: #9aa7b8;
    --ageing-warn-fg: #f0c268;
    --ageing-warn-bg: #362a13;
    --ageing-late-fg: #f4a49d;
    --ageing-late-bg: #3d1f1d;

    --monogram-bg: #253040;
    --monogram-fg: #cdd7e4;

    --row-selected-bg: #202b3a;
    --row-selected-marker: #4d87f5;

    --bulk-bar-bg: #eef1f5;
    --bulk-bar-fg: #11161d;
    --bulk-bar-border: rgba(17, 22, 29, 0.24);
    --bulk-bar-hover: rgba(17, 22, 29, 0.10);

    --skeleton-bg: #1f2731;
    --skeleton-sheen: rgba(255, 255, 255, 0.07);

    --text-empty: #8b97a8;

    --dot-neutral: #7b8798;
    --dot-positive: #56c281;
    --dot-info: #8fa8cd;
    --dot-attention: #e0a44a;
    --dot-critical: #f07a72;
    --dot-inactive: #6f7986;

    --shadow-1: 0 1px 2px rgba(0, 0, 0, 0.4);
    --shadow-2: 0 2px 6px rgba(0, 0, 0, 0.45);
    --shadow-3: 0 14px 34px rgba(0, 0, 0, 0.6), 0 2px 8px rgba(0, 0, 0, 0.4);
}

/* ---------------------------------------------------------------------------------------------
   3. RESET AND BASE
   --------------------------------------------------------------------------------------------- */

*,
*::before,
*::after {
    box-sizing: border-box;
}

html {
    -webkit-text-size-adjust: 100%;
    text-size-adjust: 100%;
}

body {
    margin: 0;
    /* An explicit background, so the page never inherits the user agent's white in dark mode. */
    background-color: var(--surface-page);
    color: var(--text);
    font-family: var(--font-sans);
    font-size: var(--text-body);
    line-height: var(--leading-normal);

    /* TABULAR FIGURES, GLOBALLY. This is what lets one family replace two. IBM Plex Sans had
       tabular digits by default, so columns aligned for free; GEIST DOES NOT - its "1" is
       narrow. Measured in the browser against the real file at 13px: "1111111111" renders
       45.25px wide against 87.36px for "0000000000". With font-variant-numeric: tabular-nums
       every ten-digit string measures exactly 78px.

       It is set GLOBALLY rather than per-component on purpose. A CRM is almost entirely
       figures - money, counts, dates, percentages, ageing days - and a global default means
       nobody can forget it on a column somebody adds six months from now. Proportional figures
       are correct in running prose and nowhere else in this product, so .prose opts back out. */
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;

    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
}

h1,
h2,
h3,
h4,
h5,
h6 {
    margin: 0;
    /* NOT 600. 600 is reserved for uppercase micro-labels; a heading here is distinguished by
       size and by being LIGHTER than the label under it, which is the whole editorial idea. */
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
    color: var(--text);
}

/* The ladder is 26 / 18 / 15 / 13, and the weight goes DOWN as the size goes up. */
h1 {
    font-size: var(--text-page);
    font-weight: var(--weight-light);
    letter-spacing: -0.02em;
    line-height: 1.15;
}

h2 {
    font-size: var(--text-head);
    font-weight: var(--weight-normal);
}

h3 {
    font-size: var(--text-title);
}

h4,
h5,
h6 {
    font-size: var(--text-body);
}

p {
    margin: 0 0 var(--space-3);
}

/* THE BROWSER'S OWN BOLD IS STILL A BOLD. <strong> and <b> resolve to 700 in the user-agent
   sheet, which is the one weight this design system bans, and no amount of discipline in our own
   rules removes it - the markup is what summons it. Measured in the dashboard: two elements
   rendering at 700 with nothing in any stylesheet asking for it. Emphasis here is 500 against a
   400 field, which is the same amount of emphasis at a fifth of the shout. */
b,
strong {
    font-weight: var(--weight-medium);
}

a {
    color: var(--accent);
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

hr {
    border: 0;
    border-top: 1px solid var(--border);
    margin: var(--space-5) 0;
}

/* No separate monospace family any more, so no size compensation either: Plex Mono ran optically
   large next to Plex Sans and needed 0.925em to sit down. Geist code in Geist prose is the same
   face at the same size, which is the point of having one family. */
code,
kbd,
samp {
    font-family: var(--font-sans);
    font-size: var(--text-body);
}

/* Tabular figures are the GLOBAL default now (see body), so this list is no longer what makes
   columns line up. .tabular survives as the explicit opt-back-IN for figures that appear inside
   .prose, which is the one place proportional digits are correct. */
.tabular {
    font-variant-numeric: tabular-nums;
    font-feature-settings: "tnum" 1;
}

:focus-visible {
    outline: none;
    box-shadow: var(--focus-ring);
    border-radius: var(--radius-xs);
}

::selection {
    background: var(--accent-soft);
    color: var(--accent-text);
}

/* Screen-reader-only, for labels an icon button still needs. */
.sr-only {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ---------------------------------------------------------------------------------------------
   4. LAYOUT PRIMITIVES
   --------------------------------------------------------------------------------------------- */

/* The one content column. Data components fill it; they never set a width of their own. */
/* LEFT-ALIGNED, NOT CENTRED. margin-inline:auto strands the content in the middle of a wide
   monitor: at 2560px it left a 248px gap between the sidebar and the first KPI tile, which reads
   as a broken layout. Data hugs the navigation; the max-width only stops a table becoming
   unreadable on an ultrawide. */
.content-column {
    width: 100%;
    max-width: var(--content-max);
    margin-inline: 0 auto;
    padding-inline: var(--gutter);
}

/* Forms stay narrow: a 1800px-wide text input is unreadable and unfillable. */
.form-column {
    max-width: var(--form-column);
}

/* PROPORTIONAL FIGURES BELONG IN RUNNING PROSE and nowhere else here. A tabular "1" sits in a
   slot as wide as a "0", which is exactly right in a column and looks gappy in a sentence.
   A figure inside prose that genuinely needs to align - a small table, a list of amounts -
   takes .tabular explicitly. */
.prose {
    max-width: var(--prose-max);
    line-height: var(--leading-relaxed);
    font-variant-numeric: normal;
    font-feature-settings: normal;
}

.stack {
    display: flex;
    flex-direction: column;
    gap: var(--space-4);
}

.stack--tight {
    gap: var(--space-2);
}

.row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

.row--wrap {
    flex-wrap: wrap;
}

.spacer {
    flex: 1 1 auto;
}

.grid {
    display: grid;
    gap: var(--space-4);
}

.grid--kpi {
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

.grid--2 {
    grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
}

.muted {
    color: var(--text-muted);
}

.subtle {
    color: var(--text-subtle);
}

.text-right {
    text-align: right;
}

.text-center {
    text-align: center;
}

.nowrap {
    white-space: nowrap;
}

.truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ---------------------------------------------------------------------------------------------
   5. CARD
   --------------------------------------------------------------------------------------------- */

.card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-1);
}

.card__header {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    border-bottom: 1px solid var(--border);
}

.card__title {
    font-size: var(--text-title);
    font-weight: var(--weight-medium);
    margin: 0;
}

.card__body {
    padding: var(--space-4);
}

.card__footer {
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--border);
    background: var(--surface-sunken);
    border-radius: 0 0 var(--radius-md) var(--radius-md);
}

/* A card that wraps a table has no body padding: the table draws its own. */
.card--flush .card__body {
    padding: 0;
}

/* ---------------------------------------------------------------------------------------------
   6. PAGE HEADER
   --------------------------------------------------------------------------------------------- */

.page-header {
    display: flex;
    align-items: flex-start;
    gap: var(--space-4);
    flex-wrap: wrap;
    padding-block: var(--space-5) var(--space-4);
}

.page-header__text {
    min-width: 0;
    flex: 1 1 320px;
}

.page-header__title {
    font-size: var(--text-page);
    font-weight: var(--weight-light);
    letter-spacing: -0.02em;
    line-height: 1.15;
    margin: 0;
}

.page-header__subtitle {
    margin: var(--space-1) 0 0;
    color: var(--text-muted);
    font-size: var(--text-meta);
}

.page-header__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
}

/* THE LIST-PAGE HEAD. A display-weight title over a micro-label, with the actions sitting on the
   title's own baseline rather than floating at the top of the block. No card, no rule, no fill:
   the space beneath it is what separates it from the workspace chrome below.

   align-items:baseline rather than flex-start is the whole trick. It lines the cap-height of
   "Nova tvrtka" up with the cap-height of the title, which is what a typeset page does and what a
   row of top-aligned buttons conspicuously does not. */
.page-header--display {
    align-items: baseline;
    padding-block: var(--space-7) var(--space-5);
}

/* NO TITLE OVERRIDE HERE ANY MORE. --text-page IS the display size, at --weight-light, and
   .page-header__title already carries both - there is one page-title role, not two. What this
   modifier still does is the baseline alignment and the taller padding above. */

/* The counterweight: 11px, 600, uppercase, wide-tracked. The CONTRAST between this and the 300
   above is the design; either one alone is just text. */
.page-header__meta {
    margin: var(--space-2) 0 0;
}

.breadcrumbs {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-meta);
    color: var(--text-subtle);
    margin-bottom: var(--space-1);
}

.breadcrumbs a {
    color: var(--text-muted);
}

/* ---------------------------------------------------------------------------------------------
   7. TOOLBAR
   --------------------------------------------------------------------------------------------- */

.toolbar {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    padding: var(--space-3);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    margin-bottom: var(--space-4);
}

.toolbar--flush {
    border: 0;
    border-radius: 0;
    background: transparent;
    padding-inline: 0;
}

.toolbar__group {
    display: flex;
    align-items: center;
    gap: var(--space-2);
}

.toolbar__spacer {
    flex: 1 1 auto;
}

.toolbar__divider {
    width: 1px;
    align-self: stretch;
    background: var(--border);
}

/* ---------------------------------------------------------------------------------------------
   8. BUTTONS
   --------------------------------------------------------------------------------------------- */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    min-height: 32px;
    padding: 0 var(--space-3);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: var(--text-body);
    font-weight: var(--weight-medium);
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    text-decoration: none;
    transition: background-color var(--transition-fast), border-color var(--transition-fast),
        color var(--transition-fast);
}

.btn:hover {
    background: var(--surface-hover);
    text-decoration: none;
}

.btn:active {
    background: var(--surface-active);
}

.btn:disabled,
.btn[aria-disabled="true"] {
    opacity: 0.5;
    cursor: not-allowed;
    pointer-events: none;
}

.btn--primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--text-on-accent);
}

.btn--primary:hover {
    background: var(--accent-hover);
    border-color: var(--accent-hover);
    color: var(--text-on-accent);
}

.btn--primary:active {
    background: var(--accent-active);
    border-color: var(--accent-active);
}

.btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--text-muted);
}

.btn--ghost:hover {
    background: var(--surface-active);
    color: var(--text);
}

.btn--danger {
    background: var(--danger);
    border-color: var(--danger);
    color: #ffffff;
}

.btn--danger:hover {
    filter: brightness(0.94);
    background: var(--danger);
    border-color: var(--danger);
    color: #ffffff;
}

.btn--sm {
    min-height: 26px;
    padding: 0 var(--space-2);
    font-size: var(--text-meta);
}

.btn--lg {
    min-height: 40px;
    padding: 0 var(--space-5);
    font-size: var(--text-title);
}

.btn--block {
    width: 100%;
}

.btn--icon {
    padding: 0;
    width: 32px;
    min-height: 32px;
}

.btn--icon.btn--sm {
    width: 26px;
    min-height: 26px;
}

/* A button sitting on the inverted bulk bar. It cannot use --surface or --text: on that bar the
   two are the wrong way round. */
.btn--on-inverse {
    background: transparent;
    border-color: var(--bulk-bar-border);
    color: var(--bulk-bar-fg);
}

.btn--on-inverse:hover {
    background: var(--bulk-bar-hover);
    border-color: var(--bulk-bar-border);
    color: var(--bulk-bar-fg);
}

.btn--on-inverse:active {
    background: var(--bulk-bar-hover);
}

/* ---------------------------------------------------------------------------------------------
   9. FORM CONTROLS
   --------------------------------------------------------------------------------------------- */

.field {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    margin-bottom: var(--space-4);
}

.field__label {
    font-size: var(--text-body);
    font-weight: var(--weight-medium);
    color: var(--text);
}

.field__required {
    color: var(--danger);
    margin-inline-start: 2px;
}

.field__hint {
    font-size: var(--text-meta);
    color: var(--text-subtle);
}

.field__error {
    font-size: var(--text-meta);
    color: var(--danger-text);
}

.input,
.select,
.textarea {
    width: 100%;
    min-height: 34px;
    padding: var(--space-2) var(--space-3);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: var(--text-body);
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.textarea {
    min-height: 88px;
    resize: vertical;
    line-height: var(--leading-normal);
}

.input::placeholder,
.textarea::placeholder {
    color: var(--text-subtle);
}

.input:hover,
.select:hover,
.textarea:hover {
    border-color: var(--text-subtle);
}

.input:focus,
.select:focus,
.textarea:focus {
    outline: none;
    border-color: var(--accent);
    box-shadow: 0 0 0 3px var(--accent-soft);
}

.input:disabled,
.select:disabled,
.textarea:disabled {
    background: var(--surface-sunken);
    color: var(--text-subtle);
    cursor: not-allowed;
}

.select {
    appearance: none;
    padding-inline-end: var(--space-7);
    background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
        linear-gradient(135deg, currentColor 50%, transparent 50%);
    background-position: right var(--space-4) center, right var(--space-3) center;
    background-size: 5px 5px, 5px 5px;
    background-repeat: no-repeat;
}

.input--invalid,
.select--invalid,
.textarea--invalid,
.invalid.input,
.invalid.select,
.invalid.textarea {
    border-color: var(--danger);
}

.input--invalid:focus,
.invalid.input:focus {
    box-shadow: 0 0 0 3px var(--danger-bg);
}

.checkbox {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-body);
    cursor: pointer;
}

.checkbox input[type="checkbox"],
.checkbox input[type="radio"] {
    width: 16px;
    height: 16px;
    margin: 0;
    accent-color: var(--accent);
    cursor: pointer;
}

/* Blazor's own validation classes, mapped onto the house look. */
.modified.valid {
    border-color: var(--border-strong);
}

.invalid {
    border-color: var(--danger);
}

.validation-message {
    color: var(--danger-text);
    font-size: var(--text-meta);
}

/* ---------------------------------------------------------------------------------------------
   10. TABLE - dense, sticky header, no zebra, a hover row
   --------------------------------------------------------------------------------------------- */

.table-wrap {
    width: 100%;
    overflow-x: auto;
    border-radius: var(--radius-md);
}

.table {
    /* A page never writes a row height. It sets a density class; the table reads this. */
    --row-h: var(--row-height);

    width: 100%;
    border-collapse: separate;
    border-spacing: 0;
    font-size: var(--text-body);
}

/* Density. The command bar's toggle swaps one class and nothing else moves. */
.table--compact {
    --row-h: var(--row-height-compact);
}

.table th,
.table td {
    padding: var(--space-2) var(--space-3);
    text-align: left;
    border-bottom: 1px solid var(--border);
    vertical-align: middle;
}

/* THE HEADER IS MICRO-TYPE ON THE TABLE'S OWN SURFACE, not a grey band. A filled header strip is
   a second surface tier the eye has to resolve before it reaches the data; a hairline says the
   same thing and keeps the promise the rest of the system makes.

   --table-sticky-top lets a full-bleed table park its header under the sticky top bar instead of
   under the top of the viewport. A table inside a card leaves it at 0. */
.table thead th {
    position: sticky;
    top: var(--table-sticky-top, 0px);
    z-index: 2;
    padding-block: var(--space-2);
    background: var(--surface);
    color: var(--text-muted);
    font-size: var(--text-micro);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    /* 0.08em, not the micro role's 0.1em: this is .micro--tight's tracking, for the same
       reason - at 0.1em a dense header starts breaking Croatian words apart. */
    letter-spacing: 0.08em;
    white-space: nowrap;
    border-bottom: 1px solid var(--border);
}

/* Zebra striping is banned here on purpose: a hover row plus a hairline rule reads better at this
   density, and stripes fight the stage and ageing colours that carry the real meaning. */
.table tbody tr {
    background: var(--surface);
    transition: background-color var(--transition-fast);
}

.table tbody td {
    /* A minimum, not a fixed height: a name cell that wraps to two lines still grows. */
    height: var(--row-h);
}

.table tbody tr:hover {
    background: var(--surface-hover);
}

.table tbody tr.is-selected {
    background: var(--row-selected-bg);
}

.table tbody tr:last-child td {
    border-bottom: 0;
}

.table--clickable tbody tr {
    cursor: pointer;
}

.table td.numeric,
.table th.numeric {
    text-align: right;
}

.table td.shrink,
.table th.shrink {
    width: 1%;
    white-space: nowrap;
}

/* THE WHOLE HEADER CELL IS THE CONTROL, not a word inside it. A 40px-wide hit target in a 140px
   cell is a miss waiting to happen, and a button that does not fill its cell leaves a dead strip
   that looks like a bug. */
.table__sort {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    width: 100%;
    background: none;
    border: 0;
    padding: 0;
    font: inherit;
    font-size: inherit;
    font-weight: inherit;
    color: inherit;
    text-align: inherit;
    text-transform: inherit;
    letter-spacing: inherit;
    cursor: pointer;
}

.table__sort:hover {
    color: var(--text);
}

.table th.numeric .table__sort {
    justify-content: flex-end;
}

/* A CARET ONLY WHERE IT MEANS SOMETHING: on the sorted column always, on a hovered or focused
   column as a promise. Twelve carets down a header row is noise that tells you nothing. */
.table__sort-caret {
    flex: none;
    opacity: 0;
    transition: opacity var(--transition-fast), transform var(--transition-fast);
}

.table__sort:hover .table__sort-caret,
.table__sort:focus-visible .table__sort-caret {
    opacity: 0.45;
}

.table__sort.is-sorted {
    color: var(--text);
}

.table__sort.is-sorted .table__sort-caret {
    opacity: 1;
}

.table__sort.is-sorted--desc .table__sort-caret {
    transform: rotate(180deg);
}

/* ---------------------------------------------------------------------------------------------
   11. BADGE, CHIP
   --------------------------------------------------------------------------------------------- */

.badge {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 1px var(--space-2);
    border-radius: var(--radius-full);
    background: var(--surface-sunken);
    color: var(--text-muted);
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    line-height: 1.6;
    white-space: nowrap;
}

.badge--square {
    border-radius: var(--radius-xs);
}

/* Status variants */
.badge--neutral {
    background: var(--surface-sunken);
    color: var(--text-muted);
}

.badge--info {
    background: var(--info-bg);
    color: var(--info-text);
}

.badge--success {
    background: var(--success-bg);
    color: var(--success-text);
}

.badge--warning {
    background: var(--warning-bg);
    color: var(--warning-text);
}

.badge--danger {
    background: var(--danger-bg);
    color: var(--danger-text);
}

.badge--accent {
    background: var(--accent-soft);
    color: var(--accent-text);
}

/* Pipeline stage variants. The class name is the stage code with underscores turned into dashes,
   which is exactly what StatusBadge.razor emits. */
.badge--stage-new-inquiry {
    background: var(--stage-new-inquiry-bg);
    color: var(--stage-new-inquiry-fg);
}

.badge--stage-awaiting-client {
    background: var(--stage-awaiting-client-bg);
    color: var(--stage-awaiting-client-fg);
}

.badge--stage-questionnaire-sent {
    background: var(--stage-questionnaire-sent-bg);
    color: var(--stage-questionnaire-sent-fg);
}

.badge--stage-questionnaire-returned {
    background: var(--stage-questionnaire-returned-bg);
    color: var(--stage-questionnaire-returned-fg);
}

.badge--stage-quote-sent {
    background: var(--stage-quote-sent-bg);
    color: var(--stage-quote-sent-fg);
}

.badge--stage-meeting-scheduled {
    background: var(--stage-meeting-scheduled-bg);
    color: var(--stage-meeting-scheduled-fg);
}

.badge--stage-meeting-held {
    background: var(--stage-meeting-held-bg);
    color: var(--stage-meeting-held-fg);
}

.badge--stage-negotiation {
    background: var(--stage-negotiation-bg);
    color: var(--stage-negotiation-fg);
}

.badge--stage-contract-signed {
    background: var(--stage-contract-signed-bg);
    color: var(--stage-contract-signed-fg);
}

.badge--stage-won {
    background: var(--stage-won-bg);
    color: var(--stage-won-fg);
}

.badge--stage-lost {
    background: var(--stage-lost-bg);
    color: var(--stage-lost-fg);
}

.badge--stage-disqualified {
    background: var(--stage-disqualified-bg);
    color: var(--stage-disqualified-fg);
}

.badge__dot {
    width: 6px;
    height: 6px;
    border-radius: var(--radius-full);
    background: currentColor;
    flex: none;
}

.chip {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-full);
    background: var(--surface);
    color: var(--text);
    font-size: var(--text-body);
    white-space: nowrap;
}

.chip--accent {
    background: var(--accent-soft);
    border-color: var(--accent-soft-border);
    color: var(--accent-text);
}

/* AN APPLIED FILTER, AND ONLY AN APPLIED FILTER. A pill radius here would read as a status badge,
   which is the one thing this is not: square-ish corners say "control", round says "label". */
.chip--filter {
    gap: var(--space-1);
    padding: 3px var(--space-1) 3px var(--space-2);
    border-radius: var(--radius-sm);
    border-color: var(--border);
    background: var(--surface);
    font-size: var(--text-body);
    line-height: 1.3;
}

.chip--filter:hover {
    border-color: var(--border-strong);
}

.chip__label {
    font-size: var(--text-micro);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--text-subtle);
}

.chip__value {
    color: var(--text);
    max-width: 22ch;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.chip__remove {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    border: 0;
    border-radius: var(--radius-full);
    background: transparent;
    color: inherit;
    font: inherit;
    line-height: 1;
    cursor: pointer;
    opacity: 0.6;
}

.chip__remove:hover {
    opacity: 1;
    background: var(--surface-active);
}

/* ---------------------------------------------------------------------------------------------
   12. AGEING INDICATOR
   --------------------------------------------------------------------------------------------- */

.ageing {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: 1px var(--space-2);
    border-radius: var(--radius-xs);
    font-size: var(--text-meta);
    font-weight: var(--weight-normal);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
    color: var(--ageing-fresh-fg);
    background: var(--ageing-fresh-bg);
}

.ageing--warn {
    color: var(--ageing-warn-fg);
    background: var(--ageing-warn-bg);
}

.ageing--late {
    color: var(--ageing-late-fg);
    background: var(--ageing-late-bg);
    font-weight: var(--weight-medium);
}

/* ---------------------------------------------------------------------------------------------
   13. KPI TILE
   --------------------------------------------------------------------------------------------- */

.kpi-tile {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    padding: var(--space-4);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    box-shadow: var(--shadow-1);
}

.kpi-tile__label {
    font-size: var(--text-micro);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    color: var(--text-subtle);
}

.kpi-tile__value {
    font-size: var(--text-metric);
    font-weight: var(--weight-light);
    line-height: var(--leading-tight);
    letter-spacing: -0.015em;
}

.kpi-tile__meta {
    font-size: var(--text-meta);
    color: var(--text-muted);
}

.kpi-tile__delta--up {
    color: var(--success-text);
}

.kpi-tile__delta--down {
    color: var(--danger-text);
}

/* ---------------------------------------------------------------------------------------------
   14. EMPTY STATE
   --------------------------------------------------------------------------------------------- */

.empty-state {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-9) var(--space-5);
    text-align: center;
    color: var(--text-muted);
}

.empty-state__icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-bottom: var(--space-1);
    border-radius: var(--radius-full);
    background: var(--surface-sunken);
    color: var(--text-subtle);
}

.empty-state__title {
    font-size: var(--text-title);
    font-weight: var(--weight-medium);
    color: var(--text);
}

.empty-state__description {
    max-width: 46ch;
    font-size: var(--text-body);
}

.empty-state__actions {
    margin-top: var(--space-3);
    display: flex;
    gap: var(--space-2);
}

/* An empty state standing in for a table, inside the table's own hairline-bounded band. Left
   aligned, because it replaces rows that were left aligned and a centred block in a full-bleed
   band reads as a different page. */
.empty-state--table {
    align-items: flex-start;
    text-align: left;
    padding: var(--space-9) var(--space-5);
}

.empty-state--table .empty-state__description {
    max-width: 52ch;
}

/* ---------------------------------------------------------------------------------------------
   15. ALERT - a notice set in type, not a coloured box

   THERE IS NO BOX, AND THAT IS THE POINT. A tinted panel with a matching tinted border and a
   coloured left rail is the default shape of a message in every generated admin template on the
   internet, and it costs this product twice over: it spends colour on chrome, which the pipeline
   stages and the ageing thresholds need for meaning, and it lifts the message off the page as
   though it had arrived from a different application. A message belongs to the thing it is about.
   So it is set as text, on the page it interrupts, under a rule.

   WHAT CARRIES THE SEVERITY, in the order the eye takes it:

     1. A RULE ABOVE THE MESSAGE, in the severity's own ink - not the pastel border, the ink.
        2px when something is wrong, 1px when it is not. A failure ALSO closes with a hairline
        underneath, so it is the one notice in the product fenced on both sides: the move a
        printed statement makes on the line it wants read twice. Rule weight is what keeps
        warning and danger apart once the hue is gone.
     2. A WORD, in the left column - NAPOMENA / GOTOVO / UPOZORENJE / GRESKA - set in the micro
        role the KPI tiles and the table column headers already use: 11px, 600, uppercase, 0.1em,
        in the severity ink. The word is what makes the four variants legible in a greyscale
        screenshot, to a red-green colourblind reader, and to anyone who never learned an icon
        vocabulary (WCAG 1.4.1). Nothing new is introduced to carry it.
     3. THE SENTENCE, in --text at body size. It is the most important text on the screen at the
        moment it appears, so it is set in the most legible ink the product has, not in a weaker
        tinted one. Hue here is redundancy, never the carrier.

   SIDE EFFECTS OF OWNING NO SURFACE, all of them wanted:
     - It renders identically on --surface, --surface-page, --surface-sunken and --surface-raised,
       so an alert inside a card, a drawer, a modal or a kanban column needs no special case.
     - Windows High Contrast keeps every cue. Forced colours discard author backgrounds and
       override border COLOUR, but not border WIDTH and not generated content, so the 2px/1px
       distinction and the word both survive with no @media block to maintain.
     - It prints. There is no background for a printer to drop.

   NO --*-bg TOKEN IS USED HERE ANY MORE. The four pastel fills stay in the token block for the
   badges and the ageing indicator, which do need a fill to read at 12px inside a table row.

   A VARIANT SETS FOUR VALUES and never a second block of layout: --alert-word, --alert-ink,
   --alert-rule, --alert-rule-width. A fifth severity, if one is ever justified, is four lines.

   THE FOUR WORDS LIVE IN CSS rather than in the markup, which is the one compromise here: the
   alternative is the same word typed into 117 elements across 61 files. They are Croatian because
   the product is (App.razor sets lang="hr"), and they are escaped because this sheet is ASCII and
   is served with no @charset - \000161 is s-with-caron. A second locale moves these four strings
   into the markup and the rest of this section does not change.
   --------------------------------------------------------------------------------------------- */

.alert {
    --alert-word: "Napomena";
    --alert-ink: var(--text-muted);
    --alert-rule: var(--border-strong);
    --alert-rule-width: 1px;

    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--space-1) var(--space-4);

    margin: 0 0 var(--space-4);
    padding-block: var(--space-3);
    border: 0;
    border-block-start: var(--alert-rule-width) solid var(--alert-rule);
    border-radius: 0;
    background: none;

    color: var(--text);
    font-size: var(--text-body);
    line-height: var(--leading-normal);
}

/* THE LABEL COLUMN. 5.5rem is "UPOZORENJE" - the longest of the four - at the micro role with its
   0.1em tracking, rounded up to the next half-rem, so all four words occupy one column and two
   stacked messages start their sentences on the same vertical. flex: 0 0 auto lets a longer word
   push the column rather than clip, which is what happens when a reader enlarges text.

   The word is generated content, so a screen reader reading the page announces it as part of the
   message. That is wanted - it puts back the severity that StatusMessage strips out of the string
   when it removes the "Greska:" prefix. It is NOT a replacement for role="alert": generated
   content is not reliably carried into a live-region announcement, so the markup keeps its role
   and this is the visual channel plus a bonus. */
.alert::before {
    content: var(--alert-word);
    flex: 0 0 auto;
    min-width: 5.5rem;
    color: var(--alert-ink);
    font-size: var(--text-micro);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

/* 16rem of flex-basis is the width below which the word stops being a column and becomes a kicker
   above the sentence, because the flex line wraps by itself. On the 420px sign-in card and on a
   phone that is exactly what should happen, and it costs no media query.

   --prose-max is the same exemption the house rules already grant .prose and .form-column: a
   failure message set as one 1700px line across the content column is not a sentence, it is a
   ticker. */
.alert__body {
    flex: 1 1 16rem;
    min-width: 0;
    max-width: var(--prose-max);
}

.alert__title {
    font-weight: var(--weight-medium);
    margin-bottom: 2px;
}

.alert__list {
    margin: var(--space-1) 0 0;
    padding-inline-start: var(--space-4);
}

/* The discs become en dashes, which is what a list of things that went wrong looks like when a
   person sets it rather than a browser. Styling ::marker rather than switching list-style off
   keeps the element a LIST for VoiceOver, which drops list semantics the moment list-style is
   none; a browser that ignores content on ::marker falls back to a disc and loses nothing. */
.alert__list li::marker {
    content: "\2013\00a0";
}

.alert__list li + li {
    margin-top: 2px;
}

/* THE DISMISS BUTTON. Eight alerts carry one - the "Zatvori" on the pipeline action messages, on
   StageChangeAlert, and on the reset-password confirmation in Users. It sits at the end of the
   first line and stays at the end when the line wraps. It deliberately does NOT get pushed to the
   far edge with an auto margin: --prose-max already stops the sentence at 72ch, and a dismiss
   button stranded 1400px away from the message it dismisses belongs to nothing. */
.alert > .btn {
    flex: 0 0 auto;
    align-self: flex-start;
}

/* Anything else a page puts directly inside an alert is body content, not a second label. */
.alert > *:not(.alert__body):not(.btn) {
    flex: 1 1 16rem;
    min-width: 0;
}

.alert--info {
    --alert-word: "Napomena";
    --alert-ink: var(--info-text);
    --alert-rule: var(--info);
}

.alert--success {
    --alert-word: "Gotovo";
    --alert-ink: var(--success-text);
    --alert-rule: var(--success);
}

.alert--warning {
    --alert-word: "Upozorenje";
    --alert-ink: var(--warning-text);
    --alert-rule: var(--warning);
    --alert-rule-width: 2px;
}

.alert--danger {
    --alert-word: "Gre\000161ka";
    --alert-ink: var(--danger-text);
    --alert-rule: var(--danger);
    --alert-rule-width: 2px;

    /* The closing hairline is the whole difference between a refused sign-in and a hint, and it
       is spent HERE and nowhere else in the product. */
    border-block-end: 1px solid var(--alert-rule);
}

/* A link inside a message keeps the message's ink and is told apart by its underline, not by a
   second colour - the account link StageChangeAlert offers when an OIB is missing, above all. */
.alert a {
    color: inherit;
    text-decoration: underline;
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
}

/* .stack already spaces its children by --space-4, so an alert inside one was paying the gap
   twice and rendering 32px of air beneath itself. Eleven screens do this, the dashboard among
   them. */
.stack > .alert {
    margin-bottom: 0;
}

/* ---------------------------------------------------------------------------------------------
   16. TABS
   --------------------------------------------------------------------------------------------- */

.tabs {
    display: flex;
    align-items: center;
    gap: var(--space-1);
    border-bottom: 1px solid var(--border);
    margin-bottom: var(--space-4);
    overflow-x: auto;
    scrollbar-width: thin;
}

.tabs__tab {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-3);
    border: 0;
    background: none;
    color: var(--text-muted);
    font: inherit;
    font-size: var(--text-body);
    font-weight: var(--weight-medium);
    white-space: nowrap;
    cursor: pointer;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

.tabs__tab:hover {
    color: var(--text);
    text-decoration: none;
}

.tabs__tab.is-active {
    color: var(--accent-text);
    border-bottom-color: var(--accent);
}

.tabs__count {
    font-size: var(--text-meta);
    color: var(--text-subtle);
    background: var(--surface-sunken);
    border-radius: var(--radius-full);
    padding: 0 var(--space-2);
}

/* SAVED VIEWS. The row that turns a list into a workspace. Quieter than a content tab strip -
   these switch the question being asked, not the panel being shown - so the type is smaller and
   the inactive tabs sit further back. The active one is ink with a 2px ink underline. */
/* NO RULE UNDER THIS ROW, and no negative margin on the tabs. Two reasons, one visual and one
   mechanical. Visually, the command bar directly beneath already draws a hairline, and two rules
   forty pixels apart is chrome arguing with itself; the active tab's own 2px underline is enough
   of an anchor. Mechanically, .tabs__tab pulls itself 1px past the strip with margin-bottom:-1px
   so that its border can straddle the strip's rule - with no rule to straddle, that 1px is pure
   overflow, and .tabs is a scroll container, so it produced a 1px vertical scrollbar at the end of
   the row. Dropping both the rule and the margin removes the artefact and the extra line at once. */
.tabs--views {
    gap: var(--space-4);
    margin-bottom: 0;
    border-bottom: 0;
}

.tabs--views .tabs__tab {
    padding: var(--space-2) 0 var(--space-3);
    margin-bottom: 0;
    font-size: var(--text-body);
    font-weight: var(--weight-normal);
    color: var(--text-subtle);
}

.tabs--views .tabs__tab:hover {
    color: var(--text);
}

.tabs--views .tabs__tab.is-active {
    color: var(--text);
    font-weight: var(--weight-medium);
    border-bottom-color: var(--text);
}

/* ---------------------------------------------------------------------------------------------
   17. PAGINATION
   --------------------------------------------------------------------------------------------- */

.pagination {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    padding: var(--space-3);
    font-size: var(--text-body);
    color: var(--text-muted);
}

.pagination__summary {
    margin-inline-end: auto;
}

/* A footer that sits on the page rather than in a card foot: no padding of its own, count in
   micro-type, controls quiet. */
.pagination--flush {
    width: 100%;
    padding: 0;
    gap: var(--space-4);
}

.pagination--flush .pagination__summary {
    font-size: var(--text-meta);
    color: var(--text-muted);
}

.pagination--flush .pagination__size {
    font-size: var(--text-meta);
    color: var(--text-subtle);
}

.pagination--flush .pagination__size .select {
    border-color: transparent;
    background-color: transparent;
    color: var(--text-muted);
}

.pagination--flush .pagination__size .select:hover {
    border-color: var(--border);
    background-color: var(--surface);
}

.pagination__pages {
    display: flex;
    align-items: center;
    gap: var(--space-1);
}

.pagination__page {
    min-width: 28px;
    height: 28px;
    padding: 0 var(--space-2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    border: 1px solid transparent;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text-muted);
    font: inherit;
    font-size: var(--text-body);
    font-variant-numeric: tabular-nums;
    cursor: pointer;
}

.pagination__page:hover:not(:disabled) {
    background: var(--surface-active);
    color: var(--text);
}

.pagination__page.is-active {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--text-on-accent);
    font-weight: var(--weight-medium);
}

.pagination__page:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.pagination__ellipsis {
    padding: 0 var(--space-1);
    color: var(--text-subtle);
}

.pagination__size {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
}

.pagination__size .select {
    width: auto;
    min-height: 28px;
    padding-block: 0;
    font-size: var(--text-meta);
}

/* ---------------------------------------------------------------------------------------------
   18. SPINNER
   --------------------------------------------------------------------------------------------- */

.spinner {
    display: inline-block;
    width: 18px;
    height: 18px;
    border: 2px solid var(--border-strong);
    border-top-color: var(--accent);
    border-radius: var(--radius-full);
    animation: crm-spin 700ms linear infinite;
    flex: none;
}

.spinner--sm {
    width: 13px;
    height: 13px;
    border-width: 2px;
}

.spinner--lg {
    width: 28px;
    height: 28px;
    border-width: 3px;
}

.spinner-block {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    padding: var(--space-8);
    color: var(--text-muted);
    font-size: var(--text-body);
}

@keyframes crm-spin {
    to {
        transform: rotate(360deg);
    }
}

@media (prefers-reduced-motion: reduce) {
    .spinner {
        animation-duration: 2s;
    }

    *,
    *::before,
    *::after {
        transition-duration: 1ms !important;
    }
}

/* ---------------------------------------------------------------------------------------------
   19. DRAWER AND MODAL
   --------------------------------------------------------------------------------------------- */

.scrim {
    position: fixed;
    inset: 0;
    z-index: 60;
    background: rgba(17, 22, 29, 0.45);
    backdrop-filter: blur(1px);
}

.drawer {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 61;
    width: min(560px, 100vw);
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border-left: 1px solid var(--border);
    box-shadow: var(--shadow-3);
}

.drawer__header,
.modal__header {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-4);
    border-bottom: 1px solid var(--border);
}

.drawer__title,
.modal__title {
    font-size: var(--text-title);
    font-weight: var(--weight-medium);
    margin: 0;
    flex: 1 1 auto;
    min-width: 0;
}

.drawer__body,
.modal__body {
    flex: 1 1 auto;
    overflow-y: auto;
    padding: var(--space-4);
}

.drawer__footer,
.modal__footer {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: var(--space-2);
    padding: var(--space-3) var(--space-4);
    border-top: 1px solid var(--border);
    background: var(--surface-sunken);
}

.modal {
    position: fixed;
    inset: 0;
    z-index: 61;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--space-4);
    pointer-events: none;
}

.modal__dialog {
    pointer-events: auto;
    width: min(520px, 100%);
    max-height: calc(100vh - var(--space-8));
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-3);
}

.modal__dialog--wide {
    width: min(880px, 100%);
}

/* ---------------------------------------------------------------------------------------------
   20. KANBAN
   --------------------------------------------------------------------------------------------- */

.kanban {
    display: flex;
    gap: var(--space-3);
    align-items: flex-start;
    overflow-x: auto;
    padding-bottom: var(--space-3);
    scrollbar-width: thin;
}

.kanban__column {
    flex: 0 0 280px;
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - 220px);
    background: var(--surface-sunken);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
}

.kanban__column-header {
    position: sticky;
    top: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-3);
    border-bottom: 1px solid var(--border);
    background: var(--surface-sunken);
    border-radius: var(--radius-md) var(--radius-md) 0 0;
}

.kanban__column-title {
    font-size: var(--text-body);
    font-weight: var(--weight-medium);
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.kanban__column-count {
    font-size: var(--text-meta);
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
    background: var(--surface);
    border-radius: var(--radius-full);
    padding: 0 var(--space-2);
}

.kanban__column-total {
    padding: var(--space-1) var(--space-3) var(--space-2);
    font-size: var(--text-meta);
    font-variant-numeric: tabular-nums;
    color: var(--text-muted);
    background: var(--surface-sunken);
}

.kanban__cards {
    flex: 1 1 auto;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-2);
    scrollbar-width: thin;
}

.kanban__card {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-3);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-1);
    cursor: pointer;
    transition: border-color var(--transition-fast), box-shadow var(--transition-fast);
}

.kanban__card:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-2);
}

.kanban__card.is-dragging {
    opacity: 0.55;
}

.kanban__card-title {
    font-size: var(--text-body);
    font-weight: var(--weight-medium);
    line-height: var(--leading-tight);
}

.kanban__card-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-2);
    font-size: var(--text-meta);
    color: var(--text-muted);
}

.kanban__column.is-drop-target {
    outline: 2px dashed var(--accent);
    outline-offset: -2px;
}

/* ---------------------------------------------------------------------------------------------
   20.1 THE BRAND MARK

   ONE DEFINITION. It used to be three: .login__brand-mark, .shell__wordmark-mark and
   .nav__workspace-mark, hand-copied into three scoped stylesheets, each with a comment claiming
   the other two matched it. They did not. The radius had drifted to a raw 6px in NavMenu against
   the other two on --radius-sm, and - the one a user could actually see - NavMenu painted itself
   from --surface-inverse while the other two used --accent. In light both resolve to black on
   white, which is why nobody caught it; in dark --accent is a blue, so the mark was BLUE on the
   sign-in screen and WHITE in the sidebar the user landed in. The brand changed colour inside one
   session.

   So the chip lives here, once, and the three lockups now set nothing but their own size. Three
   sizes is the deliberate part and always was: the mark sits in three different amounts of
   surround, and a mark that does not grow with its surround looks shrunken in the roomier one.

   PRINT. Browsers drop backgrounds when printing, which on an ink chip with white glyphs meant a
   white "2F" on white paper - an invisible mark leaving a 24-28px hole. print-color-adjust keeps
   the fill. This matters because the topbar lockup is hidden only above 900px and A4 at 96dpi is
   about 794px, so it DOES render on a printed page.

   FORCED COLOURS. Windows High Contrast discards the fill, which would leave the two glyphs
   floating loose beside the wordmark. An explicit border keeps the chip a chip.
   --------------------------------------------------------------------------------------------- */

.brand-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: var(--brand-mark-size, 26px);
    height: var(--brand-mark-size, 26px);
    border-radius: var(--radius-sm);
    background: var(--brand-mark-bg);
    color: var(--brand-mark-fg);
    /* 0.04em, not the micro role's 0.1em: two glyphs centred in a box read off-centre once the
       trailing letter-space is counted in. The explicit value also cancels whatever tracking the
       lockup around it inherits. */
    font-size: var(--text-micro);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.04em;
}

@media print {
    .brand-mark {
        -webkit-print-color-adjust: exact;
        print-color-adjust: exact;
    }
}

@media (forced-colors: active) {
    .brand-mark {
        border: 1px solid CanvasText;
    }
}

/* ---------------------------------------------------------------------------------------------
   21. MISCELLANY THE PAGES WILL WANT
   --------------------------------------------------------------------------------------------- */

.definition-list {
    display: grid;
    grid-template-columns: minmax(120px, max-content) 1fr;
    gap: var(--space-2) var(--space-4);
    margin: 0;
    font-size: var(--text-body);
}

.definition-list dt {
    color: var(--text-muted);
}

.definition-list dd {
    margin: 0;
}

.avatar {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 26px;
    height: 26px;
    flex: none;
    border-radius: var(--radius-full);
    background: var(--accent-soft);
    color: var(--accent-text);
    font-size: var(--text-micro);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
}

.money--negative {
    color: var(--danger-text);
}

.money__unit {
    color: var(--text-muted);
    font-size: var(--text-meta);
}

/* Blazor's unhandled-error bar, restyled so it belongs to this product. It keeps a surface, unlike
   .alert, because it is an overlay pinned over live content and has to be opaque to be read at
   all - but it takes the same language as
   section 15 otherwise: a 2px rule in the severity's own ink, the sentence in --text, and no
   tint. Left as a pastel amber band it would have been the last tinted message in the product,
   and the one thing on screen still looking like the template it came from. */
#blazor-error-ui {
    display: none;
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 1000;
    padding: var(--space-3) var(--space-5);
    background: var(--surface);
    color: var(--text);
    border-top: 2px solid var(--danger);
    box-shadow: var(--shadow-3);
    font-size: var(--text-body);
}

#blazor-error-ui .dismiss {
    position: absolute;
    right: var(--space-4);
    top: var(--space-3);
    cursor: pointer;
}

/* ---------------------------------------------------------------------------------------------
   22. ACCOUNT PAGES

   The /racun screens share LoginLayout, which supplies the centred card. These are the bits inside
   it, kept here rather than in a scoped stylesheet so every account page reads the same.
   --------------------------------------------------------------------------------------------- */

.login-title {
    font-size: var(--text-page);
    font-weight: var(--weight-light);
    letter-spacing: -0.02em;
    line-height: 1.15;
    margin: 0 0 var(--space-2);
}

.login-lead {
    margin: 0 0 var(--space-5);
    color: var(--text-muted);
}

.login-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--space-3);
    flex-wrap: wrap;
    margin-bottom: var(--space-5);
    font-size: var(--text-body);
}

.login-note {
    margin: var(--space-5) 0 0;
    font-size: var(--text-meta);
    color: var(--text-muted);
    text-align: center;
}

/* Money and dates never break across a line: half an amount on each line reads as a different
   number, and "12." at the end of a line reads as an ordinal. */
.money,
.relative-date {
    white-space: nowrap;
}

/* ---------------------------------------------------------------------------------------------
   23. THE LIST WORKBENCH

   Five screens in this product are a list of records with filters over it, and they must be the
   same screen five times. Everything below is the vocabulary those five share. A page composes
   these classes; it does not invent a variant, and it never writes a colour, a height or a radius
   of its own.

   THE IDEA IN ONE LINE: a list page is a TOOL, not a report. So the chrome is three thin bands -
   head, saved views, command bar - stacked on hairlines with no fills and no boxes, and then the
   table is the surface. Affordances appear when they are relevant and take no space when they are
   not: an unset filter is not a dropdown, it is nothing at all.

   WHAT IS DELIBERATELY ABSENT, because each of them is a tell that nobody chose anything:
     - a card wrapped round the table;
     - drop shadows used as the depth cue (hairlines do it; shadows are for things that float);
     - zebra striping (a hairline and a hover row read better and do not fight stage colour);
     - uniform 8px corners on everything;
     - a row of always-visible empty selects;
     - a centred spinner where a skeleton belongs.
   --------------------------------------------------------------------------------------------- */

/* --------------------------------------------------------------------------- 23.1 type scale */

/* THE MICRO-LABEL. 11px, 600, uppercase, wide-tracked. It is the counterweight to the 300-weight
   display title: the WEIGHT CONTRAST between the two is the whole editorial idea, and it is why
   neither is allowed to drift toward the other. Use it for a record count, a section label, a
   column heading - anything that names data rather than being data. */
.micro {
    font-size: var(--text-micro);
    font-weight: var(--weight-semibold);
    text-transform: uppercase;
    letter-spacing: 0.1em;
    line-height: 1.4;
    color: var(--text-muted);
}

/* 0.08em, for a micro-label inside a dense table header where 0.1em starts to break words up. */
.micro--tight {
    letter-spacing: 0.08em;
}

.micro--strong {
    color: var(--text);
}

/* The display title on its own, for a head that is not built from .page-header. */
.display-title {
    margin: 0;
    font-size: var(--text-page);
    font-weight: var(--weight-light);
    letter-spacing: -0.02em;
    line-height: 1.15;
    color: var(--text);
}

/* --------------------------------------------------------------------------- 23.2 command bar */

/* ONE ROW, A HAIRLINE BENEATH IT, NO BOX. It holds a search field, the filters that are actually
   applied, the control that adds one, and the view controls. NOTHING EMPTY IS EVER SHOWN: an
   unset filter occupies no pixels, which is the difference between a command bar and a form. */
.command-bar {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    min-height: 48px;
    padding-block: var(--space-2);
    border-bottom: 1px solid var(--border);
}

.command-bar__search {
    position: relative;
    display: flex;
    align-items: center;
    flex: none;
}

.command-bar__search-icon {
    position: absolute;
    left: 0;
    display: block;
    color: var(--text-subtle);
    pointer-events: none;
}

/* Borderless, and it EXPANDS ON FOCUS. A search field that is already 320px wide while empty is
   just a box; one that grows when you commit to it is an affordance appearing when it is
   relevant. The transition is on width only, so nothing else on the row moves. */
.command-bar__search-input {
    width: 200px;
    max-width: 100%;
    height: 30px;
    padding: 0 var(--space-2) 0 var(--space-6);
    border: 0;
    border-radius: var(--radius-sm);
    background: transparent;
    color: var(--text);
    font: inherit;
    font-size: var(--text-body);
    transition: width var(--transition-base), background-color var(--transition-fast);
}

.command-bar__search-input::placeholder {
    color: var(--text-subtle);
}

.command-bar__search-input:hover {
    background: var(--surface-hover);
}

.command-bar__search-input:focus {
    outline: none;
    width: 320px;
    background: var(--surface);
}

.command-bar__search-input:focus-visible {
    box-shadow: var(--focus-ring);
}

/* Chrome and Edge paint their own clear button inside a type=search field. It is a second, differently
   styled affordance sitting in a control that has deliberately been stripped of chrome. */
.command-bar__search-input::-webkit-search-cancel-button {
    appearance: none;
}

.command-bar__filters {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    min-width: 0;
}

.command-bar__end {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    margin-inline-start: auto;
}

.command-bar__divider {
    width: 1px;
    height: 20px;
    flex: none;
    background: var(--border);
}

@media (max-width: 599.98px) {
    .command-bar__search-input,
    .command-bar__search-input:focus {
        width: 100%;
    }

    .command-bar__search {
        flex: 1 1 100%;
    }

    .command-bar__end {
        margin-inline-start: 0;
    }
}

/* --------------------------------------------------------------------------- 23.3 menu */

/* A SMALL ANCHORED POPOVER, script-free. Used by the "+ Filtar" control and by anything else that
   has to offer a short list of choices without spending a row on them.

   The backdrop is a transparent fixed button rather than a document click handler: it closes the
   menu on an outside click with no JavaScript module, no interop call and no listener to leak. */
.menu {
    position: relative;
    display: inline-flex;
    flex: none;
}

.menu__panel {
    position: absolute;
    top: calc(100% + var(--space-2));
    left: 0;
    z-index: 50;
    min-width: 224px;
    max-height: 340px;
    overflow-y: auto;
    padding: var(--space-1);
    background: var(--surface-raised);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    box-shadow: var(--shadow-3);
    scrollbar-width: thin;
}

/* For a menu near the trailing edge, so the panel does not run off the content column. */
.menu__panel--end {
    left: auto;
    right: 0;
}

.menu__heading {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: var(--space-2) var(--space-2) var(--space-1);
}

.menu__item {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    width: 100%;
    padding: var(--space-2);
    border: 0;
    border-radius: var(--radius-xs);
    background: none;
    color: var(--text);
    font: inherit;
    font-size: var(--text-body);
    text-align: left;
    cursor: pointer;
}

.menu__item:hover {
    background: var(--surface-hover);
    text-decoration: none;
}

.menu__item.is-active {
    background: var(--surface-active);
    font-weight: var(--weight-medium);
}

.menu__item-hint {
    margin-inline-start: auto;
    color: var(--text-subtle);
    font-size: var(--text-meta);
}

.menu__separator {
    height: 1px;
    margin: var(--space-1) 0;
    border: 0;
    background: var(--border);
}

.menu__field {
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-1) var(--space-2) var(--space-2);
}

.menu__empty {
    padding: var(--space-2);
    font-size: var(--text-body);
    color: var(--text-subtle);
}

.menu__backdrop {
    position: fixed;
    inset: 0;
    z-index: 49;
    padding: 0;
    border: 0;
    background: transparent;
    cursor: default;
}

/* --------------------------------------------------------------------------- 23.4 segmented */

/* Two or three mutually exclusive settings that are cheap enough to show at all times. The
   density toggle is the canonical use. */
.segmented {
    display: inline-flex;
    flex: none;
    overflow: hidden;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--surface);
}

.segmented__option {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-1);
    min-width: 30px;
    height: 26px;
    padding: 0 var(--space-2);
    border: 0;
    background: transparent;
    color: var(--text-subtle);
    font: inherit;
    font-size: var(--text-body);
    cursor: pointer;
    transition: background-color var(--transition-fast), color var(--transition-fast);
}

.segmented__option + .segmented__option {
    border-inline-start: 1px solid var(--border);
}

.segmented__option:hover {
    background: var(--surface-hover);
    color: var(--text);
}

.segmented__option.is-active {
    background: var(--surface-active);
    color: var(--text);
}

/* --------------------------------------------------------------------------- 23.5 table surface */

/* THE TABLE IS THE SURFACE. No card, no radius, no shadow: a white band bounded top and bottom by
   a hairline, running the full width of the content column. A card around a table adds a second
   border, a radius that has to be clipped and a shadow that says "this floats", for a thing that
   does not float and is the entire point of the page.

   THE HORIZONTAL SCROLLER IS CONDITIONAL, and that is not a detail. overflow-x on a container
   makes it a scroll container in BOTH axes, which silently kills position:sticky on the header -
   the header sticks to the top of a box that has no height limit, i.e. never. Above 1100px there
   is room for the columns, so there is no scroller and the sticky header works against the
   viewport, parked under the shell's top bar. Below it, the scroller comes back and the sticky
   offset returns to zero because the header is now sticky inside the scroller. */
.table-surface {
    --table-sticky-top: var(--topbar-height);

    width: 100%;
    background: var(--surface);
    border-block: 1px solid var(--border);
}

@media (max-width: 1099.98px) {
    .table-surface {
        --table-sticky-top: 0px;

        overflow-x: auto;
        scrollbar-width: thin;
    }
}

/* The leading selection column. 36px: 16px of gutter, a 16px control, and 4px before the
   monogram column starts. */
.table th.table__select,
.table td.table__select {
    width: 36px;
    padding-inline: var(--space-4) 0;
}

/* --------------------------------------------------------------------------- 23.6 check */

/* A DRAWN CHECKBOX, not the operating system's. Three reasons, in order of weight: the native
   control cannot be given an indeterminate state from markup (it is a property, not an
   attribute), so a header checkbox over a partial selection would need a JavaScript module to
   exist at all; the native control looks like a different product in every browser; and role=
   checkbox with aria-checked carries "mixed", which is exactly the state we need to express. */
.check {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 16px;
    height: 16px;
    flex: none;
    padding: 0;
    border: 1px solid var(--border-strong);
    border-radius: 3px;
    background: var(--surface);
    color: transparent;
    cursor: pointer;
    transition: background-color var(--transition-fast), border-color var(--transition-fast);
}

.check:hover {
    border-color: var(--text-subtle);
}

.check[aria-checked="true"],
.check[aria-checked="mixed"] {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--text-on-accent);
}

.check svg {
    display: block;
}

/* --------------------------------------------------------------------------- 23.7 monogram */

/* THE SINGLE HIGHEST-VALUE MARK ON THE PAGE. Twenty-five rows of plain text have no anchor: the
   eye has nothing to count down and nothing to come back to after looking away. A 22px monogram
   gives every row a fixed point at a fixed x, and it costs no colour.

   ONE FLAT NEUTRAL TINT FOR EVERY ROW. A hue derived from the name would be twenty-five different
   colours meaning nothing, competing with the twelve stage colours that mean something. The
   6px radius on a 22px square is deliberately not the system's 8px: it reads as a squircle, which
   is neither the circular avatar in the top bar nor another rounded rectangle. */
.monogram {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    flex: none;
    border-radius: 6px;
    background: var(--monogram-bg);
    color: var(--monogram-fg);
    font-size: var(--text-micro);
    font-weight: var(--weight-semibold);
    letter-spacing: 0.02em;
    line-height: 1;
    text-transform: uppercase;
    user-select: none;
}

.monogram--sm {
    width: 18px;
    height: 18px;
    border-radius: 5px;
    font-size: var(--text-micro);
    letter-spacing: 0;
}

.monogram--lg {
    width: 32px;
    height: 32px;
    border-radius: 8px;
    font-size: var(--text-meta);
}

/* The name cell: monogram, then the name, then whatever the name is not. */
.cell-name {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-width: 0;
}

.cell-name__text {
    min-width: 0;
}

.cell-name__primary {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    min-width: 0;
    font-size: var(--text-body);
    font-weight: var(--weight-medium);
    line-height: 1.35;
}

.cell-name__link {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    color: var(--text);
}

.cell-name__link:hover {
    text-decoration: underline;
}

.cell-name__secondary {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: var(--text-meta);
    line-height: 1.35;
    color: var(--text-subtle);
}

/* --------------------------------------------------------------------------- 23.8 values */

/* Figures, identifiers and codes. Medium weight, tabular, so a twelve-digit OIB does not
   out-measure the column heading above it.

   A PRESENT VALUE IS NOT A SIGNAL. An OIB that exists is data; rendering it as a grey chip says
   "look at me" about the most ordinary fact on the row. Tabular and muted is enough: it aligns,
   it reads as an identifier rather than a word, and it disappears until you need it.

   THE NAME IS NOW A LIE and that is deliberate for one wave only. There is no monospace family:
   .mono is Geist with the globally-on tabular figures doing the aligning that a monospace used
   to do. The class keeps its name so the markup does not have to change in the same wave as the
   typeface; rename it to .ident when the sweep is finished. */
.mono {
    font-family: var(--font-sans);
    font-size: var(--text-meta);
    font-weight: var(--weight-medium);
    /* The -0.01em that used to be here was tightening IBM Plex Mono, which ran wide. Geist
       is not wide, and an identifier is the last string you want compressed. */
    letter-spacing: normal;
}

.mono--muted {
    color: var(--text-muted);
}

/* THE ABSENCE OF A VALUE. A single en dash, recessive, at 12px - not an em dash at body weight,
   which reads as content. Always rendered aria-hidden: an empty cell should be empty to a screen
   reader, not announced as a punctuation mark, which is also why this colour is exempt from the
   contrast floor - there is no text here to read. */
.cell-empty {
    font-size: var(--text-meta);
    color: var(--text-empty);
}

/* --------------------------------------------------------------------------- 23.9 dot label */

/* A 5px DOT AND A PLAIN LABEL, in place of a filled pill. A soft-beige badge round a word like
   "Klijent" is low contrast, muddy, and spends a filled shape on a fact that is neither a status
   nor a warning. The dot is the only colour on the row and the label carries the meaning, so the
   mark still works for a colour-blind reader and in a monochrome print.

   The variants are semantic, not domain names, so the other four list screens reuse them:
   a lead status, an activity outcome and an account type all resolve to one of these six. */
.dot-label {
    display: inline-flex;
    align-items: center;
    gap: var(--space-2);
    font-size: var(--text-body);
    color: var(--text);
    white-space: nowrap;
}

.dot-label__dot {
    width: 5px;
    height: 5px;
    flex: none;
    border-radius: var(--radius-full);
    background: var(--dot-neutral);
}

.dot-label--positive .dot-label__dot {
    background: var(--dot-positive);
}

.dot-label--info .dot-label__dot {
    background: var(--dot-info);
}

.dot-label--attention .dot-label__dot {
    background: var(--dot-attention);
}

.dot-label--critical .dot-label__dot {
    background: var(--dot-critical);
}

.dot-label--inactive .dot-label__dot {
    background: var(--dot-inactive);
}

.dot-label--inactive {
    color: var(--text-muted);
}

/* --------------------------------------------------------------------------- 23.10 row actions */

/* QUICK ACTIONS THAT FADE IN AT THE RIGHT EDGE. They are always in the DOM at opacity 0, so the
   row never changes width and the columns never jump - a cluster that is inserted on hover
   reflows the table under the cursor, which is the single most irritating thing a dense list can
   do.

   pointer-events is switched with the opacity, because an invisible button that still swallows
   clicks is worse than no button. :focus-within brings the cluster back for a keyboard user the
   moment the row's link takes focus, so it is reachable on the way through. */
.table td.row-actions,
.table th.row-actions {
    width: 1%;
    padding-inline-end: var(--space-3);
    text-align: right;
    white-space: nowrap;
}

.row-actions__cluster {
    display: inline-flex;
    align-items: center;
    gap: 2px;
    opacity: 0;
    pointer-events: none;
    transition: opacity var(--transition-fast);
}

.table tbody tr:hover .row-actions__cluster,
.table tbody tr:focus-within .row-actions__cluster,
.table tbody tr.is-selected .row-actions__cluster {
    opacity: 1;
    pointer-events: auto;
}

/* On a touch screen there is no hover, so a hover-only affordance does not exist at all. */
@media (hover: none) {
    .row-actions__cluster {
        opacity: 1;
        pointer-events: auto;
    }
}

/* --------------------------------------------------------------------------- 23.11 footer, bulk bar */

/* One band under the table. It holds the pagination, or - while a selection exists - the bulk
   bar. The min-height is what keeps the swap from moving the table by a pixel. */
.table-footer {
    display: flex;
    align-items: center;
    min-height: 52px;
    padding-block: var(--space-2);
}

/* THE ONE INVERTED SURFACE IN THE PRODUCT, and it earns the inversion by being temporary: it
   exists only while rows are selected, so it reads as a mode rather than as decoration. */
.bulk-bar {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    flex-wrap: wrap;
    width: 100%;
    min-height: 36px;
    padding: var(--space-1) var(--space-2) var(--space-1) var(--space-3);
    border-radius: var(--radius-sm);
    background: var(--bulk-bar-bg);
    color: var(--bulk-bar-fg);
}

.bulk-bar__count {
    font-size: var(--text-body);
    font-weight: var(--weight-medium);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}

.bulk-bar__divider {
    width: 1px;
    height: 18px;
    flex: none;
    background: var(--bulk-bar-border);
}

.bulk-bar__actions {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    flex-wrap: wrap;
    margin-inline-start: auto;
}

/* The global focus ring is drawn as two rings, the inner one in --surface so it separates from
   whatever is behind it. On an inverted bar that inner ring is the wrong colour, so it is
   restated here. Every control on this bar still shows a visible ring. */
.bulk-bar :focus-visible {
    box-shadow: 0 0 0 2px var(--bulk-bar-bg), 0 0 0 4px var(--bulk-bar-fg);
}

/* --------------------------------------------------------------------------- 23.12 skeleton */

/* A SKELETON OF ROWS, NOT A SPINNER. A centred spinner on a list page makes the whole screen feel
   like it restarted: the chrome is still there, the data is not, and a spinner in the middle of
   the empty space says "everything is gone" rather than "the rows are coming". A skeleton keeps
   the shape of the answer on screen, so the eye stays where it was.

   The widths are a fixed repeating pattern rather than random: a skeleton that reshuffles on
   every render draws attention to itself, and a random one cannot be reasoned about. */
.skeleton {
    display: block;
    height: 9px;
    border-radius: 2px;
    background-color: var(--skeleton-bg);
    background-image: linear-gradient(90deg, transparent, var(--skeleton-sheen), transparent);
    background-size: 180px 100%;
    background-repeat: no-repeat;
    animation: crm-sheen 1.2s ease-in-out infinite;
}

.skeleton--monogram {
    width: 22px;
    height: 22px;
    flex: none;
    border-radius: 6px;
}

.skeleton--check {
    width: 16px;
    height: 16px;
    flex: none;
    border-radius: 3px;
}

.skeleton--w-xs {
    width: 28%;
}

.skeleton--w-sm {
    width: 42%;
}

.skeleton--w-md {
    width: 58%;
}

.skeleton--w-lg {
    width: 74%;
}

.skeleton--w-full {
    width: 100%;
}

.skeleton-row {
    display: flex;
    align-items: center;
    gap: var(--space-3);
}

@keyframes crm-sheen {
    from {
        background-position: -180px 0;
    }

    to {
        background-position: calc(100% + 180px) 0;
    }
}

@media (prefers-reduced-motion: reduce) {
    .skeleton {
        animation: none;
        background-image: none;
    }
}
