/* ============================================================
   DESIGN TOKENS — single source of truth for the whole UI
   ============================================================
   Loaded FIRST in css/allStyles.css and via a separate <link>
   in odf-service/index.html. All names carry the --dc- prefix
   so they can never collide with odf-service's own local tokens
   (--radius-lg, --color-primary etc. are already taken there).

   Consumers: modals.css, ui-primitives.css, productSelector.css,
   the <style> injected by js/notify.js (with fallback values),
   odf-service/css/cabinet/components.css (modals only).
   ============================================================ */

:root {
    /* ── Semantic colors ─────────────────────────────────────── */
    --dc-c-success:      #27ae60;
    --dc-c-success-hover:#219a55;
    --dc-c-warning:      #e67e22;
    --dc-c-error:        #e74c3c;
    --dc-c-error-hover:  #c0392b;
    --dc-c-info:         #2980b9;
    --dc-c-accent:       #65b8a5;
    --dc-c-accent-hover: #52a394;
    --dc-c-accent-light: #e8f5f2;
    /* Not neutrals: at 29% saturation these read as the product's dark blue, and
       they are named as a colour rather than a step on purpose. */
    --dc-c-dark:         #2c3e50;   /* headings / primary text */
    --dc-c-confirm-surf: #34495e;   /* confirm-toast surface   */
    /* This one WAS a neutral all along - #7f8c8d is n-600 to the pixel. */
    --dc-c-muted:        var(--dc-n-600);

    /* ── User role colors ────────────────────────────────────────
       One colour per role, shared by the account modal badge and the
       users admin table so a role always reads the same everywhere. */
    --dc-c-role-admin:        #7b2d8b;
    --dc-c-role-expert:       #1a6b3c;
    --dc-c-role-professional: #1a4a8b;
    /* The plain user has no colour of its own - it is the neutral, and saying so
       keeps it moving with the scale instead of drifting beside it. */
    --dc-c-role-user:         var(--dc-n-750);

    /* ── Neutral scale ───────────────────────────────────────── */
    /* Seventy-three grey values lived across the stylesheets — two thirds of
       every colour outside this file — and they were one scale written many
       ways: #555 appeared 37 times and so did #666, #787878 and #46545a, a step
       or two away and no different in meaning.

       Fifteen rungs hold all of them. Each rung sits at a lightness the product
       already stood on (the busiest value at that level), so most declarations
       kept the colour they had; what changed is the hue. Every rung is hue 210,
       so the ramp is one family rather than the old mixture of true greys and
       the cool leftovers of a former palette (#bdc3c7, #95a5a6, #ecf0f1).
       Saturation is gentle and a little stronger in the darks, where a tint
       needs more of itself to be seen at all.

       Contrast against white, which decides where text may sit:
         n-700  4.53:1  the lightest that passes AA for body text
         n-600  3.62:1  large text only
         n-500 and lighter — decoration, never a sentence someone must read.

       Text inside the to-scale drawings keeps its own colours: css/svg-elements.css
       is the drawing's palette, not the interface's, and the same rule applies as
       for the type scale — judge by what the element draws, not by its name. */
    --dc-n-0:    #ffffff;   /* white                                  */
    --dc-n-25:   #f9f9f9;   /* surface, raised                        */
    --dc-n-50:   #eff0f1;   /* surface, sunken                        */
    --dc-n-100:  #e7e8e9;   /* surface deepest / line softest         */
    --dc-n-150:  #dee0e2;   /* line                                   */
    --dc-n-200:  #cdd0d3;   /* line, strong                           */
    --dc-n-300:  #bdc2c7;   /* line on colour / text, disabled        */
    --dc-n-400:  #a3aab1;   /* text, faintest                         */
    --dc-n-500:  #9099a2;   /* text, faint                            */
    --dc-n-600:  #7c8894;   /* text, muted — large text only          */
    --dc-n-700:  #687888;   /* text, secondary — AA from here down    */
    --dc-n-750:  #596673;   /* text, secondary-strong                 */
    --dc-n-800:  #4b555f;   /* text, body                             */
    --dc-n-900:  #3c444c;   /* text, strong                           */
    --dc-n-950:  #2d3339;   /* text, max                              */
    --dc-n-1000: #1e2226;   /* near-black; nothing needs pure #000    */

    /* ── Type scale ──────────────────────────────────────────── */
    /* Seven steps, and every piece of interface text is set in one of them.
       13px is the body size: forms, buttons, menus, most running text.

       Two kinds of text deliberately DO NOT use these, and neither is an
       oversight:

       • Text inside a to-scale drawing — unit numbers on a rack, ODF cassette
         labels, port labels on equipment. There the size follows the geometry
         (1U comes out about 20px tall), not reading distance, and every picture
         has its own scale. Those stay literal px.
         The test is what the element DRAWS, never what its class is called: the
         prefix .odf- covers both the front elevation, which is a drawing, and
         the preset picker modal, which is not; .po- is the panels overview, a
         modal throughout; .eq- is both the equipment front face and the editor
         around it. Sorting them by name got 44 declarations wrong once already.
       • The size of a GLYPH on an icon button — the × of a close button, an
         arrow, the +/− of a stepper. That is an icon size wearing font-size's
         clothes; tie it to a text step and the next tune of the scale resizes
         every close button in the product. Those stay literal px too.

       The ladder with live samples, the roles and the census this came from are
       on the stand: scripts/ui-kit-stand.html, section "Type scale". */
    --dc-text-2xs: 10px;   /* badges, pills, chips         */
    --dc-text-xs:  11px;   /* captions, meta, status lines */
    --dc-text-sm:  12px;   /* dense lists, table cells     */
    --dc-text-md:  13px;   /* body: forms, buttons, menus  */
    --dc-text-lg:  15px;   /* section and card titles      */
    --dc-text-xl:  18px;   /* modal titles                 */
    --dc-text-2xl: 22px;   /* the largest cabinet label    */
    /* Above the scale there is one size and it has one user: the welcome screen's
       title at 28px (.intro-title). A hero shown once is not a step, and a token
       with a single user is noise, so it stays a literal. */

    /* ── Modal surface and overlay backdrop ──────────────────── */
    --dc-modal-surface:    #ffffff;
    --dc-overlay-backdrop: rgba(24, 33, 41, 0.48);
    --dc-overlay-blur:     3px;
    /* Both were within a percent of n-50 and of each other: one line, two names and
       two values. One value now; the two names stay because a border and a divider
       are different jobs and may yet part company. */
    --dc-modal-border:     var(--dc-n-50);
    --dc-modal-divider:    var(--dc-n-50);

    /* ── Control height ──────────────────────────────────────── */
    /* One height for the controls that stand in a row together: a button, a
       field, a tab, an icon button. They used to be 29, 34, 34 and 30, so a
       toolbar came out ragged by five pixels and nobody could say which was
       right. 34 wins because three of the four already agreed on it.

       Deliberately NOT a min-height on every <button>: plenty of small buttons
       set only their padding — the ↑↓ of the zone list, the +/- of the canvas
       zoom — and a floor of 34 would blow them up. The height is reached by the
       same padding and type as .btn, so anything that sets its own padding keeps
       its own size on purpose. */
    --dc-control-h: 34px;

    /* ── Radius scale ────────────────────────────────────────── */
    --dc-radius-xs: 4px;    /* small buttons               */
    --dc-radius-sm: 6px;    /* toasts, inputs              */
    --dc-radius-md: 8px;    /* cards                       */
    --dc-radius-lg: 12px;   /* modal container (approved)  */

    /* ── Elevation scale ─────────────────────────────────────── */
    --dc-elev-sm:    0 1px 3px rgba(20, 30, 40, 0.10);
    --dc-elev-md:    0 4px 12px rgba(20, 30, 40, 0.15);
    --dc-elev-lg:    0 8px 30px rgba(20, 30, 40, 0.20);
    --dc-elev-toast: 0 4px 16px rgba(20, 30, 40, 0.22);
    --dc-elev-modal: 0 18px 50px -12px rgba(20, 30, 40, 0.38),
                     0 4px 14px rgba(20, 30, 40, 0.10);

    /* ── Motion ──────────────────────────────────────────────── */
    --dc-ease-pop:        cubic-bezier(0.22, 1, 0.36, 1);
    --dc-anim-in:         0.26s;
    --dc-transition:      0.2s ease;
    --dc-transition-fast: 0.15s ease;

    /* ── Spacing (4px step, for new code) ────────────────────── */
    --dc-space-1: 4px;  --dc-space-2: 6px;  --dc-space-3: 8px;
    --dc-space-4: 10px; --dc-space-5: 12px; --dc-space-6: 16px;
    --dc-space-7: 20px; --dc-space-8: 24px;

    /* ── z-index scale (modals < toasts) ─────────────────────── */
    --dc-z-modal:        2000;    /* regular dialogs            */
    --dc-z-modal-stack:  2100;    /* dialog on top of a dialog  */
    --dc-z-modal-top:    2300;    /* pickers above the stack    */
    --dc-z-modal-zoom:   10000;   /* fullscreen zoom / image    */
    /* The zoom tier stacks INSIDE itself, and each dialog says so with an offset
       from the rung above rather than a token of its own - one token per single
       user is noise. The order, which was readable only by collecting the calc()
       from css/modals.css and css/productSelector.css:

         -100  .ps-modal-overlay        (from --dc-z-modal: a catalogue card
                                         must not cover a dialog opened over it)
           +0  #imageModal
           +1  #panelEnlargedModal
           +2  #panelCabinetZoomModal
          +10  #equipmentCatalogModal
          +11  #equipmentCatalogEditModal
          +20  .modal.modal-above-zoom  (a plain dialog opened FROM a zoom-tier
                                         view - today only the rack-height form)

       This comment cannot go stale: scripts/modal-consistency-check.mjs reads the
       real offsets out of the stylesheets and fails if they stop matching it. */
    /* Two full-screen modes, and they were 7100 apart: the gallery here and the
       configurator overlay at a literal 3000, which put it UNDER the welcome
       screen (10000) and was worked around by hiding that screen. Same rung now;
       the screen is still hidden, because that is right for its own reason. */
    --dc-z-odf-gallery:         10100;  /* ODF gallery: above the welcome screen
                                           (10000) so closing it returns there  */
    --dc-z-odf-gallery-preview: 10200;  /* its 3D preview, above the grid       */
    --dc-z-modal-account:       10300;  /* account modal: reachable from every
                                           top bar, so it has to clear the tallest
                                           of them — the gallery and its preview,
                                           and the ODF overlay (3000) with its
                                           save dialog (4000), both inline in
                                           index.html. Still below the login
                                           modal (20000) and toasts (30000).    */
    --dc-z-modal-auth:   20000;   /* login modal (was 99999)    */
    --dc-z-modal-password: 20010; /* change-password / temporary-password dialogs:
                                     one opens over the account modal, the other
                                     over the users table, and the forced variant
                                     has to sit above the login modal it replaces */
    /* ── Above the dialogs ──────────────────────────────────────────────
       A popover belongs to a control inside a dialog and has to clear it, but
       must stay under a toast: a message about what just happened outranks a
       picker. 9999 puts it under the zoom tier, which is fine only because the
       one control that opens it - the link colour button - lives in the sidebar
       and in #panelVizModal, both below. Checked 2026-09-07. Put a link list
       into a zoom-tier view and this has to move above --dc-z-above-zoom. */
    --dc-z-popover:      9999;

    /* The cursor tier. A drag ghost and the magnifier follow the pointer and are
       pointer-events:none decoration, so they belong above everything a person
       can click - a confirmation included. Eight places wrote 99999, which is not
       a height but a way of saying "on top" and leaves no room to be above it.
       40000 is a rung: nothing lives between the confirm at 30020 and here. */
    --dc-z-cursor:       40000;

    --dc-z-toast:        30000;   /* toasts above any modal     */
    --dc-z-confirm-back: 30010;
    --dc-z-confirm:      30020;

    /* ── Input fields ────────────────────────────────────────── */
    --dc-field-bg:           var(--dc-n-0);
    --dc-field-border:       var(--dc-n-200);
    --dc-field-border-focus: var(--dc-c-accent);
    --dc-field-radius:       var(--dc-radius-sm);
    --dc-field-text:         var(--dc-c-dark);
    --dc-field-placeholder:  var(--dc-n-400);

    /* ── Focus ring (keyboard accessibility) ─────────────────── */
    --dc-focus-ring: 0 0 0 3px rgba(101, 184, 165, 0.35);

    /* ── Image placeholder ───────────────────────────────────── */
    --dc-img-placeholder-bg:   linear-gradient(135deg, #e8f5f2 0%, #d4ede8 100%);
    --dc-img-placeholder-icon: var(--dc-c-accent);
}

/* Modal entry animations (used by modals.css and others) */
@keyframes dcModalBackdropIn {
    from { opacity: 0; }
    to   { opacity: 1; }
}
@keyframes dcModalPopIn {
    from { opacity: 0; transform: translateY(12px) scale(0.97); }
    to   { opacity: 1; transform: translateY(0)    scale(1); }
}

/* Spinner rotation. Defined here (not in ui-primitives.css) so it is
   available on every page that links design-tokens.css — including
   odf-service, where js/notify.js injects a spinner that references it. */
@keyframes dcSpin {
    to { transform: rotate(360deg); }
}
