/* ============================================================
   BizSuite — Professional Services ERP
   Univerk / BizView Glassmorphism Dark Theme
   ============================================================ */

/* ----- CSS Variables / Theme ----- */
/* Default = Dark theme. Light theme overrides live in
   [data-theme="light"] below — toggled at runtime by setting
   document.documentElement.dataset.theme = 'light' / 'dark'. The
   theme choice is persisted in localStorage under bizsuite_theme. */
:root {
  --fiori-shell: rgba(30,42,56,0.85);
  --fiori-shell-text: #FFFFFF;
  --fiori-brand: #3B8DF8;
  --fiori-brand-hover: #2A7AE6;
  --fiori-brand-glow: rgba(59,141,248,0.25);
  --fiori-bg: #0B0F14;
  --fiori-surface: rgba(22,28,36,0.72);
  --fiori-surface2: rgba(36,44,56,0.65);
  --fiori-border: rgba(255,255,255,0.08);
  --fiori-border-light: rgba(255,255,255,0.12);
  --fiori-text: #F0F2F5;
  --fiori-text2: #B0BEC5;
  --fiori-text-muted: #8A9BB5;
  /* THE RESULT LINE of a manual chapter. Owner, 2026-09-28: *"make
     the Result field and its content in Bold Navy Blue colour."*
     Navy is the light-theme answer; on this dark ground navy is very
     nearly the background, so the dark theme takes the same hue lifted
     until it reads. One token, defined in both themes, so the rule
     cannot be written once and be invisible half the time. */
  --um-result: #8FB8E8;
  --fiori-positive: #34D399;
  --fiori-negative: #F87171;
  --fiori-warning: #FBBF24;
  --fiori-info: #60A5FA;
  --fiori-radius: 10px;
  --fiori-radius-lg: 16px;
  --glass-blur: blur(20px);
  --glass-blur-sm: blur(12px);

  /* THE DROPDOWN GLASS (owner spec 2026-10-10). A native <select>
     popup is drawn by the OS and no CSS reaches inside it, so the
     list is ours now -- see components/GlassSelect.jsx. The two
     themes need genuinely different numbers, not an inversion: white
     glass over a pale page has to carry far more body than dark glass
     over a dark one, or the text underneath reads straight through
     it. --gsel-base is an r,g,b triplet because color-mix needs a
     colour and rgba() needs channels, and both are used below. */
  --gsel-base: 17, 24, 39;
  --gsel-fill: 16%;
  --gsel-blur: 24px;
  --gsel-sat: 180%;
  --gsel-edge: rgba(255,255,255,0.22);
  --gsel-spec: rgba(255,255,255,0.16);
  --gsel-spec-blend: plus-lighter;
  --gsel-shadow: 0 24px 60px -16px rgba(0,0,0,0.72), 0 3px 10px rgba(0,0,0,0.35);

  /* Solid fallbacks for surfaces — used by chrome that doesn't want
     translucent panels. Dark = midnight slate; light overrides below. */
  --fiori-shell-solid: #1F2937;
  --fiori-surface-solid: #111827;
  --fiori-surface2-solid: #1F2937;
  --fiori-elevated-solid: #0F172A;
  --fiori-row-hover: rgba(255,255,255,0.04);
  --fiori-shadow: 0 6px 24px rgba(0,0,0,0.45);

  /* Scope Items: three levels of one tree ---------------------------
     Owner 2026-09-25: the accordion hierarchy "is not clearly coming
     out". Module > Scope Item > Steps all sat on near-identical
     surfaces, so depth was carried by indentation alone -- which
     disappears the moment a row wraps.

     Each level gets a surface AND a coloured rail down its left edge.
     The rail is what survives wrapping, scrolling and a narrow window:
     the fill says "deeper", the rail says "deeper than WHAT".

     The rail hues are wayfinding, not status -- indigo, cyan, violet.
     Green, amber and red stay reserved for what they already mean on
     this screen, so a level can never be misread as a state.

     Level 2 has no fill of its own on purpose -- it is the row you
     read most, and tinting every one of them turns the list into
     stripes. It carries a rail, and a fill only while it is open. */
  /* Alternating swimlane fill (owner 2026-09-26): the lanes were
     separated by a hairline only, and with five roles on screen the
     eye loses which row it is reading. A band, not a tint on the
     boxes -- the box colours already carry the relevance answer and
     must not be competed with. */
  --fx-lane-alt: rgba(148,163,184,0.07);
  /* The swimlane's own ground. The sticky lane-label gutter paints
     with it, so a box scrolling sideways passes BEHIND the label
     instead of through it -- which needs an opaque colour, and the
     modal's own background is not reachable from here. */
  --fx-swim-bg: var(--fiori-surface-solid);
  /* One column per step (owner 2026-09-26). Narrowed from 210px the
     same day -- *"It is too spread out"* -- and 168px is where the
     real BNX activity texts still break into sensible lines rather
     than one word each. Print redefines it again, smaller. */
  --fx-col-w: 168px;
  --fx-label-w: 124px;
  --impl-lvl1-bg:   rgba(59,141,248,0.10);
  --impl-lvl1-rail: #3B8DF8;
  --impl-lvl2-open: rgba(34,211,238,0.07);
  --impl-lvl2-rail: #22D3EE;
  --impl-lvl3-bg:   rgba(167,139,250,0.08);
  --impl-lvl3-rail: #A78BFA;
  --impl-lvl4-bg:   rgba(255,255,255,0.05);
  --impl-lvl4-rail: #94A3B8;

  /* Scope item triage, dark. Owner 2026-09-25: green when every step
     is answered Yes or No, orange when any is Not Sure, red when any
     has no answer at all.

     "Dark Green" is his word and it is right on white; on the dark
     ground a true dark green goes muddy and fails contrast against
     #0B0F14, so the dark theme takes the same green lifted to stay
     legible. Same meaning, same hue, different ground -- which is the
     whole reason these are tokens and not literals. */
  --impl-cover-settled:    #34D399;
  --impl-cover-unsure:     #FBBF24;
  --impl-cover-incomplete: #F87171;

  /* Where one scope item -- or one step -- ENDS.
     Owner 2026-09-25: *"each scope items and each scope item steps
     should be clearly differentiated visually through horizontal lines
     to separate."*
     Stronger than --fiori-border, which is the hairline between cells.
     The point is the difference between the two: a step is TWO rows
     (its controls, then Requirement and Status Comment), and with one
     border weight the line inside a step looked exactly like the line
     between steps. */
  --impl-sep: rgba(255,255,255,0.20);

  /* Three bands inside one open scope item: the ITEM's own fields, the
     STEPS, and ADD STEP. Owner 2026-09-25 -- on one surface the eye
     reads them as a single long form, and it is not: the top fields
     describe the scope item, the rows below describe its steps, and
     the bottom strip creates a new one. */
  --impl-item-fields-bg: rgba(167,139,250,0.14);
  --impl-add-step-bg:    rgba(59,141,248,0.10);

  /* The Relevant answer on a step, as a box. Owner 2026-09-25: Yes
     green, Not Sure orange, No red. An UNANSWERED step gets none --
     those three are answers, and a blank is the absence of one, so
     giving it a colour would make it look decided. */
  --impl-rel-yes-bg:    rgba(52,211,153,0.16);
  --impl-rel-unsure-bg: rgba(251,191,36,0.16);
  --impl-rel-no-bg:     rgba(248,113,113,0.16);
  --impl-rel-yes-fg:    #34D399;
  --impl-rel-unsure-fg: #FBBF24;
  --impl-rel-no-fg:     #F87171;

  /* ── Org Structure > Drag & Drop ──────────────────────────────────
     Owner report 2026-09-24: "Check the UI rendering. Its not clear in
     Light Mode."

     That screen drew its two rails, its scrim and its toast with a
     hard-coded near-black, while taking the TEXT on them from
     --fiori-text. In dark mode both are dark-on-dark's opposite and it
     looks deliberate; switch to light and the text token flips to
     near-black while the panel stays near-black, so every name on the
     screenshot he sent is invisible. A panel that sets its own
     background must take its ink from the same theme -- these tokens
     are that pair, defined once per theme. */
  --org-rail-bg: rgba(10,14,24,0.92);
  --org-scrim-bg: rgba(0,0,0,0.55);
  --org-toast-bg: rgba(10,14,24,0.96);
  --org-grid-line: rgba(255,255,255,0.025);
  --org-canvas-glow-a: rgba(0,224,255,0.06);
  --org-canvas-glow-b: rgba(191,95,255,0.06);

  /* ── My Tasks: the latest comment, line 3 ────────────────────────
     Owner spec 2026-09-24: "show the first few words of the comment
     along with the task highlighted in yellow in the 3rd line."

     Blue since 2026-10-09 ("the comment in light blue fill"); amber
     before that. Amber is what this app uses for a WARNING, and a
     comment on a task is not one -- every row with a note on it was
     reading as a row with a problem.

     Still a token PAIR, ground and ink named together: blue on a
     near-black row cannot be the blue that works on white, and
     unnamed ink over a named ground is the dark-on-dark bug reported
     on Org Structure. */
  --task-comment-bg: rgba(56,189,248,0.14);
  --task-comment-ink: #7DD3FC;
  --task-comment-edge: rgba(56,189,248,0.42);

  /* ── My Tasks: the row, and the action menu ──────────────────────
     Owner choice 2026-09-24, from the palette comparison: option
     A (rail and date) for the list, option 1 (one primary) for the
     menu.

     What it replaces, and why. Every overdue row carried a full
     rgba(239,68,68,0.08) wash. With 32 of 63 tasks overdue that
     coloured most of the screen, so "this one is late" stopped being
     a signal and became the background. Severity now rides on a 3px
     rail and the due date; the row keeps the ordinary surface.

     Every ink below is declared BESIDE the ground it sits on, and the
     rails are held to 3:1 rather than 4.5:1 because a rail is a
     graphical object, not type -- but held to it, since in this design
     the rail is the ONLY thing carrying severity. Measured in
     scripts/checks/task-row-palette-check.js. */
  --task-surface: #171D26;
  --task-line: #252D38;
  /* A master task's row (owner 2026-09-26): *"Show the master task in
     a different shade or a different box line so as to distinguish.
     Chose proper color depending on Dark and Light theme."*
     BOTH, and each doing a different job: the shade separates the row
     from the ordinary ones around it, and the left edge survives a
     zebra, a hover and a print. Violet, because the MASTER tag and the
     hierarchy icon already are -- a third colour for the same idea is
     a third thing to learn.
     On the dark ground the tint is a lift and the edge is the brighter
     of the two; in light it is the reverse, or a pale tint on white
     does nothing at all. */
  --task-master-bg:   rgba(139,92,246,0.10);
  --task-master-edge: #A78BFA;
  /* A sub-task still waiting on another one. Grey, deliberately: it is
     not a problem, it is simply not yet its turn, and a warning colour
     here would compete with the rows that do need attention. */
  --task-wait-bg:     rgba(148,163,184,0.10);
  --task-rail-overdue: #E4566B;
  --task-rail-risk: #D9A03F;
  --task-rail-ok: #717E8F;
  --task-rail-done: #3FCB9B;
  --task-due-overdue: #FF8494;
  --task-due-risk: #D9A03F;

  --menu-surface: #171D26;
  --menu-line: #262E39;
  --menu-ink: #E5EAF1;
  --menu-muted: #95A1B1;
  --menu-primary-bg: #1E9E76;
  --menu-primary-ink: #04120D;
  --menu-danger: #FF8494;
  --menu-hover: rgba(255,255,255,0.07);


  /* Input field background — intentionally darker than the parent
     panel in dark mode (sunken look). Light mode gets a true white
     so fields don't look muddier than the surface they sit on. */
  --fiori-input-bg: rgba(15,23,42,0.6);

  /* Accent palette — used for status pills, link text, side-panel
     emphasis. The dark-mode values are the neon brights the app
     was originally designed around; light mode swaps in the
     same-hue but darker variant from Tailwind's 600/700 row so
     contrast against white surfaces stays AA-readable. Component
     authors reference these via var(--fiori-accent-*) instead of
     hardcoding the hex directly. */
  --fiori-accent-cyan:   #6FE6FF;
  --fiori-accent-sky:    #7DD3FC;
  --fiori-accent-lime:   #A3E635;
  --fiori-accent-lime2:  #D9F99D;
  --fiori-accent-violet: #A78BFA;
  --fiori-accent-violet2:#C4B5FD;
  /* Run-up dots on the My Tasks week grid (owner spec 2026-09-11).
     A token, not a hex in the JSX: the dot sits on a DARK surface here
     and a light one under [data-theme="light"], so one value cannot
     serve both — and the reviewer on dark would never see the other. */
  --fiori-span-dot:      rgba(167,139,250,0.55);
  --fiori-accent-amber:  #FCD34D;
  --fiori-accent-rose:   #FB7185;
  --fiori-accent-pink:   #FCA5A5;
  --fiori-accent-emerald:#34D399;

  /* Active-pill (capsule tab) tokens — referenced by the in-page
     tab strips on Leave Configurator, WFH Roster, Accounting, etc.
     Dark mode uses a faint cyan tint with white text (the original
     neon look). Light mode flips both: a saturated cyan fill with
     dark navy text so the active pill actually reads against a
     white page. Inactive pill is just transparent + muted text in
     both themes. */
  --fiori-pill-active-bg:     linear-gradient(135deg, rgba(0,224,255,0.18), rgba(59,143,244,0.18));
  --fiori-pill-active-border: rgba(0,224,255,0.45);
  --fiori-pill-active-text:   #FFFFFF;

  /* Text and icons that sit ON a SOLID accent fill (a selected segment
     in a toggle, a checked checkbox). This MUST be a token: the accent
     itself flips from a bright neon in dark mode to a deep cyan-700 in
     light mode, so a single hardcoded value is legible in one theme and
     unreadable in the other. The DUE toggle on My Tasks shipped with a
     hardcoded #04222A and rendered near-black on dark teal in light
     mode, at about 3:1 -- under the 4.5:1 small text needs.
       dark:  near-black on bright cyan  ~13:1
       light: white on cyan-700          ~5.5:1 */
  --fiori-on-accent: #04222A;

  /* NEON palette — the bright sidebar / section icon colours. The
     Sidebar.jsx NEON object reads these via var() so the SVG
     strokes resolve to the right hue for the current theme. Dark
     keeps the original brights; light uses Tailwind 700 row so
     the icons read as colourful badges on white instead of
     washed-out pastels. */
  --neon-cyan:    #00F0FF;
  --neon-magenta: #FF00E5;
  --neon-lime:    #39FF14;
  --neon-orange:  #FF6B00;
  --neon-violet:  #BF5FFF;
  --neon-pink:    #FF2D7B;
  --neon-teal:    #00E5A0;
  --neon-blue:    #3B8FF4;
  --neon-yellow:  #FFE500;
  --neon-coral:   #FF4F4F;
  --neon-mint:    #00FFC8;
  --neon-gold:    #FFB300;
  --neon-sky:     #00BFFF;
  --neon-rose:    #FF6B9D;

  /* Legacy aliases for backward compatibility */
  --color-primary: #1E3A5F;
  --color-accent: var(--fiori-brand);
  --color-accent-light: #60A5FA;
  --color-accent-dark: var(--fiori-brand-hover);
  --color-success: var(--fiori-positive);
  --color-warning: var(--fiori-warning);
  --color-danger: var(--fiori-negative);
  --color-info: var(--fiori-info);

  --sidebar-width: 250px;
  --sidebar-collapsed-width: 64px;
  --topbar-height: 48px;

  --transition-fast: 150ms ease;
  --transition-normal: 250ms ease;

  /* ---- Inline-style text palette (--ct-*) -------------------------
     Owner spec 2026-08-28. Hundreds of JSX inline styles hardcoded
     dark-tuned hexes for TEXT, e.g. color: '#22D3EE'. Inline styles
     beat any stylesheet, so light mode could never override them —
     which is exactly why the app "kept the same font colours" when
     the theme flipped.

     Each --ct-* below is named for its Tailwind step and its DARK
     value is byte-identical to the hex it replaces, so dark mode is
     unchanged by construction. Only the light column is new.
     Use these for text only; backgrounds and borders keep their own
     literals because a green pill should stay green in both themes.
     -------------------------------------------------------------- */
  --ct-cyan-bright: #00E0FF;
  --ct-cyan: #22D3EE;
  --ct-slate-100: #F1F5F9;
  --ct-slate-200: #E2E8F0;
  --ct-slate-300: #CBD5E1;
  --ct-slate-400: #94A3B8;
  --ct-green-200: #86EFAC;
  --ct-green-500: #22C55E;
  --ct-emerald-500: #10B981;
  --ct-amber-300: #FCD34D;
  --ct-amber-400: #FBBF24;
  --ct-amber-500: #F59E0B;
  --ct-red-300: #FCA5A5;
  --ct-red-400: #F87171;
  --ct-red-500: #EF4444;
  --ct-blue-400: #60A5FA;
  --ct-blue-500: #3B82F6;
  --ct-sky-500: #0EA5E9;
  --ct-violet-400: #A78BFA;
  --ct-violet-500: #8B5CF6;
  --ct-purple-bright: #BF5FFF;
  --ct-indigo-500: #6366F1;

}

/* ----- Light theme override ------------------------------------
   Activated when `<html data-theme="light">` is set. We override
   every --fiori-* variable so anything class-driven (.card, .btn-*,
   tables, body bg, login screen) flips automatically. Inline-styled
   JSX is migrated separately as it gets touched.
   ---------------------------------------------------------------- */
[data-theme="light"] {
  --fiori-shell: rgba(255,255,255,0.92);
  --fiori-shell-text: #111827;
  --fiori-brand: #2563EB;
  --fiori-brand-hover: #1D4ED8;
  --fiori-brand-glow: rgba(37,99,235,0.18);

  --fiori-bg: #F4F6FA;
  --fiori-surface: #FFFFFF;
  --fiori-surface2: #F0F2F6;
  --fiori-border: rgba(15,23,42,0.18);
  --fiori-border-light: rgba(15,23,42,0.22);
  --fiori-text: #0F172A;
  --fiori-text2: #1E293B;        /* slate-800 */
  --fiori-text-muted: #334155;   /* slate-700 — bumped from 500 → 600 → 700 for label readability on bright white */
  --um-result: #12314F;          /* navy */

  --fiori-positive: #059669;
  --fiori-negative: #DC2626;
  --fiori-warning: #D97706;
  --fiori-info: #2563EB;

  --fiori-shell-solid: #FFFFFF;
  --fiori-surface-solid: #FFFFFF;
  --fiori-surface2-solid: #F0F2F6;
  --fiori-elevated-solid: #FFFFFF;
  --fiori-row-hover: rgba(15,23,42,0.04);

  /* The dropdown glass, light. 62% fill against 16% on dark: the same
     panel at the dark opacity is unreadable over a white dashboard.
     plus-lighter blows out to flat white here, so the specular edge
     blends normally instead. */
  --gsel-base: 255, 255, 255;
  --gsel-fill: 62%;
  --gsel-blur: 22px;
  --gsel-sat: 170%;
  --gsel-edge: rgba(15,23,42,0.14);
  --gsel-spec: rgba(255,255,255,0.75);
  --gsel-spec-blend: normal;
  --gsel-shadow: 0 20px 50px -14px rgba(15,23,42,0.28), 0 2px 8px rgba(15,23,42,0.10);

  /* Scope Items depth ramp, light. Same three steps, rebuilt rather
     than inverted: on white, a translucent white fill does nothing, so
     each level takes a solid tint, and the rails go a shade deeper to
     hold their contrast against the lighter ground. */
  --fx-lane-alt: #F4F6F9;
  --fx-swim-bg: var(--fiori-surface-solid);
  --impl-lvl1-bg:   #E8F1FE;
  --impl-lvl1-rail: #2563EB;
  --impl-lvl2-open: #ECFBFE;
  --impl-lvl2-rail: #0891B2;
  --impl-lvl3-bg:   #F3F0FD;
  --impl-lvl3-rail: #7C3AED;
  --impl-lvl4-bg:   #EEF1F6;
  --impl-lvl4-rail: #64748B;

  /* Scope item triage, light: the literal dark green he asked for, and
     amber and red pulled down to hold contrast on white. */
  /* His word was "Dark Green", and #166534 is darker than the #15803D
     first tried -- which measured 4.46:1 on the steps panel tint, just
     under AA. This clears 4.5:1 on all three light grounds with room
     to spare, and reads more like the dark green he asked for. */
  --impl-cover-settled:    #166534;
  /* #C2620A read as the right orange but measured 4.16:1 on white and
     3.92:1 on the open-row tint -- under AA for normal text, and this
     text is 13px, so the large-text allowance does not apply. Darkened
     until it clears 4.5:1 on white, the open-row tint AND the steps
     panel, the three grounds it can actually sit on. */
  --impl-cover-unsure:     #A85008;
  --impl-cover-incomplete: #B91C1C;

  /* Same separator, light. */
  --impl-sep: rgba(15,23,42,0.22);

  /* The same three bands, light. */
  --impl-item-fields-bg: #EDE9FB;
  --impl-add-step-bg:    #E8F1FE;

  /* The same three, light. Foregrounds are the contrast-checked values
     from the scope item triage row -- same meanings, so the same
     colours rather than a second set that drifts. */
  --impl-rel-yes-bg:    #DCFCE7;
  --impl-rel-unsure-bg: #FEF3C7;
  --impl-rel-no-bg:     #FEE2E2;
  --impl-rel-yes-fg:    #166534;
  --impl-rel-unsure-fg: #A85008;
  --impl-rel-no-fg:     #B91C1C;
  --fiori-shadow: 0 4px 14px rgba(15,23,42,0.10);

  /* The light half of the pair above. White rails rather than inverted
     ones: the rest of this screen -- the filter bar, the nodes inside
     the rails -- is already light here, and a black panel floating in
     it would read as a rendering fault rather than as a design. The
     grid lines go dark for the same reason: white lines on a white
     canvas are not faint, they are absent. */
  --org-rail-bg: rgba(255,255,255,0.92);
  --org-scrim-bg: rgba(255,255,255,0.82);
  --org-toast-bg: rgba(255,255,255,0.97);
  --org-grid-line: rgba(15,23,42,0.05);
  --org-canvas-glow-a: rgba(0,150,190,0.05);
  --org-canvas-glow-b: rgba(140,70,200,0.05);

  /* The light half. A pale blue ground with a DARK blue ink on it:
     the owner asked for a light blue fill, and the obvious pairing --
     a light blue ink to match -- is 1.4:1 and unreadable. #0C4A6E on
     #E0F2FE is 9.6:1. */
  --task-comment-bg: #E0F2FE;
  --task-comment-ink: #0C4A6E;
  --task-comment-edge: #7DD3FC;

  /* The light half of the pair above. White rows, so the rail and the
     red date are what carry state -- and both are darkened until they
     hold up on white, which the dark theme's values do not. */
  --task-surface: #FFFFFF;
  --task-line: #E4E8EE;
  --task-master-bg:   #F3EFFE;
  --task-master-edge: #7C3AED;
  --task-wait-bg:     #F5F7FA;
  --task-rail-overdue: #D93A52;
  --task-rail-risk: #B45309;
  --task-rail-ok: #6F7C8C;
  --task-rail-done: #17976D;
  --task-due-overdue: #B3253C;
  --task-due-risk: #B45309;

  --menu-surface: #FFFFFF;
  --menu-line: #E4E8EE;
  --menu-ink: #1B2430;
  --menu-muted: #6A7686;
  --menu-primary-bg: #0F7B5A;
  --menu-primary-ink: #FFFFFF;
  --menu-danger: #B3253C;
  --menu-hover: rgba(15,23,42,0.06);


  /* Inputs sit on white surfaces — give them the same white plus
     a real border (defined where the input is rendered) instead
     of the sunken-translucent look that works in dark mode. */
  --fiori-input-bg: #FFFFFF;

  /* Accent darkening — Tailwind 700/800 row. The 600 row was still
     too light against tinted button backgrounds (pale cyan text on
     pale cyan bg = no contrast). 700+ keeps the hue identity but
     lifts to AA on both white and the rgba(...,0.10) tint backgrounds
     used for outlined accent buttons / pills. */
  --fiori-accent-cyan:    #0E7490;  /* cyan-700 */
  --fiori-accent-sky:     #075985;  /* sky-800 */
  --fiori-accent-lime:    #4D7C0F;  /* lime-700 */
  --fiori-accent-lime2:   #365314;  /* lime-800 */
  --fiori-accent-violet:  #6D28D9;  /* violet-700 */
  --fiori-accent-violet2: #5B21B6;  /* violet-800 */
  /* Darker and more opaque than the dark-theme dot: the same 55%
     lavender is nearly invisible on a white card. */
  --fiori-span-dot:       rgba(109,40,217,0.60);
  --fiori-accent-amber:   #92400E;  /* amber-800 */
  --fiori-accent-rose:    #BE123C;  /* rose-700 */
  --fiori-accent-pink:    #9F1239;  /* rose-800 */
  --fiori-accent-emerald: #047857;  /* emerald-700 */

  /* Neon palette darkening — same family as the dark-mode neons but
     dropped to Tailwind 700-row darkness for AA contrast on white.
     Used as SVG stroke colours by Sidebar.jsx (section icons). */
  --neon-cyan:    #0E7490;
  --neon-magenta: #BE185D;
  --neon-lime:    #4D7C0F;
  --neon-orange:  #C2410C;
  --neon-violet:  #6D28D9;
  --neon-pink:    #BE123C;
  --neon-teal:    #0F766E;
  --neon-blue:    #1D4ED8;
  --neon-yellow:  #A16207;
  --neon-coral:   #B91C1C;
  --neon-mint:    #047857;
  --neon-gold:    #B45309;
  --neon-sky:     #075985;
  --neon-rose:    #9F1239;

  /* Active-pill light-mode flip — saturated cyan fill + dark navy
     text. The dark-mode default was a faint translucent gradient
     with white text, which on a light page rendered as
     near-invisible white on near-white. This pair is what every
     in-page tab strip across the app should reference (var()), so
     toggling theme automatically flips the contrast. */
  --fiori-pill-active-bg:     linear-gradient(135deg, rgba(14,116,144,0.20), rgba(29,78,216,0.20));
  --fiori-pill-active-border: rgba(14,116,144,0.55);
  --fiori-pill-active-text:   #0F172A;
  --fiori-on-accent:          #FFFFFF;

  /* Text palette — same names, darkened for white backgrounds.
     See the --ct-* note in :root. */
  --ct-cyan-bright: #0E7490;   /* cyan-700 */
  --ct-cyan: #0E7490;   /* cyan-700 */
  --ct-slate-100: #0F172A;   /* slate-900 */
  --ct-slate-200: #0F172A;   /* slate-900 */
  --ct-slate-300: #1E293B;   /* slate-800 */
  --ct-slate-400: #475569;   /* slate-600 */
  --ct-green-200: #15803D;   /* green-700 */
  --ct-green-500: #15803D;   /* green-700 */
  --ct-emerald-500: #047857;   /* emerald-700 */
  --ct-amber-300: #B45309;   /* amber-700 */
  --ct-amber-400: #B45309;   /* amber-700 */
  --ct-amber-500: #B45309;   /* amber-700 */
  --ct-red-300: #B91C1C;   /* red-700 */
  --ct-red-400: #B91C1C;   /* red-700 */
  --ct-red-500: #B91C1C;   /* red-700 */
  --ct-blue-400: #1D4ED8;   /* blue-700 */
  --ct-blue-500: #1D4ED8;   /* blue-700 */
  --ct-sky-500: #0369A1;   /* sky-700 */
  --ct-violet-400: #6D28D9;   /* violet-700 */
  --ct-violet-500: #6D28D9;   /* violet-700 */
  --ct-purple-bright: #7E22CE;   /* purple-700 */
  --ct-indigo-500: #4338CA;   /* indigo-700 */

}

/* ----- Theme-aware scrollbar. The light variant needs a DARK thumb,
         and at a weight that survives a white background: 18% slate
         was the light-mode half of the same too-faint problem the owner
         reported on 2026-09-15. ---------------------------------- */
[data-theme="light"] ::-webkit-scrollbar-track {
  background: rgba(15,23,42,0.06);
}
[data-theme="light"] ::-webkit-scrollbar-thumb {
  /* 0.46 over white is 3.03:1 -- the same 3:1 floor, reached from the
     other side. A single alpha cannot serve both themes, which is why
     this override exists at all. */
  background: rgba(15,23,42,0.46);
  background-clip: padding-box;
}
[data-theme="light"] ::-webkit-scrollbar-thumb:hover {
  background: rgba(15,23,42,0.62);
  background-clip: padding-box;
}
[data-theme="light"] ::-webkit-scrollbar-thumb:active {
  background: rgba(15,23,42,0.76);
  background-clip: padding-box;
}
[data-theme="light"] * {
  scrollbar-color: rgba(15,23,42,0.46) rgba(15,23,42,0.06);
}

/* ============================================================
   LIGHT THEME — chrome polish
   ============================================================
   Targets the surfaces that have hardcoded dark tints (rgba whites
   on dark backgrounds) so they read correctly on light. Component-
   level inline styles in JSX pages aren't covered here — they need
   a separate progressive migration. ---------------------------- */
[data-theme="light"] .sidebar {
  background: var(--fiori-shell-solid);
  border-right: 1px solid var(--fiori-border);
  color: var(--fiori-text2);
}
[data-theme="light"] .sidebar-header .logo,
[data-theme="light"] .sidebar-header .logo span {
  color: var(--fiori-text);
}
[data-theme="light"] .sidebar-toggle:hover {
  background: rgba(15,23,42,0.06);
  color: var(--fiori-text);
}
[data-theme="light"] .sidebar-section-title:hover {
  background: rgba(15,23,42,0.05);
}
[data-theme="light"] .sidebar-item {
  color: var(--fiori-text2);
}
[data-theme="light"] .sidebar-item:hover {
  background: rgba(15,23,42,0.05);
  color: var(--fiori-text);
}
[data-theme="light"] .sidebar-item.active {
  background: var(--fiori-brand-glow);
  color: var(--fiori-brand);
}
[data-theme="light"] .sidebar-item .coming-soon {
  background: rgba(15,23,42,0.06);
  color: var(--fiori-text-muted);
}

/* Topbar — already uses var(--fiori-shell), but the search input
   and user-dropdown carry rgba-white tints that need flipping. */
[data-theme="light"] .topbar {
  background: var(--fiori-shell-solid);
  border-bottom: 1px solid var(--fiori-border);
}
[data-theme="light"] .topbar-search input {
  background: rgba(15,23,42,0.04);
  color: var(--fiori-text);
  border: 1px solid var(--fiori-border);
}
[data-theme="light"] .topbar-search input::placeholder { color: var(--fiori-text-muted); }
[data-theme="light"] .user-info .name { color: var(--fiori-text); }
[data-theme="light"] .user-info .role { color: var(--fiori-text-muted); }
[data-theme="light"] .user-dropdown {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  box-shadow: var(--fiori-shadow);
}
[data-theme="light"] .user-dropdown a { color: var(--fiori-text); }
[data-theme="light"] .user-dropdown a:hover { background: rgba(15,23,42,0.05); }

/* Cards / surfaces — the workhorse class on most pages. */
[data-theme="light"] .card {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text);
  box-shadow: var(--fiori-shadow);
}
[data-theme="light"] .card-header {
  border-bottom: 1px solid var(--fiori-border);
  background: transparent;
}
[data-theme="light"] .card-header h2,
[data-theme="light"] .card-header h3 { color: var(--fiori-text); }

/* Generic data-table styling — inverted hover + striping. */
[data-theme="light"] table.data-table th {
  background: var(--fiori-surface2-solid);
  color: var(--fiori-text-muted);
  border-bottom: 1px solid var(--fiori-border);
}
[data-theme="light"] table.data-table td {
  border-bottom: 1px solid var(--fiori-border);
  color: var(--fiori-text);
}
[data-theme="light"] table.data-table tr:hover td {
  background: var(--fiori-row-hover);
}
/* The generic light-theme th/td rules above would otherwise repaint the
   sticky Actions column with the ordinary (or hover) background and it
   would stop occluding what scrolls beneath it. */
[data-theme="light"] table.data-table th.actions-cell,
[data-theme="light"] table.data-table td.actions-cell,
[data-theme="light"] table.data-table tr:hover td.actions-cell {
  background: var(--fiori-surface-solid);
}
[data-theme="light"] table.data-table th.actions-cell.actions-header {
  background: var(--fiori-surface2-solid);
}

/* Form controls — inputs / selects / textareas inherit the page
   bg by default which looks washed out on light. Give them a
   subtle surface and a real border. */
[data-theme="light"] input,
[data-theme="light"] select,
[data-theme="light"] textarea {
  color: var(--fiori-text);
}
[data-theme="light"] input:not([type="checkbox"]):not([type="radio"]),
[data-theme="light"] select,
[data-theme="light"] textarea {
  /* background-COLOR. As a shorthand this also reset background-image,
     and background-image is where the app's select caret lives -- so
     in LIGHT theme every <select> in the product lost its arrow and
     was drawn as a plain box. (0,1,1) here beats the (0,0,1) that
     paints the caret, and it sits later in the file, so it won twice
     over. Owner report 2026-09-20 with a screenshot of the Customer
     team. Dark was never affected: it has no equivalent override. */
  background-color: var(--fiori-surface-solid);
  border-color: var(--fiori-border);
}

/* ...except the Implementation module's own fields, which must agree
   with each other.

   The rule above is (0,3,1) because of its two :not() attribute
   arguments, so in LIGHT it beat `.impl-root .impl-field` (0,2,0) for
   an <input> -- but not for a <select> or <textarea>, whose light
   rules are only (0,1,1). The result was a white input sitting beside
   a grey select on the same row, everywhere in the module. Found
   2026-09-19 on the Deliverables tab, where Format and Module are
   adjacent. Matched at (0,3,1) and placed after, so all three land on
   the same surface. */
[data-theme="light"] .impl-root input.impl-field,
[data-theme="light"] .impl-root select.impl-field,
[data-theme="light"] .impl-root textarea.impl-field {
  /* background-COLOR: the shorthand would reset background-image and
     take the select caret with it. See .impl-root .impl-field. */
  background-color: var(--fiori-surface2);
  border-color: var(--fiori-border);
}

/* Modal backdrops — many modals use inline rgba(0,0,0,0.55) which
   stays correct on light too. The modal panel itself is class-driven
   (.modal in bizsuite.css), so we flip it via the same selector.
   The header has no background of its own (it sits transparent over
   .modal with a bottom border), so flipping the panel solves both. */
[data-theme="light"] .modal {
  background: var(--fiori-surface-solid);
  color: var(--fiori-text);
  box-shadow: 0 24px 60px rgba(15,23,42,0.18);
  border: 1px solid var(--fiori-border);
}
[data-theme="light"] .modal-header h2,
[data-theme="light"] .modal-header h3,
[data-theme="light"] .modal-title {
  color: var(--fiori-text);
}
[data-theme="light"] .modal-header {
  border-bottom: 1px solid var(--fiori-border);
}
[data-theme="light"] .modal-body,
[data-theme="light"] .dialog-body {
  background: var(--fiori-surface-solid);
  color: var(--fiori-text);
}

/* Loading overlay shown before React mounts. */
[data-theme="light"] .loading-overlay { color: var(--fiori-text2); }

/* ── Buttons in light theme — solid fills instead of neon outlines.
   The default .btn-primary uses a translucent white bg + neon text
   pattern that disappears on white surfaces. Light theme flips it
   to a SOLID button: dark slate fill + white text + tight shadow.
   This is independent of whatever --page-neon value the route
   carries, so every primary button reads cleanly. */
[data-theme="light"] .btn-primary {
  background: var(--fiori-text);              /* near-black */
  color: #FFFFFF;
  border: 1px solid var(--fiori-text);
  box-shadow: 0 1px 2px rgba(15,23,42,0.10);
}
[data-theme="light"] .btn-primary:hover:not(:disabled) {
  background: #1E293B;                        /* slate-800 — slightly lift */
  border-color: #1E293B;
  box-shadow: 0 4px 10px rgba(15,23,42,0.18);
  transform: translateY(-1px);
}
[data-theme="light"] .btn-secondary {
  background: var(--fiori-surface-solid);
  color: var(--fiori-text);
  border: 1px solid var(--fiori-border-light);
}
[data-theme="light"] .btn-secondary:hover:not(:disabled) {
  background: var(--fiori-surface2-solid);
  border-color: var(--fiori-text-muted);
}
/* Tab bar pills get the same treatment. */
[data-theme="light"] .tab-bar {
  background: var(--fiori-surface2-solid);
  border: 1px solid var(--fiori-border);
}
[data-theme="light"] .tab-btn { color: var(--fiori-text-muted); }
[data-theme="light"] .tab-btn:hover {
  background: rgba(15,23,42,0.04);
  color: var(--fiori-text);
}
[data-theme="light"] .tab-btn.active {
  background: var(--fiori-text);
  color: #FFFFFF;
}

/* ── Form controls in light mode — selects + inputs match each other.
   Specifically targets `.form-control` and bare elements so the
   native <select> doesn't render its own slate-grey UA fallback
   over our white surface, AND the dropdown indicator arrow flips
   from the dark-mode bright slate to a dark glyph that reads on
   white. ----------------------------------------------------------- */
[data-theme="light"] .form-control,
[data-theme="light"] input.form-control,
[data-theme="light"] textarea.form-control {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text);
}
[data-theme="light"] .form-control:focus,
[data-theme="light"] select.form-control:focus,
[data-theme="light"] textarea.form-control:focus {
  background-color: var(--fiori-surface-solid);
  border-color: var(--fiori-brand);
}
/* select.form-control in light mode — explicit longhand declarations
   so the dark-arrow SVG renders ONCE on the right edge instead of
   tiling across the whole field. The previous rules used the
   `background:` shorthand, which silently reset background-repeat
   to its default ('repeat') and produced a striped overlay. */
[data-theme="light"] select.form-control {
  background-color: var(--fiori-surface-solid);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23334155' d='M6 8.5L1 3.5h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px 12px;
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text);
  padding-right: 32px;
}

/* ----- Reset / Base ----- */
*, *::before, *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html, body {
  height: 100%;
  font-family: "Inter", "SF Pro Display", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  font-size: 14px;
  line-height: 1.5;
  color: var(--fiori-text);
  background: var(--fiori-bg);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a {
  color: var(--fiori-brand);
  text-decoration: none;
  transition: color var(--transition-fast);
}
a:hover {
  color: var(--fiori-brand-hover);
  text-decoration: underline;
}

ul, ol { list-style: none; }
img { max-width: 100%; display: block; }
input, button, select, textarea { font-family: inherit; font-size: inherit; }

::selection {
  background: var(--fiori-brand-glow);
  color: #fff;
}

/* ── Scrollbars (owner report 2026-09-15: too thin, too faint) ──
 *
 * Was 6px wide with a thumb at 10% white. On the near-black surfaces
 * this app uses that is roughly a 1.2:1 contrast ratio -- visible only
 * if you already knew it was there, which is how a horizontal strip of
 * tabs reads as "these are all the tabs" when six more are off-screen.
 *
 * Now 12px of track with an 8px thumb. The 2px transparent border plus
 * background-clip gives the thumb breathing room inside its track, so
 * it reads as a pill in a groove rather than a bar filling the gutter --
 * the extra width is hit area, not visual weight.
 *
 * The track is no longer transparent. A visible groove is what tells
 * you how far the content runs and how much of it you are seeing; a
 * floating thumb on nothing shows position but not extent.
 *
 * Contrast is set per theme below, because a single rgba cannot be
 * right on both a near-black and a white surface -- which is the same
 * mistake --fiori-on-accent exists to stop. */
::-webkit-scrollbar {
  width: 12px;
  height: 12px;
}
::-webkit-scrollbar-track {
  background: rgba(255,255,255,0.05);
  border-radius: 6px;
}
::-webkit-scrollbar-thumb {
  /* 0.34, not a rounder 0.30: over #0B0F14 that lands at 3.08:1, just
     past the 3:1 WCAG 1.4.11 asks of a non-text UI component. A thumb
     you cannot see at rest is a thumb that does not tell you there is
     more content, which is the whole complaint. */
  background: rgba(255,255,255,0.34);
  border-radius: 6px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
  background: rgba(255,255,255,0.55);
  background-clip: padding-box;
}
/* :active matters more than it looks -- while dragging, the pointer
   leaves the thumb constantly and a hover-only highlight flickers. */
::-webkit-scrollbar-thumb:active {
  background: rgba(255,255,255,0.70);
  background-clip: padding-box;
}
/* Where a vertical and a horizontal bar meet. Left unstyled it renders
   as a pale browser-default square against the dark chrome. */
::-webkit-scrollbar-corner {
  background: transparent;
}

/* Firefox understands none of the above and was getting its default
   thin, low-contrast bar. These two properties are the whole of what it
   supports, so they are set rather than left to chance. */
* {
  scrollbar-width: thin;
  scrollbar-color: rgba(255,255,255,0.34) rgba(255,255,255,0.05);
}

/* ============================================================
   LOGIN PAGE
   ============================================================ */
.login-page {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 100vh;
  background:
    radial-gradient(ellipse at 30% 20%, rgba(59,141,248,0.12) 0%, transparent 50%),
    radial-gradient(ellipse at 70% 80%, rgba(59,141,248,0.06) 0%, transparent 50%),
    var(--fiori-bg);
  padding: 20px;
}

.login-card {
  background: rgba(22,28,36,0.82);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--fiori-border-light);
  border-radius: var(--fiori-radius-lg);
  box-shadow:
    0 24px 64px rgba(0,0,0,0.4),
    0 0 0 1px rgba(255,255,255,0.04) inset;
  width: 100%;
  max-width: 420px;
  padding: 40px;
}

.login-logo {
  text-align: center;
  margin-bottom: 32px;
}
.login-logo h1 {
  font-size: 28px;
  font-weight: 700;
  color: var(--fiori-shell-text);
  letter-spacing: -0.5px;
}
.login-logo h1 span {
  color: var(--fiori-brand);
}
.login-logo p {
  color: var(--fiori-text-muted);
  font-size: 13px;
  margin-top: 4px;
}
.login-footer {
  text-align: center;
  margin-top: 28px;
  padding-top: 20px;
  border-top: 1px solid rgba(255,255,255,0.06);
  font-size: 12.5px;
  color: #64748B;
  letter-spacing: 0.3px;
}
.xeronity-brand {
  font-weight: 700;
  background: linear-gradient(135deg, #3B82F6 0%, #00F0FF 40%, #BF5FFF 70%, #FF00E5 100%);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
  font-size: 13.5px;
  letter-spacing: 0.5px;
}

/* Login → Sign In button. Reuses the Xeronity wordmark's blue → cyan
 * → violet → magenta gradient so the call-to-action visually echoes
 * the brand line at the bottom of the card. Scoped to .login-form so
 * the app-wide .btn-primary look (neon-on-translucent) is untouched. */
.login-form .btn-primary {
  background: linear-gradient(135deg, #3B82F6 0%, #00F0FF 40%, #BF5FFF 70%, #FF00E5 100%);
  background-size: 200% 200%;
  background-position: 0% 50%;
  color: #ffffff;
  border: none;
  box-shadow: 0 8px 24px rgba(59, 130, 246, 0.35), 0 0 18px rgba(191, 95, 255, 0.25);
  font-weight: 700;
  letter-spacing: 0.3px;
  transition: background-position 0.5s ease, transform 0.15s ease, box-shadow 0.2s ease;
}
.login-form .btn-primary:hover:not(:disabled) {
  background-position: 100% 50%;
  transform: translateY(-1px);
  box-shadow: 0 10px 28px rgba(255, 0, 229, 0.40), 0 0 22px rgba(0, 240, 255, 0.30);
}
.login-form .btn-primary:active:not(:disabled) {
  transform: translateY(0);
  box-shadow: 0 4px 12px rgba(59, 130, 246, 0.30);
}
.login-form .btn-primary:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

.login-form .form-group { margin-bottom: 20px; }
.login-form label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--fiori-text);
  margin-bottom: 6px;
}
.login-form input[type="email"],
.login-form input[type="password"],
.login-form input[type="text"] {
  width: 100%;
  padding: 10px 12px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  color: var(--fiori-text);
  font-size: 14px;
  outline: none;
  transition: all var(--transition-fast);
}
.login-form input::placeholder {
  color: var(--fiori-text-muted);
}
.login-form input:focus {
  border-color: var(--fiori-brand);
  box-shadow: 0 0 0 3px var(--fiori-brand-glow);
  background: rgba(255,255,255,0.06);
}

.login-form .remember-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 24px;
  font-size: 13px;
}
.login-form .remember-row label {
  display: flex;
  align-items: center;
  gap: 6px;
  font-weight: 400;
  cursor: pointer;
  margin-bottom: 0;
  color: var(--fiori-text2);
}
.login-form .remember-row a {
  color: var(--fiori-brand);
  font-size: 13px;
}

.login-error {
  background: rgba(248,113,113,0.1);
  color: var(--fiori-negative);
  border: 1px solid rgba(248,113,113,0.25);
  border-radius: var(--fiori-radius);
  padding: 10px 14px;
  font-size: 13px;
  margin-bottom: 16px;
  display: none;
}
.login-error.visible { display: block; }

.mfa-section { display: none; }
.mfa-section.visible { display: block; }
.mfa-section p {
  font-size: 13px;
  color: var(--fiori-text-muted);
  margin-bottom: 12px;
}

/* ============================================================
   APP LAYOUT
   ============================================================ */
.app-layout {
  display: flex;
  height: 100vh;
  overflow: hidden;
}

/* ============================================================
   SIDEBAR (250px, dark navy, collapsible)
   ============================================================ */
.sidebar {
  width: var(--sidebar-width);
  background: var(--fiori-sidebar-bg, rgba(16,22,30,0.92));
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  color: var(--fiori-text2);
  display: flex;
  flex-direction: column;
  transition: width var(--transition-normal);
  overflow: hidden;
  flex-shrink: 0;
  position: relative;
  z-index: 20;
  border-right: 1px solid var(--fiori-border);
}
.sidebar.collapsed {
  width: var(--sidebar-collapsed-width);
}

.sidebar-header {
  height: var(--topbar-height);
  display: flex;
  align-items: center;
  padding: 0 16px;
  border-bottom: 1px solid var(--fiori-border);
  flex-shrink: 0;
}
.sidebar-header .logo {
  font-size: 18px;
  font-weight: 700;
  color: var(--fiori-shell-text);
  white-space: nowrap;
  overflow: hidden;
}
.sidebar-header .logo span {
  color: var(--fiori-brand);
}
.sidebar.collapsed .sidebar-header .logo { display: none; }

.sidebar-toggle {
  background: none;
  border: none;
  color: var(--fiori-text-muted);
  cursor: pointer;
  padding: 6px;
  border-radius: var(--fiori-radius);
  font-size: 18px;
  margin-left: auto;
  flex-shrink: 0;
  transition: all var(--transition-fast);
}
.sidebar-toggle:hover {
  background: rgba(255,255,255,0.06);
  color: var(--fiori-shell-text);
}

.sidebar-nav {
  flex: 1;
  overflow-y: auto;
  padding: 12px 0;
}

/* Sidebar sections */
.sidebar-section {
  padding: 0 8px;
  margin-top: 2px;
}
.sidebar-section:first-child { margin-top: 0; }

/* Accordion section title (clickable header) */
.sidebar-section-title {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: var(--fiori-radius);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 1px;
  color: var(--fiori-text-muted);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  transition: all var(--transition-fast);
  user-select: none;
}
.sidebar-section-title:hover {
  background: rgba(255,255,255,0.04);
  color: var(--fiori-text-secondary);
}
.sidebar-section-title.active {
  color: var(--fiori-brand);
}
.sidebar-section-title .section-icon {
  width: 20px;
  text-align: center;
  font-size: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.sidebar-section-title .section-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}
.sidebar-section-title .section-chevron {
  margin-left: auto;
  color: var(--fiori-text-muted);
  display: flex;
  align-items: center;
}

/* Collapsed sidebar: show only section icon, hide label+chevron */
.sidebar.collapsed .sidebar-section-title .section-label { display: none; }
.sidebar.collapsed .sidebar-section-title .section-chevron { display: none; }
.sidebar.collapsed .sidebar-section-title {
  justify-content: center;
  padding: 8px 4px;
}

/* Submenu – accordion panel with smooth height transition.
   Owner spec 2026-05-22: max-height of the OPEN state was 500px,
   which silently clipped the last item once a section grew past
   ~16-17 entries (HCM with the new Audit Log lands right at the
   limit). Bumped to 2000px — large enough to fit every realistic
   section without clipping; the parent .sidebar-nav has
   overflow-y:auto so the whole sidebar still scrolls when the
   sections collectively exceed viewport height. */
.sidebar-submenu {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.25s ease, opacity 0.2s ease;
  opacity: 0;
  padding-left: 8px;
}
.sidebar-submenu.open {
  max-height: 2000px;
  opacity: 1;
}
/* In collapsed mode, submenu always hidden */
.sidebar.collapsed .sidebar-submenu {
  max-height: 0 !important;
  opacity: 0 !important;
}

/* Submenu items */
.sidebar-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 10px 6px 14px;
  border-radius: var(--fiori-radius);
  color: var(--fiori-text2);
  cursor: pointer;
  font-size: 12.5px;
  white-space: nowrap;
  transition: all var(--transition-fast);
  user-select: none;
  position: relative;
}
.sidebar-item::before {
  content: '';
  position: absolute;
  left: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 0;
  border-radius: 2px;
  background: var(--fiori-brand);
  transition: height 0.2s ease;
}
.sidebar-item:hover {
  background: rgba(255,255,255,0.06);
  color: var(--fiori-text);
}
.sidebar-item.active {
  background: var(--fiori-brand-glow);
  color: var(--fiori-shell-text);
}
.sidebar-item.active::before {
  height: 16px;
}
.sidebar-item .icon {
  width: 18px;
  text-align: center;
  font-size: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.sidebar-item .label { overflow: hidden; text-overflow: ellipsis; }
.sidebar.collapsed .sidebar-item .label { display: none; }

.sidebar-item.disabled {
  opacity: 0.3;
  cursor: default;
}
.sidebar-item.disabled:hover {
  background: none;
  color: var(--fiori-text2);
}
.sidebar-item .coming-soon {
  font-size: 9px;
  background: rgba(255,255,255,0.08);
  padding: 1px 6px;
  border-radius: 10px;
  margin-left: auto;
  color: var(--fiori-text-muted);
}
.sidebar.collapsed .sidebar-item .coming-soon { display: none; }

/* ============================================================
   TOP BAR (48px, glass effect)
   ============================================================ */
.topbar {
  height: var(--topbar-height);
  background: var(--fiori-shell);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--fiori-border);
  display: flex;
  align-items: center;
  padding: 0 24px;
  flex-shrink: 0;
  z-index: 9000;
  gap: 16px;
}

.topbar-search {
  flex: 1;
  max-width: 400px;
  position: relative;
}
.topbar-search input {
  width: 100%;
  padding: 8px 12px 8px 36px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  color: var(--fiori-text);
  font-size: 13px;
  outline: none;
  transition: all var(--transition-fast);
}
.topbar-search input::placeholder {
  color: var(--fiori-text-muted);
}
.topbar-search input:focus {
  border-color: var(--fiori-brand);
  background: rgba(255,255,255,0.06);
  box-shadow: 0 0 0 3px var(--fiori-brand-glow);
}
.topbar-search .search-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--fiori-text-muted);
  font-size: 14px;
}

.topbar-right {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-left: auto;
}

.topbar-btn {
  background: none;
  border: none;
  padding: 8px;
  border-radius: var(--fiori-radius);
  cursor: pointer;
  color: var(--fiori-text2);
  font-size: 18px;
  position: relative;
  transition: all var(--transition-fast);
}
.topbar-btn:hover {
  background: rgba(255,255,255,0.06);
  color: var(--fiori-shell-text);
}
.topbar-btn .badge-dot {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--fiori-negative);
  border: 2px solid rgba(30,42,56,0.85);
}

.user-menu {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 8px;
  border-radius: var(--fiori-radius);
  cursor: pointer;
  transition: background var(--transition-fast);
  position: relative;
}
.user-menu:hover {
  background: rgba(255,255,255,0.06);
}

.user-avatar {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--fiori-brand) 0%, #5BA3FF 100%);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 13px;
  font-weight: 600;
}

.user-info { line-height: 1.3; }
.user-info .name {
  font-size: 13px;
  font-weight: 600;
  color: var(--fiori-shell-text);
}
.user-info .role {
  font-size: 11px;
  color: var(--fiori-text-muted);
}

.user-dropdown {
  position: absolute;
  top: 100%;
  right: 0;
  margin-top: 4px;
  background: rgba(22,28,36,0.92);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--fiori-border-light);
  border-radius: var(--fiori-radius);
  box-shadow: 0 12px 40px rgba(0,0,0,0.4);
  min-width: 180px;
  z-index: 9999;
  overflow: hidden;
}
.user-dropdown a {
  display: block;
  padding: 10px 16px;
  font-size: 13px;
  color: var(--fiori-text);
  transition: background var(--transition-fast);
}
.user-dropdown a:hover {
  background: rgba(255,255,255,0.06);
  text-decoration: none;
}
.user-dropdown .divider {
  height: 1px;
  background: var(--fiori-border);
  margin: 4px 0;
}

/* ============================================================
   CONTENT AREA
   ============================================================ */
.main-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--fiori-bg);
  /* min-width: 0 stops this flex child from inheriting min-width:auto and
     expanding to fit oversized descendants (e.g. wide tables in the
     Contract editor). Without it, the whole page can scroll horizontally
     when any deep child is wider than the viewport. */
  min-width: 0;
}

.content-area {
  flex: 1;
  overflow-y: auto;
  /* Same reason — keep horizontal overflow inside per-component scrollers
     instead of letting it bubble up to the page. */
  overflow-x: hidden;
  min-width: 0;
  padding: 14px 24px 24px;
}

.page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 16px;
}
.page-header h1 {
  font-size: 22px;
  font-weight: 700;
  color: var(--fiori-shell-text);
}
.page-header .actions {
  display: flex;
  gap: 8px;
}

/* ============================================================
   BREADCRUMBS
   ============================================================ */
.breadcrumbs {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13px;
  color: var(--fiori-text-muted);
  margin-bottom: 16px;
}
.breadcrumbs a {
  color: var(--fiori-text2);
}
.breadcrumbs a:hover {
  color: var(--fiori-brand);
  text-decoration: none;
}
.breadcrumbs .separator { font-size: 10px; }
.breadcrumbs .current {
  color: var(--fiori-text);
  font-weight: 600;
}

/* ============================================================
   BUTTONS
   ============================================================ */
.btn {
  --btn-edge: var(--page-neon, var(--neon-cyan, #00F0FF));
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  border-radius: var(--fiori-radius);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: all var(--transition-fast);
  white-space: nowrap;
  outline: none;

  /* Owner 2026-09-14: "APPLY SAME BUTTON THEME TO ALL THE BUTTONS ON
     THE VARIOUS TABS." A plain .btn used to declare `border: none` and
     NO background, so a <button> fell back to the browser's own
     ButtonFace -- near-white whatever the theme, which is why Portfolio
     and Take baseline were pale chips on a black header. Declaring
     background AND colour is the whole fix; inheriting either from the
     user agent is how a dark page ends up with light buttons in it.

     This lives on the BASE rule, at one class of specificity, on
     purpose. Every colour modifier (.btn-primary, .btn-secondary,
     .btn-success, .btn-danger, .btn-warning, .btn-ghost, .btn-neon) is
     declared BELOW this block and so still wins by source order, and any
     page-level override still outranks it. A tempting :not() chain over
     all seven would have been eight classes of specificity and would
     have quietly beaten both.

     --btn-edge follows --page-neon, so each section's buttons carry that
     section's hue rather than one hard-coded cyan. */
  background: transparent;
  color: var(--fiori-text);
  border: 1px solid color-mix(in srgb, var(--btn-edge) 42%, transparent);
  box-shadow: 0 0 8px -3px color-mix(in srgb, var(--btn-edge) 55%, transparent);
}
.btn:hover:not(:disabled):not([aria-disabled="true"]) {
  border-color: color-mix(in srgb, var(--btn-edge) 80%, transparent);
  box-shadow: 0 0 14px -3px color-mix(in srgb, var(--btn-edge) 75%, transparent);
}
.btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
  /* A lit edge reads as "available". */
  box-shadow: none;
  border-color: var(--fiori-border);
}

/* Light mode gets a plain, confident rule instead. A glow needs darkness
   behind it to read as light rather than as a printing fault. */
[data-theme="light"] .btn {
  border: 1px solid var(--fiori-border);
  box-shadow: none;
}
[data-theme="light"] .btn:hover:not(:disabled):not([aria-disabled="true"]) {
  border-color: var(--fiori-text-muted);
  background: var(--fiori-surface2);
  box-shadow: none;
}

/* A browser without color-mix must still get a VISIBLE border, not a
   transparent one -- the fallback has to be usable, not merely valid. */
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  .btn { border: 1px solid var(--fiori-border); box-shadow: none; }
}

.btn-primary {
  --btn-neon: var(--page-neon, #94A3B8);
  background: rgba(255,255,255,0.04);
  color: var(--btn-neon);
  border: 1px solid color-mix(in srgb, var(--btn-neon) 35%, transparent);
  box-shadow: 0 0 8px color-mix(in srgb, var(--btn-neon) 10%, transparent);
}
.btn-primary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--btn-neon) 12%, transparent);
  border-color: color-mix(in srgb, var(--btn-neon) 50%, transparent);
  box-shadow: 0 0 16px color-mix(in srgb, var(--btn-neon) 18%, transparent);
  transform: translateY(-1px);
}
.btn-primary:active:not(:disabled) {
  transform: translateY(0);
}

/* ── Tab Bar (generic page-level tabs) ── */
.tab-bar {
  display: inline-flex;
  gap: 2px;
  background: rgba(255,255,255,0.03);
  border-radius: 10px;
  padding: 3px;
  border: 1px solid rgba(255,255,255,0.06);
}
.tab-btn {
  padding: 6px 16px;
  font-size: 13px;
  font-weight: 500;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: #94A3B8;
  cursor: pointer;
  transition: all 0.2s;
  white-space: nowrap;
}
.tab-btn:hover {
  background: rgba(255,255,255,0.06);
  color: #CBD5E1;
}
.tab-btn.active {
  --btn-neon: var(--page-neon, #94A3B8);
  background: color-mix(in srgb, var(--btn-neon) 12%, transparent);
  color: var(--btn-neon);
  font-weight: 600;
}

.btn-secondary {
  background: var(--fiori-surface2);
  color: var(--fiori-text);
  border: 1px solid var(--fiori-border-light);
}
.btn-secondary:hover:not(:disabled) {
  background: rgba(255,255,255,0.1);
  border-color: rgba(255,255,255,0.18);
}

.btn-success {
  /* Filled buttons carry their own colour; the base rule's neon ring
     would be a second, contradictory signal. */
  border-color: transparent;
  background: linear-gradient(135deg, var(--fiori-positive) 0%, #4AE8AD 100%);
  color: #fff;
  box-shadow: 0 2px 12px rgba(52,211,153,0.25);
}
.btn-success:hover:not(:disabled) {
  box-shadow: 0 4px 20px rgba(52,211,153,0.35);
  transform: translateY(-1px);
}

.btn-danger {
  /* Filled buttons carry their own colour; the base rule's neon ring
     would be a second, contradictory signal. */
  border-color: transparent;
  background: linear-gradient(135deg, var(--fiori-negative) 0%, #FF9090 100%);
  color: #fff;
  box-shadow: 0 2px 12px rgba(248,113,113,0.25);
}
.btn-danger:hover:not(:disabled) {
  box-shadow: 0 4px 20px rgba(248,113,113,0.35);
  transform: translateY(-1px);
}

.btn-warning {
  /* Filled buttons carry their own colour; the base rule's neon ring
     would be a second, contradictory signal. */
  border-color: transparent;
  background: linear-gradient(135deg, var(--fiori-warning) 0%, #FCD34D 100%);
  color: #1a1a1a;
}

.btn-ghost {
  background: transparent;
  color: var(--fiori-text2);
  border: 1px solid transparent;
}
.btn-ghost:hover:not(:disabled) {
  background: rgba(255,255,255,0.06);
  color: var(--fiori-text);
}

/* ── Neon button (uses --page-neon from page context) ── */
.btn-neon {
  --btn-neon: var(--page-neon, #00e0ff);
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.15s ease;
  background: rgba(255,255,255,0.04);
  color: var(--btn-neon);
  border: 1px solid color-mix(in srgb, var(--btn-neon) 35%, transparent);
  box-shadow: 0 0 8px color-mix(in srgb, var(--btn-neon) 10%, transparent);
}
.btn-neon:hover:not(:disabled) {
  background: color-mix(in srgb, var(--btn-neon) 12%, transparent);
  border-color: color-mix(in srgb, var(--btn-neon) 50%, transparent);
  box-shadow: 0 0 16px color-mix(in srgb, var(--btn-neon) 18%, transparent);
  transform: translateY(-1px);
}
.btn-neon:active:not(:disabled) {
  transform: translateY(0);
}

.btn-sm { padding: 5px 10px; font-size: 12px; }
.btn-lg { padding: 11px 22px; font-size: 15px; }
.btn-block { width: 100%; justify-content: center; }
.btn-icon { padding: 6px; min-width: 32px; justify-content: center; }

/* ============================================================
   CARDS (Glass effect)
   ============================================================ */
.card {
  background: var(--fiori-surface);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-radius: var(--fiori-radius-lg);
  border: 1px solid var(--fiori-border);
  box-shadow: 0 4px 24px rgba(0,0,0,0.2);
}
.card-header {
  padding: 16px 20px;
  border-bottom: 1px solid var(--fiori-border);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.card-header h2 {
  font-size: 15px;
  font-weight: 600;
  color: var(--fiori-text);
}
.card-body { padding: 20px; }
.card-footer {
  padding: 12px 20px;
  border-top: 1px solid var(--fiori-border);
  background: rgba(255,255,255,0.02);
  border-radius: 0 0 var(--fiori-radius-lg) var(--fiori-radius-lg);
}

/* ============================================================
   SUMMARY CARDS (Dashboard)
   ============================================================ */
.summary-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 16px;
  margin-bottom: 24px;
}

.summary-card {
  background: var(--fiori-surface);
  backdrop-filter: var(--glass-blur-sm);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  border-radius: var(--fiori-radius-lg);
  border: 1px solid var(--fiori-border);
  box-shadow: 0 4px 24px rgba(0,0,0,0.15);
  padding: 20px;
  display: flex;
  align-items: flex-start;
  gap: 16px;
  transition: all var(--transition-fast);
}
.summary-card:hover {
  border-color: var(--fiori-border-light);
  box-shadow: 0 8px 32px rgba(0,0,0,0.25);
  transform: translateY(-2px);
}

.summary-card .card-icon {
  width: 48px;
  height: 48px;
  border-radius: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  flex-shrink: 0;
}
.summary-card .card-icon.blue {
  background: rgba(59,141,248,0.15);
  color: var(--fiori-brand);
}
.summary-card .card-icon.green {
  background: rgba(52,211,153,0.15);
  color: var(--fiori-positive);
}
.summary-card .card-icon.orange {
  background: rgba(251,191,36,0.15);
  color: var(--fiori-warning);
}
.summary-card .card-icon.purple {
  background: rgba(96,165,250,0.15);
  color: var(--fiori-info);
}
/* Neon icon backgrounds for dashboard cards */
.summary-card .card-icon.neon-mint {
  background: rgba(0,229,160,0.12);
}
.summary-card .card-icon.neon-blue {
  background: rgba(59,143,244,0.12);
}
.summary-card .card-icon.neon-gold {
  background: rgba(255,179,0,0.12);
}
.summary-card .card-icon.neon-violet {
  background: rgba(191,95,255,0.12);
}

.summary-card .card-content h3 {
  font-size: 12px;
  font-weight: 600;
  color: var(--fiori-text-muted);
  text-transform: uppercase;
  letter-spacing: 0.5px;
  margin-bottom: 4px;
}
.summary-card .card-content .value {
  font-size: 26px;
  font-weight: 700;
  color: var(--fiori-shell-text);
  line-height: 1.2;
}
.summary-card .card-content .sub {
  font-size: 12px;
  color: var(--fiori-text-muted);
  margin-top: 4px;
}
.summary-card.clickable {
  cursor: pointer;
  position: relative;
}
.summary-card.clickable:hover {
  border-color: var(--fiori-brand);
}
.summary-card .card-arrow {
  position: absolute;
  top: 50%;
  right: 16px;
  transform: translateY(-50%);
  color: var(--fiori-text-muted);
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.summary-card.clickable:hover .card-arrow {
  opacity: 1;
  transform: translateY(-50%) translateX(2px);
}

/* ============================================================
   DATA TABLES (Dark themed with hover)
   ============================================================ */
.data-table-wrapper {
  background: var(--fiori-surface);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-radius: var(--fiori-radius-lg);
  border: 1px solid var(--fiori-border);
  box-shadow: 0 4px 24px rgba(0,0,0,0.2);
  overflow: hidden;
}

.table-toolbar {
  padding: 16px 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  border-bottom: 1px solid var(--fiori-border);
  flex-wrap: wrap;
}

.table-search {
  position: relative;
  flex: 1;
  min-width: 200px;
  max-width: 320px;
}
.table-search input {
  width: 100%;
  padding: 7px 12px 7px 34px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  color: var(--fiori-text);
  font-size: 13px;
  outline: none;
  transition: all var(--transition-fast);
}
.table-search input::placeholder {
  color: var(--fiori-text-muted);
}
.table-search input:focus {
  border-color: var(--fiori-brand);
  box-shadow: 0 0 0 3px var(--fiori-brand-glow);
}
.table-search .icon {
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--fiori-text-muted);
}

.table-filters {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.table-filters select {
  padding: 7px 10px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  color: var(--fiori-text);
  font-size: 13px;
  outline: none;
  cursor: pointer;
  transition: border-color var(--transition-fast);
}
.table-filters select:focus {
  border-color: var(--fiori-brand);
}
.table-filters select option {
  background: #161C24;
  color: var(--fiori-text);
}

.data-table {
  width: 100%;
  border-collapse: collapse;
  table-layout: auto;
}
.data-table-fixed {
  table-layout: fixed;
}
.data-table-fixed th {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.data-table-fixed td:last-child {
  overflow: visible;
}
.data-table th {
  padding: 10px 16px;
  text-align: left;
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--fiori-text-muted);
  background: rgba(255,255,255,0.02);
  border-bottom: 1px solid var(--fiori-border);
  cursor: pointer;
  user-select: none;
  white-space: nowrap;
}
.data-table th:hover {
  color: var(--fiori-text);
}
.data-table th .sort-icon {
  margin-left: 4px;
  font-size: 10px;
}

.data-table td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--fiori-border);
  font-size: 13px;
  vertical-align: middle;
  color: var(--fiori-text);
}
.data-table tr:last-child td { border-bottom: none; }
.data-table tr:hover td {
  background: rgba(255,255,255,0.03);
}

/* Actions column — 2026-09-14 RENDER FIX. Two faults, both visible as
   the Actions column painting ON TOP of Status/Created with the header
   text merged into "STATAGSIONS":

   1. `display: flex` was set on the <td>/<th> ITSELF. That takes the cell
      out of table layout, so it no longer fills the row box — the sticky
      cells rendered as free-floating blocks, vertically offset from their
      own rows with seams between them. The flex now lives on an inner
      wrapper (.actions-inner) so the cell stays a table-cell.

   2. The backgrounds were dark-theme assumptions. `--fiori-surface` is
      rgba(22,28,36,0.72) — TRANSLUCENT — and the header used
      rgba(255,255,255,0.02), which on the light theme is effectively
      transparent. A sticky column must be fully OPAQUE or the columns it
      overlaps show straight through it. Both now use *-solid tokens,
      which are defined in both themes. */
.data-table .actions-cell { white-space: nowrap; }

/* DataTable renders its buttons inside this wrapper. */
.data-table .actions-cell .actions-inner {
  display: flex;
  gap: 4px;
  align-items: center;
  justify-content: flex-end;
}

/* Compatibility — five pages (Resources, Customers, Admin, Leave, ESS)
   hand-roll `<td class="actions-cell">` with the buttons as DIRECT
   children and relied on the old `display:flex` for the row layout and
   the 4px gap. Those cells must stay table-cells for the sticky fix
   above to hold, so the spacing is done with a sibling margin instead of
   a flex gap. Admin also uses a <div class="actions-cell">, which is not
   a table cell and still wants a real flex row. */
.data-table td.actions-cell,
.data-table th.actions-cell {
  display: table-cell;
  vertical-align: middle;
}
.data-table td.actions-cell > * + * { margin-left: 4px; }
.data-table div.actions-cell {
  display: flex;
  gap: 4px;
  align-items: center;
}

/* ---- Sticky right Actions column: always visible even when other columns overflow ---- */
.data-table th.actions-cell,
.data-table td.actions-cell {
  position: sticky;
  right: 0;
  z-index: 2;
  background: var(--fiori-surface-solid);
  box-shadow: -8px 0 12px -8px rgba(0,0,0,0.35);
}
.data-table tr:hover td.actions-cell {
  background: var(--fiori-surface-solid);
}
.data-table th.actions-cell.actions-header {
  background: var(--fiori-surface2-solid);
  z-index: 3; /* above the sticky body cells when the header is sticky too */
}
.data-table .actions-cell .action-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.06);
  width: 34px;
  height: 34px;
  /* The cell is a flex row, so buttons are flex items and would
     otherwise shrink below 34px once the row runs out of space —
     squashing into slivers instead of overflowing visibly. Pin them.
     Owner spec 2026-08-28. */
  flex: 0 0 34px;
  box-sizing: border-box;
  padding: 0;
  cursor: pointer;
  color: var(--fiori-text-secondary);
  border-radius: 8px;
  font-size: 15px;
  transition: all 0.2s ease;
}
.data-table .actions-cell .action-btn:hover {
  background: rgba(255,255,255,0.10);
  color: var(--fiori-brand);
  border-color: rgba(255,255,255,0.12);
  transform: translateY(-1px);
  box-shadow: 0 2px 8px rgba(0,0,0,0.2);
}
.data-table .actions-cell .action-btn--danger:hover {
  color: var(--fiori-negative);
  background: rgba(239,68,68,0.10);
  border-color: rgba(239,68,68,0.20);
}
.data-table .actions-cell .action-btn--success {
  color: #34D399;
}
.data-table .actions-cell .action-btn--success:hover {
  color: #34D399;
  background: rgba(52,211,153,0.12);
  border-color: rgba(52,211,153,0.25);
}
.data-table .actions-cell .action-btn--link {
  color: #60A5FA;
}
.data-table .actions-cell .action-btn--link:hover {
  color: #60A5FA;
  background: rgba(96,165,250,0.12);
  border-color: rgba(96,165,250,0.25);
}
.data-table .actions-cell a.action-btn {
  text-decoration: none;
}
.data-table .actions-cell .action-btn svg {
  flex-shrink: 0;
}

.table-pagination {
  padding: 12px 20px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-top: 1px solid var(--fiori-border);
  font-size: 13px;
  color: var(--fiori-text-muted);
}
.table-pagination .page-buttons {
  display: flex;
  gap: 4px;
}
.table-pagination .page-btn {
  padding: 4px 10px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  color: var(--fiori-text2);
  cursor: pointer;
  font-size: 13px;
  transition: all var(--transition-fast);
}
.table-pagination .page-btn:hover {
  background: rgba(255,255,255,0.08);
  border-color: var(--fiori-border-light);
}
.table-pagination .page-btn.active {
  background: linear-gradient(135deg, var(--fiori-brand) 0%, #5BA3FF 100%);
  color: #fff;
  border-color: var(--fiori-brand);
  box-shadow: 0 2px 8px var(--fiori-brand-glow);
}

.table-empty {
  padding: 48px 20px;
  text-align: center;
  color: var(--fiori-text-muted);
}
.table-empty .icon {
  font-size: 40px;
  margin-bottom: 12px;
}
.table-loading {
  padding: 48px 20px;
  text-align: center;
  color: var(--fiori-text-muted);
}

/* ============================================================
   STATUS BADGES
   ============================================================ */
.badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 10px;
  border-radius: 20px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.3px;
  white-space: nowrap;
}
.badge-blue {
  background: rgba(59,141,248,0.15);
  color: var(--fiori-info);
}
.badge-green {
  background: rgba(52,211,153,0.15);
  color: var(--fiori-positive);
}
.badge-yellow {
  background: rgba(251,191,36,0.15);
  color: var(--fiori-warning);
}
.badge-red {
  background: rgba(248,113,113,0.15);
  color: var(--fiori-negative);
}
.badge-gray {
  background: rgba(255,255,255,0.06);
  color: var(--fiori-text-muted);
}
.badge-purple {
  background: rgba(139,92,246,0.15);
  color: #A78BFA;
}
.badge-teal {
  background: rgba(45,212,191,0.15);
  color: #2DD4BF;
}

/* Named status badges */
.badge-new { background: rgba(59,141,248,0.15); color: var(--fiori-info); }
.badge-active { background: rgba(52,211,153,0.15); color: var(--fiori-positive); }
.badge-won { background: rgba(52,211,153,0.2); color: var(--fiori-positive); }
.badge-lost { background: rgba(248,113,113,0.15); color: var(--fiori-negative); }
.badge-pending { background: rgba(251,191,36,0.15); color: var(--fiori-warning); }
.badge-closed { background: rgba(255,255,255,0.06); color: var(--fiori-text-muted); }

/* ============================================================
   FORMS
   ============================================================ */
.form-group { margin-bottom: 16px; }

.form-label {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--fiori-text);
  margin-bottom: 6px;
}
.form-label .required {
  color: var(--fiori-negative);
  margin-left: 2px;
}

.form-control {
  width: 100%;
  /* min-width: 0 is critical for native <select>: without it, the select
     element sizes itself to fit its longest <option>'s text, and inside
     a CSS grid track that means the track expands to match — pushing
     the whole row past its parent. With min-width:0 the select truncates
     to fit the cell, the grid stays balanced, and the page never grows
     wider than the viewport. */
  min-width: 0;
  max-width: 100%;
  box-sizing: border-box;
  padding: 8px 12px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  color: var(--fiori-text);
  font-size: 13px;
  outline: none;
  transition: all var(--transition-fast);
}
/* Defensive — applies the same clamp to the form-row grid cells so a
   bare <select> inside a row never widens the row by sizing to its
   longest option. */
.form-row > * { min-width: 0; max-width: 100%; }

/* Browser default for <fieldset> is `min-width: min-content`, which
   makes the element size itself to its widest descendant. With wide
   children (data tables, milestone grids) that pushes the parent past
   the viewport. Overriding to 0 lets the fieldset shrink with its
   container while still grouping `disabled` semantics for inputs. */
fieldset { min-width: 0; max-width: 100%; }

/* Hide the up/down spinner arrows on <input type="number"> across the
   whole app — they look noisy in a dark dense UI and HR / Finance
   prefer to type values directly. The mouse wheel still adjusts the
   value when the field is focused, but the visible arrows are gone.
   Scoped narrowly to `type="number"` so other inputs are untouched. */
input[type="number"] {
  -moz-appearance: textfield;     /* Firefox */
  appearance: textfield;
}
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;        /* Chrome, Safari, Edge */
  margin: 0;
}
.form-control::placeholder {
  color: var(--fiori-text-muted);
}
.form-control:focus {
  border-color: var(--fiori-brand);
  box-shadow: 0 0 0 3px var(--fiori-brand-glow);
  background: rgba(255,255,255,0.06);
}
.form-control.error {
  border-color: var(--fiori-negative);
  box-shadow: 0 0 0 3px rgba(248,113,113,0.15);
}
.form-error {
  color: var(--fiori-negative);
  font-size: 12px;
  margin-top: 4px;
}

textarea.form-control {
  min-height: 80px;
  resize: vertical;
}

select.form-control {
  cursor: pointer;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%238A9BB5' d='M6 8.5L1 3.5h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 32px;
}

/* Modern date input styling */
input[type="date"].form-control {
  cursor: pointer;
  position: relative;
}
/* Replace the webkit calendar picker indicator with a hand-authored bright
   SVG so it's legible on every dark background. `opacity: 1` + inline SVG
   avoids the quirk where `color-scheme: dark` already lightens the icon
   and a follow-up filter re-darkens it. */
input[type="date"]::-webkit-calendar-picker-indicator,
input[type="datetime-local"]::-webkit-calendar-picker-indicator,
input[type="month"]::-webkit-calendar-picker-indicator,
input[type="week"]::-webkit-calendar-picker-indicator {
  -webkit-appearance: none;
  appearance: none;
  opacity: 1;
  cursor: pointer;
  width: 20px;
  height: 20px;
  padding: 0;
  margin: 0 2px;
  border-radius: 4px;
  background-color: transparent;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 16px 16px;
  /* Cyan calendar outline — always pops on the dark panels */
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2300E0FF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><rect x='3' y='4' width='18' height='18' rx='2' ry='2'/><line x1='16' y1='2' x2='16' y2='6'/><line x1='8' y1='2' x2='8' y2='6'/><line x1='3' y1='10' x2='21' y2='10'/></svg>");
  transition: background-color 0.15s ease;
}
input[type="date"]::-webkit-calendar-picker-indicator:hover,
input[type="datetime-local"]::-webkit-calendar-picker-indicator:hover,
input[type="month"]::-webkit-calendar-picker-indicator:hover,
input[type="week"]::-webkit-calendar-picker-indicator:hover {
  background-color: rgba(0,224,255,0.18);
}
/* Clock icon for time inputs */
input[type="time"]::-webkit-calendar-picker-indicator {
  -webkit-appearance: none;
  appearance: none;
  opacity: 1;
  cursor: pointer;
  width: 20px;
  height: 20px;
  padding: 0;
  margin: 0 2px;
  border-radius: 4px;
  background-color: transparent;
  background-repeat: no-repeat;
  background-position: center;
  background-size: 16px 16px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2300E0FF' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><circle cx='12' cy='12' r='10'/><polyline points='12 6 12 12 16 14'/></svg>");
}
input[type="time"]::-webkit-calendar-picker-indicator:hover {
  background-color: rgba(0,224,255,0.18);
}
/* Firefox fallback */
input[type="date"], input[type="datetime-local"], input[type="time"], input[type="month"], input[type="week"] {
  color-scheme: dark;
}
select.form-control option {
  background: #161C24;
  color: var(--fiori-text);
}

.form-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}
.form-actions {
  display: flex;
  gap: 8px;
  justify-content: flex-end;
  margin-top: 24px;
}

/* Checkbox and Radio */
input[type="checkbox"],
input[type="radio"] {
  accent-color: var(--fiori-brand);
}

/* ============================================================
   MODALS (Glass overlay)
   ============================================================ */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0,0,0,0.6);
  backdrop-filter: var(--glass-blur-sm);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  opacity: 0;
  visibility: hidden;
  transition: all var(--transition-normal);
}
.modal-overlay.open {
  opacity: 1;
  visibility: visible;
}

.modal {
  background: rgba(22,28,36,0.92);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--fiori-border-light);
  border-radius: var(--fiori-radius-lg);
  box-shadow: 0 24px 64px rgba(0,0,0,0.5);
  width: 100%;
  transform: translateY(10px) scale(0.98);
  transition: transform var(--transition-normal);
  overflow: hidden;
}
.modal-overlay.open .modal {
  transform: translateY(0) scale(1);
}
.modal-sm { max-width: 420px; }
.modal-md { max-width: 560px; }
.modal-lg { max-width: 720px; }
.modal-xl { max-width: 900px; }
/* 'full' — near-full-screen. For content that is a wide flow of items
   (chip lists, wide tables) where a 900px column forces a scrollbar that
   the extra width would have removed. Its body is allowed to run taller
   than the standard modal for the same reason. */
.modal-full { max-width: 96vw; }
.modal-full .modal-body { max-height: calc(94vh - 120px); }

/* 'sheet' — the whole screen height. Owner 2026-09-26: *"Increase the
   height of this modal to cover the entire screen height."* A flow of
   28 steps in a 720px box is read four steps at a time; the drawing is
   the point of that modal, so it gets the height. */
.modal-sheet { max-width: 96vw; }
.modal-sheet .modal-body { max-height: calc(100vh - 140px); height: calc(100vh - 140px); }

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 24px;
  border-bottom: 1px solid var(--fiori-border);
}
.modal-header h2 {
  font-size: 16px;
  font-weight: 700;
  color: var(--fiori-shell-text);
  margin: 0;
}
/* Optional second line under the title — small, muted. Use this to
   show the row identity (e.g. emp_code + name) so the user knows
   exactly which record the modal is acting on. */
.modal-subtitle {
  font-size: 12.5px;
  color: var(--fiori-text-muted);
  line-height: 1.4;
}
.modal-close {
  background: none;
  border: none;
  padding: 4px;
  cursor: pointer;
  color: var(--fiori-text-muted);
  font-size: 20px;
  line-height: 1;
  border-radius: var(--fiori-radius);
  transition: all var(--transition-fast);
}
.modal-close:hover {
  background: rgba(255,255,255,0.06);
  color: var(--fiori-text);
}
.modal-body {
  padding: 24px;
  max-height: calc(80vh - 120px);
  overflow-y: auto;
}
.modal-footer {
  padding: 16px 24px;
  border-top: 1px solid var(--fiori-border);
  display: flex;
  gap: 8px;
  justify-content: flex-end;
}

/* ══════════════════════════════════════════════════════════════════
   THE APP DIALOG — what replaces window.confirm / prompt / alert.
   Owner spec 2026-09-18: "In Dark theme, such popups should also be in
   dark theme with a glowing outline. Make changes at CSS level so that
   wherever this is used, the UI changes accordingly. Also the location
   should be near center of screen."

   Everything visual is HERE, on classes, rather than in style objects
   inside ToastHost.jsx. That is the point of the request: one edit in
   this block changes every confirm, prompt and alert in the app.

   It is a replacement, not a restyle. The browser's own dialog is
   drawn by Chrome, pinned to the TOP of the viewport, in its own light
   chrome, and no stylesheet can reach it -- which is why the app had to
   stop calling it before any of this could apply.
   ══════════════════════════════════════════════════════════════════ */
.bz-dlg-overlay{
  position: fixed; inset: 0; z-index: 10001;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
  background: rgba(0,0,0,0.55);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
}
.bz-dlg{
  width: 100%; max-width: 380px;
  background: var(--fiori-elevated-solid, var(--fiori-surface-solid));
  color: var(--fiori-text);
  border: 1px solid var(--fiori-border);
  border-radius: 14px;
  overflow: hidden;
  animation: bzDlgPop .18s cubic-bezier(.2,1.1,.4,1.2);
  /* THE GLOW. A ring in the brand hue plus the drop shadow that lifts
     it off the page -- on a dark shell a plain bordered box reads as
     part of the background. The ring is a box-shadow rather than an
     outline so it can be soft and so it does not affect layout. */
  box-shadow:
    0 0 0 1px rgba(59,141,248,.35),
    0 0 22px rgba(59,141,248,.28),
    0 24px 60px rgba(0,0,0,.55);
}
/* A destructive dialog glows red instead. Colour is not the only cue --
   the icon changes too -- but a delete that looks identical to a
   "are you sure you want to continue" is the one people click through. */
.bz-dlg.danger{
  box-shadow:
    0 0 0 1px rgba(239,68,68,.40),
    0 0 22px rgba(239,68,68,.30),
    0 24px 60px rgba(0,0,0,.55);
}
@keyframes bzDlgPop { from { transform: scale(.96); opacity: 0 } to { transform: none; opacity: 1 } }
@media (prefers-reduced-motion: reduce){ .bz-dlg{ animation: none } }

.bz-dlg-iconwrap{ display: flex; justify-content: center; padding: 20px 20px 6px; }
.bz-dlg-icon{
  width: 48px; height: 48px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px; font-weight: 700;
  background: rgba(245,158,11,.16); color: var(--fiori-warning, #F59E0B);
}
.bz-dlg.danger .bz-dlg-icon{ background: rgba(239,68,68,.16); color: var(--fiori-negative, #EF4444); }

.bz-dlg-title{ margin: 8px 20px 4px; font-size: 15px; text-align: center; font-weight: 600; color: var(--fiori-text); }
.bz-dlg-body{ margin: 0 20px 18px; font-size: 12.5px; text-align: center; color: var(--fiori-text-muted); line-height: 1.5; }
.bz-dlg-field{ padding: 0 20px 16px; }
.bz-dlg-input{
  width: 100%; height: 34px; padding: 0 10px;
  border-radius: 8px; border: 1px solid var(--fiori-border);
  background: var(--fiori-bg); color: var(--fiori-text); font: inherit; font-size: 13px;
}
.bz-dlg-input:focus{ outline: none; border-color: var(--fiori-brand); box-shadow: 0 0 0 3px rgba(59,141,248,.18); }

.bz-dlg-row{ display: grid; grid-template-columns: 1fr 1fr; border-top: 1px solid var(--fiori-border); }
.bz-dlg-cancel, .bz-dlg-ok{
  padding: 12px 0; background: transparent; border: 0;
  font-size: 13px; cursor: pointer; font-family: inherit;
}
.bz-dlg-cancel{ border-right: 1px solid var(--fiori-border); color: var(--fiori-text-muted); }
.bz-dlg-ok{ font-weight: 600; color: var(--fiori-brand); }
.bz-dlg.danger .bz-dlg-ok{ color: var(--fiori-negative, #EF4444); }
.bz-dlg-cancel:hover, .bz-dlg-ok:hover{ background: var(--fiori-row-hover); }
.bz-dlg-cancel:focus-visible, .bz-dlg-ok:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: -2px; }

/* Unsaved-changes confirmation overlay (inside modal) */
.modal-confirm-overlay {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.55);
  backdrop-filter: blur(4px);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  border-radius: 12px;
  animation: fadeIn 0.15s ease;
}
.modal-confirm-dialog {
  background: var(--fiori-surface);
  border: 1px solid var(--fiori-border);
  border-radius: 10px;
  padding: 28px 32px;
  text-align: center;
  max-width: 340px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
}
.modal-confirm-icon {
  margin-bottom: 12px;
}
.modal-confirm-dialog h3 {
  color: var(--fiori-text);
  font-size: 16px;
  font-weight: 600;
  margin: 0 0 8px;
}
.modal-confirm-dialog p {
  color: var(--fiori-muted);
  font-size: 13px;
  margin: 0 0 20px;
  line-height: 1.5;
}
.modal-confirm-actions {
  display: flex;
  gap: 10px;
  justify-content: center;
}
.modal-confirm-actions .btn-danger {
  background: #DC2626;
  color: #fff;
  border: none;
}
.modal-confirm-actions .btn-danger:hover {
  background: #B91C1C;
}

/* Ensure modal has position relative for the confirm overlay */
.modal {
  position: relative;
}

/* ============================================================
   PIPELINE / KANBAN BOARD
   ============================================================ */
.pipeline-board {
  display: flex;
  gap: 10px;
  /* No horizontal scroll — columns shrink to fit so the whole pipeline fits
     on screen. Wider min-width (280px) was forcing the overflow before. */
  overflow-x: hidden;
  padding-bottom: 8px;
  min-height: 400px;
}

.pipeline-column {
  min-width: 0;
  flex: 1 1 0;
  background: var(--fiori-surface);
  backdrop-filter: var(--glass-blur-sm);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  border-radius: var(--fiori-radius-lg);
  border: 1px solid var(--fiori-border);
  display: flex;
  flex-direction: column;
}

.pipeline-column-header {
  padding: 14px 16px;
  border-bottom: 1px solid var(--fiori-border);
  background: rgba(255,255,255,0.02);
  border-radius: var(--fiori-radius-lg) var(--fiori-radius-lg) 0 0;
}
.pipeline-column-header .stage-name {
  font-size: 13px;
  font-weight: 700;
  color: var(--fiori-text);
}
.pipeline-column-header .stage-meta {
  display: flex;
  gap: 12px;
  margin-top: 4px;
  font-size: 12px;
  color: var(--fiori-text-muted);
}
.pipeline-column-header .stage-meta .total-value {
  font-weight: 600;
  color: var(--fiori-brand);
}

.pipeline-column-body {
  flex: 1;
  padding: 12px;
  overflow-y: auto;
  min-height: 100px;
}
.pipeline-column-body.drag-over {
  background: rgba(59,141,248,0.08);
  border: 2px dashed var(--fiori-brand);
  border-radius: 0 0 var(--fiori-radius-lg) var(--fiori-radius-lg);
}

.pipeline-card {
  background: var(--fiori-surface2);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  padding: 12px;
  margin-bottom: 8px;
  cursor: grab;
  transition: all var(--transition-fast);
}
.pipeline-card:hover {
  border-color: var(--fiori-border-light);
  box-shadow: 0 4px 16px rgba(0,0,0,0.2);
  transform: translateY(-1px);
}
.pipeline-card.dragging {
  opacity: 0.5;
  transform: rotate(2deg);
}
.pipeline-card .opp-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--fiori-text);
  margin-bottom: 4px;
}
.pipeline-card .opp-customer {
  font-size: 12px;
  color: var(--fiori-text-muted);
  margin-bottom: 8px;
}
.pipeline-card .opp-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.pipeline-card .opp-value {
  font-size: 13px;
  font-weight: 700;
  color: var(--fiori-brand);
}
.pipeline-card .opp-probability {
  font-size: 11px;
  color: var(--fiori-text-muted);
}

/* ============================================================
   TABS (Customer 360, Admin sections)
   ============================================================ */
.tabs {
  display: flex;
  gap: 0;
  border-bottom: 1px solid var(--fiori-border);
  margin-bottom: 20px;
}
.tab {
  padding: 10px 20px;
  /* A <button> brings its own UA styling: ButtonFace grey, a 3D
     border and the system font. This rule was written for a <div>, so
     the first tab bar built from buttons rendered as pale grey slabs
     in dark mode -- reported 2026-09-18 with a screenshot. Reset the
     three here rather than in the page, so the NEXT tab bar built from
     buttons is right too. A button is the correct element: a clickable
     div is not reachable by keyboard and announces nothing. */
  background: transparent;
  border: 0;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  color: var(--fiori-text-muted);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: all var(--transition-fast);
}
.tab:focus-visible {
  outline: 2px solid var(--fiori-brand);
  outline-offset: -2px;
}
.tab:hover {
  color: var(--fiori-text);
}
.tab.active {
  color: var(--fiori-brand);
  border-bottom-color: var(--fiori-brand);
}

/* ============================================================
   ACTIVITY LIST
   ============================================================ */
.activity-list { list-style: none; }
.activity-item {
  display: flex;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 1px solid var(--fiori-border);
}
.activity-item:last-child { border-bottom: none; }

.activity-icon {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
  flex-shrink: 0;
}
.activity-icon.call {
  background: rgba(59,141,248,0.15);
  color: var(--fiori-brand);
}
.activity-icon.email {
  background: rgba(52,211,153,0.15);
  color: var(--fiori-positive);
}
.activity-icon.meeting {
  background: rgba(251,191,36,0.15);
  color: var(--fiori-warning);
}
.activity-icon.note {
  background: rgba(255,255,255,0.06);
  color: var(--fiori-text-muted);
}

.activity-content .activity-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--fiori-text);
}
.activity-content .activity-desc {
  font-size: 12px;
  color: var(--fiori-text2);
  margin-top: 2px;
}
.activity-content .activity-time {
  font-size: 11px;
  color: var(--fiori-text-muted);
  margin-top: 4px;
}

/* ============================================================
   QUICK ACTIONS
   ============================================================ */
.quick-actions {
  display: flex;
  gap: 12px;
  margin-bottom: 24px;
  flex-wrap: wrap;
}
.quick-action {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 16px;
  background: var(--fiori-surface);
  backdrop-filter: var(--glass-blur-sm);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  cursor: pointer;
  font-size: 13px;
  font-weight: 600;
  color: var(--fiori-text);
  transition: all var(--transition-fast);
}
.quick-action:hover {
  border-color: var(--fiori-brand);
  color: var(--fiori-brand);
  box-shadow: 0 4px 16px var(--fiori-brand-glow);
  transform: translateY(-1px);
}
.quick-action .icon { font-size: 16px; display: inline-flex; align-items: center; }

/* ============================================================
   HEALTH SCORE
   ============================================================ */
.health-score {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--fiori-text);
}
.health-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
}
.health-dot.excellent { background: var(--fiori-positive); box-shadow: 0 0 8px rgba(52,211,153,0.4); }
.health-dot.good { background: #22D3EE; box-shadow: 0 0 8px rgba(34,211,238,0.4); }
.health-dot.average { background: var(--fiori-warning); box-shadow: 0 0 8px rgba(251,191,36,0.4); }
.health-dot.poor { background: var(--fiori-negative); box-shadow: 0 0 8px rgba(248,113,113,0.4); }

/* ============================================================
   DETAIL VIEW / CUSTOMER 360
   ============================================================ */
.detail-header {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 20px;
  margin-bottom: 24px;
  background: var(--fiori-surface);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-radius: var(--fiori-radius-lg);
  border: 1px solid var(--fiori-border);
  box-shadow: 0 4px 24px rgba(0,0,0,0.2);
}
.detail-avatar {
  width: 56px;
  height: 56px;
  border-radius: 12px;
  background: linear-gradient(135deg, var(--fiori-brand) 0%, #5BA3FF 100%);
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 22px;
  font-weight: 700;
  flex-shrink: 0;
}
.detail-info h2 {
  font-size: 18px;
  font-weight: 700;
  color: var(--fiori-shell-text);
}
.detail-info .meta {
  font-size: 13px;
  color: var(--fiori-text-muted);
  display: flex;
  gap: 16px;
  margin-top: 4px;
}
.detail-actions {
  margin-left: auto;
  display: flex;
  gap: 8px;
}

/* ============================================================
   TOAST / NOTIFICATIONS
   ============================================================ */
.toast-container {
  position: fixed;
  top: 16px;
  right: 16px;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}

.toast {
  background: rgba(22,28,36,0.95);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--fiori-border-light);
  border-radius: var(--fiori-radius);
  padding: 12px 16px;
  min-width: 280px;
  max-width: 400px;
  box-shadow: 0 12px 40px rgba(0,0,0,0.4);
  display: flex;
  align-items: flex-start;
  gap: 10px;
  pointer-events: auto;
  animation: toast-in 0.3s ease forwards;
}
.toast.toast-out {
  animation: toast-out 0.3s ease forwards;
}

@keyframes toast-in {
  from { opacity: 0; transform: translateX(40px); }
  to { opacity: 1; transform: translateX(0); }
}
@keyframes toast-out {
  from { opacity: 1; transform: translateX(0); }
  to { opacity: 0; transform: translateX(40px); }
}

.toast-icon {
  font-size: 18px;
  flex-shrink: 0;
  margin-top: 1px;
}
.toast-success .toast-icon { color: var(--fiori-positive); }
.toast-error .toast-icon { color: var(--fiori-negative); }
.toast-warning .toast-icon { color: var(--fiori-warning); }
.toast-info .toast-icon { color: var(--fiori-info); }

.toast-success { border-left: 3px solid var(--fiori-positive); }
.toast-error { border-left: 3px solid var(--fiori-negative); }
.toast-warning { border-left: 3px solid var(--fiori-warning); }
.toast-info { border-left: 3px solid var(--fiori-info); }

/* Light-mode override — without this, .toast renders as dark navy
   bg with dark navy text (because --fiori-shell-text and
   --fiori-text-muted both flip to dark slate in light mode), which
   produces a "blank" toast where the operator can't read the
   message. Reported 2026-05-08: photo-upload errors were firing
   but the error toast looked empty so the operator couldn't see
   why the upload failed. */
[data-theme="light"] .toast {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  box-shadow: 0 12px 40px rgba(15,23,42,0.15);
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}
[data-theme="light"] .toast-title    { color: var(--fiori-text); }
[data-theme="light"] .toast-message  { color: var(--fiori-text-muted); }
[data-theme="light"] .toast-dismiss  { color: var(--fiori-text-muted); }
[data-theme="light"] .toast-dismiss:hover { color: var(--fiori-text); }

.toast-content { flex: 1; }
.toast-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--fiori-shell-text);
  margin-bottom: 2px;
}
.toast-message {
  font-size: 12px;
  color: var(--fiori-text-muted);
}
.toast-dismiss {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--fiori-text-muted);
  font-size: 16px;
  padding: 0;
  line-height: 1;
  opacity: 0.6;
  transition: opacity var(--transition-fast);
}
.toast-dismiss:hover { opacity: 1; }

/* ============================================================
   ALERTS / INLINE NOTIFICATIONS
   ============================================================ */
.alert {
  padding: 12px 16px;
  border-radius: var(--fiori-radius);
  margin-bottom: 16px;
  font-size: 13px;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  border: 1px solid;
}
.alert-info {
  background: rgba(96,165,250,0.1);
  border-color: rgba(96,165,250,0.2);
  color: var(--fiori-info);
}
.alert-success {
  background: rgba(52,211,153,0.1);
  border-color: rgba(52,211,153,0.2);
  color: var(--fiori-positive);
}
.alert-warning {
  background: rgba(251,191,36,0.1);
  border-color: rgba(251,191,36,0.2);
  color: var(--fiori-warning);
}
.alert-danger {
  background: rgba(248,113,113,0.1);
  border-color: rgba(248,113,113,0.2);
  color: var(--fiori-negative);
}
.alert .alert-icon {
  font-size: 16px;
  flex-shrink: 0;
  margin-top: 1px;
}
.alert .alert-content { flex: 1; }
.alert .alert-title {
  font-weight: 700;
  margin-bottom: 2px;
}
.alert .alert-dismiss {
  background: none;
  border: none;
  cursor: pointer;
  font-size: 16px;
  color: inherit;
  opacity: 0.6;
  padding: 0;
}
.alert .alert-dismiss:hover { opacity: 1; }

/* ============================================================
   SKELETON LOADING
   ============================================================ */
.skeleton {
  background: linear-gradient(90deg,
    rgba(255,255,255,0.04) 25%,
    rgba(255,255,255,0.08) 50%,
    rgba(255,255,255,0.04) 75%);
  background-size: 200% 100%;
  animation: shimmer 1.5s infinite;
  border-radius: var(--fiori-radius);
}
@keyframes shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
.skeleton-text { height: 14px; margin-bottom: 8px; width: 80%; }
.skeleton-title { height: 20px; margin-bottom: 12px; width: 60%; }
.skeleton-card { height: 120px; border-radius: var(--fiori-radius-lg); }
.skeleton-avatar { width: 40px; height: 40px; border-radius: 50%; }
.skeleton-row {
  display: flex;
  gap: 16px;
  margin-bottom: 12px;
}

/* ============================================================
   ADMIN LAYOUT
   ============================================================ */
.admin-layout {
  display: flex;
  height: 100vh;
}
.admin-sidebar {
  width: 220px;
  background: rgba(16,22,30,0.92);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  color: var(--fiori-text2);
  padding: 20px 0;
  flex-shrink: 0;
  border-right: 1px solid var(--fiori-border);
}
.admin-content {
  flex: 1;
  overflow-y: auto;
  padding: 24px;
  background: var(--fiori-bg);
}

.permission-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 8px;
}
.permission-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: rgba(255,255,255,0.03);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  font-size: 13px;
  color: var(--fiori-text);
}

/* ============================================================
   PROGRESS BAR
   ============================================================ */
.progress-bar {
  height: 8px;
  background: rgba(255,255,255,0.06);
  border-radius: 4px;
  overflow: hidden;
}
.progress-bar .fill {
  height: 100%;
  border-radius: 4px;
  transition: width 0.3s ease;
}
.progress-bar .fill.blue {
  background: linear-gradient(90deg, var(--fiori-brand), #5BA3FF);
}
.progress-bar .fill.green {
  background: linear-gradient(90deg, var(--fiori-positive), #4AE8AD);
}
.progress-bar .fill.orange {
  background: linear-gradient(90deg, var(--fiori-warning), #FCD34D);
}
.progress-bar .fill.red {
  background: linear-gradient(90deg, var(--fiori-negative), #FF9090);
}

/* ============================================================
   CHIP / TAG
   ============================================================ */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 500;
  background: rgba(255,255,255,0.06);
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text);
}
.chip .chip-remove {
  background: none;
  border: none;
  cursor: pointer;
  padding: 0 0 0 4px;
  font-size: 14px;
  color: var(--fiori-text-muted);
  line-height: 1;
}
.chip .chip-remove:hover {
  color: var(--fiori-negative);
}

/* ============================================================
   TOGGLE SWITCH
   ============================================================ */
.toggle {
  position: relative;
  display: inline-block;
  width: 40px;
  height: 22px;
}
.toggle input { opacity: 0; width: 0; height: 0; }
.toggle-slider {
  position: absolute;
  cursor: pointer;
  top: 0; left: 0; right: 0; bottom: 0;
  background: rgba(255,255,255,0.1);
  border-radius: 22px;
  transition: var(--transition-fast);
}
.toggle-slider::before {
  content: '';
  position: absolute;
  height: 16px;
  width: 16px;
  left: 3px;
  bottom: 3px;
  background: var(--fiori-text2);
  border-radius: 50%;
  transition: var(--transition-fast);
}
.toggle input:checked + .toggle-slider {
  background: var(--fiori-brand);
  box-shadow: 0 0 12px var(--fiori-brand-glow);
}
.toggle input:checked + .toggle-slider::before {
  transform: translateX(18px);
  background: #fff;
}

/* ============================================================
   TOOLTIPS
   ============================================================ */
.tooltip { position: relative; display: inline-block; }
.tooltip .tooltip-text {
  visibility: hidden;
  background: rgba(22,28,36,0.95);
  backdrop-filter: var(--glass-blur-sm);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  color: var(--fiori-text);
  text-align: center;
  padding: 6px 12px;
  border-radius: var(--fiori-radius);
  border: 1px solid var(--fiori-border-light);
  font-size: 12px;
  position: absolute;
  z-index: 50;
  bottom: 125%;
  left: 50%;
  transform: translateX(-50%);
  white-space: nowrap;
  opacity: 0;
  transition: opacity var(--transition-fast);
  box-shadow: 0 4px 16px rgba(0,0,0,0.3);
}
.tooltip .tooltip-text::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  margin-left: -5px;
  border-width: 5px;
  border-style: solid;
  border-color: rgba(22,28,36,0.95) transparent transparent transparent;
}
.tooltip:hover .tooltip-text {
  visibility: visible;
  opacity: 1;
}

/* ============================================================
   EMPTY STATE
   ============================================================ */
.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 48px 24px;
  text-align: center;
}
.empty-state .empty-icon {
  font-size: 48px;
  margin-bottom: 16px;
  opacity: 0.4;
}
.empty-state h3 {
  font-size: 16px;
  font-weight: 700;
  color: var(--fiori-text);
  margin-bottom: 6px;
}
.empty-state p {
  font-size: 13px;
  color: var(--fiori-text-muted);
  max-width: 400px;
  margin-bottom: 20px;
}

/* ============================================================
   STAT GRID (Dashboard, Admin)
   ============================================================ */
.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
  margin-bottom: 24px;
}
.stat-box {
  background: var(--fiori-surface);
  backdrop-filter: var(--glass-blur-sm);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius-lg);
  padding: 16px;
  text-align: center;
}
.stat-box .stat-value {
  font-size: 24px;
  font-weight: 700;
  color: var(--fiori-shell-text);
}
.stat-box .stat-label {
  font-size: 11px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--fiori-text-muted);
  margin-top: 4px;
}

/* ============================================================
   SPINNER / LOADING
   ============================================================ */
.spinner {
  display: inline-block;
  width: 20px;
  height: 20px;
  border: 2px solid var(--fiori-border);
  border-top-color: var(--fiori-brand);
  border-radius: 50%;
  animation: spin 0.6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.loading-overlay {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 40px;
  color: var(--fiori-text-muted);
  font-size: 14px;
}

/* Full page loader */
.page-loader {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: var(--fiori-bg);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 3000;
}
.page-loader .spinner {
  width: 36px;
  height: 36px;
  border-width: 3px;
}

/* ============================================================
   DROPDOWN MENU (Context menus)
   ============================================================ */
.dropdown-menu {
  background: rgba(22,28,36,0.95);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--fiori-border-light);
  border-radius: var(--fiori-radius);
  box-shadow: 0 12px 40px rgba(0,0,0,0.4);
  min-width: 160px;
  z-index: 200;
  overflow: hidden;
  padding: 4px 0;
}
.dropdown-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  font-size: 13px;
  color: var(--fiori-text);
  cursor: pointer;
  transition: background var(--transition-fast);
}
.dropdown-item:hover {
  background: rgba(255,255,255,0.06);
}
.dropdown-item.danger {
  color: var(--fiori-negative);
}
.dropdown-divider {
  height: 1px;
  background: var(--fiori-border);
  margin: 4px 0;
}

/* ============================================================
   AVATAR GROUP
   ============================================================ */
.avatar-group {
  display: flex;
}
.avatar-group .user-avatar {
  margin-left: -8px;
  border: 2px solid var(--fiori-bg);
}
.avatar-group .user-avatar:first-child {
  margin-left: 0;
}
.avatar-group .avatar-overflow {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--fiori-surface2);
  color: var(--fiori-text-muted);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
  font-weight: 600;
  margin-left: -8px;
  border: 2px solid var(--fiori-bg);
}

/* ============================================================
   RESPONSIVE BREAKPOINTS
   ============================================================ */

/* ---- Mobile sidebar overlay & hamburger ---- */
.sidebar-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  z-index: 99;
}
.sidebar-overlay.visible { display: block; }

.mobile-hamburger {
  display: none;
  background: none;
  border: none;
  color: var(--fiori-text2);
  font-size: 22px;
  cursor: pointer;
  padding: 6px;
  border-radius: var(--fiori-radius);
  flex-shrink: 0;
  transition: all var(--transition-fast);
}
.mobile-hamburger:hover {
  background: rgba(255,255,255,0.06);
  color: var(--fiori-shell-text);
}

/* ---- Data table horizontal scroll ---- */
.data-table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

/* ---- Form row: 3 columns on wider screens ---- */
.form-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 16px;
}

/* ---- Form input shared ---- */
.form-input {
  padding: 8px 12px;
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  color: var(--fiori-text);
  font-size: 13px;
  outline: none;
  transition: all var(--transition-fast);
}
.form-input::placeholder { color: var(--fiori-text-muted); }
.form-input:focus {
  border-color: var(--fiori-brand);
  box-shadow: 0 0 0 3px var(--fiori-brand-glow);
}
select.form-input option {
  background: #161C24;
  color: var(--fiori-text);
}

/* ---- Modern <select> normalisation (2026-05-10) ----
 * Native macOS / iOS selects rendered with the OS-default "aqua"
 * chrome (light gray button + native arrow) which clashed with the
 * dark theme. Suppress the native chrome and paint our own caret.
 * Applies to BOTH .form-input selects AND any bare <select> in the
 * app so legacy pages get the fix without per-page changes.
 *
 * Caveat: this is a visual normalisation only — pages that need
 * type-ahead search should still migrate to <SearchableSelect>.
 * Long lists (300+ employees, 200+ customers) are unusable as
 * native <select> regardless of styling. */
select,
select.form-input {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2394A3B8' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><polyline points='6 9 12 15 18 9'/></svg>");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 30px !important;
  cursor: pointer;
}
select:disabled, select.form-input:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}
/* Bare <select> with no class — give them the same dark-theme baseline
 * that .form-input has, so consistency holds even on legacy pages.
 * Opt out via data-unstyled if you genuinely want a vanilla select. */
select:not(.form-input):not([data-unstyled]) {
  background-color: rgba(255,255,255,0.04);
  border: 1px solid var(--fiori-border);
  border-radius: var(--fiori-radius);
  color: var(--fiori-text);
  padding: 8px 12px 8px 12px;
  font-size: 13px;
  font-family: inherit;
}
[data-theme="light"] select.form-input,
[data-theme="light"] select:not(.form-input):not([data-unstyled]) {
  background-color: #fff;
}

/* ======= XL (max 1400px) ======= */
@media (max-width: 1400px) {
  .form-row {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ======= Desktop-tablet transition (max 1200px) ======= */
@media (max-width: 1200px) {
  .summary-cards {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ======= Tablet (max 1024px) ======= */
@media (max-width: 1024px) {
  /* Sidebar becomes overlay on tablet */
  .sidebar {
    position: fixed;
    left: 0;
    top: 0;
    bottom: 0;
    z-index: 100;
    transform: translateX(0);
    transition: transform var(--transition-normal), width var(--transition-normal);
    box-shadow: 4px 0 24px rgba(0,0,0,0.4);
  }
  .sidebar.collapsed {
    transform: translateX(-100%);
    width: var(--sidebar-width);
    pointer-events: none;
  }

  /* Show hamburger */
  .mobile-hamburger { display: flex; }

  .form-row {
    grid-template-columns: repeat(2, 1fr);
  }
  .pipeline-board {
    flex-direction: column;
  }
  .pipeline-column {
    min-width: 100%;
    max-width: 100%;
  }

  /* Data tables: wrap action cells, allow horizontal scroll */
  .data-table th,
  .data-table td {
    padding: 10px 12px;
    font-size: 12px;
  }

  /* Admin layout sidebar */
  .admin-sidebar {
    position: fixed;
    left: 0;
    top: 0;
    bottom: 0;
    z-index: 100;
    box-shadow: 4px 0 24px rgba(0,0,0,0.4);
  }

  .permission-grid {
    grid-template-columns: repeat(2, 1fr);
  }
}

/* ======= Small tablet / large phone (max 768px) ======= */
@media (max-width: 768px) {
  .summary-cards {
    grid-template-columns: 1fr;
  }
  .content-area { padding: 14px; }
  .topbar { padding: 0 10px; gap: 8px; }
  .topbar-search { max-width: 200px; }

  .page-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 12px;
  }
  .page-header h1 { font-size: 18px; }

  .table-toolbar {
    flex-direction: column;
    padding: 12px;
  }
  .table-search { max-width: 100%; min-width: 0; }
  .table-filters { width: 100%; }
  .table-filters select { flex: 1; min-width: 0; }

  .stat-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .detail-header {
    flex-direction: column;
    text-align: center;
  }
  .detail-actions {
    margin-left: 0;
  }

  .modal { margin: 10px; }
  .modal-body { padding: 16px; }
  .modal-xl { max-width: 100%; }
  .modal-lg { max-width: 100%; }
  .modal-full { max-width: 100%; }

  .form-row {
    grid-template-columns: 1fr;
  }

  /* Admin layout responsive */
  .admin-layout { flex-direction: column; }
  .admin-sidebar {
    width: 100%;
    position: relative;
    border-right: none;
    border-bottom: 1px solid var(--fiori-border);
    padding: 10px 0;
  }

  .permission-grid {
    grid-template-columns: 1fr;
  }

  /* Tabs scroll on mobile */
  .tabs { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .tab { padding: 8px 14px; font-size: 12px; flex-shrink: 0; }

  /* Card body tighter */
  .card-body { padding: 14px; }
  .card-header { padding: 12px 14px; }

  /* Pipeline cards full width */
  .pipeline-card { margin-bottom: 6px; }

  /* Toast mobile */
  .toast-container { left: 8px; right: 8px; top: 8px; }
  .toast { min-width: auto; max-width: 100%; }

  /* Quick actions */
  .quick-actions { gap: 8px; }
  .quick-action { padding: 8px 12px; font-size: 12px; }

  /* Table pagination mobile */
  .table-pagination {
    flex-direction: column;
    gap: 10px;
    align-items: center;
    padding: 10px 14px;
  }
}

/* ======= Phone (max 480px) ======= */
@media (max-width: 480px) {
  .login-card { padding: 24px 20px; }
  .login-logo h1 { font-size: 24px; }

  .user-info { display: none; }
  .topbar-search { display: none; }

  .content-area { padding: 10px; }

  .quick-actions { flex-direction: column; }
  .stat-grid {
    grid-template-columns: 1fr;
  }
  .summary-card { padding: 14px; gap: 12px; }
  .summary-card .card-content .value { font-size: 22px; }
  .summary-card .card-icon { width: 40px; height: 40px; font-size: 18px; }

  /* Buttons stack */
  .form-actions {
    flex-direction: column;
  }
  .form-actions .btn { width: 100%; justify-content: center; }

  .btn-sm { padding: 6px 10px; font-size: 11px; }

  /* Data table compact */
  .data-table th,
  .data-table td {
    padding: 8px 8px;
    font-size: 11.5px;
  }
  .data-table th {
    font-size: 10px;
  }

  /* Breadcrumbs */
  .breadcrumbs { font-size: 12px; overflow-x: auto; flex-wrap: nowrap; white-space: nowrap; }

  /* Dropdown full-width on mobile */
  .user-dropdown { min-width: 240px; right: -40px; }

  /* Modal almost full screen on phone */
  .modal-overlay { padding: 8px; }
  .modal-header { padding: 12px 16px; }
  .modal-header h2 { font-size: 14px; }
  .modal-body { padding: 12px; max-height: calc(90vh - 100px); }
  .modal-footer { padding: 12px 16px; }
}

/* ======= Very small phone (max 360px) ======= */
@media (max-width: 360px) {
  .login-card { padding: 20px 16px; }
  .content-area { padding: 8px; }
  .topbar { height: 44px; }
  .btn { padding: 6px 12px; font-size: 12px; }
}

/* ============================================================
   PRINT STYLES
   ============================================================ */
/* ================================================================
   MOBILE NAVIGATION DRAWER — owner spec 2026-08-28.

   The ≤1024px rules earlier in this file already turn .sidebar into a
   fixed overlay, but stopped short of making it usable on a phone:

     • the drawer started at top:0, so its own header sat UNDER the
       topbar (z-index 9000) and 48px of the panel was unreachable;
     • there was no scrim, so tapping the page behind did nothing —
       the only way out was the hamburger or navigating away;
     • .sidebar-item is ~30px tall, well under the 44px minimum both
       Apple and Google publish for touch targets;
     • nothing accounted for notch / home-indicator safe areas, so the
       last menu item could sit under the gesture bar.

   This block comes last so it wins on cascade order.
   ================================================================ */
@media (max-width: 1024px) {
  /* Sit BELOW the topbar rather than beneath it. Reclaims the 48px
     that used to be hidden, and keeps the hamburger tappable. */
  .sidebar {
    top: var(--topbar-height);
    width: min(84vw, 300px);
    /* Above page content, below the topbar (9000) so the hamburger
       stays clickable while the drawer is open. */
    z-index: 8000;
    border-right: none;
    box-shadow: 6px 0 28px rgba(0,0,0,0.45);
  }
  .sidebar.collapsed {
    width: min(84vw, 300px);
    transform: translateX(-100%);
  }
  /* The drawer's own logo row duplicates the topbar on mobile. */
  .sidebar-header { display: none; }

  /* Let the list use the full drawer height and keep rubber-band
     scrolling inside the drawer instead of dragging the page behind. */
  .sidebar-nav {
    padding: 8px 0 calc(28px + env(safe-area-inset-bottom, 0px));
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  /* Touch targets. 44px min-height + roomier type; the desktop
     12.5px/6px padding is a mouse-sized target. */
  .sidebar-item {
    min-height: 44px;
    padding: 10px 12px 10px 16px;
    font-size: 14px;
    white-space: normal;      /* long labels wrap instead of clipping */
    line-height: 1.3;
  }
  .sidebar-section-title {
    min-height: 40px;
    font-size: 11.5px;
    padding: 10px 12px;
  }
  .sidebar-item .icon,
  .sidebar-section-title .icon { flex-shrink: 0; }

  /* Scrim — tap anywhere off the drawer to dismiss. Rendered by
     Layout.jsx only while the drawer is open on mobile. */
  .sidebar-backdrop {
    position: fixed;
    inset: var(--topbar-height) 0 0 0;
    background: rgba(2, 6, 23, 0.55);
    z-index: 7999;
    border: none;
    padding: 0;
    width: 100%;
    -webkit-tap-highlight-color: transparent;
    animation: sidebarScrimIn 160ms ease;
  }
}

/* ================================================================
   PAGE CONTENT ON PHONES — owner spec 2026-08-28.
   Only the parts CSS can actually reach; the Self Service pages are
   inline-styled, so their grid templates are collapsed in JSX via the
   useIsMobile hook instead.
   ================================================================ */
@media (max-width: 768px) {
  /* iOS Safari zooms the whole viewport when a focused input has a
     font-size below 16px, and does NOT zoom back out afterwards — the
     user is left pinching. Forcing 16px is the standard cure, and
     !important is needed because these are inline styles. */
  input, select, textarea { font-size: 16px !important; }

  /* .content-area carries `overflow-x: hidden` in its base rule (see
     ~line 1097). On a desktop that's harmless — content fits. On a
     360px phone it is the single biggest reason the app feels
     "not responsive": anything wider than the screen is CLIPPED and
     simply unreachable, rather than merely needing a sideways swipe.
     You cannot scroll to it, so it may as well not exist.

     On phones, allow the scroll. Most content now fits anyway because
     the flex rows wrap, but where something genuinely can't shrink —
     a wide report table — reachable-but-scrolly beats invisible. */
  .content-area {
    overflow-x: auto !important;
    -webkit-overflow-scrolling: touch;
    padding: 12px;
  }

  /* Tap targets. 40px is the practical floor for a dense toolbar;
     primary actions get the full 44px. */
  .btn { min-height: 40px; }
  .btn-primary, .btn-danger { min-height: 44px; }

  /* Wide tables scroll inside their own box rather than stretching
     the page. */
  .data-table-scroll,
  .table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

/* Horizontal strip that scrolls instead of wrapping — for tab rows.
   A ten-tab strip wrapped onto three lines pushes the content down and
   makes the selected tab hard to find; sliding sideways is the pattern
   every mobile app uses. Applies at ALL widths so a narrow desktop
   window behaves the same way. */
.scroll-strip {
  overflow-x: auto;
  overflow-y: hidden;
  flex-wrap: nowrap !important;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;            /* Firefox */
  scroll-snap-type: x proximity;
}
.scroll-strip::-webkit-scrollbar { display: none; }   /* Chrome / Safari */
.scroll-strip > * { scroll-snap-align: start; }

/* Desktop never shows the scrim even if the element is in the DOM. */
.sidebar-backdrop { display: none; }
@media (max-width: 1024px) { .sidebar-backdrop { display: block; } }

@keyframes sidebarScrimIn { from { opacity: 0; } to { opacity: 1; } }

@media (max-width: 480px) {
  /* Wider on phones — 84vw of a 360px screen is only ~300px, and the
     remaining sliver of page behind is useless anyway. */
  .sidebar,
  .sidebar.collapsed { width: min(88vw, 320px); }
  .sidebar-item { font-size: 14.5px; min-height: 46px; }
}

@media print {
  .sidebar, .topbar, .sidebar-toggle, .btn,
  .table-pagination, .toast-container { display: none !important; }
  .main-content { margin: 0; }
  .content-area { padding: 0; }
  .card {
    box-shadow: none;
    border: 1px solid #ddd;
    background: #fff;
    backdrop-filter: none;
  }
  body {
    background: #fff;
    color: #000;
  }
  .data-table th,
  .data-table td {
    color: #000;
    border-color: #ddd;
  }

  /* ── Scope item flows, printed (owner 2026-09-26) ──────────────
   *
   * *"allow user to print this to PDF ... mass create PDF for all the
   * flow ... Each scope item should start from a new page."*
   *
   * Printed from the SAME component the screen draws, so the PDF is
   * the picture he approved rather than a second renderer that drifts
   * from it. That is only possible because the flow is HTML and CSS
   * and not a canvas.
   *
   * When a print sheet is mounted, EVERYTHING else is hidden -- the
   * modal, the page behind it, the shell. Printing a modal otherwise
   * puts the greyed-out page underneath on the paper. */
  /* `body.is-printing > *`, and the host is PORTALLED to body so it is
   * a direct child. The first version selected body's children while
   * the host sat deep inside the React tree: the rule hid the app root
   * and the host went with it as a descendant. The preview came out
   * blank -- one sheet with a header and a footer and nothing between
   * them. A body class rather than :has(), so the rule does not depend
   * on selector support in whatever engine renders the PDF. */
  body.is-printing > *:not(.flow-print-host) { display: none !important; }
  .flow-print-host, .flow-print-sheet { display: block !important; }

  /* And put it back ON the page. Off-screen is how it stays invisible
     while the app is on screen; left at -10000px it prints an empty
     sheet just as surely as display:none would. Both bugs were in the
     first version, and either alone was enough. */
  .flow-print-host {
    position: static !important;
    left: auto !important;
    top: auto !important;
    width: auto !important;
  }

  /* The paper follows the screen. A swimlane is wider than it is
     tall; on portrait paper it shrinks to a strip down the middle,
     which is the one thing landscape mode exists to avoid.
     
     A NAMED page, because @page cannot be scoped by a class: the
     sheet marks its items with `page: fxland` and the at-rule below
     gives that page its size. Portrait stays the unnamed default, so
     nothing changes for the column view. */

  @page { size: A4 portrait; }
  @page fxland { size: A4 landscape; }
  .flow-print-sheet.is-land .flow-print-item { page: fxland; }

  .flow-print-item {
    /* break-after, not the deprecated page-break-after: the modern
       property is what current engines honour inside flex and grid
       ancestors, which this sits in. Both are set -- the old one is
       still what some PDF back-ends read. */
    break-after: page;
    page-break-after: always;
    /* A node split across two sheets is a node you cannot read. */
    break-inside: avoid-page;
  }
  /* EXCEPT IN LANDSCAPE, where the item is the whole wrapped flow and
     a 25-step one is three sheets tall. `avoid-page` on a box that
     CANNOT fit a page is a constraint the engine has to break, and
     Chrome breaks it by fragmenting everything underneath instead --
     the cards came out of the PDF stretched down the whole sheet with
     their role and buttons dropped, while the same cards measured
     118px in the DOM. The bands already carry `break-inside: avoid`,
     which is the honest version of this rule: keep a BAND whole, and
     let the sheet break between bands, which is what the wrap is for. */
  .flow-print-sheet.is-land .flow-print-item{ break-inside: auto; }

  /* ONE BAND, ONE SHEET -- stated, not left to the engine.
     Asking Chrome to decide where to break a page inside a CSS grid
     is asking for the fragmentation bugs this went through twice:
     first cards stretched down the whole sheet with their role and
     buttons dropped, then eleven pages for a five-band flow. Telling
     it where the breaks go removes the decision entirely.
     It is also the right model. A band was wrapped to fit one A4
     landscape sheet, the tab strip on screen calls it "Sheet 3", and
     this is what makes that true of the paper as well -- sheet 3 on
     screen is page 3 out of the printer. */
  /* ON THE WRAPPER, NOT THE GRID. `break-inside: avoid` on a grid
     container is a request Chrome refused twice -- once by stretching
     every card down the sheet, once by splitting the last band so the
     final card's footer printed alone on a sixth page. On the plain
     block around it the rule is honoured.
     break-BEFORE the sheets after the first, not break-after all of
     them: there is then no trailing break to excuse, and break-after
     fell off the end and printed a blank sheet. */
  .flow-print-sheet.is-land .fx-sheet{
    break-inside: avoid; page-break-inside: avoid;
  }
  .flow-print-sheet.is-land .fx-sheet + .fx-sheet{
    break-before: page; page-break-before: always;
  }
  .flow-print-item:last-child { break-after: auto; page-break-after: auto; }

  /* The column width for print is NOT set here -- see the rule
     outside this media query, keyed on body.is-printing-landscape.
     It has to apply while JS can still see it: the arrows are drawn
     from MEASURED box positions, and a layout that only exists inside
     @media print is a layout nothing can measure. That is what put
     the stray lines across the printed sheet -- paths pointing at
     where the boxes sat on screen. */
  /* A hover card cannot be hovered on paper, and :focus-within can
     leave one open at print time. */
  .fx-hover{ display: none !important; }
  .flow-print-item .fx-card { break-inside: avoid; page-break-inside: avoid; }

  /* The tones carry the meaning of the page. Browsers drop background
     colour when printing unless told not to, which would turn every
     node the same white and lose exactly what the colours were for. */
  .flow-print-sheet, .flow-print-sheet * {
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}






/* The tab strip is a screen affordance. On paper every sheet is
   printed, so a row of tabs naming them is a row of dead controls. */
body.is-printing .flow-print-host .fx-tabs{ display: none; }

/* Off-screen until printed. Not `display: none`, which would stop the
   browser laying it out at all and print an empty sheet.

   1080px, not 900: the host is the box the print copy is laid out in,
   and a band that fills a 1063px sheet does not fit a 900px box -- it
   overflowed, the printed page took its width from the overflow, and
   the browser scaled the whole thing down to fit. */
.flow-print-host {
  position: absolute; left: -10000px; top: 0; width: 1080px;
}

/* ============================================================
   UTILITY CLASSES
   ============================================================ */
.text-center { text-align: center; }
.text-right { text-align: right; }
.text-muted { color: var(--fiori-text-muted); }
.text-success { color: var(--fiori-positive); }
.text-danger { color: var(--fiori-negative); }
.text-warning { color: var(--fiori-warning); }
.text-info { color: var(--fiori-info); }
.text-brand { color: var(--fiori-brand); }
.text-white { color: var(--fiori-shell-text); }

.mt-0 { margin-top: 0; }
.mt-8 { margin-top: 8px; }
.mt-16 { margin-top: 16px; }
.mt-24 { margin-top: 24px; }
.mb-0 { margin-bottom: 0; }
.mb-8 { margin-bottom: 8px; }
.mb-16 { margin-bottom: 16px; }
.mb-24 { margin-bottom: 24px; }

.flex { display: flex; }
.flex-1 { flex: 1; }
.items-center { align-items: center; }
.justify-between { justify-content: space-between; }
.gap-4 { gap: 4px; }
.gap-8 { gap: 8px; }
.gap-12 { gap: 12px; }
.gap-16 { gap: 16px; }
.gap-24 { gap: 24px; }

.hidden { display: none; }
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.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;
}

/* Glass utility */
.glass {
  background: var(--fiori-surface);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--fiori-border);
}
.glass-sm {
  background: var(--fiori-surface2);
  backdrop-filter: var(--glass-blur-sm);
  -webkit-backdrop-filter: var(--glass-blur-sm);
  border: 1px solid var(--fiori-border);
}

/* Glow effect utility */
.glow-brand {
  box-shadow: 0 0 20px var(--fiori-brand-glow);
}
.glow-positive {
  box-shadow: 0 0 20px rgba(52,211,153,0.25);
}
.glow-negative {
  box-shadow: 0 0 20px rgba(248,113,113,0.25);
}

/* ============================================================
   METADATA PAGE
   ============================================================ */
.metadata-section { padding: 16px; overflow: visible; }
.metadata-section .data-table-scroll { overflow-x: auto; overflow-y: visible; }
.metadata-table tbody tr:last-child td { padding-bottom: 48px; }
.metadata-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
  flex-wrap: wrap;
  gap: 8px;
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--fiori-surface, #1E293B);
  padding: 12px 0 8px;
}
.metadata-toolbar h3 {
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  color: var(--fiori-text);
}
/* `.inline-input` used to be styled ONLY as `.metadata-table
   .inline-input`, so an input carrying the class outside a metadata
   table matched nothing and fell back to the browser's own control:
   white field, grey border, system font -- in a dark app.

   Reported 2026-09-18 for the Typical Role search box, which sits in
   the card header above the table rather than inside it. The three
   inputs on the Customers page had the same fault and nobody had
   mentioned it yet.

   So the base class is real now, and the table selector keeps only
   what is genuinely table-specific. Same shape of fix as `.tab`
   above: define the control, let the context refine it. */
.inline-input {
  background: rgba(255,255,255,0.04);
  border: 1px solid var(--fiori-border);
  border-radius: 6px;
  padding: 6px 8px;
  font: inherit;
  font-size: 13px;
  color: var(--fiori-text);
  transition: border-color 0.15s, background 0.15s;
}
.inline-input::placeholder {
  color: var(--fiori-text-muted);
  opacity: 1;                 /* Firefox dims placeholders by default */
}
.inline-input:focus {
  border-color: var(--fiori-brand);
  outline: none;
  background: rgba(255,255,255,0.07);
}
select.inline-input {
  cursor: pointer;
  appearance: auto;
}
/* Inside a table the border is carried by the cell, and a field
   should fill its column. */
.metadata-table .inline-input {
  width: 100%;
  border-color: transparent;
}
.metadata-table .inline-input:focus {
  border-color: var(--fiori-brand);
}
.metadata-table tr.row-dirty {
  background: rgba(59,141,248,0.06);
}
.btn-icon-sm {
  background: none;
  border: none;
  cursor: pointer;
  padding: 4px;
  border-radius: 4px;
  color: var(--fiori-text-secondary);
  transition: all 0.15s;
}
.btn-icon-sm:hover {
  background: rgba(255,255,255,0.08);
}
.btn-icon-sm.danger:hover {
  color: var(--fiori-negative);
  background: rgba(248,113,113,0.12);
}
.btn-icon-sm.success {
  color: #34D399;
}
.btn-icon-sm.success:hover {
  background: rgba(52,211,153,0.15);
}
.tab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 18px;
  margin-left: 6px;
  padding: 0 5px;
  border-radius: 9px;
  background: rgba(59,141,248,0.15);
  color: var(--fiori-brand);
  font-size: 11px;
  font-weight: 600;
}
.flash-msg {
  padding: 10px 16px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  margin-bottom: 12px;
  animation: flashIn 0.3s ease;
}
.flash-msg.success {
  background: rgba(52,211,153,0.12);
  color: #34D399;
  border: 1px solid rgba(52,211,153,0.2);
}
.flash-msg.error {
  background: rgba(248,113,113,0.12);
  color: #F87171;
  border: 1px solid rgba(248,113,113,0.2);
}
@keyframes flashIn {
  from { opacity: 0; transform: translateY(-8px); }
  to { opacity: 1; transform: translateY(0); }
}

/* ── Multi-select dropdown ── */
.multi-select { position: relative; width: 100%; }
.multi-select-trigger {
  display: flex;
  align-items: center;
  gap: 4px;
  min-height: 32px;
  background: rgba(255,255,255,0.04);
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 3px 8px;
  cursor: pointer;
  transition: border-color 0.15s;
}
.multi-select-trigger:hover,
.multi-select-trigger:focus-within {
  border-color: var(--fiori-brand);
}
.multi-select-placeholder { color: #64748B; font-size: 13px; }
.multi-select-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  flex: 1;
}
.multi-select-tag {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  padding: 2px 6px;
  border-radius: 4px;
  background: rgba(59,141,248,0.15);
  color: var(--fiori-brand);
  font-size: 11.5px;
  font-weight: 500;
  white-space: nowrap;
}
.multi-select-tag-x {
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  opacity: 0.6;
  transition: opacity 0.15s;
}
.multi-select-tag-x:hover { opacity: 1; }
.multi-select-arrow {
  margin-left: auto;
  color: #64748B;
  flex-shrink: 0;
}
.multi-select-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  z-index: 50;
  background: var(--fiori-surface, #1E293B);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
  max-height: 200px;
  overflow-y: auto;
  padding: 4px;
}
.multi-select-dropdown.flip-up {
  top: auto;
  bottom: calc(100% + 4px);
}
.multi-select-option {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 4px;
  font-size: 13px;
  color: var(--fiori-text);
  cursor: pointer;
  transition: background 0.1s;
}
.multi-select-option:hover { background: rgba(255,255,255,0.06); }
.multi-select-option.checked { background: rgba(59,141,248,0.08); }
.multi-select-option input[type="checkbox"] {
  accent-color: var(--fiori-brand);
  width: 14px;
  height: 14px;
}
.multi-select-empty { padding: 12px; text-align: center; color: #64748B; font-size: 13px; }

/* ============================================================
   MODERN METADATA TABS & BUTTONS
   ============================================================ */

/* ── Tab bar ── */
.meta-tabs {
  display: flex;
  gap: 2px;
  background: rgba(255,255,255,0.03);
  border: 1px solid rgba(255,255,255,0.06);
  border-bottom: none;
  border-radius: 12px 12px 0 0;
  padding: 6px 6px 0;
}
.meta-tab {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 20px;
  font-size: 13px;
  font-weight: 600;
  color: #94A3B8;
  background: transparent;
  border: none;
  border-radius: 8px 8px 0 0;
  cursor: pointer;
  transition: all 0.2s ease;
  white-space: nowrap;
}
.meta-tab:hover {
  color: #CBD5E1;
  background: rgba(255,255,255,0.04);
}
.meta-tab.active {
  color: #fff;
  background: var(--fiori-surface, #1E293B);
  box-shadow: inset 0 2px 0 var(--fiori-brand, #3B8DF8);
}
.meta-tab.active::after {
  content: '';
  position: absolute;
  bottom: -1px;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--fiori-surface, #1E293B);
}
.meta-tab-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 18px;
  padding: 0 6px;
  border-radius: 9px;
  font-size: 11px;
  font-weight: 700;
  background: rgba(59,141,248,0.15);
  color: var(--fiori-brand, #3B8DF8);
  transition: all 0.2s;
}
.meta-tab.active .meta-tab-badge {
  background: rgba(59,141,248,0.25);
  color: #60A5FA;
}

/* ── Modern buttons ── */
.meta-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 16px;
  font-size: 12.5px;
  font-weight: 600;
  border-radius: 8px;
  border: none;
  cursor: pointer;
  transition: all 0.2s ease;
  white-space: nowrap;
  outline: none;
  letter-spacing: 0.01em;
}
.meta-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  pointer-events: none;
}
.meta-btn-primary {
  --btn-neon: var(--page-neon, #94A3B8);
  background: rgba(255,255,255,0.04);
  color: var(--btn-neon);
  border: 1px solid color-mix(in srgb, var(--btn-neon) 35%, transparent);
  box-shadow: 0 0 8px color-mix(in srgb, var(--btn-neon) 10%, transparent);
}
.meta-btn-primary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--btn-neon) 12%, transparent);
  border-color: color-mix(in srgb, var(--btn-neon) 50%, transparent);
  box-shadow: 0 0 16px color-mix(in srgb, var(--btn-neon) 18%, transparent);
  transform: translateY(-1px);
}
.meta-btn-primary:active:not(:disabled) {
  transform: translateY(0);
  box-shadow: 0 0 4px color-mix(in srgb, var(--btn-neon) 8%, transparent);
}
.meta-btn-outline {
  background: rgba(255,255,255,0.04);
  color: #CBD5E1;
  border: 1px solid rgba(255,255,255,0.12);
}
.meta-btn-outline:hover:not(:disabled) {
  background: rgba(255,255,255,0.08);
  border-color: rgba(255,255,255,0.2);
  color: #fff;
  transform: translateY(-1px);
}
.meta-btn-outline:active:not(:disabled) {
  transform: translateY(0);
}

/* ── Filter select (Skillset product filter) ── */
.meta-filter-select {
  padding: 7px 12px;
  font-size: 12.5px;
  font-weight: 500;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 8px;
  color: var(--fiori-text, #E2E8F0);
  cursor: pointer;
  outline: none;
  transition: all 0.2s;
  appearance: auto;
}
.meta-filter-select:hover {
  border-color: rgba(255,255,255,0.2);
  background: rgba(255,255,255,0.06);
}
.meta-filter-select:focus {
  border-color: var(--fiori-brand, #3B8DF8);
}

/* ============================================================
   EMPLOYEE SKILLS FILTER PANEL
   ============================================================ */
.skill-filter-panel {
  background: rgba(255,255,255,0.02);
  border: 1px solid rgba(255,255,255,0.06);
  border-radius: 10px;
  padding: 16px;
  margin-bottom: 16px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}
.skill-filter-row {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  flex-wrap: wrap;
}
.skill-filter-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 140px;
  flex: 1;
}
.skill-filter-label {
  font-size: 11px;
  font-weight: 600;
  color: #94A3B8;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.skill-filter-select {
  padding: 7px 12px;
  font-size: 13px;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 8px;
  color: var(--fiori-text, #E2E8F0);
  cursor: pointer;
  outline: none;
  transition: all 0.2s;
  appearance: auto;
}
.skill-filter-select:focus {
  border-color: var(--fiori-brand, #3B8DF8);
}
.skill-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  min-height: 32px;
  align-items: center;
}
.skill-chip {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  font-size: 12px;
  font-weight: 500;
  border-radius: 16px;
  border: 1px solid rgba(255,255,255,0.1);
  background: rgba(255,255,255,0.04);
  color: #CBD5E1;
  cursor: pointer;
  transition: all 0.2s ease;
  white-space: nowrap;
}
.skill-chip:hover {
  border-color: rgba(59,141,248,0.4);
  background: rgba(59,141,248,0.08);
  color: #E2E8F0;
}
.skill-chip.active {
  background: rgba(59,141,248,0.18);
  border-color: rgba(59,141,248,0.4);
  color: #60A5FA;
  font-weight: 600;
}

/* ── Skill Sub-Tabs ── */
.skill-subtabs {
  display: flex;
  gap: 2px;
  margin-bottom: 12px;
  background: rgba(255,255,255,0.03);
  border-radius: 10px;
  padding: 3px;
  border: 1px solid rgba(255,255,255,0.06);
}
.skill-subtab {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 7px 14px;
  font-size: 12.5px;
  font-weight: 500;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: #94A3B8;
  cursor: pointer;
  transition: all 0.2s;
  white-space: nowrap;
}
.skill-subtab:hover {
  background: rgba(255,255,255,0.05);
  color: #CBD5E1;
}
.skill-subtab.active {
  background: rgba(59,141,248,0.15);
  color: #60A5FA;
  font-weight: 600;
}
.skill-subtab-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 18px;
  padding: 0 5px;
  font-size: 10.5px;
  font-weight: 700;
  border-radius: 9px;
  background: rgba(255,255,255,0.06);
  color: #64748B;
}
.skill-subtab.active .skill-subtab-count {
  background: rgba(96,165,250,0.2);
  color: #60A5FA;
}

/* ── Skill Filter Bar (compact inline) ── */
.skill-filter-bar {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-bottom: 12px;
}

/* ── Skill Dropdown (multi-select) ── */
.skill-dropdown-wrap {
  position: relative;
}
.skill-dropdown-trigger {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 12px;
  font-size: 12.5px;
  font-weight: 500;
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.1);
  border-radius: 8px;
  color: #CBD5E1;
  cursor: pointer;
  transition: all 0.2s;
  white-space: nowrap;
}
.skill-dropdown-trigger:hover {
  border-color: rgba(59,141,248,0.4);
  background: rgba(59,141,248,0.06);
}
.skill-dropdown-menu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  min-width: 200px;
  max-height: 240px;
  overflow-y: auto;
  background: var(--fiori-shell, #1E293B);
  border: 1px solid rgba(255,255,255,0.12);
  border-radius: 10px;
  padding: 4px;
  z-index: 100;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
}
.skill-dropdown-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 10px;
  font-size: 12.5px;
  color: #CBD5E1;
  border-radius: 6px;
  cursor: pointer;
  transition: background 0.15s;
}
.skill-dropdown-item:hover {
  background: rgba(255,255,255,0.06);
}
.skill-dropdown-item.checked {
  color: #60A5FA;
}
.skill-dropdown-item input[type="checkbox"] {
  width: 14px;
  height: 14px;
  accent-color: #3B8DF8;
}
.skill-dropdown-empty {
  padding: 10px;
  font-size: 12px;
  color: #64748B;
  text-align: center;
}
.skill-dropdown-clear {
  display: block;
  width: 100%;
  padding: 6px;
  font-size: 11.5px;
  text-align: center;
  color: #94A3B8;
  background: none;
  border: none;
  border-top: 1px solid rgba(255,255,255,0.06);
  cursor: pointer;
  margin-top: 2px;
}
.skill-dropdown-clear:hover {
  color: #F87171;
}

/* ════════════════════════════════════════════════════════════════════════ */
/*  COST SHEET                                                            */
/* ════════════════════════════════════════════════════════════════════════ */
.cs-container { display: flex; flex-direction: column; gap: 12px; }
.cs-loading { color: #94A3B8; text-align: center; padding: 40px; font-size: 14px; }

/* Toolbar */
.cs-toolbar {
  display: flex; justify-content: space-between; align-items: center;
  gap: 8px;
  padding: 8px 12px;
  background: rgba(30,41,59,0.5); border-radius: 10px;
  border: 1px solid rgba(255,255,255,0.06);
  flex-wrap: nowrap;
}
.cs-toolbar h4 { color: #E2E8F0; font-size: 15px; }

/* Settings bar */
.cs-settings-bar {
  display: flex; flex-wrap: wrap; gap: 12px; align-items: flex-end;
  padding: 12px 16px;
  background: rgba(30,41,59,0.35); border-radius: 10px;
  border: 1px solid rgba(255,255,255,0.06);
  font-size: 12px; color: #94A3B8;
}
.cs-settings-bar > span { padding: 4px 0; }
.cs-setting { display: flex; flex-direction: column; gap: 3px; }
.cs-setting label { font-size: 11px; color: #64748B; white-space: nowrap; }

/* Inputs */
.cs-input {
  background: rgba(15,23,42,0.6); border: 1px solid rgba(255,255,255,0.1);
  color: #E2E8F0; border-radius: 6px; padding: 5px 8px; font-size: 12px;
  outline: none; transition: border-color 0.15s;
}
.cs-input:focus { border-color: rgba(59,141,248,0.5); }
.cs-input[type="number"] {
  text-align: center;
  padding: 5px 6px 5px 6px;
  -moz-appearance: textfield;
}
/* Hide native number-input spinners entirely (user preference) */
.cs-input[type="number"]::-webkit-inner-spin-button,
.cs-input[type="number"]::-webkit-outer-spin-button {
  -webkit-appearance: none;
  margin: 0;
  display: none;
}
/* Accordion header for cost-sheet collapsible sections */
.cs-accordion-head {
  all: unset;
  box-sizing: border-box;
  display: flex; align-items: center; gap: 10px;
  width: 100%;
  padding: 9px 14px;
  margin-top: 8px;
  background: rgba(15,23,42,0.6);
  border: 1px solid rgba(255,255,255,0.08);
  border-radius: 8px;
  color: #E2E8F0;
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.3px;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
}
.cs-accordion-head:hover {
  background: rgba(30,41,59,0.7);
  border-color: rgba(59,141,248,0.3);
}
.cs-accordion-caret {
  color: #60A5FA; font-size: 10px; width: 12px;
}
.cs-accordion-hint {
  margin-left: auto;
  color: #64748B; font-size: 11px; font-weight: 500;
  letter-spacing: 0;
}
select.cs-input { padding-right: 4px; }
select.cs-input option {
  background: #161C24;
  color: #E2E8F0;
}
.cs-readonly { color: #94A3B8; font-size: 12px; display: inline-block; padding: 5px 0; }
/* Warning highlight for over-FTE or missing rate */
.cs-input-warn { border-color: rgba(245,158,11,0.6) !important; background: rgba(245,158,11,0.08) !important; }
/* Rate warning banner */
.cs-rate-warning {
  font-size: 11px; color: #FBBF24; padding: 3px 8px;
  background: rgba(245,158,11,0.08); border-radius: 4px;
  border-left: 2px solid #F59E0B; align-self: center;
  white-space: nowrap;
}

/* Settings divider */
.cs-setting-divider {
  width: 1px; height: 32px; background: rgba(255,255,255,0.08);
  margin: 0 4px; align-self: center;
}

/* Mode group (Offshore/Onsite/Mixed/Hide) */
.cs-mode-group {
  display: flex; border: 1px solid rgba(255,255,255,0.1); border-radius: 6px; overflow: hidden;
}
.cs-mode-btn {
  background: rgba(15,23,42,0.4); border: none; color: #64748B;
  padding: 5px 10px; font-size: 11px; font-weight: 500;
  cursor: pointer; transition: all 0.15s;
  border-right: 1px solid rgba(255,255,255,0.06);
  letter-spacing: 0.2px;
}
.cs-mode-btn:last-child { border-right: none; }
.cs-mode-btn:hover { color: #CBD5E1; background: rgba(30,41,59,0.6); }
.cs-mode-btn.active {
  background: rgba(59,141,248,0.15); color: #60A5FA; font-weight: 600;
}

/* Table */
.cs-table-wrap {
  overflow-x: auto; border-radius: 10px;
  border: 1px solid rgba(255,255,255,0.06);
  background: rgba(15,23,42,0.4);
}
.cs-table {
  width: 100%; border-collapse: collapse; font-size: 12px;
  white-space: nowrap;
}
.cs-table thead { background: rgba(30,41,59,0.8); }
.cs-table th {
  padding: 8px 6px; text-align: center; color: #94A3B8;
  font-weight: 600; font-size: 11px; text-transform: uppercase;
  letter-spacing: 0.3px; border-bottom: 1px solid rgba(255,255,255,0.08);
  position: sticky; top: 0; z-index: 2;
  /* Let long headers like "ON RATE (L₹/MONTH)" / "TOTAL AMT (L₹)" wrap onto
     multiple short lines instead of forcing a wide column. */
  white-space: normal;
  word-break: break-word;
  line-height: 1.25;
  vertical-align: bottom;
}
.cs-table td {
  padding: 4px 4px; border-bottom: 1px solid rgba(255,255,255,0.04);
  vertical-align: middle;
}
.cs-table tbody tr:hover { background: rgba(59,141,248,0.04); }
.cs-row-num { text-align: center; color: #64748B; font-size: 11px; }
.cs-row-excluded { opacity: 0.55; }
.cs-calc { text-align: right; color: #94A3B8; font-size: 12px; padding-right: 8px !important; }
.cs-amount { color: #60A5FA; font-weight: 500; }
.cs-total { color: #E2E8F0; font-weight: 600; }

/* Grouped month column headers */
.cs-th-offshore-group {
  background: rgba(59,130,246,0.10) !important;
  color: #93C5FD !important; font-size: 10px; letter-spacing: 0.5px;
  border-bottom: 2px solid rgba(59,130,246,0.25) !important;
}
.cs-th-onsite-group {
  background: rgba(168,85,247,0.10) !important;
  color: #C4B5FD !important; font-size: 10px; letter-spacing: 0.5px;
  border-bottom: 2px solid rgba(168,85,247,0.25) !important;
}
.cs-th-offshore {
  background: rgba(59,130,246,0.06) !important; color: #93C5FD !important;
}
.cs-th-onsite {
  background: rgba(168,85,247,0.06) !important; color: #C4B5FD !important;
}

/* Column shading for offshore/onsite data cells */
.cs-td-offshore { background: rgba(59,130,246,0.03); }
.cs-td-onsite { background: rgba(168,85,247,0.03); }
.cs-table tbody tr:hover .cs-td-offshore { background: rgba(59,130,246,0.07); }
.cs-table tbody tr:hover .cs-td-onsite { background: rgba(168,85,247,0.07); }

/* Extras toggle */
.cs-extras-toggle {
  cursor: pointer; color: #F59E0B; transition: color 0.15s;
}
.cs-extras-toggle:hover { color: #FBBF24; }

/* Extras row */
.cs-extras-row { background: rgba(30,41,59,0.4) !important; }
.cs-extras-row:hover { background: rgba(30,41,59,0.5) !important; }
.cs-extras-grid {
  display: flex; flex-wrap: wrap; gap: 16px; padding: 8px 12px;
}
.cs-extra-item {
  display: flex; align-items: center; gap: 6px; font-size: 12px;
}
.cs-extra-item label { color: #94A3B8; white-space: nowrap; min-width: 100px; }
.cs-extra-calc { color: #64748B; font-size: 11px; white-space: nowrap; }

/* Summary bar */
.cs-summary-bar {
  display: flex; flex-wrap: wrap; gap: 12px;
  padding: 14px 16px;
  background: rgba(30,41,59,0.5); border-radius: 10px;
  border: 1px solid rgba(255,255,255,0.06);
}
.cs-summary-item {
  display: flex; flex-direction: column; gap: 2px;
  padding: 6px 16px; min-width: 120px;
  border-right: 1px solid rgba(255,255,255,0.06);
}
.cs-summary-item:last-child { border-right: none; }
.cs-summary-item span { font-size: 11px; color: #64748B; }
.cs-summary-item strong { font-size: 16px; color: #E2E8F0; }
.cs-summary-highlight {
  background: rgba(59,141,248,0.08); border-radius: 8px;
}
.cs-summary-highlight strong { color: #60A5FA; font-size: 18px; }

/* Small buttons */
.cs-btn-sm {
  background: rgba(255,255,255,0.06); border: 1px solid rgba(255,255,255,0.1);
  color: #94A3B8; border-radius: 6px; padding: 3px 8px; font-size: 12px;
  cursor: pointer; transition: all 0.15s; line-height: 1;
}
.cs-btn-sm:hover { background: rgba(255,255,255,0.12); color: #E2E8F0; }
.cs-btn-danger:hover { background: rgba(239,68,68,0.15); color: #EF4444; border-color: rgba(239,68,68,0.3); }

/* Version panel */
.cs-version-panel {
  padding: 14px 16px;
  background: rgba(30,41,59,0.4); border-radius: 10px;
  border: 1px solid rgba(255,255,255,0.06);
}
.cs-version-save { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.cs-version-prefix {
  display: inline-block; padding: 4px 10px; font-size: 12px; font-weight: 600;
  color: #60A5FA; background: rgba(59,141,248,0.10); border: 1px solid rgba(59,141,248,0.18);
  border-radius: 6px; letter-spacing: 0.3px; white-space: nowrap; user-select: all;
}
.cs-version-table {
  width: 100%; border-collapse: collapse; font-size: 12px; margin-top: 12px;
}
.cs-version-table th {
  padding: 8px 10px; text-align: left; color: #64748B;
  font-weight: 600; font-size: 11px; text-transform: uppercase;
  border-bottom: 1px solid rgba(255,255,255,0.08);
}
.cs-version-table td {
  padding: 8px 10px; color: #CBD5E1;
  border-bottom: 1px solid rgba(255,255,255,0.04);
}
.cs-version-table tbody tr:hover { background: rgba(59,141,248,0.05); }
.cs-version-note {
  padding: 8px 14px; margin-bottom: 4px;
  background: rgba(245,158,11,0.08); border-left: 3px solid #F59E0B;
  border-radius: 0 6px 6px 0; color: #FBBF24; font-size: 13px;
}

/* Compare grid */
.cs-compare-grid {
  display: grid; gap: 0; font-size: 13px;
  border: 1px solid rgba(255,255,255,0.06); border-radius: 10px; overflow: hidden;
}
.cs-compare-label, .cs-compare-val {
  padding: 8px 14px; border-bottom: 1px solid rgba(255,255,255,0.04);
}
.cs-compare-label { color: #94A3B8; background: rgba(30,41,59,0.4); font-weight: 500; }
.cs-compare-val { color: #E2E8F0; text-align: right; }
.cs-compare-header {
  background: rgba(30,41,59,0.7) !important; font-weight: 600 !important;
  font-size: 13px; border-bottom: 1px solid rgba(255,255,255,0.1) !important;
}
.cs-compare-section {
  background: rgba(59,141,248,0.06) !important; color: #60A5FA !important;
  font-weight: 600 !important; font-size: 12px; text-transform: uppercase;
  letter-spacing: 0.3px;
}
.cs-compare-total { font-weight: 700 !important; font-size: 14px; }
.cs-compare-highlight { color: #60A5FA !important; font-size: 16px !important; }

/* Cost Sheet tab button */
.cs-tab-active {
  background: rgba(59,141,248,0.15) !important;
  border-color: rgba(59,141,248,0.4) !important;
  color: #60A5FA !important;
}

/* ─── Employee Multi-Select Dropdown ─── */
.cs-emp-select { position: relative; min-width: 140px; }
.cs-emp-trigger {
  background: rgba(15,23,42,0.6); border: 1px solid rgba(255,255,255,0.1);
  border-radius: 6px; padding: 3px 6px; cursor: pointer;
  min-height: 28px; display: flex; align-items: center; flex-wrap: wrap; gap: 3px;
  transition: border-color 0.15s;
}
.cs-emp-trigger:hover { border-color: rgba(59,141,248,0.4); }
.cs-emp-trigger.disabled { opacity: 0.6; cursor: default; }
.cs-emp-placeholder { color: #64748B; font-size: 11px; }
.cs-emp-pills { display: flex; flex-wrap: wrap; gap: 3px; }
.cs-emp-pill {
  display: inline-flex; align-items: center; gap: 3px;
  background: rgba(30,41,59,0.8); border: 1px solid;
  border-radius: 4px; padding: 1px 6px; font-size: 10px;
  color: #E2E8F0; white-space: nowrap;
}
.cs-emp-pill-x {
  cursor: pointer; font-size: 12px; color: #94A3B8;
  margin-left: 2px; line-height: 1;
}
.cs-emp-pill-x:hover { color: #EF4444; }
.cs-emp-dot {
  width: 7px; height: 7px; border-radius: 50%;
  display: inline-block; flex-shrink: 0;
}
.cs-emp-dropdown {
  min-width: 260px; max-height: 320px;
  background: rgba(15,23,42,0.98);
  border: 1px solid rgba(255,255,255,0.15);
  border-radius: 8px;
  box-shadow: 0 12px 32px rgba(0,0,0,0.6);
  display: flex; flex-direction: column;
  backdrop-filter: blur(12px);
}
/* Portal variant — inline styles position it; just make sure nothing from the
   old absolute/bottom rule leaks in. */
.cs-emp-dropdown--portal {
  margin: 0;
}
.cs-emp-search {
  margin: 6px; width: calc(100% - 12px) !important;
  font-size: 12px !important;
}
.cs-emp-list {
  overflow-y: auto; max-height: 220px; padding: 2px 0;
}
.cs-emp-empty { color: #64748B; font-size: 12px; padding: 12px; text-align: center; }
.cs-emp-option {
  display: flex; align-items: center; gap: 6px;
  padding: 6px 10px; cursor: pointer; font-size: 12px;
  transition: background 0.1s;
}
.cs-emp-option:hover { background: rgba(59,141,248,0.1); }
.cs-emp-option.selected { background: rgba(59,141,248,0.08); }
.cs-emp-option.disabled { opacity: 0.4; cursor: default; }
.cs-emp-name { font-weight: 500; white-space: nowrap; }
.cs-emp-meta { color: #64748B; font-size: 10px; white-space: nowrap; margin-left: auto; }
.cs-emp-check { color: #34D399; font-weight: 700; font-size: 13px; margin-left: 4px; }

/* ─── Cost columns ─── */
.cs-th-cost {
  background: rgba(34,197,94,0.08) !important;
  color: #86EFAC !important;
}
.cs-td-cost { color: #94A3B8; }
.cs-margin-pos { color: #34D399 !important; }
.cs-margin-neg { color: #F87171 !important; }

/* Cost summary bar */
.cs-cost-summary {
  border-top: 1px solid rgba(34,197,94,0.15);
  background: rgba(34,197,94,0.04) !important;
}
.cs-summary-margin-pos strong { color: #34D399 !important; }
.cs-summary-margin-neg strong { color: #F87171 !important; }

/* Default Extras bar */
.cs-extras-defaults-bar {
  display: flex; flex-wrap: wrap; gap: 12px 20px; align-items: flex-end;
  padding: 10px 16px;
  background: rgba(30,41,59,0.35); border-radius: 8px;
  border: 1px solid rgba(255,255,255,0.04);
  margin-bottom: 10px;
}
.cs-extras-defaults-label {
  font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
  color: #64748B; width: 100%; margin-bottom: -4px;
}
.cs-extras-defaults-bar .cs-extra-item {
  display: flex; align-items: center; gap: 6px;
}
.cs-extras-defaults-bar .cs-extra-item label {
  font-size: 11px; color: #94A3B8; white-space: nowrap;
}
.cs-extras-defaults-bar .cs-extra-calc {
  font-size: 11px; color: #64748B;
}
.cs-apply-defaults-btn {
  font-size: 11px !important; padding: 5px 12px !important;
  margin-left: auto;
}

/* Actual Period toggle */
.cs-toggle-label {
  display: flex; align-items: center; gap: 6px;
  cursor: pointer; user-select: none; font-size: 12px;
  color: #94A3B8; white-space: nowrap;
}
.cs-toggle-label input[type="checkbox"] {
  accent-color: #3B8DF8; width: 14px; height: 14px; cursor: pointer;
}

/* Rate Scale selector */
.cs-scale-select {
  min-width: 120px; font-size: 12px; font-weight: 500;
  background: rgba(59,141,248,0.08); border: 1px solid rgba(59,141,248,0.18);
  color: #60A5FA;
}
.cs-scale-select:focus {
  border-color: rgba(59,141,248,0.4);
  box-shadow: 0 0 0 2px rgba(59,141,248,0.12);
}

/* Employee detail sticky header */
.emp-sticky-header {
  position: sticky;
  top: -1px;
  z-index: 20;
  background: var(--fiori-bg);
  padding-bottom: 0;
  margin: -24px -24px 0 -24px;
  padding: 16px 24px 0 24px;
}

/* Employee row flash highlight after save */
@keyframes empRowFlash {
  0%   { background: rgba(59,141,248,0.25); }
  50%  { background: rgba(59,141,248,0.10); }
  100% { background: transparent; }
}
.emp-row-flash {
  animation: empRowFlash 2s ease-out;
}

/* ============================================================
   COST SHEET — Light theme overrides
   ============================================================
   All .cs-* class definitions above use hardcoded dark slate
   translucent backgrounds that disappear (or look slate-grey via
   the UA fallback) on white surfaces. Override the relevant ones
   here so Cost Sheet flips with the rest of the app.
   ------------------------------------------------------------ */
[data-theme="light"] .cs-loading { color: var(--fiori-text-muted); }

[data-theme="light"] .cs-toolbar {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
}
[data-theme="light"] .cs-toolbar h4 { color: var(--fiori-text); }

[data-theme="light"] .cs-settings-bar {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text-muted);
}
[data-theme="light"] .cs-setting label { color: var(--fiori-text-muted); }

[data-theme="light"] .cs-input {
  background: var(--fiori-input-bg);
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text);
}
[data-theme="light"] .cs-input:focus { border-color: var(--fiori-brand); }

[data-theme="light"] .cs-accordion-head {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text);
}
[data-theme="light"] .cs-accordion-head:hover {
  background: var(--fiori-surface2-solid);
  border-color: var(--fiori-brand);
}
[data-theme="light"] .cs-accordion-caret { color: var(--fiori-brand); }
[data-theme="light"] .cs-accordion-hint { color: var(--fiori-text-muted); }

[data-theme="light"] select.cs-input option {
  background: var(--fiori-surface-solid);
  color: var(--fiori-text);
}

[data-theme="light"] .cs-readonly { color: var(--fiori-text-muted); }

/* The big effort matrix — table itself + header band sit on dark
   navy in dark mode; in light they need white surfaces with a
   real border so the role/level/employee columns read against
   the page bg. */
[data-theme="light"] .cs-container table,
[data-theme="light"] .cs-grid,
[data-theme="light"] .cs-effort-table {
  background: var(--fiori-surface-solid);
  color: var(--fiori-text);
}
[data-theme="light"] .cs-container th,
[data-theme="light"] .cs-grid th,
[data-theme="light"] .cs-effort-table th {
  background: var(--fiori-surface2-solid);
  color: var(--fiori-text-muted);
  border-bottom: 1px solid var(--fiori-border);
}
[data-theme="light"] .cs-container td,
[data-theme="light"] .cs-grid td,
[data-theme="light"] .cs-effort-table td {
  border-bottom: 1px solid var(--fiori-border);
  color: var(--fiori-text);
}

/* Cost-sheet table head + body styling. The .cs-table class has its
   own thead background (the dark navy strip above the JUN-26/JUL-26
   month headers); flip it to a soft slate-on-white. */
[data-theme="light"] .cs-table { color: var(--fiori-text); }
[data-theme="light"] .cs-table thead { background: var(--fiori-surface2-solid); }
[data-theme="light"] .cs-table th {
  color: var(--fiori-text-muted);
  border-bottom: 1px solid var(--fiori-border);
}
[data-theme="light"] .cs-table tbody tr:hover {
  background: var(--fiori-row-hover);
}
[data-theme="light"] .cs-table-wrap {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
}

/* Mode-toggle buttons in the toolbar (Mix / Off / On / Hide pill row,
   plus Columns / Spread Effort / Versions / Export). All variants on
   .cs-mode-btn use rgba(15,23,42,0.4) which is invisible on white. */
[data-theme="light"] .cs-mode-btn {
  background: var(--fiori-surface-solid);
  color: var(--fiori-text-muted);
  border-right: 1px solid var(--fiori-border);
}
[data-theme="light"] .cs-mode-btn:hover {
  background: var(--fiori-row-hover);
  color: var(--fiori-text);
}
[data-theme="light"] .cs-mode-btn.active {
  background: color-mix(in srgb, var(--fiori-brand) 14%, transparent);
  color: var(--fiori-brand);
}

/* Small buttons (Spread Effort, etc.) */
[data-theme="light"] .cs-btn-sm {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text-muted);
}
[data-theme="light"] .cs-btn-sm:hover {
  background: var(--fiori-row-hover);
  color: var(--fiori-text);
}

/* Group header tints (Offshore / Onsite swimlanes) — keep the brand
   hue but drop the alpha so they read on white without dominating. */
[data-theme="light"] .cs-th-offshore-group,
[data-theme="light"] .cs-th-offshore {
  background: color-mix(in srgb, var(--fiori-accent-sky) 10%, transparent);
  color: var(--fiori-accent-sky);
}
[data-theme="light"] .cs-th-onsite-group,
[data-theme="light"] .cs-th-onsite {
  background: color-mix(in srgb, var(--fiori-accent-violet) 10%, transparent);
  color: var(--fiori-accent-violet);
}

/* Summary highlight + version panel — both used rgba(30,41,59,*)
   surfaces that fade to nothing on white. Give them subtle real
   backgrounds with a real border. */
[data-theme="light"] .cs-summary-highlight {
  background: color-mix(in srgb, var(--fiori-brand) 8%, transparent);
}
[data-theme="light"] .cs-summary-highlight strong { color: var(--fiori-brand); }
[data-theme="light"] .cs-version-panel {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
}

/* Bottom totals strip — Total Effort / Total Extras / Grand Total /
   Discount / Proposed Value. Was rendering on a dark slate-50%
   translucent which read as a murky grey block on white. Flip it to
   a clean white card with real borders + bumped label/value contrast
   so the numbers and labels both stand out. */
[data-theme="light"] .cs-summary-bar {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  box-shadow: var(--fiori-shadow);
}
[data-theme="light"] .cs-summary-item {
  border-right: 1px solid var(--fiori-border);
}
[data-theme="light"] .cs-summary-item:last-child { border-right: none; }
[data-theme="light"] .cs-summary-item span {
  color: var(--fiori-text-muted);
  font-weight: 600;
}
[data-theme="light"] .cs-summary-item strong { color: var(--fiori-text); }
[data-theme="light"] .cs-summary-margin-pos strong { color: var(--fiori-positive) !important; }
[data-theme="light"] .cs-summary-margin-neg strong { color: var(--fiori-negative) !important; }

/* ============================================================
   EMPLOYEE SKILLS — light theme
   ============================================================
   The Skills tab on the Employees master uses .meta-btn / .meta-
   filter-select / .skill-dropdown-* classes. All of them are
   styled with rgba-white tints + pale-slate text that vanish on
   white. Override every variant so triggers / dropdowns / pill
   buttons read against the new surface. */

/* Meta buttons (Populate Skills, Save Skills, Clear All) — flip
   from translucent neon outline to solid surface with visible
   borders + dark text. The .meta-btn-primary keeps its neon
   accent via --btn-neon (already var-driven from --page-neon)
   so the brand identity carries through, just darker. */
[data-theme="light"] .meta-btn-primary {
  background: var(--fiori-surface-solid);
  border: 1px solid color-mix(in srgb, var(--btn-neon) 50%, transparent);
  color: var(--btn-neon);
  box-shadow: 0 1px 2px rgba(15,23,42,0.06);
}
[data-theme="light"] .meta-btn-primary:hover:not(:disabled) {
  background: color-mix(in srgb, var(--btn-neon) 12%, var(--fiori-surface-solid));
  border-color: var(--btn-neon);
  box-shadow: 0 2px 6px rgba(15,23,42,0.10);
}
[data-theme="light"] .meta-btn-outline {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border-light);
  color: var(--fiori-text);
}
[data-theme="light"] .meta-btn-outline:hover:not(:disabled) {
  background: var(--fiori-row-hover);
  border-color: var(--fiori-text-muted);
  color: var(--fiori-text);
}

/* Filter select used as the Product / Skillset filter */
[data-theme="light"] .meta-filter-select {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text);
}

/* Multi-select dropdown triggers (All Products, All Skill Groups) */
[data-theme="light"] .skill-dropdown-trigger {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text);
}
[data-theme="light"] .skill-dropdown-trigger:hover {
  border-color: var(--fiori-brand);
  background: color-mix(in srgb, var(--fiori-brand) 8%, var(--fiori-surface-solid));
}
[data-theme="light"] .skill-dropdown-menu {
  background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border);
  box-shadow: var(--fiori-shadow);
}
[data-theme="light"] .skill-dropdown-item { color: var(--fiori-text); }
[data-theme="light"] .skill-dropdown-item:hover {
  background: var(--fiori-row-hover);
}
[data-theme="light"] .skill-dropdown-item.checked { color: var(--fiori-brand); }
[data-theme="light"] .skill-dropdown-empty { color: var(--fiori-text-muted); }
[data-theme="light"] .skill-dropdown-clear { color: var(--fiori-accent-rose); }

/* =====================================================================
   IMPLEMENTATION PROJECT MANAGEMENT  (Delivery › Implementation)
   Owner spec 2026-08-30. Ported verbatim from
   prototypes/implementation-pm.html so the shipped module is the UI
   that was signed off, not a re-interpretation of it.

   Every colour is a var(--fiori-*) or var(--neon-*) token, so the
   module themes with the rest of the app and the light-mode flip is
   automatic. There are NO literal hex values below — a hardcoded hex
   in an inline style is exactly why the app "kept the same font
   colours" when the theme flipped (see the --ct-* note above).

   All rules are scoped under .impl-root so the generic names here
   (.card, .kpi, .tab) cannot leak into the rest of the app.
   ===================================================================== */

.impl-root { color: var(--fiori-text); }

/* ---- generic bits ---- */
.impl-root .impl-card{
  background: var(--fiori-surface); border: 1px solid var(--fiori-border);
  border-radius: 14px; box-shadow: 0 1px 2px rgba(0,0,0,.18), 0 8px 24px rgba(0,0,0,.10);
}
.impl-root .impl-pad{ padding: 14px 16px; }
/* Improvement Catalog rows: one line per cell, full text in the View modal. */
.impl-root .ic-clip{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
.impl-root td.ic-clip{ max-width: 0; }
/* Scope Items (DDA) tab — owner 2026-09-13 "improve the UI". */
.impl-root .impl-card-h{
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 10px 14px; font-size: 13px; font-weight: 650;
  border-bottom: 1px solid var(--fiori-border); border-radius: 14px 14px 0 0;
  background: var(--fiori-elevated-solid);
}
.impl-root .impl-card-h:focus-visible{ outline: 2px solid var(--fiori-accent-blue, #0a6ed1); outline-offset: -2px; }
.impl-root .impl-table{ width: 100%; border-collapse: collapse; font-size: 12.5px; }
.impl-root .impl-table th{
  text-align: left; padding: 7px 10px; font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 600; border-bottom: 1px solid var(--fiori-border);
  position: sticky; top: 0; background: var(--fiori-surface); z-index: 1; white-space: nowrap;
}
.impl-root .impl-table td{ padding: 5px 10px; border-bottom: 1px solid var(--fiori-border); vertical-align: top; line-height: 1.4; }
.impl-root .impl-table tbody tr:last-child td{ border-bottom: 0; }
.impl-root .impl-table tbody tr:hover td{ background: rgba(127,127,127,.06); }
.impl-root .impl-table input{ font-size: 12.5px; }

/* ── Steps tracker: a frozen, resizable first column ────────────────
   Owner 2026-09-25. The steps table is thirteen columns wide and the
   one you need while reading any of them is Solution Activity, so it
   stays put while the rest scrolls sideways.

   The right edge is a box-shadow, not a border: the table is
   border-collapse: collapse, and collapsed borders on a sticky cell
   are dropped by Chrome while the cell itself keeps painting -- the
   column would scroll with no edge at all. */
.impl-root .impl-steps-table .impl-sticky-col{
  position: sticky; left: 0; z-index: 2;
  background: var(--fiori-surface);
  box-shadow: 1px 0 0 0 var(--fiori-border);
}
.impl-root .impl-steps-table th.impl-sticky-col{ z-index: 3; }

/* ── The "set for all steps" row (owner 2026-10-11) ────────────────
   A second header row whose pickers fill a whole column. It has to
   READ as a control strip rather than as a second set of headings, so
   it is tinted, its type is normal weight, and it keeps the header's
   own sticky behaviour -- a bulk picker that scrolled away from its
   column would be worse than no bulk picker.

   Colour comes from tokens on both sides of the theme: a literal here
   would be legible in one theme only, and this strip sits over the
   table surface in both. */
.impl-root .impl-steps-table thead tr.impl-bulk-row > th{
  font-weight: 400;
  padding-top: 4px; padding-bottom: 4px;
  background: color-mix(in srgb, var(--fiori-surface) 88%, var(--fiori-accent-cyan));
  border-bottom: 1px solid var(--fiori-border);
}
.impl-root .impl-steps-table thead tr.impl-bulk-row > th.impl-sticky-col{
  /* The sticky cell cannot inherit the tint through a translucent
     layer -- the scrolling columns would show through it -- so it is
     given the same colour outright. */
  background: color-mix(in srgb, var(--fiori-surface) 88%, var(--fiori-accent-cyan));
  box-shadow: 1px 0 0 0 var(--fiori-border);
  white-space: nowrap;
}
/* The generic row-hover tint is translucent, and translucent over a
   sticky cell lets the scrolling columns show through it. Opaque
   equivalent, so the frozen cell still responds to hover. */
.impl-root .impl-steps-table tbody tr:hover .impl-sticky-col{
  background: color-mix(in srgb, var(--fiori-surface) 94%, var(--fiori-text));
}
/* The drag handle. Sits on the column's right edge, widens its hit
   area beyond its visible width so it is grabbable without being a
   line of furniture. */
/* ---- label above its control -------------------------------------
 *
 * Found 2026-09-25 alongside impl-chip-on, by the same question: every
 * `impl-*` class the client uses was checked against the stylesheet,
 * and seven were never defined.
 *
 * `impl-col` is used seven times in ScopeItemTracker and EVERY one
 * passes a `gap` inline. `gap` does nothing outside flex or grid, so
 * without this rule the label and its box laid out as plain blocks --
 * which is why Requirement and Status Comment sat beside their inputs
 * instead of above them, with the gap ignored. */
.impl-root .impl-col{ display: flex; flex-direction: column; min-width: 0; }

/* ---- Scope Items: the three levels of the tree --------------------
 * Tokens and reasoning at :root. Rails are inset box-shadows rather
 * than border-left: border-collapse on the table drops real borders on
 * cells, and a shadow is not part of the box, so nothing reflows and
 * the columns stay where they were.  */
.impl-root .impl-lvl1 > .impl-card-h{
  background: var(--impl-lvl1-bg);
  box-shadow: inset 3px 0 0 0 var(--impl-lvl1-rail);
  font-weight: 650;
}
.impl-root .impl-lvl2 > td:first-child{
  box-shadow: inset 3px 0 0 0 var(--impl-lvl2-rail);
}
/* An open row is the parent of what is showing below it, so it stays
   marked while the panel is out -- otherwise, scrolled down inside a
   long step list, there is nothing on screen saying which scope item
   you are in. */
.impl-root .impl-lvl2.is-open > td{ background: var(--impl-lvl2-open); }
.impl-root .impl-lvl2.is-open > td:first-child{ font-weight: 600; }

.impl-root td.impl-lvl3{
  background: var(--impl-lvl3-bg);
  box-shadow: inset 3px 0 0 0 var(--impl-lvl3-rail);
}

/* ---- how far a scope item's steps have been triaged --------------
 * Tokens and reasoning at :root. The colour is on the CELL so the
 * editable name inherits it -- the input sets `color: inherit` rather
 * than a colour of its own, or the inline style would win.
 *
 * Colour is never the only channel: every one of these carries the
 * count in its title, so the same fact is available to a reader who
 * cannot separate the hues and to anyone who simply wants the number. */
.impl-root .impl-cover-settled{    color: var(--impl-cover-settled);    font-weight: 600; }
.impl-root .impl-cover-unsure{     color: var(--impl-cover-unsure);     font-weight: 600; }
.impl-root .impl-cover-incomplete{ color: var(--impl-cover-incomplete); font-weight: 600; }

/* ---- one step is TWO rows, and the pair is the unit ---------------
 * .impl-table gives every td a border-bottom, so a step's controls and
 * its own Requirement row were divided by the same line that divided
 * one step from the next -- which is why the list read as a wall.
 * The line inside a step goes; the line that ENDS a step gets weight. */
.impl-root .impl-steps-table tbody tr.impl-step-head > td{ border-bottom: 0; }
.impl-root .impl-steps-table tbody tr.impl-step-body > td{
  border-bottom: 2px solid var(--impl-sep);
  padding-bottom: 8px;
}
.impl-root .impl-steps-table tbody tr.impl-step-body:last-child > td{ border-bottom: 0; }

/* ---- and the same for the scope items above them -----------------
 * An open item is followed by its steps panel, so the item's own line
 * would cut between the item and the panel it opened. The line moves
 * to the bottom of the panel, which is where the item actually ends. */
.impl-root .impl-table tbody tr.impl-lvl2 > td{ border-bottom: 2px solid var(--impl-sep); }
.impl-root .impl-table tbody tr.impl-lvl2.is-open > td{ border-bottom: 0; }
.impl-root .impl-table tbody td.impl-lvl3{ border-bottom: 2px solid var(--impl-sep); }

/* ---- the three bands inside an open scope item -------------------
 * Tokens and reasoning at :root. Each band is inset from the panel's
 * edge rather than bled to it, so the steps table between them keeps
 * reading as the panel's main content. */
.impl-root .impl-item-fields{
  background: var(--impl-item-fields-bg);
  border-radius: 8px;
  padding: 10px 12px;
  border: 1px solid var(--impl-sep);
}
/* ── the node hover card (owner 2026-09-26) ───────────────────────
 *
 * *"On hover, do not show the country codes ... Instead show the
 * requirement that has been captured (if at all) for the step. Also
 * show an attachment icon button."*
 *
 * A CSS hover, not React state. The card is a CHILD of the box you
 * hover, so moving the pointer onto the card keeps the box hovered and
 * the button stays clickable -- the gap between box and card that a
 * timer-based tooltip has to paper over does not exist. :focus-within
 * does the same for the keyboard. */
.sim-root .fx-hover{
  display: none;
  position: absolute; left: 0; top: calc(100% + 6px);
  z-index: 40; width: max(220px, 100%); max-width: 340px;
  padding: 8px 10px; border-radius: 8px;
  background: var(--fiori-surface-solid); color: var(--fiori-text);
  border: 1px solid var(--fiori-border);
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  text-align: left; font-weight: 400;
  cursor: default;
}
.sim-root .fx-card:hover > .fx-hover,
.sim-root .fx-card:focus-within > .fx-hover{ display: block; }
/* Portrait is ONE COLUMN, so a card below the box lands squarely on
   the next step and you cannot reach it without travelling through
   the card. To the side, where the column leaves the room free.
   The swimlane keeps it below: there the rows run across, so a card
   to the right would cover the very next step instead. */
.sim-root .fx > * .fx-hover,
.sim-root .fx > .fx-card .fx-hover{ left: calc(100% + 10px); top: 0; }
/* The box has to establish the positioning context, or the card lands
   against the page instead of against its own node. */
.sim-root .fx-card{ position: relative; }
.sim-root .fx-hover-t{ font-weight: 600; margin-bottom: 4px; }
.sim-root .fx-hover-req{
  font-size: 11.5px; white-space: pre-wrap;
  /* A requirement can run long. It scrolls rather than growing a card
     taller than the drawing. */
  max-height: 160px; overflow-y: auto;
}
.sim-root .fx-hover-none{
  font-size: 11.5px; font-style: italic; color: var(--fiori-text-muted);
}
/* .fx-hover-doc is gone. The attachment button moved off the hover
   card and into the box on 2026-09-26 -- see .fx-doc above. */

/* ── shared RICEF requirements (owner 2026-09-26) ─────────────────
   One extension standing on several scope item steps. */
.impl-root .impl-share-toggle{ margin: 2px 0; }
/* A text button, not a chip: it is a way INTO the picker, not a third
   state of the two-state toggle beside it. */
.impl-root .impl-share-count{
  background: none; border: 0; padding: 0 2px; margin: 0;
  font: inherit; color: var(--fiori-brand); cursor: pointer;
  text-decoration: underline dotted;
}
.impl-root .impl-share-count:hover{ text-decoration: underline; }
.impl-root .impl-share-group{
  padding: 6px 8px; border: 1px solid var(--impl-sep); border-radius: 6px;
}
.impl-root .impl-share-row{
  padding: 3px 2px; border-radius: 4px; cursor: pointer;
}
.impl-root .impl-share-row:hover{ background: var(--fiori-surface2); }

/* ── Master Tasks (owner 2026-09-26) ──────────────────────────────
   *"monitored together as a master task as well as at task item
   level."* The bar on the row IS that monitoring. */
/* My Tasks: the task description.
   Owner 2026-10-09: *"In light mode, show the Task description in
   Black font."* It was the cyan accent in both themes -- which is the
   clickable colour, and right on a near-black row, but on white it
   read as a link in a list where the whole row is clickable anyway.

   Dark keeps the cyan: there, near-black ink would be the thing that
   is actually unreadable. A class, not an inline style, so the two
   themes can differ at all -- and !important because the element
   carries an inline colour that would otherwise win. */
.mt-title{ color: #0B1220 !important; }
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]) .mt-title{ color: var(--fiori-accent-cyan) !important; }
}
:root[data-theme="dark"] .mt-title{ color: var(--fiori-accent-cyan) !important; }

/* Done, so struck through (owner, same day). It replaces the Status
   badge the owner removed in the same breath: the row still has to
   say the work is finished, and a line through it says so without
   spending a chip on it. The colour softens too -- a finished row
   that shouts as loudly as a live one is the reason people stop
   reading the list. */
.mt-title.is-done{ text-decoration: line-through; opacity: .62; }

.mt-type-pick{ display: inline-flex; gap: 6px; }
.mt-type-btn{
  padding: 4px 12px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface2-solid); color: var(--fiori-text);
  font-size: 12px; font-weight: 600;
}
.mt-type-btn.on{
  background: var(--fiori-brand); color: #fff; border-color: var(--fiori-brand);
}
/* Owner 2026-09-26: a master with tasks under it may not become an
   ordinary task. The button is DISABLED rather than hidden -- a
   control that vanishes leaves the person wondering where it went,
   and one that is greyed out with the reason beside it answers the
   question they were about to ask. */
.mt-type-btn:disabled{ cursor: not-allowed; opacity: .45; }
.mt-type-why{
  display: block; margin-top: 4px;
  font-size: 11px; line-height: 1.4; color: var(--fiori-text-muted);
}
/* ── the master bar, and the way to a sub-task (owner 2026-09-26) ──
 *
 * *"it should be possible to create a Sub-Task by clicking on a
 * hierarchy icon which only appears on the Master Task."*
 *
 * The bar and the icon are two buttons side by side, not one button
 * with something clickable inside it -- a nested button is dropped by
 * the browser and the icon simply stops working. */
.mt-master-wrap{ display: inline-flex; align-items: center; gap: 4px; margin-left: 8px; }
.mt-master-sub{
  display: inline-flex; align-items: center; justify-content: center;
  width: 22px; height: 22px; padding: 0; cursor: pointer;
  border: 1px solid rgba(139,92,246,0.55); border-radius: 6px;
  background: rgba(139,92,246,0.12);
  color: var(--fiori-text);
}
.mt-master-sub:hover{ background: rgba(139,92,246,0.24); }
.mt-master-sub:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }

.mt-master{
  display: inline-flex; align-items: center; gap: 6px;
  padding: 1px 8px 1px 4px;
  border: 1px solid rgba(139,92,246,0.55); border-radius: 999px;
  background: rgba(139,92,246,0.12);
  font-size: 10px; font-weight: 700; cursor: pointer;
  color: var(--fiori-text);
}
.mt-master-tag{ color: var(--ct-violet-500); letter-spacing: 0.04em; }
.mt-master-bar{
  width: 70px; height: 6px; border-radius: 999px; overflow: hidden;
  background: var(--fiori-surface2-solid);
  border: 1px solid var(--fiori-border);
}
.mt-master-fill{ display: block; height: 100%; background: var(--ct-violet-500); }
.mt-master-bar.is-done .mt-master-fill{ background: var(--fiori-positive); }
.mt-master-bar.is-open .mt-master-fill{ background: var(--fiori-border); }
.mt-master-pct{ font-variant-numeric: tabular-nums; }
.mt-master-count{ font-weight: 600; opacity: 0.8; }
.mt-master-pin{
  padding: 0 5px; border-radius: 999px;
  background: var(--fiori-surface2-solid); border: 1px solid var(--fiori-border);
}
/* The slipping signal, so it reads as a warning and not as decoration. */
.mt-master-late{ color: var(--ct-amber-500); font-weight: 700; }
.mt-master-chip{
  margin-left: 8px; padding: 1px 8px; border-radius: 999px;
  font-size: 10px; font-weight: 700;
  background: rgba(139,92,246,0.10); color: var(--ct-violet-500);
  border: 1px solid rgba(139,92,246,0.35);
}
.mt-master-warn{
  padding: 6px 10px; border-radius: 6px;
  background: rgba(245,158,11,0.12); border: 1px solid rgba(245,158,11,0.5);
  color: var(--ct-amber-500); font-size: 12px; font-weight: 600;
}
.mt-master-row{ padding: 3px 2px; border-radius: 4px; }
.mt-master-row:hover{ background: var(--fiori-surface2); }

/* ── the document panel (owner 2026-09-26) ────────────────────────
 *
 * *"a full height and half screen width panel locked to the right of
 * the screen till closed."*
 *
 * Deliberately NOT a modal: no backdrop and no focus trap, because
 * the point is to read the BPD WHILE answering the steps beside it.
 * A backdrop would make it one or the other. */
.doc-panel{
  position: fixed; top: 0; right: 0; bottom: 0;
  width: 50vw;
  /* Half the screen is unreadable on a laptop at 1280 and absurd on
     an ultrawide, so it is bounded at both ends. */
  min-width: 420px; max-width: 900px;
  display: flex; flex-direction: column;
  background: var(--fiori-surface-solid);
  border-left: 1px solid var(--fiori-border);
  box-shadow: -8px 0 28px rgba(0,0,0,0.18);
  /* ABOVE THE APP HEADER, not merely above a modal.
   *
   * This was 1200, which clears the modal overlays (1000-1200) but
   * NOT the shell's top bar at 9000. The bar is 85px tall and fixed,
   * so it painted straight over the top of a panel anchored at
   * top: 0 -- and the panel's whole header lives there: the file
   * name, Download, "< All", and the close button. The panel opened,
   * looked right, and could not be shut (owner, 2026-09-26: *"Unable
   * to close the right panel"*).
   *
   * 9500 sits above the shell (9000), above the Project Visualizer
   * drawer (9100-9102), and deliberately BELOW the replacement
   * confirm dialog at 10001 -- "remove this attachment?" has to be
   * readable over the panel that asked it. */
  z-index: 9500;
}
.doc-panel-h{
  display: flex; align-items: center; gap: 8px;
  padding: 10px 12px;
  border-bottom: 1px solid var(--fiori-border);
  background: var(--fiori-surface2-solid);
}
.doc-panel-t{
  font-weight: 600; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.doc-panel-b{ flex: 1 1 auto; min-height: 0; overflow: auto; padding: 14px 16px; }
/* The converted document runs the FULL width of the panel, and only
   its prose is measured.
   A BPD is mostly tables -- the real BNX one has 109 of them, five
   columns wide. Holding the whole document to a 70ch measure squeezed
   those columns to one word per line, which is technically inside the
   panel and useless to read. Paragraphs still get a measure; tables
   get the room. */
.doc-panel-doc{ line-height: 1.55; }
.doc-panel-doc > p, .doc-panel-doc > ul, .doc-panel-doc > ol{ max-width: 70ch; }
.doc-panel-doc img{ max-width: 100%; height: auto; }
/* `width: 100%` alone does NOT shrink a table whose content wants
   more room -- the five-column tables in the real BNX BPD came out
   779px wide and hung out of the panel. Breaking long words inside
   the cells is what lets the auto layout actually fit; auto rather
   than fixed, so a narrow column of step numbers stays narrow
   instead of claiming a fifth of the width. */
.doc-panel-doc table{
  border-collapse: collapse; width: 100%; max-width: 100%;
  margin: 10px 0;
  /* Its own horizontal scroll, which is the only answer that is both
     correct and readable. Squeezing the columns to fit (table-layout:
     fixed) made "Test Step #" wrap to one letter per line; letting
     cells break anywhere did the same to headings; breaking only long
     words left a 12px overflow and clipped content. A table that
     cannot fit scrolls, and the panel never does. */
  display: block; overflow-x: auto;
}
.doc-panel-doc td, .doc-panel-doc th{
  border: 1px solid var(--fiori-border); padding: 4px 6px; vertical-align: top;
  /* break-word, not `anywhere`: `anywhere` breaks eagerly and turned
     a narrow "Test Step #" heading into "Tes / t / Ste / p #". This
     breaks only a word that genuinely cannot fit. */
  overflow-wrap: break-word;
}
.doc-panel-doc h1, .doc-panel-doc h2, .doc-panel-doc h3{ margin: 14px 0 6px; line-height: 1.25; }
/* The frame fills the body: a PDF viewer with its own scrollbar
   inside a scrolling parent is two scrollbars for one document. */
.doc-panel-b:has(> .doc-panel-frame){ overflow: hidden; padding: 0; }
.doc-panel-frame{ width: 100%; height: 100%; border: 0; display: block; }
.doc-panel-img{ max-width: 100%; height: auto; display: block; margin: 0 auto; }
.doc-panel-pre{ white-space: pre-wrap; word-break: break-word; font-size: 12px; margin: 0; }

/* On a phone there is no "half the screen" worth having. */
@media (max-width: 860px) {
  .doc-panel{ width: 100vw; min-width: 0; max-width: none; }
}
@media print { .doc-panel{ display: none !important; } }

/* The file name in the list is a control now, not a link. */
.impl-root .impl-doc-open{
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; text-align: left; cursor: pointer;
  color: var(--fiori-brand);
}
.impl-root .impl-doc-open:hover{ text-decoration: underline; }

/* ── folder upload progress (owner 2026-09-26) ────────────────────
   *"show a progress bar. I tried uploading but seems like it did not
   go through."* */
.sim-root .sim-upl{ max-width: 420px; }
.sim-root .sim-upl-bar{
  height: 6px; border-radius: 999px; overflow: hidden;
  background: var(--fiori-surface2-solid);
  border: 1px solid var(--fiori-border);
}
.sim-root .sim-upl-fill{
  height: 100%; width: 0; border-radius: 999px;
  background: var(--fiori-brand);
  transition: width 120ms linear;
}
/* The server phase has no measurable length, so the bar travels
   instead of claiming a number it does not have. */
.sim-root .sim-upl-fill.is-busy{
  width: 40%;
  animation: sim-upl-slide 1.1s ease-in-out infinite;
}
@keyframes sim-upl-slide {
  0%   { margin-left: -40%; }
  100% { margin-left: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .sim-root .sim-upl-fill.is-busy{ animation: none; width: 100%; opacity: 0.6; }
}

/* ── scope item documents (owner 2026-09-26) ──────────────────────
   SAP's BPD / test script / setup guide, attached in bulk against the
   master and read from the project screens. */
.impl-root .impl-doc-btn{
  display: inline-flex; align-items: center; gap: 4px;
  cursor: pointer; vertical-align: middle;
}
.impl-root .impl-doc-list{
  list-style: none; margin: 0; padding: 0;
  display: flex; flex-direction: column; gap: 4px;
}
.impl-root .impl-doc-row{
  display: flex; align-items: center; gap: 8px;
  padding: 6px 8px; border: 1px solid var(--impl-sep); border-radius: 6px;
}
/* The name takes the room and the size stays put, so a column of
   documents lines up instead of ragging on the longest file name. */
.impl-root .impl-doc-name{
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  color: var(--fiori-brand); text-decoration: none;
}
.impl-root .impl-doc-name:hover{ text-decoration: underline; }

/* The scope item's name, which opens its steps. A button, because it
   is a control -- but it must still read as the row's title, so it
   keeps the cell's colour (the coverage tint) and its own weight. */
.impl-root .impl-cell-open{
  background: none; border: 0; padding: 0; margin: 0;
  font: inherit; color: inherit; font-weight: inherit;
  text-align: left; cursor: pointer;
  /* The whole name is the target, not just the first line of a
     wrapped one. */
  display: block; width: 100%;
}
.impl-root .impl-cell-open:hover{ text-decoration: underline; }
.impl-root .impl-cell-open:focus-visible{
  outline: 2px solid var(--fiori-brand); outline-offset: 2px; border-radius: 3px;
}
/* A value that is displayed and not edited: same box as the fields
   beside it so the row lines up, but plainly not a control. */
.impl-root .impl-ro-text{
  display: block; width: 100%;
  background: transparent;
  border: 0; border-bottom: 1px dashed var(--impl-sep);
  padding: 4px 0; min-height: 22px;
  opacity: 0.85; cursor: default; white-space: normal;
}
.impl-root .impl-add-step{
  background: var(--impl-add-step-bg);
  border-radius: 8px;
  padding: 8px 12px;
  border: 1px dashed var(--impl-sep);
}

/* ---- the Relevant answer on a step, as a box --------------------
 * Tokens and reasoning at :root. The box wraps the SELECT, so the
 * answer is readable at a glance down the column without opening
 * anything -- which is the point of the All/Selected toggle beside it. */
.impl-root .impl-rel-yes,
.impl-root .impl-rel-unsure,
.impl-root .impl-rel-no{
  display: inline-block; border-radius: 6px; padding: 2px;
  border: 1px solid transparent;
}
.impl-root .impl-rel-yes{    background: var(--impl-rel-yes-bg);    border-color: var(--impl-rel-yes-fg); }
.impl-root .impl-rel-unsure{ background: var(--impl-rel-unsure-bg); border-color: var(--impl-rel-unsure-fg); }
.impl-root .impl-rel-no{     background: var(--impl-rel-no-bg);     border-color: var(--impl-rel-no-fg); }

/* The same three answers on a flow node. The flow lives under
   .sim-root, not .impl-root, so the rules are stated for both scopes
   rather than the tokens being duplicated -- one set of colours, two
   places that draw with them. */
.sim-root .fx-card.impl-rel-yes{    background: var(--impl-rel-yes-bg);    border-color: var(--impl-rel-yes-fg); }
.sim-root .fx-card.impl-rel-unsure{ background: var(--impl-rel-unsure-bg); border-color: var(--impl-rel-unsure-fg); }
.sim-root .fx-card.impl-rel-no{     background: var(--impl-rel-no-bg);     border-color: var(--impl-rel-no-fg); }

/* The Flow button is now an icon, so it needs to be square and to hold
   the glyph centred -- the chip padding was written for text. */
/* The steps table must fit the panel it sits in, whatever that panel's
   width turns out to be. Owner 2026-09-25: *"The fields on the right
   edge are getting cut off."*
   
   table-layout: fixed makes the declared column widths the ANSWER
   rather than a hint the browser may exceed to fit content. Without it
   a long activity name or a wide select re-expands the column and the
   last one leaves the screen again. */
.impl-root .impl-steps-table{ table-layout: fixed; max-width: 100%; }
.impl-root .impl-steps-table td,
.impl-root .impl-steps-table th{ overflow-wrap: anywhere; }
/* The chips already wrap; this stops their cell from demanding the
   width of the widest unwrapped row. */
.impl-root .impl-steps-table .impl-row{ min-width: 0; }

/* The filter bar sits above the list rather than inside each group's
   table -- it used to be drawn once per module, seventeen copies of one
   control all driving the same state. */
.impl-root .impl-scope-filters{ background: var(--fiori-surface2); }

.impl-root button.impl-chip.impl-flow-btn{
  padding: 3px 5px; line-height: 0;
}

/* Level 4: one extension object under a step. */
.impl-root .impl-lvl4{
  background: var(--impl-lvl4-bg);
  box-shadow: inset 3px 0 0 0 var(--impl-lvl4-rail);
}

/* The rails are a second channel, not the only one: depth also reads
   from the fill and the indent, so nothing here is lost to a colour
   vision difference or a monochrome print. */
@media (prefers-contrast: more){
  .impl-root .impl-lvl1 > .impl-card-h,
  .impl-root .impl-lvl2 > td:first-child,
  .impl-root td.impl-lvl3,
  .impl-root .impl-lvl4{ box-shadow: inset 4px 0 0 0 currentColor; }
}

.impl-root .impl-col-grip{
  position: absolute; top: 0; right: -3px; width: 7px; height: 100%;
  cursor: col-resize; user-select: none; touch-action: none;
}
.impl-root .impl-col-grip::after{
  content: ''; position: absolute; top: 20%; bottom: 20%; left: 3px;
  width: 1px; background: var(--fiori-border);
}
.impl-root .impl-col-grip:hover::after,
.impl-root .impl-col-grip.is-dragging::after{
  background: var(--fiori-accent-cyan); top: 0; bottom: 0; width: 2px;
}
.impl-root .impl-dda-meta{ display: flex; align-items: center; gap: 18px; flex-wrap: wrap; padding: 12px 16px; }
.impl-root .impl-dda-kpi{ display: flex; flex-direction: column; min-width: 90px; }
.impl-root .impl-dda-kpi b{ font-size: 20px; font-weight: 700; line-height: 1.1; }
.impl-root .impl-dda-kpi span{ font-size: 10.5px; color: var(--fiori-text-muted); text-transform: uppercase; letter-spacing: .06em; }
.impl-root .impl-dda-bar{ display: inline-block; width: 90px; height: 6px; border-radius: 3px; background: rgba(127,127,127,.18); overflow: hidden; vertical-align: middle; }
.impl-root .impl-dda-bar i{ display: block; height: 100%; background: var(--fiori-accent-blue, #0a6ed1); }
.impl-root .impl-lbl{
  font-size: 10px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 600;
}
.impl-root .impl-tiny{ font-size: 11px; }
.impl-root .impl-muted{ color: var(--fiori-text-muted); }
/* The negative counterpart, beside .impl-muted because it is used the
   same way: a short line of text that has to read as a problem. Added
   2026-09-27 for the questionnaire upload report, where a workbook that
   could not be parsed states why -- and stated it in body colour, which
   is the one thing that line must not look like. */
.impl-root .impl-negative{ color: var(--fiori-negative); }
.impl-root .impl-row{ display: flex; align-items: center; gap: 10px; }
.impl-root .impl-wrapflex{ display: flex; flex-wrap: wrap; gap: 10px; }
.impl-root .impl-spacer{ flex: 1; }
.impl-root .impl-mono{ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; }
.impl-root .impl-nowrap{ white-space: nowrap; }
.impl-root .impl-num{ text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }

.impl-root .impl-bar{
  height: 6px; border-radius: 999px; background: var(--fiori-surface2); overflow: hidden;
}
.impl-root .impl-bar > i{
  display: block; height: 100%; border-radius: 999px; background: var(--neon-cyan);
}

.impl-root .impl-dotrag{ width: 8px; height: 8px; border-radius: 50%; flex: none; }
.impl-root .rag-green{ background: var(--fiori-positive); }
.impl-root .rag-amber{ background: var(--fiori-warning); }
.impl-root .rag-red  { background: var(--fiori-negative); }
/* Fourth state, Dashboards > Delivery (owner spec 2026-09-19): a
   project with no plan and no dates is NOT green. */
.impl-root .rag-unrated{ background: var(--fiori-text-muted); }

.impl-root .impl-avatar{
  border-radius: 50%; display: grid; place-items: center;
  font-weight: 800; color: var(--fiori-bg); flex: none;
}

/* ---- KPI strip ---- */
.impl-root .impl-kpis{
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px; margin: 14px 0;
}
.impl-root .impl-kpi{
  padding: 12px 14px; border-radius: 13px; border: 1px solid var(--fiori-border);
  background: var(--fiori-surface); transition: .14s;
}
.impl-root .impl-kpi.clickable{ cursor: pointer; }
.impl-root .impl-kpi.clickable:hover{ transform: translateY(-1px); border-color: var(--fiori-brand); }
.impl-root .impl-kpi .v{ font-size: 22px; font-weight: 700; letter-spacing: -.6px; margin-top: 3px; }
.impl-root .impl-kpi.sel{ border-color: var(--neon-cyan); box-shadow: 0 0 0 1px var(--neon-cyan) inset; }

/* ---- portfolio cards ---- */
.impl-root .impl-pgrid{
  display: grid; grid-template-columns: repeat(auto-fill, minmax(330px, 1fr)); gap: 12px;
}
.impl-root .impl-pcard{
  padding: 14px; border-radius: 14px; border: 1px solid var(--fiori-border);
  background: var(--fiori-surface); cursor: pointer; transition: .14s;
  display: flex; flex-direction: column; gap: 10px;
}
.impl-root .impl-pcard:hover{ transform: translateY(-2px); border-color: var(--fiori-brand); }
.impl-root .impl-pcard .ttl{ font-weight: 660; font-size: 13.5px; line-height: 1.3; }
.impl-root .impl-pcard .cust{ font-size: 11.5px; color: var(--fiori-text-muted); }
.impl-root .impl-metarow{
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; font-size: 11px;
}
.impl-root .impl-metarow .v{ font-weight: 660; font-size: 12.5px; margin-top: 1px; }

.impl-root .impl-pips{ display: flex; gap: 3px; }
.impl-root .impl-pip{ flex: 1; height: 4px; border-radius: 2px; background: var(--fiori-surface2); }
.impl-root .impl-pip.done{ background: var(--fiori-positive); }
.impl-root .impl-pip.now { background: var(--neon-cyan); }

.impl-root .impl-viewtoggle{
  display: flex; border: 1px solid var(--fiori-border); border-radius: 9px; overflow: hidden;
}
.impl-root .impl-viewtoggle button{
  padding: 0 12px; height: 30px; font-size: 11.5px; font-weight: 600;
  color: var(--fiori-text-muted); background: none; border: 0; cursor: pointer;
}
.impl-root .impl-viewtoggle button.on{ background: var(--fiori-brand); color: #fff; }

/* ---- SAP Activate phase ribbon ---- */
.impl-root .impl-ribbon{
  display: flex; gap: 6px; margin: 12px 0 4px; overflow-x: auto; padding-bottom: 4px;
}
/* Every clickable card in the implementation module is a <button> for
   keyboard reach. A button's DEFAULT colour is the UA's black -- invisible
   on the dark theme (owner report 2026-09-13: "Black font is not
   visible"). Inherit, once, for all of them; the .btn class keeps its own.

   This weighs (0,2,1) -- `.impl-root` + `:not(.btn)` + `button` -- so it
   OUTWEIGHS any rule that colours a button by class alone, `.ts-tab.on`
   (0,2,0) among about a hundred across this file. Those colours are set
   and never applied, and a dropped colour leaves no trace: the element
   simply inherits. Owner report 2026-09-27, the selected test-procedure
   tab drawing black on the brand blue, was one of them -- its white lost
   here, while the number beside it is a <span>, never subject to this
   rule, and stayed white.

   Writing this `:where(.impl-root) button:where(:not(.btn))` would weigh
   (0,0,1), still beat the UA default, and let every component rule
   through -- but that would make a hundred dead colours live at once,
   across the visualizer, the master tables and receiving, none of them
   measured. So it stays as it is, each offender is raised above it where
   it is found, and check-button-colour.js holds the rest as a baseline
   and fails on any new one. */
.impl-root button:not(.btn){ color: inherit; font-family: inherit; }
.impl-root .impl-phase{
  flex: 1; min-width: 132px; padding: 9px 12px; border-radius: 11px; cursor: pointer;
  border: 1px solid var(--fiori-border); background: var(--fiori-surface);
  position: relative; transition: .14s; text-align: left;
}
.impl-root .impl-phase:hover{ border-color: var(--fiori-brand); }
.impl-root .impl-phase .pn{ font-size: 12px; font-weight: 660; display: flex; align-items: center; gap: 6px; color: var(--fiori-text); }
.impl-root .impl-phase .pd{ font-size: 10.5px; color: var(--fiori-text-muted); margin-top: 3px; }
.impl-root .impl-phase.done{ border-color: color-mix(in srgb, var(--fiori-positive) 45%, transparent); }
.impl-root .impl-phase.done .pn{ color: var(--fiori-positive); }
.impl-root .impl-phase.active{ border-color: var(--neon-cyan); box-shadow: 0 0 0 1px var(--neon-cyan) inset; }
.impl-root .impl-phase.active .pn{ color: var(--neon-cyan); }
.impl-root .impl-gate{
  font-size: 9.5px; padding: 1px 6px; border-radius: 999px;
  border: 1px solid var(--fiori-border); color: var(--fiori-text-muted); display: inline-block;
}

/* ---- tab bar ---- */
.impl-root .impl-tabs{
  display: flex; gap: 2px; overflow-x: auto;
  border-bottom: 1px solid var(--fiori-border); margin: 12px 0 14px;
}
.impl-root .impl-tab{
  padding: 8px 13px; font-size: 12.5px; font-weight: 600; color: var(--fiori-text-muted);
  border: 0; border-bottom: 2px solid transparent; background: none; cursor: pointer;
  white-space: nowrap; display: flex; align-items: center; gap: 6px;
}

/* Two levels (owner spec 2026-09-16: "group each of the tabs under a
 * Header Tab"). Nineteen equal-weight tabs on one scrolling strip is a
 * list to read rather than a bar to navigate.
 *
 * The two rows have to look DIFFERENT or they read as one wrapped strip
 * and the hierarchy is lost: the headers are bigger and carry the rule
 * under them, the sub-tabs are smaller, lighter, and sit on no rule at
 * all. Only the open sub-tab is underlined, because that is the one
 * thing on screen that says where you are. */
.impl-root .impl-tabs-top{ margin-bottom: 0; }
.impl-root .impl-tabs-top .impl-tab{
  font-size: 13.5px; font-weight: 700; padding: 9px 15px; letter-spacing: 0.2px;
}
.impl-root .impl-tabs-sub{
  border-bottom: 0; margin: 6px 0 14px; gap: 1px;
}
.impl-root .impl-tabs-sub .impl-tab{
  font-size: 12px; font-weight: 600; padding: 5px 11px; border-bottom-width: 2px;
  border-radius: 6px 6px 0 0;
}
.impl-root .impl-tabs-sub .impl-tab:hover{ color: var(--fiori-text); }
.impl-root .impl-tab:hover{ color: var(--fiori-text); }
.impl-root .impl-tab.on{ color: var(--fiori-text); border-bottom-color: var(--neon-cyan); }
.impl-root .impl-tab .cnt{
  font-size: 10px; padding: 0 5px; border-radius: 999px;
  background: var(--fiori-surface2); color: var(--fiori-text-muted);
}

.impl-root .impl-toolbar{
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px;
}
/* background-COLOR, not the `background` shorthand. The shorthand also
   resets background-image, and background-image is where the app's
   select caret lives (the bare-select normalisation earlier in this
   file paints one on every <select>). Written as a shorthand, this
   rule silently erased the arrow from every `.impl-field` select in
   the Implementation module -- ComboField's Role field, the Customer
   team's Typical role, and every one like them -- so a dropdown was
   drawn as a plain box with nothing saying it opened. Found
   2026-09-20 while restyling the Typical role cell, which landed on
   exactly this. The same applies to the light-theme override above. */
.impl-root .impl-field{
  height: 30px; padding: 0 9px; border-radius: 8px; border: 1px solid var(--fiori-border);
  background-color: var(--fiori-surface2); font-size: 12px; color: var(--fiori-text);
}
.impl-root .impl-grid{ border-collapse: separate; border-spacing: 0; }
.impl-root .impl-grid th.impl-grid-th{
  /* The containing block for the absolutely-positioned resizer. It is
     HERE rather than inline on the cell so that a more specific rule can
     override it -- `.impl-plan-grid thead th` needs `sticky`, and an
     inline `position: relative` silently beat it for years. */
  position: relative;
  white-space: normal; line-height: 1.2; vertical-align: bottom; padding: 6px 8px;
  overflow: hidden; text-overflow: ellipsis;
  border-right: 1px solid var(--fiori-border);
}
.impl-root .impl-grid .impl-grid-th-text{
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.impl-root .impl-grid .impl-grid-resizer{
  position: absolute; top: 0; right: -3px; width: 7px; height: 100%; cursor: col-resize;
  touch-action: none; user-select: none; z-index: 2;
}
.impl-root .impl-grid .impl-grid-resizer:hover{ background: var(--fiori-brand); opacity: .6; }
.impl-root .impl-grid td.impl-grid-td{
  padding: 2px 4px; vertical-align: top; overflow: hidden;
  border-right: 1px solid var(--fiori-border); border-bottom: 1px solid var(--fiori-border);
}
/* Cell editors: no box of their own -- the cell IS the box. */
.impl-root .impl-grid .impl-cell{
  display: block; width: 100%; min-height: 28px; padding: 4px 6px; margin: 0;
  border: 1px solid transparent; border-radius: 4px; background: transparent;
  font-size: 12px; color: var(--fiori-text); font-family: inherit; box-sizing: border-box;
}
.impl-root .impl-grid select.impl-cell{
  -webkit-appearance: auto; -moz-appearance: auto; appearance: auto;
  padding: 4px 6px !important; background-image: none; height: 28px;
}
.impl-root .impl-grid .impl-cell-area{ resize: vertical; min-height: 44px; line-height: 1.35; white-space: pre-wrap; }
.impl-root .impl-grid .impl-cell:hover{ border-color: var(--fiori-border); }
.impl-root .impl-grid .impl-cell:focus{ outline: none; border-color: var(--fiori-brand); background: var(--fiori-surface2); }
.impl-root .impl-grid .impl-cell::placeholder{ color: var(--fiori-text-muted); opacity: .7; }
.impl-root .impl-grid tr.impl-grid-bad td.impl-grid-td{ box-shadow: inset 0 -1px 0 var(--fiori-negative); }
.impl-root .impl-plan-grid{ font-size: 12px; }

/* ── Project plan: no scrollbars of its own (owner spec 2026-09-16) ──
 *
 * The grid used to scroll in a box of its own, which put a second
 * vertical scrollbar inside the page and buried the horizontal one at
 * the bottom of a 300-row table.
 *
 * It cannot simply keep overflow-x and drop overflow-y: setting either
 * axis forces the other to `auto`, so a horizontally-scrolling wrapper
 * is a vertical one too -- and a sticky heading inside it scrolls away
 * with the page, which is exactly what a browser check showed. So the
 * grid scrolls in NEITHER axis and the page does both. That is what
 * lets the heading stick to the top and the first columns to the left
 * at the same time. */
/* BOTH classes in the selector, deliberately.
 *
 * The wrapper is `class="impl-scroll impl-plan-scroll"`, and
 * `.impl-root .impl-scroll { overflow: auto; max-height: 62vh }` is
 * declared ~300 lines BELOW this. Two selectors of equal specificity
 * (0,2,0) and the later one wins -- so for a day this rule was in the
 * file, passed its check, deployed, and did nothing at all. The plan
 * stayed a 62vh box: seventeen rows, a scrollbar, and a screen of
 * blank underneath (owner, 2026-09-17: "No change. Still the same").
 *
 * `.impl-scroll.impl-plan-scroll` is (0,3,0) and wins wherever it
 * sits, so this cannot be undone by somebody moving a block. Matching
 * on one class and relying on source order is the trap; do not go
 * back to it. */
.impl-root .impl-scroll.impl-plan-scroll{ overflow: visible; max-height: none; }

/* The heading, locked while the page scrolls down.
 *
 * The thead has TWO rows -- the labels and the filter boxes -- and this
 * rule matches every `th` in both. Pinned at one shared offset they
 * stack on top of each other as soon as the page scrolls, which is what
 * the owner saw on 2026-09-17 ("the column headers are overlapping"):
 * WEIGHT..PM/TEAM sitting half-behind the filter inputs while the
 * frozen WBS/ACTIVITY headings stayed put. So the offset comes per row
 * from --impl-plan-rowtop, which PlanGrid's measure() writes onto each
 * `thead tr` as a running total of the bar plus the rows above it; a
 * `th` inherits it from its row. --impl-plan-headtop is the fallback
 * for the first row and for any thead this has not measured yet. */
/* TWO selectors, deliberately. The heading cells carry `impl-grid-th`,
 * and `.impl-root .impl-grid th.impl-grid-th` -- which gives them the
 * `position: relative` the resizer needs -- is (0,3,1). A single
 * `.impl-root .impl-plan-grid thead th` is only (0,2,2) and LOSES to it,
 * so the cells would stay relative and the heading would not pin at all.
 * The second selector is (0,3,2) and wins. Measured in a browser both
 * ways: with the weak selector alone the frozen WBS cell sat at the top
 * of the viewport while its neighbour was 1199px above it. */
.impl-root .impl-plan-grid thead th,
.impl-root .impl-plan-grid thead th.impl-grid-th{ position: sticky; top: var(--impl-plan-rowtop, var(--impl-plan-headtop, 0px)); z-index: 30; background: var(--fiori-elevated-solid); }

/* WBS and Activity, locked while the page scrolls sideways. The left
 * offset is set per cell from the live column widths, because these
 * columns are resizable and a hard-coded offset would tear the moment
 * somebody dragged one. An opaque background is not decoration: without
 * it the columns scrolling underneath show through. */
.impl-root .impl-plan-grid td.impl-frozen,
.impl-root .impl-plan-grid th.impl-frozen{ background: var(--fiori-bg); }
.impl-root .impl-plan-grid thead th.impl-frozen{ background: var(--fiori-elevated-solid); }
.impl-root .impl-plan-grid tr.impl-plan-sum td.impl-frozen{ background: var(--fiori-surface2); }
.impl-root .impl-plan-grid tr.impl-plan-active td.impl-frozen{ background: var(--fiori-row-hover); }
/* The seam where the frozen block ends, so it reads as a boundary
 * rather than as two columns that happen not to move. */
.impl-root .impl-plan-grid td.impl-frozen:last-of-type{ box-shadow: 1px 0 0 var(--fiori-border); }

/* The horizontal scrollbar, a thin strip at the top of the grid: a real
 * scrollbar over a spacer the width of the grid, kept in step with the
 * page both ways. It sticks at the very top and the heading sticks
 * directly below it, at --impl-plan-headtop, which the grid sets from
 * this strip's measured height. Fixed height so that measurement is
 * stable. */
.impl-root .impl-plan-hbar{
  position: sticky; top: 0; left: 0; z-index: 35;
  overflow-x: auto; overflow-y: hidden;
  height: 14px; width: 100%;
  background: var(--fiori-bg);
}
/* Arrow-key navigation scrolls the row into view; this keeps it clear
 * of the bar and heading pinned above it instead of under them. */
.impl-root .impl-plan-grid tbody td{ scroll-margin-top: var(--impl-plan-pintop, 0px); }
.impl-root .impl-plan-grid td.impl-grid-td{ padding: 1px 6px; height: 28px; vertical-align: middle; white-space: nowrap; text-overflow: ellipsis; }
.impl-root .impl-plan-grid tr.impl-plan-sum td{ background: var(--fiori-surface2); }
.impl-root .impl-plan-grid tr.impl-plan-active td{ background: var(--fiori-row-hover); }
.impl-root .impl-plan-grid td.impl-plan-cell-active{ box-shadow: inset 0 0 0 2px var(--fiori-brand); }
.impl-root .impl-plan-grid .impl-cell{ min-height: 24px; padding: 2px 4px; }
.impl-root .impl-plan-st{ display: inline-block; border-radius: 99px; padding: 0 7px; font-size: 11px; font-weight: 500; line-height: 18px; }
.impl-root .impl-plan-st-not_started{ background: var(--fiori-surface2); color: var(--fiori-text-muted); }
.impl-root .impl-plan-st-wip{ background: color-mix(in srgb, var(--fiori-brand) 18%, transparent); color: var(--fiori-brand); }
.impl-root .impl-plan-st-done{ background: color-mix(in srgb, var(--fiori-positive) 18%, transparent); color: var(--fiori-positive); }
.impl-root .impl-plan-st-blocked{ background: color-mix(in srgb, var(--fiori-negative) 18%, transparent); color: var(--fiori-negative); }
.impl-root .impl-plan-pop{
  position: fixed; right: 24px; bottom: 24px; width: min(420px, calc(100vw - 32px)); max-height: 70vh; overflow: auto; z-index: 60;
  background: var(--fiori-elevated-solid); border: 1px solid var(--fiori-border); border-radius: 10px; padding: 12px;
  box-shadow: 0 12px 40px rgba(0,0,0,.35); font-size: 12.5px;
}
.impl-root .impl-plan-pick-row{ display: grid; grid-template-columns: 18px 1fr auto; gap: 8px; align-items: center; padding: 4px 2px; cursor: pointer; border-bottom: 1px solid var(--fiori-border); }
.impl-root .impl-plan-pick-row:hover{ background: var(--fiori-row-hover); }
.impl-root .impl-cal-months{ display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 12px; }
.impl-root .impl-cal-month{ border: 1px solid var(--fiori-border); border-radius: 8px; padding: 8px; }
.impl-root .impl-cal-title{ font-weight: 650; font-size: 12px; margin-bottom: 4px; }
.impl-root .impl-cal-grid{ display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.impl-root .impl-cal-dow{ font-size: 9px; color: var(--fiori-text-muted); text-align: center; text-transform: uppercase; }
.impl-root .impl-cal-day{ position: relative; height: 30px; border-radius: 4px; font-size: 10.5px; padding: 2px 3px; background: var(--fiori-surface2); color: var(--fiori-text); }
.impl-root .impl-cal-day.work{ background: color-mix(in srgb, var(--fiori-positive) 14%, transparent); }
.impl-root .impl-cal-day.off{ background: var(--fiori-surface2); color: var(--fiori-text-muted); }
.impl-root .impl-cal-day.hol{ background: color-mix(in srgb, var(--fiori-accent-amber) 28%, transparent); }
.impl-root .impl-cal-day.out{ opacity: .35; }
.impl-root .impl-cal-dots{ position: absolute; right: 2px; bottom: 2px; display: flex; gap: 1px; align-items: center; }
.impl-root .impl-cal-dots i{ font-style: normal; font-size: 8px; color: var(--fiori-text-muted); }
.impl-root .impl-cal-key{ display: inline-block; width: 10px; height: 10px; border-radius: 2px; vertical-align: middle; background: var(--fiori-surface2); }
.impl-root .impl-cal-key.work{ background: color-mix(in srgb, var(--fiori-positive) 30%, transparent); }
.impl-root .impl-cal-key.hol{ background: color-mix(in srgb, var(--fiori-accent-amber) 40%, transparent); }

/* ── a day marked by hand on this project ────────────────────────────
   Owner spec 2026-09-19: four options, two of which mean the day is
   worked. Colour alone cannot carry four values -- so the two working
   marks share one colour and are told apart by a letter, and the
   marked states carry a ring that the derived ones do not. What the
   ring says is "somebody decided this", which is the difference that
   matters when you are looking for the exceptions. */
.impl-root .impl-cal-day.markwork{
  background: color-mix(in srgb, var(--fiori-brand) 20%, transparent);
}
.impl-root .impl-cal-day.markhol{
  background: color-mix(in srgb, var(--fiori-accent-amber) 34%, transparent);
}
.impl-root .impl-cal-day.marked{ box-shadow: inset 0 0 0 1.5px var(--fiori-brand); }
.impl-root .impl-cal-day[role="button"]{ cursor: pointer; }
.impl-root .impl-cal-day[role="button"]:hover{ outline: 1px solid var(--fiori-brand); }
.impl-root .impl-cal-day.picking{ z-index: 12; }
.impl-root .impl-cal-mk{
  position: absolute; right: 3px; top: 1px; font-style: normal;
  font-size: 8.5px; font-weight: 700; color: var(--fiori-brand);
}
.impl-root .impl-cal-key.markwork{ background: color-mix(in srgb, var(--fiori-brand) 40%, transparent); }
.impl-root .impl-cal-key.markhol{ background: color-mix(in srgb, var(--fiori-accent-amber) 45%, transparent); }

/* The little menu. Anchored to the day rather than floated to the
   pointer: it belongs to that date, and a menu that follows the mouse
   makes you check which day you are actually editing. */
.impl-root .impl-cal-pick{
  position: absolute; top: calc(100% + 3px); left: 0; z-index: 30;
  min-width: 132px; padding: 4px;
  display: flex; flex-direction: column; gap: 2px;
  border-radius: 8px; border: 1px solid var(--fiori-border);
  background: var(--fiori-elevated-solid);
  box-shadow: 0 8px 26px rgba(0,0,0,.32);
  cursor: default;
}
.impl-root .impl-cal-pick-h{
  font-size: 10px; color: var(--fiori-text-muted); padding: 2px 6px 3px;
}
.impl-root .impl-cal-pick button{
  border: 0; background: none; color: inherit; font: inherit; font-size: 11px;
  text-align: left; padding: 4px 6px; border-radius: 5px; cursor: pointer;
}
.impl-root .impl-cal-pick button:hover:not(:disabled){ background: rgba(127,127,127,.16); }
.impl-root .impl-cal-pick button.on{ background: var(--fiori-brand); color: #fff; }
.impl-root .impl-cal-pick button.clr{ color: var(--fiori-text-muted); border-top: 1px solid var(--fiori-border); border-radius: 0 0 5px 5px; }
.impl-root .impl-cal-pick button:disabled{ opacity: .5; cursor: default; }
/* The month card clips its grid; a menu inside it would be cut off. */
.impl-root .impl-cal-month:has(.impl-cal-pick){ overflow: visible; }
.impl-root .impl-cal-grid:has(.impl-cal-pick){ overflow: visible; }
.impl-root .impl-fullscreen{
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  background: var(--fiori-bg); padding: 10px 14px; overflow: auto;
}
/* Owner 2026-09-16: "I want the full plan to be rendered without a
 * vertical scrolling bar. I should be able to move down using only the
 * up and down arrow keys."
 *
 * The box still SCROLLS -- wheel, trackpad, arrow keys and Page keys
 * all work; only the bar itself is gone, which is what was eating the
 * width. Scoped to the plan's own full-screen shell: the network view
 * shares the shell and keeps its bar, and the ordinary page keeps the
 * browser's, because hiding a scrollbar somebody is not expecting to
 * lose is how a page becomes unnavigable by mouse.
 *
 * The horizontal bar goes too -- the grid renders its own at the top,
 * where the owner asked for it, and two bars for one axis is one too
 * many. */
.impl-root .impl-fullscreen-plan{ scrollbar-width: none; -ms-overflow-style: none; }
.impl-root .impl-fullscreen-plan::-webkit-scrollbar{ width: 0; height: 0; }

.impl-root .impl-net-canvas{ position: relative; overflow: auto; border: 1px solid var(--fiori-border); border-radius: 10px; background: var(--fiori-surface2); user-select: none; touch-action: none; }
.impl-root .impl-net-lane{ position: absolute; left: 8px; border: 1px dashed var(--fiori-border); border-radius: 8px; background: color-mix(in srgb, var(--fiori-elevated-solid) 60%, transparent); }
.impl-root .impl-net-lane-head{ padding: 6px 10px; font-weight: 650; font-size: 12.5px; border-bottom: 1px dashed var(--fiori-border); }
.impl-root .impl-net-edges{ position: absolute; left: 8px; top: 0; pointer-events: none; overflow: visible; }
.impl-root .impl-net-edge{ pointer-events: auto; cursor: pointer; }
.impl-root .impl-net-edge .hit{ fill: none; stroke: transparent; stroke-width: 12px; }
.impl-root .impl-net-edge .line{ fill: none; stroke: var(--fiori-text-muted); stroke-width: 1.5px; }
.impl-root .impl-net-edge.rolled .line{ stroke-dasharray: 4 3; opacity: .7; }
.impl-root .impl-net-edge:hover .line, .impl-root .impl-net-edge.sel .line{ stroke: var(--fiori-brand); stroke-width: 2.5px; }
.impl-root .impl-net-edge .lbl{ font-size: 10px; fill: var(--fiori-text-muted); }
.impl-root .impl-net-linking{ fill: none; stroke: var(--fiori-brand); stroke-width: 2px; stroke-dasharray: 5 4; }
.impl-root .impl-net-box{ position: absolute; left: 8px; box-sizing: border-box; border: 1px solid var(--fiori-border); border-radius: 8px; background: var(--fiori-elevated-solid); padding: 5px 8px; cursor: grab; overflow: hidden; font-size: 12px; box-shadow: 0 2px 8px rgba(0,0,0,.18); }
.impl-root .impl-net-box.l2{ border-color: var(--fiori-brand); }
.impl-root .impl-net-box.l2.open{ background: color-mix(in srgb, var(--fiori-brand) 8%, var(--fiori-elevated-solid)); }
.impl-root .impl-net-box.l3{ font-size: 11px; border-style: solid; z-index: 2; }
.impl-root .impl-net-box.target:hover{ outline: 2px solid var(--fiori-positive); }
.impl-root .impl-net-box .hd{ display: flex; gap: 6px; align-items: baseline; font-size: 10.5px; color: var(--fiori-text-muted); }
.impl-root .impl-net-box .hd .pct{ margin-left: auto; font-weight: 600; color: var(--fiori-text); }
.impl-root .impl-net-box .nm{ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 500; }
.impl-root .impl-net-box .dt{ margin-top: 1px; }
.impl-root .impl-net-port{ position: absolute; right: -1px; top: 50%; transform: translate(50%, -50%); width: 14px; height: 14px; line-height: 14px; text-align: center; font-size: 12px; color: var(--fiori-brand); background: var(--fiori-elevated-solid); border-radius: 50%; cursor: crosshair; z-index: 3; }
.impl-root .impl-net-box.l2.open .nm, .impl-root .impl-net-box.l2.open .dt{ max-width: 100%; }
.impl-root .impl-chk{
  display: inline-flex; align-items: center; gap: 6px; font-size: 11.5px;
  color: var(--fiori-text-muted); cursor: pointer;
}

/* ---- WBS tree ---- */
.impl-root .impl-twist{
  width: 16px; display: inline-grid; place-items: center;
  color: var(--fiori-text-muted); cursor: pointer; flex: none;
}
.impl-root .impl-wname{ display: flex; align-items: center; gap: 7px; min-width: 0; }
.impl-root .impl-wname .t{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.impl-root .lvl0 .t{ font-weight: 680; }
.impl-root .lvl1 .t{ font-weight: 600; }
.impl-root .impl-wcode{
  font-family: ui-monospace, Menlo, monospace; font-size: 10.5px;
  color: var(--fiori-text-muted); flex: none;
}
.impl-root .impl-miniprog{ display: flex; align-items: center; gap: 7px; min-width: 104px; }
.impl-root .impl-miniprog .impl-bar{ flex: 1; }
.impl-root .impl-chips{ display: flex; gap: 4px; align-items: center; }
.impl-root .impl-chip{
  display: inline-flex; align-items: center; gap: 4px; padding: 1px 7px 1px 2px;
  border-radius: 999px; background: var(--fiori-surface2);
  border: 1px solid var(--fiori-border); font-size: 10.5px;
}
.impl-root .impl-chip.add{
  padding: 1px 8px; color: var(--fiori-text-muted); border-style: dashed; cursor: pointer;
}
.impl-root .impl-chip.add:hover{ color: var(--fiori-brand); border-color: var(--fiori-brand); }

/* ---- a chip you can CLICK, and one that is ON --------------------
 *
 * Owner 2026-09-25: *"How do I select the extension requirements
 * R I C ...."* -- he could not, and it was not his fault. ExtPicker
 * renders each code as a <button class="impl-chip impl-chip-on"> when
 * it is selected, and `impl-chip-on` was never written. The class
 * appeared in exactly one place in the codebase: the JSX that adds it.
 *
 * So the click worked, the value saved, and NOTHING on screen changed.
 * A control that accepts input and gives no feedback reads as broken,
 * and the natural next move is to click it again -- which deselects
 * what was just selected.
 *
 * Only button chips get this. ExtLinks renders read-only <span> chips
 * with the same class, and those must keep looking like labels. */
.impl-root button.impl-chip{
  padding: 1px 8px;
  cursor: pointer;
  color: var(--fiori-text-muted);
  transition: background-color .12s ease, border-color .12s ease, color .12s ease;
}
.impl-root button.impl-chip:hover:not(:disabled){
  color: var(--fiori-brand); border-color: var(--fiori-brand);
}
.impl-root button.impl-chip.impl-chip-on{
  background: var(--fiori-brand);
  border-color: var(--fiori-brand);
  color: #fff;
  font-weight: 650;
}
.impl-root button.impl-chip.impl-chip-on:hover:not(:disabled){
  background: var(--fiori-brand-hover); border-color: var(--fiori-brand-hover); color: #fff;
}
.impl-root button.impl-chip:disabled{ cursor: default; }
/* Keyboard users need to see where they are before they press space. */
.impl-root button.impl-chip:focus-visible{
  outline: 2px solid var(--fiori-brand); outline-offset: 1px;
}

/* ---- a module the Scope Item Master import ADDED (owner spec
   2026-09-22: "Add. But color the Scope IT with red fill") ----

   The import now widens a project where the master says it must --
   PRJ/0002 had 36 real scope items sitting blank because their module
   was not on Overall Scope -- and these two rules are the whole reason
   that is safe to do. A module somebody agreed to and one that arrived
   from a spreadsheet look different until a person says otherwise.

   A tint on the ROW, not red text in a cell: the state belongs to the
   scope item, and red text would read as "this value is wrong" when
   every value on the line is right. `!important` on the hover only,
   because the generic tbody hover is declared later and would
   otherwise wash the tint out under the pointer -- which is exactly
   when somebody is looking at the row. */
.impl-root .impl-table tbody tr.impl-row-from-master td{
  background: rgba(239, 68, 68, .13);
}
.impl-root .impl-table tbody tr.impl-row-from-master:hover td{
  background: rgba(239, 68, 68, .2) !important;
}
.impl-root .impl-chip.impl-chip-danger{
  padding: 1px 8px;
  color: var(--ct-red-500, #EF4444);
  border-color: var(--ct-red-500, #EF4444);
  background: rgba(239, 68, 68, .12);
}

/* ---- the Module picker on a team row (owner spec 2026-09-20) ----
   Chips at rest, this while it is open. It sits in a TABLE CELL, so it
   gets its own surface and border: without them the checkbox chips
   read as part of the row beside them and there is no edge saying
   where the question stops. A max-width rather than a fixed one --
   a project scoped to three modules should not reserve room for
   twenty. */
.impl-root .impl-mpick{
  display: flex; flex-wrap: wrap; gap: 4px; align-items: center;
  max-width: 340px; padding: 5px 6px; margin: -3px -2px;
  border: 1px solid var(--fiori-border); border-radius: 8px;
  background: var(--fiori-surface2);
}
.impl-root .impl-mpick-g{ display: flex; flex-wrap: wrap; gap: 4px; align-items: center; width: 100%; }

/* ---- Scope Item Master (owner spec 2026-09-20) -------------------
   A master with twenty-one fields and a thousand rows. The grid shows
   eight of them and the modal shows all, because a twenty-one column
   grid is a horizontal scrollbar nobody uses. */
.sim-root .sim-upload{ display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.sim-root .sim-upload label{ display: flex; flex-direction: column; gap: 3px; }

.sim-root .sim-releases{ display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.sim-root .sim-rel{
  display: flex; flex-direction: column; gap: 1px; text-align: left;
  padding: 7px 11px; border: 1px solid var(--fiori-border); border-radius: 10px;
  background: var(--fiori-surface2); color: var(--fiori-text); cursor: pointer; font: inherit;
}
.sim-root .sim-rel b{ font-size: 14px; }
.sim-root .sim-rel.on{ border-color: var(--fiori-brand); background: color-mix(in srgb, var(--fiori-brand) 12%, transparent); }

.sim-root .sim-filters{
  display: grid; gap: 9px 8px; margin-top: 9px; padding: 9px 10px;
  grid-template-columns: repeat(auto-fill, minmax(185px, 1fr));
  border: 1px solid var(--fiori-border); border-radius: 10px; background: var(--fiori-surface2);
  align-items: start;
}
.sim-root .sim-filter{ display: flex; flex-direction: column; gap: 2px; min-width: 0; }

/* ── Filter cell: a label, an operator and any number of values ──
   Owner spec 2026-09-20: "In filters show dropdowns and mutiple
   selection should be possible. Also possible to set values not equal
   to or does not contain."

   The popover is absolutely positioned against the cell rather than
   rendered in a portal, which is why the cell is position:relative and
   the cell is NOT overflow-hidden -- a clipped popover on the last
   column of the grid is the usual way this goes wrong. */
/* A full-width heading inside the filter grid, marking where the
   step's own fields end and the scope item's begin. */
.sim-root .sim-fgroup{
  grid-column: 1 / -1; margin-top: 4px;
  font-size: 10.5px; font-weight: 700; letter-spacing: .5px; text-transform: uppercase;
  color: var(--fiori-text-muted);
  border-top: 1px solid var(--fiori-border); padding-top: 8px;
}
.sim-root .sim-fgroup:first-child{ border-top: 0; padding-top: 0; margin-top: 0; }
.sim-root .sim-fcell{ position: relative; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.sim-root .sim-flabel{ white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.sim-root .sim-fop{ height: 26px; padding: 0 22px 0 7px; font-size: 11px; }
/* Wraps rather than clips: every chosen value is on screen, however
   many there are. `align-items: start` on .sim-filters means a cell
   that grows does not stretch its neighbours. */
.sim-root .sim-fbtn{
  display: flex; align-items: flex-start; gap: 6px; width: 100%;
  text-align: left; cursor: pointer; min-height: 30px; height: auto;
  padding-top: 4px; padding-bottom: 4px;
}
.sim-root .sim-fbtn.on{ border-color: var(--fiori-brand); }
.sim-root .sim-fbtn:disabled{ cursor: default; opacity: .6; }
.sim-root .sim-fcaret{ margin-left: auto; font-size: 9px; opacity: .55; padding-top: 3px; }
.sim-root .sim-fchips{ display: flex; gap: 4px; align-items: center; flex-wrap: wrap; min-width: 0; }
.sim-root .sim-fchip{
  display: inline-block; max-width: 110px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; padding: 1px 7px; border-radius: 999px; font-size: 11px; font-weight: 600;
  background: color-mix(in srgb, var(--fiori-brand) 14%, transparent);
  border: 1px solid color-mix(in srgb, var(--fiori-brand) 40%, transparent);
}
.sim-root .sim-fpop{
  position: absolute; z-index: 40; top: 100%; left: 0; margin-top: 4px;
  width: max(250px, 100%); max-width: 340px;
  background: var(--fiori-surface-solid); border: 1px solid var(--fiori-border);
  border-radius: 10px; box-shadow: 0 12px 30px rgba(0,0,0,.24);
  display: flex; flex-direction: column; overflow: hidden;
}
.sim-root .sim-fsearch{ margin: 7px; width: calc(100% - 14px); }
.sim-root .sim-flist{ max-height: 244px; overflow-y: auto; padding: 0 5px 5px; }
.sim-root .sim-fopt{
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 5px 8px; border: 0; border-radius: 7px; background: transparent;
  color: inherit; font: inherit; font-size: 12px; text-align: left; cursor: pointer;
}
.sim-root .sim-fopt:hover{ background: var(--fiori-surface2); }
.sim-root .sim-fopt.on{ background: color-mix(in srgb, var(--fiori-brand) 12%, transparent); font-weight: 600; }
.sim-root .sim-ftick{ width: 12px; flex: 0 0 12px; font-size: 11px; color: var(--fiori-brand); }
.sim-root .sim-fv{ flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sim-root .sim-ftyped{ font-style: italic; }
.sim-root .sim-ffoot{
  display: flex; gap: 6px; justify-content: flex-end;
  padding: 7px; border-top: 1px solid var(--fiori-border);
}

/* ── Sortable column headers ── */
.sim-root .sim-th.sorted{ background: color-mix(in srgb, var(--fiori-brand) 9%, transparent); }
.sim-root .sim-sort{
  display: flex; align-items: center; gap: 5px; width: 100%;
  border: 0; background: transparent; color: inherit;
  font: inherit; font-weight: inherit; padding: 0; cursor: pointer; text-align: left;
}
.sim-root .sim-sort:hover .sim-arrow{ opacity: 1; }
.sim-root .sim-arrow{ font-size: 9px; opacity: .3; margin-left: auto; }
.sim-root .sim-th.sorted .sim-arrow{ opacity: 1; color: var(--fiori-brand); }

/* ── The detail modal, in two columns (owner spec 2026-09-20) ──
   The item on the left, its steps on the right. The right column
   scrolls on its own: an item with ninety-one steps would otherwise
   push the fields it belongs to off the top of the dialog. */
.sim-root .sim-dcols{
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 420px); gap: 16px;
  align-items: start;
}
.sim-root .sim-dleft{ min-width: 0; }
.sim-root .sim-dright{
  min-width: 0; max-height: 64vh; overflow-y: auto;
  border-left: 1px solid var(--fiori-border); padding-left: 14px;
}
@media (max-width: 900px) {
  /* One column on a narrow screen, with the steps beneath rather than
     squeezed beside. */
  .sim-root .sim-dcols{ grid-template-columns: 1fr; }
  .sim-root .sim-dright{ border-left: 0; padding-left: 0; border-top: 1px solid var(--fiori-border); padding-top: 12px; max-height: none; }
}
.sim-root .sim-steps h4{ margin: 0 0 8px; font-size: 13px; font-weight: 700; display: flex; gap: 7px; align-items: baseline; }
.sim-root .sim-sact{ margin-bottom: 12px; }
.sim-root .sim-sact-h{
  font-size: 12px; font-weight: 700; color: var(--fiori-brand);
  margin-bottom: 5px; line-height: 1.35;
}
.sim-root .sim-step{
  /* align-items: a country chip is as wide as its two letters. Left to
     stretch, a lone "Global" fills the card and reads as a banner. */
  display: flex; flex-direction: column; gap: 2px; align-items: flex-start;
  padding: 6px 9px; margin-bottom: 5px;
  border: 1px solid var(--fiori-border); border-radius: 8px;
  background: var(--fiori-surface2); font-size: 12px;
}
.sim-root .sim-step-t{ display: flex; gap: 7px; align-items: baseline; flex-wrap: wrap; }
.sim-root .sim-fid{
  font-size: 10.5px; padding: 0 5px; border-radius: 4px;
  background: var(--fiori-surface-solid); border: 1px solid var(--fiori-border);
  color: var(--fiori-text-muted);
}


/* ── The step flow (owner spec 2026-09-20) ──────────────────────
   "the different apps as two or more parallel paths that combine at
    the next step ... the flowchart must be modern UI and beautiful"
   and then: "compact with just the step description (solution activity)
   and Business Role description (below in small font)".

   Drawn with CSS rather than SVG: the text stays selectable, it reflows
   at a narrow width, and it takes the theme's own tokens rather than a
   second palette that drifts from the first.

   The RAIL is the whole trick. Every connector is a 2px line centred by
   the column's own flex alignment, so a split and a merge are one
   element each rather than a set of coordinates that has to be
   recomputed for two lanes, three lanes or nine. */
.sim-root .fx{
  display: flex; flex-direction: column; align-items: center;
  padding: 6px 2px 10px; gap: 0;
}
.sim-root .fx-start,
.sim-root .fx-end{
  font-size: 10.5px; font-weight: 700; letter-spacing: .6px; text-transform: uppercase;
  padding: 4px 16px; border-radius: 999px;
  background: color-mix(in srgb, var(--fiori-brand) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--fiori-brand) 40%, transparent);
  color: var(--fiori-brand);
}
/* The connector between two nodes, with its arrowhead. */
.sim-root .fx-link{
  width: 2px; height: 16px; flex: 0 0 16px;
  background: var(--fiori-border); position: relative;
}
.sim-root .fx-link::after{
  content: ''; position: absolute; left: 50%; bottom: -1px;
  width: 6px; height: 6px; margin-left: -3px;
  border-right: 2px solid var(--fiori-border);
  border-bottom: 2px solid var(--fiori-border);
  transform: rotate(45deg);
}

/* ── one step ── */
.sim-root .fx-card{
  width: 100%; max-width: 340px; min-width: 0;
  box-sizing: border-box;
  padding: 8px 11px; border-radius: 10px;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface-solid);
  /* The brand edge is what makes a column of cards read as a sequence
     rather than as a stack of boxes. */
  border-left: 3px solid color-mix(in srgb, var(--fiori-brand) 55%, transparent);
  box-shadow: 0 1px 2px rgba(0,0,0,.05);
  position: relative;
}
.sim-root .fx-t{
  font-size: 12.5px; font-weight: 600; line-height: 1.35; color: var(--fiori-text);
  overflow-wrap: anywhere;
}
/* Room for the app tag, only where one is drawn. */
.sim-root .fx-card:has(> .fx-app) .fx-t{ padding-right: 58px; }
.sim-root .fx-r{
  font-size: 10.5px; line-height: 1.4; margin-top: 2px; color: var(--fiori-text-muted);
  overflow-wrap: anywhere;
}
.sim-root .fx-r.fx-none{ font-style: italic; opacity: .8; }

/* ── the three relevance buttons on a node (owner 2026-09-26) ──────
 *
 * *"3 buttons on the right bottom side of each box ... No, Not Sure,
 * Yes (in this order to make it easier for use to select Yes option)."*
 *
 * His order and his reason: Yes is pressed most, so it sits last --
 * nearest the thumb and nearest where the eye finishes.
 *
 * The UNSELECTED buttons are deliberately quiet. Three saturated
 * buttons on every one of 28 nodes is a wall of colour that drowns the
 * node fill, which is the thing actually carrying the answer. */
.sim-root .fx-rel{
  display: flex; gap: 3px; justify-content: flex-end;
}

/* ── the bottom row of a node (owner 2026-09-26) ────────────────────
 *
 * *"The attachment option should be on the box on the left bottom
 * inside of the step box (in line with the No, Yes...buttons)."*
 *
 * One row, attachments left, answer right. The gap between them is a
 * flex spacer rather than `justify-content: space-between`, so a box
 * with nothing on the left still puts its answer hard right instead
 * of centring it. */
.sim-root .fx-foot{
  display: flex; align-items: center; gap: 4px;
  margin-top: 6px;
}
.sim-root .fx-foot-gap{ flex: 1 1 auto; min-width: 6px; }
.sim-root .fx-doc{ display: flex; align-items: center; gap: 3px; }
/* The clip and the count are the same pill, because they are one
   affordance read left to right -- "documents: three". They are two
   BUTTONS because he asked them to do two different things. */
.sim-root .fx-doc-clip,
.sim-root .fx-doc-n{
  display: inline-flex; align-items: center; gap: 3px;
  font-size: 9.5px; font-weight: 600; line-height: 1;
  padding: 3px 6px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface2);
  color: var(--fiori-text-muted);
  transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.sim-root .fx-doc-clip{ padding: 3px 5px; }
.sim-root .fx-doc-clip:hover:not(:disabled),
.sim-root .fx-doc-n:hover{ border-color: var(--fiori-brand); color: var(--fiori-brand); }
.sim-root .fx-doc-clip:disabled{ cursor: default; opacity: .6; }
.sim-root .fx-doc-clip:focus-visible,
.sim-root .fx-doc-n:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }
/* A step that HAS files says so without being hovered: the count is
   the only thing on the box that carries information the box does not
   already show. */
.sim-root .fx-doc-n{
  border-color: var(--fiori-brand); color: var(--fiori-brand);
  font-variant-numeric: tabular-nums;
}
.sim-root .fx-rel-btn{
  font-size: 9.5px; font-weight: 600; line-height: 1;
  padding: 3px 7px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface2);
  color: var(--fiori-text-muted);
  transition: background-color .12s ease, color .12s ease, border-color .12s ease;
}
.sim-root .fx-rel-btn:hover:not(:disabled){ border-color: var(--fiori-text-muted); color: var(--fiori-text); }
.sim-root .fx-rel-btn:disabled{ cursor: default; opacity: .6; }
.sim-root .fx-rel-btn:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }

/* The chosen one takes the same colour its answer gives the node, so
   the button and the fill say the same thing. */
.sim-root .fx-rel-btn.fx-rel-yes.is-on{
  background: var(--impl-rel-yes-fg); border-color: var(--impl-rel-yes-fg); color: #fff;
}
.sim-root .fx-rel-btn.fx-rel-not_sure.is-on{
  background: var(--impl-rel-unsure-fg); border-color: var(--impl-rel-unsure-fg); color: #fff;
}
.sim-root .fx-rel-btn.fx-rel-no.is-on{
  background: var(--impl-rel-no-fg); border-color: var(--impl-rel-no-fg); color: #fff;
}

/* ── the swimlane (owner 2026-09-26) ────────────────────────────────
 *
 * *"an option for flow to be displayed in horizontal form (landscape
 * mode) where the boxes have word wrap and boxes also wrap to the next
 * line"*, then: *"It should be like a swimlane based on user role."*
 *
 * One row per role, the role's steps left to right, wrapping within
 * the row. A 28-step process is 28 scroll-lengths tall as a column;
 * as lanes it is a handful of rows and readable at once, and it also
 * answers a question the column cannot: who does most of this.
 *
 * The step NUMBER carries the order across lanes. A connector between
 * rows cannot be drawn honestly once a row wraps -- it would have to
 * leave the end of one line and arrive somewhere that is not where the
 * next step is. The number does the job a connector would, which is
 * how swimlanes on paper have always done it. */
/* ── ONE COLUMN PER STEP (owner 2026-09-26) ─────────────────────────
 *
 * *"The arrows and lines dont look proper."*
 *
 * They were not the fault. Each lane used to pack its own steps from
 * the left in a wrapping flexbox, so a box's horizontal position meant
 * "this role's Nth step" -- step 1 sat above step 12, and 8 of the 24
 * connectors on the real BNX flow ran right-to-left, some of them
 * 1,500px. Measured on that drawing: 55 crossings, and 0px of line
 * inside any box. The router was already doing its job.
 *
 * A swimlane's horizontal axis is TIME. Column = step number, shared
 * by every lane; a role is simply absent from the columns it has no
 * step in. Step N and N + 1 are then always neighbours. Re-measured on
 * the same 25 boxes: 0 crossings, 0 backwards, ink nearly halved.
 *
 * ONE grid for the whole drawing, not a grid per lane: columns line up
 * across lanes only if every box is placed in the same grid.
 *
 * --fx-cols is set inline by the component. Custom properties are
 * substituted before the grid shorthand is parsed, which is what makes
 * repeat(var(--fx-cols), ...) legal -- verified in Chromium rather
 * than assumed. It is a variable rather than a hand-built template so
 * print can narrow every column by redefining --fx-col-w alone. */
/* ── THE WRAP (owner 2026-09-27) ───────────────────────────────────
 *
 * *"instead of having so many columns, we can wrap (like word wrap)
 * the boxes by increasing the height of the swimlane(role). This way
 * the landscape format can be printed easily on an A4 size paper."*
 *
 * .fx-swim is the stack of sheets, and each sheet holds one bpmn-js
 * canvas. It is a plain block: there is no CSS layout left in the
 * drawing at all. Everything inside a sheet is BPMN, positioned by
 * coordinates in the document and drawn by bpmn-js into an SVG --
 * which is what ended three rounds of the cards clipping, the app
 * chips breaking out of their boxes and Chromium fragmenting the grid
 * on paper however it liked. */
.sim-root .fx-swim{
  display: block;
  width: 100%;
  background: var(--fx-swim-bg);
  padding: 6px 2px 10px;
  position: relative;
}
/* ── THE SHEET TABS (owner 2026-09-27) ─────────────────────────────
 *
 * *"Show tabs on top for different sheets where the roles can
 * repeat."* The wrap made the drawing five bands tall, which on
 * screen is five screenfuls with the lane labels repeating down the
 * page and no way to tell how many sheets there are. One tab per
 * sheet answers both, and it is the same unit the paper uses -- what
 * you are looking at is what comes out of the printer.
 *
 * Sticky, because the drawing scrolls sideways under it and a tab
 * strip that slides off is a tab strip you have to scroll back for.
 * Opaque for the same reason the lane label is. */
.sim-root .fx-tabs{
  position: sticky; left: 0; top: 0; z-index: 5;
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 2px 0 10px;
  background: var(--fx-swim-bg);
}
.sim-root .fx-tab{
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px;
  padding: 5px 11px; border-radius: 8px; cursor: pointer;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface2); color: var(--fiori-text2);
  font: inherit; text-align: left;
}
.sim-root .fx-tab:hover{ border-color: var(--fiori-brand); }
.sim-root .fx-tab:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }
/* The selected tab takes the brand fill rather than only a heavier
   border: on a drawing this busy a 1px difference does not read. */
.sim-root .fx-tab.is-on{
  background: var(--fiori-brand); border-color: var(--fiori-brand);
  color: var(--fiori-on-accent, #fff);
}
.sim-root .fx-tab-n{ font-size: 11px; font-weight: 700; line-height: 1.2; }
/* The step range under the sheet number: the number is how you say
   which sheet, the range is how you find the step you are after. */
.sim-root .fx-tab-r{ font-size: 9.5px; font-weight: 600; opacity: .78; line-height: 1.2; }






/* A band after the first is a new line of the paragraph, and it needs
   to read as one -- hence the rule above it and the space. In print
   this is also where a page break is allowed to fall, and nowhere
   else: break-inside keeps a band whole on one sheet. */
/* ── THE bpmn-js CANVAS ────────────────────────────────────────────
 *
 * A height is REQUIRED. bpmn-js measures its container to decide the
 * viewport, and a container that sizes itself to its content measures
 * zero before the diagram is in it -- the canvas then lays out at
 * zero and `zoom('fit-viewport')` fits the drawing into nothing. So
 * the sheet is given a height and the drawing is fitted into it,
 * rather than the other way round.
 *
 * 58vh keeps a sheet and the tab strip above it on one screen without
 * the modal itself scrolling, with a floor for short windows. */
.sim-root .fx-bpmn-wrap{ position: relative; }
.sim-root .fx-bpmn{
  height: 58vh; min-height: 360px; width: 100%;
  background: var(--fx-swim-bg);
}
/* bpmn-js paints its own white and its own greys. On a dark theme
   that is a white sheet in a dark modal, which is exactly what a
   printed page looks like and reads as deliberate -- so the canvas
   keeps its own colours and only the frame around it is themed. */
.sim-root .fx-bpmn .djs-container{ background: #fff; }
/* The bpmn.io mark is a licence condition: it must stay visible and
   must not be overlapped. Nothing here may give it a z-index, an
   opacity or a background that covers it. */
.sim-root .fx-bpmn .bjs-powered-by{ opacity: 1; }
/* ── THE PRINTED SHEET ─────────────────────────────────────────────
 *
 * The live canvas stays mounted while printing -- it is what produces
 * the SVG -- but it is taken off the page rather than unmounted,
 * because unmounting it would take the thing that draws with it.
 * Off-screen, not display:none: a canvas with no layout measures zero
 * and bpmn-js fits the drawing into nothing. */
.sim-root .fx-bpmn.is-source{
  position: absolute; left: -10000px; top: 0;
  width: 1100px; height: 700px;
}
/* The exported SVG is the sheet. Width 100% with the SVG's own
   viewBox doing the scaling, so the same drawing fits whatever the
   printer gives it -- and an SVG is one atomic element, which is what
   makes it unfragmentable where the CSS grid was not. */
.sim-root .fx-bpmn-print{ width: 100%; }
/* PAPER IS WHITE. The drawing takes --fx-swim-bg, which on a dark
   theme is dark -- and the print sheet sets print-color-adjust: exact
   so that backgrounds survive, which means a reader on the dark theme
   was printing a black page with a pale strip of diagram on it.
   The theme is for the screen; the sheet is a sheet. */
body.is-printing .flow-print-host .fx-swim,
body.is-printing .flow-print-host .fx-sheet,
body.is-printing .flow-print-host .fx-bpmn-print{ background: #fff; }
body.is-printing .flow-print-host .fx-key{ color: #333; }
body.is-printing .flow-print-host .fx-key-note{ color: #666; }
/* THE LICENCE, ON PAPER TOO.
   bpmn.io's terms require the mark to stay visible on rendered
   diagrams, and bpmn-js's own saveSVG does not put it in the export
   -- so a printed sheet would carry none. It is added here rather
   than left to chance: the owner accepted the mark on screen and on
   paper, and this is the half bpmn-js does not do for us. */
.sim-root .fx-bpmn-credit{
  font-size: 9px; letter-spacing: .04em; text-align: right;
  padding: 2px 4px 0; color: #667;
}
.sim-root .fx-bpmn-print svg{ width: 100%; height: auto; max-width: 100%; }
.sim-root .fx-bpmn-fail{
  padding: 10px 12px; font-size: 12px;
  color: var(--fiori-negative); background: var(--fiori-surface2);
  border: 1px solid var(--fiori-border); border-radius: 8px;
}

/* ── THE CONTROLS OVER THE CANVAS ─────────────────────────────────
 *
 * The relevance buttons and the paperclip are controls, not notation,
 * so they are bpmn-js overlays rather than anything in the BPMN. They
 * are HTML over the canvas and bpmn-js leaves them out of saveSVG, so
 * they do not print -- which is why the ANSWER is carried by the
 * task's fill colour, which is in the document and does print. */
.sim-root .fx-ov{
  display: flex; align-items: center; gap: 3px;
  white-space: nowrap; pointer-events: all;
}
.sim-root .fx-ov button{
  font: inherit; font-size: 10px; font-weight: 700; line-height: 1;
  padding: 3px 7px; border-radius: 999px; cursor: pointer;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface-solid); color: var(--fiori-text2);
}
.sim-root .fx-ov button:hover{ border-color: var(--fiori-brand); }
.sim-root .fx-ov button:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }
.sim-root .fx-ov button:disabled{ opacity: .5; cursor: default; }
.sim-root .fx-ov-clip{ padding: 3px 5px; }
.sim-root .fx-ov-count{
  background: var(--fiori-brand); color: var(--fiori-on-accent, #fff);
  border-color: var(--fiori-brand);
}
.sim-root .fx-ov-rel.is-on{
  background: var(--fiori-brand); color: var(--fiori-on-accent, #fff);
  border-color: var(--fiori-brand);
}

/* ── WHAT THE COLOURS MEAN ────────────────────────────────────────
 *
 * The three buttons cannot print. The answer prints as the task's
 * fill, so the sheet has to say what a fill means -- otherwise a
 * printed flow is a set of coloured boxes with no key.
 *
 * The swatches are the same values flow-bpmn writes into the
 * document. They are literals in both places on purpose: one is a
 * stylesheet and the other is a BPMN file that may be opened in
 * Signavio, and a custom property does not survive the trip. */
.sim-root .fx-key{
  display: flex; flex-wrap: wrap; align-items: center; gap: 14px;
  padding: 10px 2px 2px; font-size: 11px; color: var(--fiori-text2);
}
.sim-root .fx-key span{ display: inline-flex; align-items: center; gap: 5px; }
.sim-root .fx-key-d{
  width: 11px; height: 11px; border-radius: 3px;
  background: #FFFFFF; border: 1.5px solid #7A8697;
}
.sim-root .fx-key-d.is-yes{ background: #D6F5DF; border-color: #1B7F3B; }
.sim-root .fx-key-d.is-no{ background: #FBDDDD; border-color: #B3261E; }
.sim-root .fx-key-d.is-uns{ background: #FDF0D2; border-color: #9A6B00; }
.sim-root .fx-key-note{ color: var(--fiori-text-muted); font-style: italic; }

/* On .fx-sheet, the block each band is wrapped in. It used to be
   .fx-band + .fx-band, and the wrapper broke the adjacency silently:
   the rule matched nothing and the sheets ran together with no rule
   between them. Only visible with every sheet drawn -- which is the
   printed copy, where it matters most. */
.sim-root .fx-sheet + .fx-sheet{
  margin-top: 18px; padding-top: 14px;
  border-top: 1px dashed var(--fiori-border);
}

























/* The number sits ON the node's corner rather than beside it: beside
   it, a wrapped row puts the number of the first box on one line and
   its box on the next. */
.sim-root .fx-seq{
  position: absolute; z-index: 1; left: -6px; top: -6px;
  min-width: 18px; height: 18px; padding: 0 4px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 10px; font-weight: 700; border-radius: 999px;
  background: var(--fiori-brand); color: #fff;
}

/* ── landscape (owner 2026-09-26) ───────────────────────────────────
 *
 * *"an option for flow to be displayed in horizontal form (landscape
 * mode) where the boxes have word wrap and boxes also wrap to the next
 * line to accomodate the flow."*
 *
 * A 28-step process is 28 scroll-lengths tall in a column. Across and
 * wrapping, the same process is four rows and readable at once — which
 * is the whole reason to have a picture.
 *
 * The connector becomes horizontal, and at a wrap it would point off
 * the end of the line at nothing. So in landscape the arrow is drawn
 * INSIDE the flow as an inline chevron that wraps with the boxes,
 * rather than as a rail that assumes a straight line. */
.sim-root .fx.fx-landscape{
  flex-direction: row; flex-wrap: wrap; align-items: stretch;
  justify-content: flex-start; gap: 8px 6px;
}
.sim-root .fx.fx-landscape .fx-card,
.sim-root .fx.fx-landscape .fx-par{
  width: 230px; max-width: 230px; flex: 0 0 230px;
}
/* Word wrap inside the box is already on (.fx-t / .fx-r use
   overflow-wrap: anywhere); what landscape adds is a floor, so a long
   activity name grows the box DOWN rather than squeezing the column. */
.sim-root .fx.fx-landscape .fx-card{ min-height: 74px; }
.sim-root .fx.fx-landscape .fx-link{
  width: 14px; height: auto; flex: 0 0 14px;
  background: none; align-self: center;
}
.sim-root .fx.fx-landscape .fx-link::after{
  content: ''; position: absolute; top: 50%; left: 2px;
  width: 6px; height: 6px; margin-top: -3px; margin-left: 0;
  border-right: 2px solid var(--fiori-border);
  border-bottom: 2px solid var(--fiori-border);
  transform: rotate(-45deg);
}
.sim-root .fx.fx-landscape .fx-start,
.sim-root .fx.fx-landscape .fx-end{ align-self: center; }

/* ── the parallel node ── */
/* Held to the same width as a single card, so a node with two lanes
   does not push the column wider than the node above it -- the rail has
   to stay straight or the picture stops reading as one flow. */
.sim-root .fx-par{
  display: flex; flex-direction: column; align-items: center;
  width: 100%; max-width: 340px;
}
/* WRAPS. Five alternatives at flex-basis 0 inside 340px gave five
   columns sixty pixels wide, one word per line. A basis of 104px with
   wrapping gives three to a row and then two, each wide enough to read
   an app id and a role. */
.sim-root .fx-lanes{
  display: flex; gap: 6px; width: 100%; justify-content: center;
  align-items: stretch; flex-wrap: wrap;
}
/* flex-basis 0, so two lanes share the width evenly however long their
   text is, and min-width 0 so a long activity wraps instead of forcing
   the row wider. */
.sim-root .fx-lane{ flex: 1 1 104px; min-width: 0; max-width: none; padding: 6px 8px; }
/* The app is the heading of a lane, since the activity is written once
   above the split. */
.sim-root .fx-appname{ font-size: 11.5px; font-weight: 700; letter-spacing: .2px; }
/* The activity above a set of alternatives: a node like any other, so
   the rail reads straight through it. */
.sim-root .fx-head{ margin-bottom: 0; }
/* An alternative path is dashed, so "either of these" is legible
   without a legend. */
.sim-root .fx-lane{ border-style: dashed; border-left-style: solid; }
/* The split and the merge: one bar with a stem, mirrored. */
.sim-root .fx-split,
.sim-root .fx-merge{
  width: 72%; height: 10px; position: relative;
  border-top: 2px solid var(--fiori-border);
}
.sim-root .fx-split{ border-radius: 3px 3px 0 0; border-left: 2px solid var(--fiori-border); border-right: 2px solid var(--fiori-border); }
.sim-root .fx-merge{
  border-top: 0; border-bottom: 2px solid var(--fiori-border);
  border-left: 2px solid var(--fiori-border); border-right: 2px solid var(--fiori-border);
  border-radius: 0 0 3px 3px;
}
/* What tells two lanes apart, since their two lines read alike. */
/* Top-right of the card, clear of the title. On a single node it names
   the app the step is done in; on a lane it is the lane's own heading
   and this tag is not drawn. */
.sim-root .fx-app{
  position: absolute; top: -8px; right: 8px; z-index: 1; max-width: 60%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 9.5px; font-weight: 700; letter-spacing: .3px;
  padding: 1px 6px; border-radius: 999px;
  background: var(--fiori-surface2); border: 1px solid var(--fiori-border);
  color: var(--fiori-text-muted);
}

/* ── the filter above it ── */
.sim-root .fx-filter{
  display: flex; flex-direction: column; gap: 6px;
  margin: 8px 0 4px; padding: 8px 9px;
  border: 1px solid var(--fiori-border); border-radius: 10px;
  background: var(--fiori-surface2);
}
.sim-root .fx-proj{ display: flex; flex-direction: column; gap: 2px; }
.sim-root .fx-filter > .btn{ align-self: flex-start; }
.sim-root .fx-note{ margin: 4px 0 0; }

/* A two-way segmented switch: Flow / List on the master, Module /
   Business priority on a project's Scope Items. Scoped to .impl-root
   rather than to .sim-root, because both screens live under it and a
   control styled on one and bare on the other is the same control
   twice. */
.impl-root .fx-seg{
  display: inline-flex; border: 1px solid var(--fiori-border); border-radius: 999px; overflow: hidden;
}
.impl-root .fx-seg button{
  border: 0; background: transparent; color: var(--fiori-text-muted);
  font: inherit; font-size: 11px; font-weight: 600; padding: 3px 11px; cursor: pointer;
  white-space: nowrap;
}
.impl-root .fx-seg button:hover{ color: var(--fiori-text); }
.impl-root .fx-seg button.on{ background: var(--fiori-brand); color: #fff; }

/* ── Countries on the Overall Scope ── */
.impl-root .scope-ctry{ margin: 0 0 10px; padding: 9px 10px; border: 1px solid var(--fiori-border); border-radius: 10px; background: var(--fiori-surface2); }
.impl-root .scope-ccs{ display: flex; flex-wrap: wrap; gap: 5px; }
.impl-root .scope-cc{
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 2px 8px; border-radius: 999px; font-size: 12px;
  background: var(--fiori-surface2); border: 1px solid var(--fiori-border);
}
.impl-root .scope-cc b{ font-size: 10px; font-weight: 700; letter-spacing: .3px; color: var(--fiori-brand); }
/* A country with no ISO code cannot filter a process. Visible here
   rather than on a flowchart that quietly shows nothing. */
.impl-root .scope-cc.nocode{ border-color: var(--fiori-accent-amber); }
.impl-root .scope-cc.nocode i{ font-size: 10px; color: var(--fiori-accent-amber); font-style: normal; }

/* ── The two tabs (owner spec 2026-09-20) ──
   Scope Items and Steps are two different SAP downloads against one
   release, so they are tabs on one screen rather than two menu items:
   the release you are looking at is the thing they share. */
.sim-root .sim-tabs{
  display: flex; gap: 2px; margin-bottom: 10px;
  border-bottom: 1px solid var(--fiori-border);
}
.sim-root .sim-tab{
  padding: 7px 14px; border: 0; background: transparent; color: var(--fiori-text-muted);
  font: inherit; font-size: 13px; font-weight: 600; cursor: pointer;
  border-bottom: 2px solid transparent; margin-bottom: -1px;
}
.sim-root .sim-tab:hover{ color: var(--fiori-text); }
.sim-root .sim-tab.on{ color: var(--fiori-brand); border-bottom-color: var(--fiori-brand); }

/* The releases that have steps loaded. */
.sim-root .sim-rels{ display: flex; gap: 8px; flex-wrap: wrap; margin-top: 9px; }

/* "Global" is a fact about the step, not a place, so it is drawn as
   itself rather than as one more country chip. */
.sim-root .sim-gl{
  display: inline-block; padding: 1px 7px; border-radius: 999px;
  font-size: 10px; font-weight: 700; letter-spacing: .3px;
  background: color-mix(in srgb, var(--fiori-brand) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--fiori-brand) 45%, transparent);
  color: var(--fiori-brand);
}

/* What an upload did, and what it did NOT reach. */
.sim-root .sim-report{
  margin-top: 10px; padding: 9px 11px; border-radius: 10px; font-size: 12.5px;
  border: 1px solid var(--fiori-border); background: var(--fiori-surface2);
  display: flex; flex-direction: column; gap: 3px;
}
.sim-root .sim-warn{ color: var(--fiori-accent-amber); }

/* ── Add from the Scope Item Master (owner spec 2026-09-20) ──
   Lives outside .sim-root: the dialog opens on the project's Scope
   Items tab, not on the master screen. */
.sim-pick .sim-pick-src{ display: flex; gap: 8px; flex-wrap: wrap; }
.sim-pick .sim-rel{
  display: flex; flex-direction: column; gap: 1px; text-align: left;
  padding: 7px 11px; border: 1px solid var(--fiori-border); border-radius: 10px;
  background: var(--fiori-surface2); color: var(--fiori-text); cursor: pointer; font: inherit;
}
.sim-pick .sim-rel b{ font-size: 13px; }
.sim-pick .sim-rel.on{ border-color: var(--fiori-brand); background: color-mix(in srgb, var(--fiori-brand) 12%, transparent); }
.sim-pick .sim-rel:disabled{ cursor: default; opacity: .65; }
.sim-pick .sim-pick-list{
  margin-top: 9px; max-height: 46vh; overflow-y: auto;
  border: 1px solid var(--fiori-border); border-radius: 10px;
}
.sim-pick .sim-pick-row{
  display: flex; align-items: baseline; gap: 9px; padding: 6px 10px;
  font-size: 12.5px; cursor: pointer; border-bottom: 1px solid var(--fiori-border);
}
.sim-pick .sim-pick-row:last-child{ border-bottom: 0; }
.sim-pick .sim-pick-row:hover{ background: var(--fiori-surface2); }
/* Already on the version: shown, not hidden, so nobody wonders whether
   they forgot it -- but plainly not on offer. */
.sim-pick .sim-pick-row.had{ opacity: .55; cursor: default; }
.sim-pick .sim-pick-id{ flex: 0 0 56px; font-weight: 600; }
.sim-pick .sim-pick-desc{ flex: 1 1 auto; min-width: 0; }
.sim-pick .sim-pick-lob{ flex: 0 0 150px; text-align: right; }

/* ── Lookup code upload (Configuration › Metadata › Country) ── */
.code-upload{ margin: 6px 0 10px; }
.code-upload-bar{ display: flex; gap: 8px; align-items: center; }
.code-upload-note{
  margin-top: 8px; padding: 8px 10px; border-radius: 8px; font-size: 12px;
  background: var(--fiori-surface2); border: 1px solid var(--fiori-border);
}
.code-upload-bad{ border-color: var(--fiori-accent-rose); color: var(--fiori-accent-rose); }
.code-upload-tally{ display: flex; gap: 16px; flex-wrap: wrap; }
.code-upload-left{ margin-top: 6px; line-height: 1.5; }
.code-upload-warn{ margin-top: 6px; color: var(--fiori-accent-amber); }

.sim-root .sim-bulk{
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 9px; padding: 8px 9px;
  border: 1px solid var(--fiori-brand); border-radius: 10px; font-size: 12px;
  background: color-mix(in srgb, var(--fiori-brand) 10%, transparent);
}
.sim-root .sim-bulk.off{ opacity: .6; border-color: var(--fiori-border); background: var(--fiori-surface2); }

.sim-root .sim-grid{ width: 100%; font-size: 12px; }
.sim-root .sim-grid tbody td{ cursor: pointer; vertical-align: top; }
.sim-root .sim-grid tbody td:first-child{ cursor: default; }
.sim-root .sim-grid tbody tr:hover{ background: var(--fiori-surface2); }
.sim-root .sim-grid tbody tr.on{ background: color-mix(in srgb, var(--fiori-brand) 9%, transparent); }

/* A country code is a chip, not a word in a comma list: eight of them
   in a cell have to be countable at a glance. */
.sim-countries{ display: inline-flex; flex-wrap: wrap; gap: 3px; align-items: center; }
.sim-cc{
  font-size: 10px; font-family: ui-monospace, Menlo, monospace; line-height: 15px;
  padding: 0 5px; border-radius: 3px;
  background: color-mix(in srgb, var(--fiori-negative) 14%, transparent);
  color: var(--fiori-negative);
}

.sim-detail{ display: grid; gap: 1px; grid-template-columns: 1fr; margin-bottom: 12px; }
@media (min-width: 720px){ .sim-detail{ grid-template-columns: 1fr 1fr; gap: 1px 16px; } }
.sim-drow{ display: grid; grid-template-columns: 180px 1fr; gap: 8px; padding: 4px 0; font-size: 12px;
           box-shadow: 0 -1px 0 var(--fiori-border-light); align-items: baseline; }

.sim-refs{ margin-top: 10px; }
.sim-refs h4{ font-size: 12.5px; font-weight: 650; margin: 0 0 5px; }
.sim-ref{ display: flex; gap: 7px; align-items: baseline; flex-wrap: wrap; padding: 4px 0;
          box-shadow: 0 -1px 0 var(--fiori-border-light); }
.sim-ref-items{ display: flex; flex-direction: column; gap: 2px; flex: 1 1 260px; min-width: 0; }
.sim-ref-i{ display: flex; gap: 7px; font-size: 12px; align-items: baseline; }
.sim-ref-i b{ min-width: 34px; }
/* Shown, not hidden: 18% of the references in a release point at items
   that are retired or not yet shipped, and dropping them would make
   the list look complete when it is not. */
.sim-ref-i.unknown{ color: var(--fiori-text-muted); }
.sim-tag{ font-size: 10px; line-height: 16px; padding: 0 6px; border-radius: 99px;
          border: 1px solid var(--fiori-border); color: var(--fiori-text-muted); white-space: nowrap; }
.sim-also{ display: block; width: 100%; text-align: left; font: inherit; font-size: 12px;
           padding: 4px 6px; border: 0; background: none; color: var(--fiori-brand); cursor: pointer;
           border-radius: 6px; }
.sim-also:hover{ background: var(--fiori-surface2); }

/* ---- Client Team (owner spec 2026-09-20) ------------------------
   Two columns where there is room: the role-and-module LINES, and the
   pool of contacts waiting to be put on one. Below 1000px they stack,
   because the assign flow is tick-then-pick and both halves have to be
   readable at once -- side by side in a 400px column is neither. */
.impl-root .impl-clgrid{ display: grid; gap: 14px; grid-template-columns: 1fr; align-items: start; }
@media (min-width: 1000px){
  .impl-root .impl-clgrid{ grid-template-columns: minmax(0, 1.3fr) minmax(0, .8fr); }
}

.impl-root .impl-clline{ border: 1px solid var(--fiori-border-light); border-radius: 10px; overflow: hidden; }
.impl-root .impl-clline + .impl-clline{ margin-top: 8px; }
.impl-root .impl-clhead{
  display: flex; align-items: center; gap: 7px; padding: 6px 10px;
  background: color-mix(in srgb, var(--fiori-brand) 10%, transparent);
  border-bottom: 1px solid var(--fiori-border-light);
}
.impl-root .impl-clrole{ font-size: 12.5px; font-weight: 650; }
.impl-root .impl-clmod{
  font-size: 10.5px; border: 1px solid var(--fiori-brand); color: var(--fiori-brand);
  border-radius: 999px; padding: 0 7px; line-height: 17px; white-space: nowrap;
}
/* Amber, like the "no plan row can reach them" line it is the group
   form of: an empty line is the finding this panel exists to surface,
   not a blank to scroll past. */
.impl-root .impl-clempty{ padding: 7px 11px; color: var(--fiori-accent-amber); }

/* A person, on two lines -- the Partner Team panel's own shape. */
.impl-root .impl-clrow{ display: flex; align-items: flex-start; gap: 8px; padding: 6px 9px; }
.impl-root .impl-clrow + .impl-clrow{ box-shadow: 0 -1px 0 var(--fiori-border-light); }
.impl-root .impl-clwho{ display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.impl-root .impl-clname{ font-size: 13px; }
.impl-root .impl-clmeta{ display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.impl-root .impl-clact{ display: flex; gap: 5px; flex: none; }

.impl-root .impl-clpool{
  border: 1px solid var(--fiori-border-light); border-radius: 10px;
  max-height: 340px; overflow: auto;
}
.impl-root .impl-clpick{ display: flex; align-items: center; gap: 8px; padding: 5px 9px; cursor: pointer; }
.impl-root .impl-clpick + .impl-clpick{ box-shadow: 0 -1px 0 var(--fiori-border-light); }
.impl-root .impl-clpick:hover{ background: var(--fiori-surface2); }

/* The assign bar. Dimmed rather than hidden while nothing is ticked:
   it is the instruction for what to do with the list above it, and a
   control that appears out of nowhere on the first tick teaches
   nobody. */
.impl-root .impl-clbar{
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap; margin-top: 9px; padding: 8px 9px;
  border: 1px solid var(--fiori-brand); border-radius: 10px; font-size: 12px;
  background: color-mix(in srgb, var(--fiori-brand) 10%, transparent);
}
.impl-root .impl-clbar.off{ opacity: .6; border-color: var(--fiori-border); background: var(--fiori-surface2); }

.impl-root .impl-clform{
  display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; margin-top: 9px; padding: 9px 10px;
  border: 1px solid var(--fiori-border); border-radius: 10px; background: var(--fiori-surface2);
}
.impl-root .impl-crit{ color: var(--fiori-negative); }

/* ---- gantt ---- */
.impl-root .impl-gantt{
  display: grid; grid-template-columns: 340px 1fr; border: 1px solid var(--fiori-border);
  border-radius: 13px; overflow: hidden; background: var(--fiori-surface);
}
.impl-root .impl-gleft{ border-right: 1px solid var(--fiori-border-light); overflow: hidden; }
.impl-root .impl-gright{ overflow-x: auto; overflow-y: hidden; position: relative; }
.impl-root .impl-grow{
  height: 30px; display: flex; align-items: center; gap: 7px; padding: 0 10px;
  border-bottom: 1px solid var(--fiori-border); font-size: 12px;
}
.impl-root .impl-grow:hover{ background: var(--fiori-row-hover); }
.impl-root .impl-ghead{
  height: 42px; border-bottom: 1px solid var(--fiori-border-light);
  background: var(--fiori-surface-solid); position: relative;
}
.impl-root .impl-gmonth{
  position: absolute; top: 0; height: 21px; border-right: 1px solid var(--fiori-border);
  font-size: 10.5px; font-weight: 650; padding: 3px 6px; color: var(--fiori-text-muted);
  white-space: nowrap; overflow: hidden;
}
.impl-root .impl-gweek{
  position: absolute; top: 21px; height: 21px; border-right: 1px solid var(--fiori-border);
  font-size: 9px; padding: 5px 3px; color: var(--fiori-text-muted); text-align: center;
}
.impl-root .impl-gbody{ position: relative; }
.impl-root .impl-gline{ position: absolute; top: 0; bottom: 0; width: 1px; background: var(--fiori-border); }
.impl-root .impl-gtoday{ position: absolute; top: 0; bottom: 0; width: 2px; background: var(--neon-orange); z-index: 3; }
.impl-root .impl-gbar{
  position: absolute; height: 15px; border-radius: 5px; cursor: pointer;
  display: flex; align-items: center; overflow: visible; transition: .12s; border: 0;
}
.impl-root .impl-gbar:hover{ filter: brightness(1.18); }
.impl-root .impl-gbar .fill{
  position: absolute; left: 0; top: 0; bottom: 0; border-radius: 5px 0 0 5px;
  background: rgba(255,255,255,.42);
}
[data-theme="light"] .impl-root .impl-gbar .fill{ background: rgba(255,255,255,.60); }
.impl-root .impl-gbar .lab{
  position: absolute; left: calc(100% + 7px); font-size: 10.5px; white-space: nowrap;
  color: var(--fiori-text-muted);
}
.impl-root .impl-gsummary{ height: 9px !important; border-radius: 2px !important; }
.impl-root .impl-gdraggable{ cursor: grab; }
.impl-root .impl-gdraggable:active{ cursor: grabbing; }
.impl-root .impl-ghandle{ position: absolute; top: 0; bottom: 0; width: 8px; cursor: ew-resize; }
.impl-root .impl-ghandle.r{ right: 0; border-right: 2px solid rgba(255,255,255,.55); border-radius: 0 4px 4px 0; }
.impl-root .impl-glink{
  position: absolute; z-index: 4; width: 16px; height: 16px; border-radius: 4px; padding: 0; line-height: 14px;
  font-size: 13px; font-weight: 700; text-align: center; cursor: crosshair;
  background: var(--fiori-brand); color: #fff; border: 1px solid rgba(255,255,255,.5);
}
.impl-root .impl-glink-target{ outline: 2px dashed var(--fiori-brand); outline-offset: 2px; }
.impl-root .impl-gdrag-tip{
  position: absolute; z-index: 5; font-size: 10.5px; padding: 2px 6px; border-radius: 4px; white-space: nowrap;
  background: var(--fiori-text-primary); color: var(--fiori-bg); pointer-events: none;
}
.impl-root .impl-gmile{
  position: absolute; width: 13px; height: 13px; transform: rotate(45deg);
  border-radius: 2px; cursor: pointer; border: 0; background: var(--neon-gold);
}
.impl-root .impl-gbaseline{
  position: absolute; height: 3px; border-radius: 2px;
  background: var(--fiori-text-muted); opacity: .42;
}
.impl-root .impl-gcrit{ outline: 1.5px solid var(--fiori-negative); outline-offset: 1px; }
.impl-root .impl-deps{ position: absolute; inset: 0; pointer-events: none; z-index: 2; }

/* ---- board ---- */
.impl-root .impl-board{
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(250px, 1fr);
  gap: 11px; overflow-x: auto; padding-bottom: 8px; align-items: start;
}
.impl-root .impl-bcol{
  background: var(--fiori-surface2); border-radius: 13px;
  border: 1px solid var(--fiori-border); padding: 10px; min-height: 120px;
}
.impl-root .impl-bcol h3{
  font-size: 11.5px; font-weight: 660; display: flex; align-items: center;
  gap: 7px; margin-bottom: 9px;
}
.impl-root .impl-bcard{
  background: var(--fiori-surface-solid); border: 1px solid var(--fiori-border);
  border-radius: 10px; padding: 9px 10px; margin-bottom: 7px; cursor: pointer;
  width: 100%; text-align: left;
}
.impl-root .impl-bcard:hover{ border-color: var(--fiori-brand); }
.impl-root .impl-bcard[draggable="true"]{ cursor: grab; }
.impl-root .impl-bcard-drag{ opacity: .45; }
.impl-root .impl-bcol-over{ outline: 2px dashed var(--fiori-brand); outline-offset: -2px; background: rgba(10,110,209,.06); }
.impl-root .impl-bcol-no{ outline: 2px dashed var(--fiori-negative); outline-offset: -2px; cursor: not-allowed; }
.impl-root .impl-bcard .bt{ font-size: 12px; font-weight: 600; line-height: 1.35; margin-bottom: 7px; color: var(--fiori-text); }
.impl-root .impl-bcard .bm{ display: flex; align-items: center; justify-content: space-between; gap: 6px; }

/* ---- calendar ---- */
.impl-root .impl-cal{
  display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 1px;
  background: var(--fiori-border); border: 1px solid var(--fiori-border);
  border-radius: 13px; overflow: hidden;
}
.impl-root .impl-cal .dh{
  background: var(--fiori-surface-solid); padding: 7px 9px; font-size: 10px;
  text-transform: uppercase; letter-spacing: .07em; color: var(--fiori-text-muted); font-weight: 650;
}
.impl-root .impl-cal .dc{ background: var(--fiori-surface); min-height: 96px; padding: 6px 7px; }
.impl-root .impl-cal .dc.out{ opacity: .4; }
.impl-root .impl-cal .dc.today{ box-shadow: 0 0 0 1.5px var(--neon-cyan) inset; }
.impl-root .impl-cal .dn{ font-size: 11px; font-weight: 650; margin-bottom: 5px; }
.impl-root .impl-ev{
  font-size: 10.5px; padding: 2px 6px; border-radius: 5px; margin-bottom: 3px;
  cursor: pointer; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  display: block; width: 100%; text-align: left; border: 0;
}

/* ---- workload heat grid ---- */
.impl-root .impl-heat{ border-collapse: separate; border-spacing: 2px; }
.impl-root .impl-heat td.c{
  width: 44px; height: 30px; text-align: center; font-size: 10.5px; font-weight: 650;
  border-radius: 6px; cursor: pointer; border: 1px solid transparent;
}
/* `position: static` lived here, from the days of a fourteen-column
   grid you could see all at once. It is three classes and an element,
   so it OUTRANKS the sticky rule below and silently un-sticks the
   header row -- which is how it was found. The week label now rides
   the top of the scroller, because on a thirty-eight week grid the
   column you are reading is rarely the one whose date you can see. */
.impl-root .impl-heat th.w{
  font-size: 9.5px; text-align: center; padding: 3px; text-transform: none;
  letter-spacing: 0;
  position: sticky; top: 0; z-index: 2;
  background: var(--fiori-elevated-solid);
}
.impl-root .impl-heat td.n{ white-space: nowrap; font-size: 12px; border: 0; }

/* ── two lines per resource, and only two ────────────────────────────
   Owner spec 2026-09-18: "Make each resource consume maximum 2 lines
   in the first column. Line 1 - Name. Line 2 - Typical Role, Module."

   It was six: name, typical role, team, roles, module -- each on its
   own line, under every person, in a grid whose data rows are 30px
   tall. Team and Roles are gone (derived from the Typical Role master
   now). The two that remain sit SIDE BY SIDE on line 2, which is what
   makes it two lines rather than three.

   `open` is the exception: a picker needs the room, and it is
   transient. */
.impl-root .impl-tm2{
  display: flex; flex-wrap: nowrap; align-items: baseline; gap: 10px;
}
.impl-root .impl-tm2 > *{ display: block; }
.impl-root .impl-tm2.open{ display: block; }
/* The frozen column must be wide enough for the longest NAME, or the
   name is cut off by the column it is frozen against -- which is what
   it did at 187px. It sizes to its content; this is the floor. */
.impl-root .impl-heat th.n,
.impl-root .impl-heat td.n{ min-width: 260px; }

/* ── the capacity grid scrolls sideways over the whole project ───────
   Owner spec 2026-09-18: "Display for full duration of the project ...
   by allowing horizontal scrolling with Person column being frozen.
   Provide a thick horizontal scrolling bar on top."

   Three things, and they interact:

   1. The PERSON column is frozen. It is sticky at left:0 with an
      opaque background -- the table is border-collapse: separate with
      2px spacing, so a transparent sticky cell lets the columns it is
      covering show through the gaps. The header cell of that column is
      sticky in BOTH axes and outranks everything, because it is the
      corner: it has to sit over the column AND over the header row.

   2. The header row is sticky at the top of the scroller. It used to
      be explicitly static, which was fine for fourteen columns you
      could see all at once and useless for forty you cannot.

   3. The bar on top is a SEPARATE scroller (see the component). It is
      thick on purpose: the default 8px bar at the bottom of a 62vh
      pane is not something you find by accident, and the whole point
      of moving it up here is that it should be the obvious way to
      travel across a nine-month plan. */
.impl-root .impl-heatwrap{ padding: 12px; }
.impl-root .impl-heatgrid{ overflow: auto; max-height: 62vh; }
/* Without this the table does NOT overflow: a 39-column auto table
   squeezes its cells down to whatever the pane is rather than pushing
   past it, so nothing ever scrolls and the `width: 44px` on a cell is
   read as a suggestion. max-content makes the columns keep their size
   and the pane do the scrolling; min-width keeps a short plan from
   leaving the card half empty. */
.impl-root .impl-heatgrid .impl-heat{ width: max-content; min-width: 100%; }
.impl-root .impl-heat thead th{
  position: sticky; top: 0; z-index: 2;
  background: var(--fiori-elevated-solid);
}
.impl-root .impl-heat th.n,
.impl-root .impl-heat td.n{
  position: sticky; left: 0; z-index: 3;
  background: var(--fiori-elevated-solid);
  box-shadow: 1px 0 0 var(--fiori-border);
}
.impl-root .impl-heat th.n{ z-index: 4; text-align: left; }
/* This week, so the eye finds now in a grid that is mostly not now. */
.impl-root .impl-heat th.w.now{ color: var(--fiori-brand); font-weight: 750; }
.impl-root .impl-heat td.c.now{ box-shadow: inset 0 0 0 1px var(--fiori-brand); }
.impl-root .impl-heat td.c.peeked{ outline: 2px solid var(--fiori-brand); outline-offset: -1px; }

/* A DRAWN bar, not a styled native one. macOS uses overlay
   scrollbars: a second native scroller above the grid reserves no
   height and fades out when idle, so the bar the owner asked to be
   given is simply not there. Measured at 0px in a real browser before
   this was replaced. */
.impl-root .impl-heatbar{
  position: relative; height: 14px; margin-bottom: 10px;
  border-radius: 7px; background: var(--fiori-surface2);
  border: 1px solid var(--fiori-border); cursor: pointer;
}
.impl-root .impl-heatbar.off{ visibility: hidden; }
.impl-root .impl-heatbar .thumb{
  position: absolute; top: 1px; bottom: 1px; left: 0;
  border-radius: 6px; background: var(--fiori-brand);
  cursor: grab; will-change: transform;
}
.impl-root .impl-heatbar .thumb:hover{
  background: color-mix(in srgb, var(--fiori-brand) 82%, #fff);
}
.impl-root .impl-heatbar .thumb:active{ cursor: grabbing; }

/* ── what is in one person-week ──────────────────────────────────────
   Owner spec 2026-09-18: "When a particular block for a week and
   resource is clicked, show the list of the tasks (WBS) assigned to
   this resource for that week." Anchored to the cell, fixed to the
   viewport so the grid's own scrolling cannot drag it out of sight. */
.impl-root .impl-peek-veil{
  position: fixed; inset: 0; z-index: 80; background: transparent;
}
.impl-root .impl-peek{
  position: fixed; z-index: 81; max-height: 340px;
  display: flex; flex-direction: column;
  border-radius: 12px; border: 1px solid var(--fiori-border);
  background: var(--fiori-elevated-solid);
  box-shadow: 0 10px 34px rgba(0,0,0,.34);
  overflow: hidden;
}
.impl-root .impl-peek-h{
  display: flex; align-items: flex-start; gap: 8px;
  padding: 9px 11px; font-size: 12px;
  border-bottom: 1px solid var(--fiori-border);
}
.impl-root .impl-peek-b{ overflow: auto; padding: 4px; }
.impl-root .impl-peek-i{
  display: grid; grid-template-columns: auto 1fr; column-gap: 8px;
  width: 100%; text-align: left; padding: 6px 8px; border: 0; border-radius: 8px;
  background: none; color: inherit; cursor: pointer; font: inherit;
}
.impl-root .impl-peek-i:hover{ background: rgba(127,127,127,.12); }
.impl-root .impl-peek-i .nm{ font-size: 12px; font-weight: 600; }
.impl-root .impl-peek-i > :last-child{ grid-column: 2; }

/* ---- dashboard ---- */
.impl-root .impl-charts{
  display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 12px;
}
.impl-root .impl-legend{
  display: flex; gap: 12px; flex-wrap: wrap; font-size: 11px; color: var(--fiori-text-muted);
}
.impl-root .impl-legend i{
  width: 9px; height: 9px; border-radius: 2px; display: inline-block; margin-right: 5px;
}

/* ---- template picker ---- */
.impl-root .impl-tmplgrid{
  display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); gap: 10px;
}
.impl-root .impl-tmpl{
  padding: 12px; border-radius: 12px; border: 1px solid var(--fiori-border);
  background: var(--fiori-surface2); cursor: pointer; transition: .14s; text-align: left;
}
.impl-root .impl-tmpl:hover{ border-color: var(--fiori-brand); transform: translateY(-1px); }
.impl-root .impl-tmpl.sel{ border-color: var(--neon-cyan); box-shadow: 0 0 0 1px var(--neon-cyan) inset; }
.impl-root .impl-tmpl .tn{ font-weight: 660; font-size: 12.5px; margin-bottom: 4px; }

/* ---- explanatory note block ---- */
.impl-root .impl-note{
  border-left: 2px solid var(--neon-cyan); padding: 9px 12px; border-radius: 0 9px 9px 0;
  background: color-mix(in srgb, var(--neon-cyan) 8%, transparent);
  font-size: 11.5px; line-height: 1.55;
}
.impl-root .impl-note.warn{
  border-left-color: var(--fiori-negative);
  background: color-mix(in srgb, var(--fiori-negative) 8%, transparent);
}
.impl-root .impl-note.good{
  border-left-color: var(--neon-lime);
  background: color-mix(in srgb, var(--neon-lime) 8%, transparent);
}

.impl-root .impl-scroll{
  overflow: auto; max-height: 62vh; border-radius: 12px; border: 1px solid var(--fiori-border);
}

/* ---- responsive -----------------------------------------------------
   Owner spec: full desktop; read plus light updates on mobile. Views
   that cannot survive a narrow screen (Gantt, workload heat grid) are
   NOT crushed to fit. They scroll, and offer the list instead, which
   is more honest than a 320px Gantt nobody can read.               */
.impl-root .impl-mobile-only{ display: none; }
@media (max-width: 860px){
  .impl-root .impl-gantt{ grid-template-columns: 1fr; }
  .impl-root .impl-gleft{ display: none; }
  .impl-root .impl-desk-only{ display: none !important; }
  .impl-root .impl-mobile-only{ display: block; }
  .impl-root .impl-metarow{ grid-template-columns: repeat(2, 1fr); }
  .impl-root .impl-pgrid{ grid-template-columns: 1fr; }
  .impl-root .impl-hide-sm{ display: none !important; }
  .impl-root .impl-tab{ padding: 8px 10px; font-size: 12px; }
  .impl-root .impl-phase{ min-width: 118px; }
}

/* Role multi-select inside a plan-grid cell (owner 2026-09-14). The cell
   is narrow, so the picker floats above the row rather than stretching
   the column; z-index keeps it over the sticky header and neighbours. */
/* The grid cell clips its content (overflow:hidden, set above) and has no
   positioning context, so an absolutely-positioned picker would be both
   mis-anchored and cut off. Give every cell the context, and lift the
   clip only on the one cell currently holding a picker. */
.impl-root .impl-grid td.impl-grid-td { position: relative; }
.impl-root .impl-grid td.impl-grid-td:has(.impl-rolepick) {
  overflow: visible;
  z-index: 41;
}
.impl-root .impl-rolepick {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 40;
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  align-items: center;
  min-width: 240px;
  max-width: 420px;
  padding: 6px;
  border-radius: 8px;
  background: var(--fiori-elevated-solid);
  border: 1px solid var(--fiori-border);
  box-shadow: var(--fiori-shadow);
}

/* Plan grid drag-to-reorder (owner 2026-09-14). The drop line sits under
   the row you are hovering, because a drop places the dragged row AFTER
   the target. */
.impl-root .impl-grid tr.impl-plan-dragging td { opacity: 0.45; }
.impl-root .impl-grid tr.impl-plan-dropafter td {
  box-shadow: inset 0 -2px 0 0 var(--fiori-brand);
}
.impl-root .impl-grid tr[draggable="true"] .impl-wname { cursor: grab; }
.impl-root .impl-grid tr.impl-plan-dragging .impl-wname { cursor: grabbing; }

/* Summary-row date that disagrees with the rows beneath it (owner spec
   2026-09-14). A parent left on auto takes its children's span; one
   somebody TYPED is left alone and marked here instead, because quietly
   overwriting a date a human entered is how "it keeps reverting" starts.
   Colour is not the only signal -- the underline carries it for anyone
   who cannot separate the red, and the cell keeps a title tooltip
   saying what the children actually imply. */
.impl-plan-datebad {
  color: var(--fiori-negative, #DC2626);
  font-weight: 600;
  text-decoration: underline;
  text-decoration-style: wavy;
  text-decoration-color: var(--fiori-negative, #DC2626);
  text-underline-offset: 3px;
  cursor: help;
}

/* A date the plan worked out from a predecessor (owner spec 2026-09-17:
   "All such auto-calculated dates should show in blue font"). 4.03 ends
   1-Jan-27, so 4.04 starts 2-Jan-27 and both of 4.04's dates go blue.

   Blue, and NOT the muted grey a rolled-up summary date uses: the two
   are different derivations -- one comes from the rows beneath, one from
   the row before -- and giving them one colour would make the grid
   unable to answer "why is this date what it is".

   Colour is not the only signal. The weight carries it for anyone who
   cannot separate the blue, and the cell keeps a tooltip naming the
   predecessor it came from. The date stays editable: typing over it
   claims the row (date_mode manual) and the link stops driving it. */
.impl-plan-datelink {
  color: var(--neon-blue, #1D4ED8);
  font-weight: 600;
  cursor: help;
}

/* The invoice preview on the Receivables dashboard (owner spec
   2026-09-18). The paper itself is white whatever the app theme is --
   it is a printed document, and a dark invoice is not what the customer
   holds. Only the chrome around it follows the theme. */
.rcv-invlink{
  font: inherit; font-weight: 600; color: var(--fiori-brand);
  background: none; border: 0; padding: 0; cursor: pointer;
  text-decoration: underline; text-underline-offset: 3px;
  text-decoration-color: rgba(59,141,248,.45);
}
.rcv-invlink:hover{ text-decoration-color: var(--fiori-brand); }
.rcv-invlink:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 2px; border-radius: 3px; }
.rcv-modal{
  position: fixed; inset: 0; background: rgba(0,0,0,.6); z-index: 1200;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.rcv-modal-box{
  background: var(--fiori-bg); border: 1px solid var(--fiori-border);
  border-radius: 12px; width: 100%; max-width: 900px; max-height: 90vh;
  display: flex; flex-direction: column; min-height: 0;
}
.rcv-modal-h{
  display: flex; align-items: center; gap: 10px; padding: 12px 16px;
  border-bottom: 1px solid var(--fiori-border); font-size: 13px;
}
.rcv-modal-b{ overflow: auto; flex: 1 1 auto; min-height: 0; padding: 14px; background: var(--fiori-surface2); }
.rcv-tiny{ font-size: 11.5px; color: var(--fiori-text-muted); }

/* ── Project Tables (owner spec 2026-09-18, Option A) ──────────────
   The project spreadsheets, in the app. The three KEY columns are
   tinted so the link to the plan is visible at a glance -- that tint is
   the whole point of the screen, and it is repeated in the Excel
   export so a printout says the same thing. */
.impl-root .pt-scroll{ overflow: auto; max-height: calc(100vh - 300px); border: 1px solid var(--fiori-border); border-radius: 10px; }
.impl-root .pt-grid{ border-collapse: separate; border-spacing: 0; width: 100%; }
.impl-root .pt-grid th{ position: sticky; top: 0; z-index: 5; background: var(--fiori-elevated-solid); }
.impl-root .pt-grid td{ padding: 3px 6px; border-bottom: 1px solid var(--fiori-border); vertical-align: top; }
.impl-root .pt-grid th.pt-key,
.impl-root .pt-grid td.pt-key{ background: rgba(59,141,248,.07); }
.impl-root .pt-grid th.pt-key{ background: var(--fiori-elevated-solid); box-shadow: inset 0 -2px 0 rgba(59,141,248,.45); }
/* A row that has not been saved yet reads as unsaved, rather than
   looking identical to one that has. */
.impl-root .pt-grid tr.pt-new td{ background: rgba(0,229,160,.06); }
.impl-root .pt-grid .impl-cell{ width: 100%; min-width: 90px; }
/* A cell whose text does not match its column type. Kept, not emptied,
   and flagged -- silently blanking what somebody pasted is worse. */
.impl-root .pt-grid .pt-bad{ border-color: var(--fiori-accent-amber, #B45309); }

.impl-root .pt-lib{ display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 14px; }
.impl-root .pt-card{
  background: var(--fiori-surface); border: 1px solid var(--fiori-border);
  border-radius: 12px; padding: 14px; display: flex; flex-direction: column; gap: 8px;
}
.impl-root .pt-card:hover{ border-color: var(--fiori-border-light); }
.impl-root .pt-card-t{ font-weight: 700; font-size: 14px; display: flex; align-items: center; gap: 8px; }
.impl-root .pt-card-d{ color: var(--fiori-text-muted); font-size: 12px; min-height: 32px; }
.impl-root .pt-card-m{ display: flex; gap: 6px; flex-wrap: wrap; }
.impl-root .pt-shared{ color: var(--neon-violet); border-color: var(--neon-violet); }
.impl-root .pt-ico{
  width: 28px; height: 28px; border-radius: 8px; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: rgba(59,141,248,.15); color: var(--fiori-brand);
}

.pt-modal{
  position: fixed; inset: 0; background: rgba(0,0,0,.55); z-index: 1200;
  display: flex; align-items: center; justify-content: center; padding: 20px;
}
.pt-modal-box{
  background: var(--fiori-bg); border: 1px solid var(--fiori-border);
  border-radius: 12px; padding: 18px; width: 100%; max-width: 560px;
  max-height: 86vh; overflow: auto;
}
.pt-modal-box.pt-wide{ max-width: 860px; }
/* The scope-item detail modal, which carries the item on the left and
   its steps on the right (owner spec 2026-09-20). Written here as a
   rule rather than an inline style so the width sits with the other
   two, and so the class is not a name that styles nothing. */
.pt-modal-box.sim-dbox{ max-width: 1240px; }
.pt-modal-h{ display: flex; align-items: center; gap: 10px; margin-bottom: 12px; }
.pt-keybox{
  border: 1px dashed rgba(59,141,248,.5); background: rgba(59,141,248,.05);
  border-radius: 10px; padding: 11px 12px;
}

/* The collapsed summary-mismatch bar (owner spec 2026-09-17: "Provide a
   hide button for this"). Deliberately still tinted with the warning
   colour and still carrying the count: hiding the paragraph must not
   read as the problem having gone away. */
/* ── The plan toolbar's ribbon (owner spec 2026-10-09) ───────────────
   *"Rearrange the menu in Delivery > Implementation Projects, the same
   as it is in Project Visualizer."* Same shape as .pv-quick / .pv-tabs
   / .pv-ribbon, in this module's own tokens: four wrapped rows of
   controls become one tab's worth at a time.

   THE QUICK STRIP never folds -- the tabs, the two grid-wide icons,
   and Undo / Discard / Save. It is also where the fold control lives,
   because a Hide button inside the thing it hides cannot bring it
   back. */
.impl-root .impl-quick{
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; row-gap: 4px;
  padding: 4px 2px;
  border-bottom: 1px solid var(--fiori-border);
}
.impl-root .impl-ribtabs{
  display: flex; align-items: stretch; gap: 2px; flex: none;
  overflow-x: auto; scrollbar-width: none;
}
.impl-root .impl-ribtabs::-webkit-scrollbar{ display: none; }
.impl-root .impl-ribtabs button{
  border: 0; background: transparent; color: var(--fiori-text-muted);
  font: inherit; font-size: 12px; cursor: pointer; white-space: nowrap;
  padding: 4px 11px; border-radius: 8px;
}
.impl-root .impl-ribtabs button:hover{ color: var(--fiori-text); }
.impl-root .impl-ribtabs button[aria-selected="true"]{
  color: #FFFFFF; background: var(--fiori-brand, #2563EB); font-weight: 600;
}
/* The groups keep their neon hues and their hairline separators -- the
   2026-09-16 grouping is still doing its job, it just no longer has to
   do it for thirty controls at once. */
.impl-root .impl-ribbon{
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  padding: 6px 0 2px; margin-bottom: 8px;
  border-bottom: 1px solid var(--fiori-border);
}
/* The disagreement count, in the quick strip. Red because it is the
   one control there that reports a problem, and the same red the
   mismatching cells use in the grid, so the icon and the rows it is
   about are visibly the same thing. */
.impl-root .impl-mmbtn{
  border-color: var(--fiori-negative, #DC2626) !important;
  color: var(--fiori-negative, #DC2626) !important;
}
.impl-root .impl-mmbtn .impl-tb-t{ font-variant-numeric: tabular-nums; font-weight: 700; }

/* The quick-start panel at the top of the task dialog (owner spec
   2026-09-17): Now / Tomorrow / Tom+1 / Tom+2, setting the start date.
   Its own strip above the form rather than a row of buttons inside it,
   so it reads as a shortcut for the whole task and not as one field's
   decoration. */
.mt-startquick{
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 0 0 12px; padding: 8px 10px;
  border: 1px solid var(--fiori-border);
  border-radius: 10px;
  background: var(--fiori-surface2);
}
.mt-sq-label{
  font-size: 11px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--fiori-text-muted);
}
.mt-sq-btn{
  font: inherit; font-weight: 600; font-size: 13px;
  padding: 7px 18px;
  border: 1px solid var(--fiori-border);
  border-radius: 9px;
  background: var(--fiori-surface);
  color: var(--fiori-text);
  cursor: pointer;
}
.mt-sq-btn:hover{ background: var(--fiori-row-hover); }
/* The picked one is a real pressed state: these buttons set a field
   that is visible further down the form, and nothing else on screen
   would say which of the four is in force. */
.mt-sq-btn.on{
  background: var(--fiori-brand); border-color: var(--fiori-brand);
  color: var(--fiori-on-accent, #fff);
}
.mt-sq-btn:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 2px; }
/* "Select Date" is not one of the dated choices -- it hands over to
   the Start date field. A dashed outline says that: it sets nothing
   itself, so it must never look like the four buttons that do, and it
   can never be the pressed one. */
.mt-sq-btn.pick{
  border-style: dashed;
  color: var(--fiori-text-muted);
  background: transparent;
}
.mt-sq-btn.pick:hover{ color: var(--fiori-text); background: var(--fiori-row-hover); }
.mt-sq-btn.pick::before{ content: '\1F4C5'; margin-right: 6px; }
.mt-sq-now{ margin-left: auto; font-size: 11px; color: var(--fiori-text-muted); }
@media (max-width: 560px){
  .mt-sq-now{ margin-left: 0; width: 100%; }
}

/* ── Details view: master (WBS tree) + detail (the fields) ──────────
   Owner spec 2026-09-17, Option A of three mocked up on the day:
   "master-slave modal showing the WBS, Description on the left as
   master and all the column details as fields in the slave."

   A GRID rather than floats, so the two panels are the same height
   whichever is longer and each scrolls inside itself -- a detail pane
   that pushes the page down would put the master's toolbar off screen,
   which is the one thing that must stay reachable.

   Height: the panels fill the viewport below wherever the grid starts,
   so the form is never a 300px letterbox at the bottom of a long page.
   `--impl-md-top` is the app chrome above it; 260px is the floor for a
   very short window. */
.impl-root .impl-plan-md{
  display: grid;
  grid-template-columns: minmax(280px, 380px) minmax(0, 1fr);
  border: 1px solid var(--fiori-border);
  border-radius: 10px;
  overflow: hidden;
  background: var(--fiori-surface);
  height: max(260px, calc(100vh - var(--impl-md-top, 300px)));
}
/* The third pane (owner spec 2026-09-17). A column only when it is
   open, so a plan nobody is reading descriptions on keeps the full
   width for the fields. */
.impl-root .impl-plan-md.info{
  grid-template-columns: minmax(260px, 340px) minmax(0, 1fr) minmax(260px, 360px);
}
.impl-root .impl-plan-master{
  display: flex; flex-direction: column; min-width: 0;
  /* min-height: 0 is the whole reason the left panel scrolls.
     A grid item defaults to min-height: auto, which refuses to shrink
     below its content -- so a 310-row list made the PANEL as tall as
     the plan, `overflow: auto` on the list never had anything to
     scroll, and the page scrolled instead, carrying the toolbar off the
     top (owner, 2026-09-17: "Allow vertical scrolling on left panel").
     Same for the detail column and for the list inside this one. */
  min-height: 0;
  border-right: 1px solid var(--fiori-border);
  background: var(--fiori-surface);
}
/* The row operations, on top of the master panel (the attachment). The
   toolbar group keeps its own hue and spacing -- this only gives it a
   home and lets it wrap on a narrow panel instead of clipping. */
.impl-root .impl-plan-mtools{
  padding: 6px 8px;
  border-bottom: 1px solid var(--fiori-border);
  background: var(--fiori-surface);
}
.impl-root .impl-plan-mtools .impl-tbg{ flex-wrap: wrap; border: 0; padding: 0; margin: 0; }
.impl-root .impl-plan-mhead{
  display: flex; gap: 8px; align-items: center;
  padding: 6px 10px;
  font-size: 10px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--fiori-text-muted);
  background: var(--fiori-surface2);
  border-bottom: 1px solid var(--fiori-border);
}
.impl-root .impl-plan-mlist{ overflow-y: auto; overflow-x: hidden; flex: 1 1 auto; min-height: 0; }
/* It takes the arrow keys, so it takes focus -- and says so. */
.impl-root .impl-plan-mlist:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: -2px; }
.impl-root .impl-plan-mrow{
  display: flex; gap: 8px; align-items: center;
  padding: 5px 10px; cursor: pointer;
  border-bottom: 1px solid var(--fiori-border);
}
.impl-root .impl-plan-mrow:hover{ background: var(--fiori-row-hover); }
/* An inset bar rather than only a fill: on a dense list a background
   change alone is easy to lose track of while the eyes are on the form. */
.impl-root .impl-plan-mrow.sel{
  background: var(--fiori-row-hover);
  box-shadow: inset 3px 0 0 var(--fiori-brand);
}
.impl-root .impl-plan-mrow.par .impl-plan-mname{ font-weight: 600; }
/* Drag to reorder, the same gesture as the grid. The row being carried
   fades; the row it would land AFTER grows a line along its bottom edge,
   because "after this one" is what the drop actually means and an
   outline around the target would say "into this one". */
.impl-root .impl-plan-mrow[draggable="true"]{ cursor: grab; }
.impl-root .impl-plan-mrow.dragging{ opacity: .45; cursor: grabbing; }
.impl-root .impl-plan-mrow.dropafter{ box-shadow: inset 0 -2px 0 var(--fiori-brand); }
.impl-root .impl-plan-mrow.sel.dropafter{ box-shadow: inset 3px 0 0 var(--fiori-brand), inset 0 -2px 0 var(--fiori-brand); }
.impl-root .impl-plan-mwbs{
  font: 600 11px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
  color: var(--fiori-text-muted); min-width: 58px; flex: none;
}
.impl-root .impl-plan-mname{
  flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12px;
}
.impl-root .impl-plan-mdot{ width: 8px; height: 8px; border-radius: 50%; flex: none; }
.impl-root .impl-plan-mdot-not_started{ background: var(--fiori-border-light); }
.impl-root .impl-plan-mdot-wip{ background: var(--fiori-accent-amber); }
.impl-root .impl-plan-mdot-done{ background: var(--fiori-accent-emerald); }
.impl-root .impl-plan-mdot-blocked{ background: var(--fiori-negative, #DC2626); }

.impl-root .impl-plan-detail{ display: flex; flex-direction: column; min-width: 0; min-height: 0; }
.impl-root .impl-plan-dempty{ padding: 28px; font-size: 13px; }
.impl-root .impl-plan-dhead{
  display: flex; gap: 9px; align-items: center;
  padding: 8px 14px;
  border-bottom: 1px solid var(--fiori-border);
  background: var(--fiori-surface);
}
.impl-root .impl-plan-dname{ font-size: 14px; }
.impl-root .impl-plan-dbody{ overflow: auto; flex: 1 1 auto; min-height: 0; padding: 12px 14px 18px; }
.impl-root .impl-plan-dgrp{ margin: 0 0 16px; }
.impl-root .impl-plan-dgrp > h4{
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 8px;
  font-size: 10px; font-weight: 800; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fiori-text-muted);
}
/* The rule after the heading is what separates the groups without
   drawing five boxes inside a box. */
.impl-root .impl-plan-dgrp > h4::after{ content: ""; flex: 1; height: 1px; background: var(--fiori-border); }
.impl-root .impl-plan-dfields{
  display: grid; gap: 9px 14px;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.impl-root .impl-plan-dfield{ display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.impl-root .impl-plan-dfield.wide{ grid-column: 1 / -1; }
.impl-root .impl-plan-dfield > label{ font-size: 11px; font-weight: 700; color: var(--fiori-text2); }
.impl-root .impl-plan-dfield .impl-field{ width: 100%; }
/* A dashed edge for anything the form does not own: read-only columns,
   and the role columns that the Assign panel sets over many rows. */
.impl-root .impl-plan-dro{
  min-height: 30px; display: flex; align-items: center; padding: 0 9px;
  border: 1px dashed var(--fiori-border); border-radius: 8px;
  background: var(--fiori-surface2);
}
.impl-root .impl-plan-dpick{
  min-height: 30px; width: 100%; text-align: left;
  display: flex; align-items: center; padding: 0 9px;
  border: 1px solid var(--fiori-border); border-radius: 8px;
  background: var(--fiori-surface); color: var(--fiori-text); cursor: pointer;
}
.impl-root .impl-plan-dpick:hover{ background: var(--fiori-row-hover); }
.impl-root .impl-plan-droles{ display: flex; flex-wrap: wrap; gap: 4px; padding: 3px 0; }

/* The ℹ button on the right of the record header, and the pane it
   opens. Pressed state is a real one -- the button toggles a panel, so
   it has to look different while that panel is showing. */
.impl-root .impl-plan-ibtn{
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  border: 1px solid var(--fiori-border); border-radius: 8px;
  background: var(--fiori-surface); cursor: pointer;
}
.impl-root .impl-plan-ibtn:hover{ background: var(--fiori-row-hover); }
.impl-root .impl-plan-ibtn.on{ border-color: var(--neon-cyan); background: var(--fiori-row-hover); }
.impl-root .impl-plan-info{
  display: flex; flex-direction: column; min-width: 0; min-height: 0;
  border-left: 1px solid var(--fiori-border);
  background: var(--fiori-surface2);
}
.impl-root .impl-plan-ihead{
  display: flex; align-items: center; gap: 8px;
  padding: 8px 12px;
  border-bottom: 1px solid var(--fiori-border);
  background: var(--fiori-surface);
  font-size: 12px;
}
.impl-root .impl-plan-ibody{ overflow: auto; flex: 1 1 auto; min-height: 0; padding: 10px 12px 16px; }
/* The description is the point of the pane, so it gets the room: it
   grows with the pane rather than sitting at a fixed 120px with its own
   scrollbar inside a panel that already has one. */
.impl-root .impl-plan-idesc{
  width: 100%; min-height: 160px; height: auto;
  white-space: pre-wrap; line-height: 1.45; margin-top: 3px; padding: 7px 9px;
  resize: vertical;
}
/* The floating dialog keeps the old fixed height -- it has no pane to
   grow into. */
.impl-root .impl-plan-pop .impl-plan-idesc{ min-height: 120px; }

/* Three columns do not fit a narrow window; the pane stacks under the
   record rather than squeezing both. */
@media (max-width: 1250px){
  .impl-root .impl-plan-md.info{ grid-template-columns: minmax(260px, 340px) minmax(0, 1fr); }
  .impl-root .impl-plan-info{ grid-column: 1 / -1; border-left: 0; border-top: 1px solid var(--fiori-border); max-height: 40vh; }
}
.impl-root .impl-plan-dnotes{ align-self: start; }
/* One column under a narrow window; the master keeps its own scroll. */
@media (max-width: 900px){
  .impl-root .impl-plan-md{ grid-template-columns: minmax(0, 1fr); height: auto; }
  .impl-root .impl-plan-master{ border-right: 0; border-bottom: 1px solid var(--fiori-border); max-height: 40vh; }
}

/* Segmented Team / Individual toggle on the project plan toolbar (owner
   spec 2026-09-14). Two buttons that read as one control, so it is
   obviously a view switch and not two separate actions. */
.impl-seg {
  display: inline-flex;
  border: 1px solid var(--fiori-border);
  border-radius: 6px;
  overflow: hidden;
}
.impl-seg > .btn {
  border: 0;
  border-radius: 0;
  margin: 0;
}
.impl-seg > .btn + .btn { border-left: 1px solid var(--fiori-border); }

/* ── Project plan toolbar: icon + short label (owner 2026-09-14) ──
   "Simplify these buttons. Replace with relevant modern neon icons with
   short description. And longer description on hover."

   The long description is the button's title, so it is hover-only; the
   short one is always visible. Icons are inline SVG stroked with
   currentColor, so they inherit the button's colour and need no second
   palette for dark, light, hover or disabled. */
.impl-root .impl-tb {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0 9px;
  height: 30px;
  white-space: nowrap;
}
/* Owner 2026-09-14: "Remove the light background inside the buttons."
   The pale fill was never ours -- .btn declares `border: none` and no
   background at all, so a <button> fell back to the browser's own
   ButtonFace, which is near-white in every theme. Declaring both
   background and colour is the fix; inheriting either from the UA is
   how a dark toolbar ends up with light chips in it.

   :not(.btn-primary) because a primary button sets its own fill, and
   this selector is one class more specific than .btn-primary. */
.impl-root .impl-tb:not(.btn-primary) {
  background: transparent;
  color: var(--fiori-text);
}
.impl-root .impl-tb:not(.btn-primary):hover:not(:disabled):not([aria-disabled="true"]) {
  /* A trace of the accent, not a fill: the border is what the owner
     asked to carry the emphasis. */
  background: color-mix(in srgb, var(--tb-hue, var(--neon-cyan, #00F0FF)) 10%, transparent);
}
[data-theme="light"] .impl-root .impl-tb:not(.btn-primary):hover:not(:disabled):not([aria-disabled="true"]) {
  background: var(--fiori-surface2);
}
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  .impl-root .impl-tb:not(.btn-primary):hover:not(:disabled) { background: var(--fiori-surface2); }
}
.impl-root .impl-tb .impl-tb-i {
  display: inline-flex;
  align-items: center;
  /* The icon carries the neon, the label stays readable. On an active or
     primary button the whole control is already accented, so the icon
     drops back to matching it rather than fighting it. */
  color: var(--tb-hue, var(--neon-cyan, #22D3EE));
  transition: color .12s ease, transform .12s ease;
}
.impl-root .impl-tb.btn-primary .impl-tb-i,
.impl-root .impl-tb[aria-pressed="true"] .impl-tb-i { color: currentColor; }
.impl-root .impl-tb:hover:not(:disabled) .impl-tb-i { transform: translateY(-1px); }
.impl-root .impl-tb:disabled,
.impl-root .impl-tb[aria-disabled="true"] { opacity: .45; cursor: not-allowed; }
.impl-root .impl-tb:disabled .impl-tb-i { color: inherit; }
/* A danger button's icon follows the button, not the accent. */
.impl-root .impl-tb[style*="negative"] .impl-tb-i { color: inherit; }
.impl-root .impl-tb-t { font-size: 12px; }

/* Borders, owner 2026-09-14: "Solid border in Light mode and Neon border
   in Dark mode."

   Dark is the theme the neon palette was designed for, and a thin glow
   on a near-black surface reads as a lit edge. The same treatment on
   white reads as a smudge -- a glow needs darkness behind it to look
   like light rather than like a printing fault. So light gets a plain,
   confident 1px rule instead, and the icon keeps carrying the colour. */
.impl-root .impl-tb {
  border: 1px solid color-mix(in srgb, var(--tb-hue, var(--neon-cyan, #00F0FF)) 45%, transparent);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--tb-hue, var(--neon-cyan, #00F0FF)) 12%, transparent),
    0 0 8px -2px color-mix(in srgb, var(--tb-hue, var(--neon-cyan, #00F0FF)) 55%, transparent);
  transition: border-color .12s ease, box-shadow .12s ease, color .12s ease;
}
.impl-root .impl-tb:hover:not(:disabled):not([aria-disabled="true"]) {
  border-color: color-mix(in srgb, var(--tb-hue, var(--neon-cyan, #00F0FF)) 85%, transparent);
  box-shadow:
    0 0 0 1px color-mix(in srgb, var(--tb-hue, var(--neon-cyan, #00F0FF)) 25%, transparent),
    0 0 14px -2px color-mix(in srgb, var(--tb-hue, var(--neon-cyan, #00F0FF)) 75%, transparent);
}
/* A destructive or primary button glows in its OWN colour. A red Delete
   ringed in cyan says two things at once and neither of them lands. */
.impl-root .impl-tb[style*="negative"] {
  border-color: color-mix(in srgb, var(--fiori-negative) 55%, transparent);
  box-shadow: 0 0 8px -2px color-mix(in srgb, var(--fiori-negative) 60%, transparent);
}
.impl-root .impl-tb.btn-primary {
  border-color: color-mix(in srgb, var(--fiori-brand) 70%, transparent);
  box-shadow: 0 0 10px -2px color-mix(in srgb, var(--fiori-brand) 70%, transparent);
}
/* Disabled must not glow -- a lit edge reads as "available". */
.impl-root .impl-tb:disabled,
.impl-root .impl-tb[aria-disabled="true"] {
  box-shadow: none;
  border-color: var(--fiori-border);
}

[data-theme="light"] .impl-root .impl-tb,
[data-theme="light"] .impl-root .impl-tb:hover:not(:disabled),
[data-theme="light"] .impl-root .impl-tb[style*="negative"],
[data-theme="light"] .impl-root .impl-tb.btn-primary,
[data-theme="light"] .impl-root .impl-tb:disabled,
[data-theme="light"] .impl-root .impl-tb[aria-disabled="true"] {
  box-shadow: none;
  border: 1px solid var(--fiori-border);
}
[data-theme="light"] .impl-root .impl-tb:hover:not(:disabled):not([aria-disabled="true"]) {
  border-color: var(--fiori-text-muted);
}
[data-theme="light"] .impl-root .impl-tb.btn-primary { border-color: var(--fiori-brand); }
[data-theme="light"] .impl-root .impl-tb[style*="negative"] { border-color: var(--fiori-negative); }

/* Anything that cannot do color-mix (older Safari, older Firefox) gets a
   plain border rather than a transparent one -- the fallback has to be
   visible, not merely valid. */
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  .impl-root .impl-tb { border: 1px solid var(--fiori-border); box-shadow: none; }
}

/* Groups, separated by a hairline rule rather than by more gap: it says
   "these belong together" in a way spacing alone does not once the bar
   wraps onto a second line. */
.impl-root .impl-tbg {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding-right: 10px;
  margin-right: 2px;
  border-right: 1px solid var(--fiori-border);
}
.impl-root .impl-tbg:last-of-type { border-right: 0; padding-right: 0; }
.impl-root .impl-tbg .impl-seg .impl-tb { border-radius: 0; }

/* The Export menu. Anchored under its button; the button's group is
   position:relative. */
.impl-root .impl-tbmenu {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 60;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  min-width: 190px;
  padding: 5px;
}
.impl-root .impl-tbmenu .impl-tb { justify-content: flex-start; width: 100%; }

@media (max-width: 720px) {
  /* On a phone the labels are what wrap the bar onto five rows. The
     icons stay, and the title still carries the full explanation. */
  .impl-root .impl-tbg .impl-tb-t { display: none; }
  .impl-root .impl-tbmenu .impl-tb-t { display: inline; }
  .impl-root .impl-tb { padding: 0 8px; }
}


/* ── One hue per family (owner 2026-09-16: "Rearrange these by grouping
   based on type of use. And color them differently for different
   usage.") ──

   The group sets --tb-hue once and every border, glow and icon inside
   it follows, so no button names its own colour and a button moved
   between families needs no edit. Hues are the theme's own neon tokens,
   which are already redefined darker for light mode -- picking literal
   colours here would have looked right in exactly one theme.

   Colour is a SECOND cue, never the only one: the hairline rule between
   groups and each group's aria-label say the same thing for anyone who
   cannot separate the hues, and a colour-blind reader loses nothing
   that was not also written down. */
.impl-root .impl-tbg-edit   { --tb-hue: var(--neon-cyan); }    /* build the rows   */
.impl-root .impl-tbg-people { --tb-hue: var(--neon-violet); }  /* who does it      */
.impl-root .impl-tbg-sched  { --tb-hue: var(--neon-teal); }    /* when             */
.impl-root .impl-tbg-ver    { --tb-hue: var(--neon-gold); }    /* versions/release */
.impl-root .impl-tbg-view   { --tb-hue: var(--neon-blue); }    /* what is on screen*/
.impl-root .impl-tbg-data   { --tb-hue: var(--neon-pink); }    /* in and out       */
.impl-root .impl-tbg-save   { --tb-hue: var(--neon-orange); }  /* commit           */

/* The selects and the level toggle live inside a coloured group too,
   so they take the same edge rather than sitting there in default grey
   as the one thing in the family that did not get the memo. */
.impl-root .impl-tbg[class*="impl-tbg-"] > .impl-field,
.impl-root .impl-tbg[class*="impl-tbg-"] .impl-viewtoggle,
.impl-root .impl-tbg[class*="impl-tbg-"] > .impl-seg {
  border-color: color-mix(in srgb, var(--tb-hue, var(--neon-cyan)) 45%, transparent);
}
[data-theme="light"] .impl-root .impl-tbg[class*="impl-tbg-"] > .impl-field,
[data-theme="light"] .impl-root .impl-tbg[class*="impl-tbg-"] .impl-viewtoggle,
[data-theme="light"] .impl-root .impl-tbg[class*="impl-tbg-"] > .impl-seg {
  border-color: var(--fiori-border);
}
/* The divider between families picks up the hue of the family it closes,
   so the rule reads as a boundary of that group rather than as generic
   furniture. */
.impl-root .impl-tbg[class*="impl-tbg-"] {
  border-right-color: color-mix(in srgb, var(--tb-hue, var(--neon-cyan)) 35%, transparent);
}
@supports not (color: color-mix(in srgb, red 50%, transparent)) {
  .impl-root .impl-tbg[class*="impl-tbg-"] > .impl-field,
  .impl-root .impl-tbg[class*="impl-tbg-"] .impl-viewtoggle,
  .impl-root .impl-tbg[class*="impl-tbg-"] > .impl-seg,
  .impl-root .impl-tbg[class*="impl-tbg-"] { border-color: var(--fiori-border); }
}

/* Column chooser, filter row and sibling sort on the project plan
   (owner spec 2026-09-14). */
.impl-root .impl-colpick {
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
  z-index: 60;
  max-width: 460px;
}
.impl-root .impl-sortbtn {
  border: 0;
  background: transparent;
  color: var(--fiori-text-muted);
  cursor: pointer;
  font-size: 9px;
  line-height: 1;
  padding: 1px 2px;
  border-radius: 3px;
}
.impl-root .impl-sortbtn:hover { color: var(--neon-cyan); background: rgba(127,127,127,0.12); }

.impl-root .impl-filterrow th {
  padding: 2px 4px;
  background: var(--fiori-surface2-solid, var(--fiori-surface2));
  border-bottom: 1px solid var(--fiori-border);
  position: sticky;
  /* Under the header row, not under the viewport top: the header is
     already sticky, so this has to clear it or the two overlap. The
     offset is the measured one its row carries -- a hard-coded 30px was
     wrong for any other heading height, and lost the cascade anyway to
     `.impl-root .impl-plan-grid thead th` (0,2,2 beats 0,2,1). */
  top: var(--impl-plan-rowtop, 30px);
  z-index: 30;
}
.impl-root .impl-filter {
  width: 100%;
  height: 20px;
  padding: 0 5px;
  font-size: 11px;
  border: 1px solid var(--fiori-border);
  border-radius: 4px;
  background: var(--fiori-surface);
  color: var(--fiori-text);
}
.impl-root .impl-filter:focus {
  outline: none;
  border-color: var(--neon-cyan);
}
/* A row kept only to hold a match in place. Dimmed, not hidden -- the
   WBS number of a match is meaningless without its parents on screen. */
.impl-root tr.impl-plan-ctx > td { opacity: 0.55; }
.impl-root tr.impl-plan-ctx:hover > td { opacity: 1; }

/* The Not Working grace-window gate (owner spec 2026-09-15). Deliberately
   modal and deliberately without a close button: a dialog that can be
   waved away is one nobody answers, and the whole point is that somebody
   decides. Every button in it resolves the state, so it is never a dead
   end. */
.presence-gate {
  position: fixed;
  inset: 0;
  z-index: 9000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(2, 6, 23, 0.72);
  backdrop-filter: blur(3px);
}
.presence-gate-card {
  width: min(480px, 100%);
  padding: 22px 24px;
  border-radius: 14px;
  background: var(--fiori-elevated-solid, var(--fiori-surface));
  border: 1px solid var(--fiori-border);
  box-shadow: 0 24px 60px -12px rgba(0, 0, 0, 0.55);
  color: var(--fiori-text);
}
.presence-gate-title { font-size: 17px; font-weight: 700; margin-bottom: 10px; }
.presence-gate-body { font-size: 13px; line-height: 1.6; margin: 0 0 8px; color: var(--fiori-text2, var(--fiori-text)); }
.presence-gate-note { font-size: 12px; color: var(--fiori-accent-amber); margin: 6px 0 0; }
.presence-gate-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 16px; }
@media (max-width: 480px) {
  .presence-gate-actions .btn { flex: 1 1 100%; justify-content: center; }
}

/* ==========================================================================
   PROJECT VISUALIZER  (Delivery › Project Visualizer)

   Owner spec 2026-09-18, after picking "Blueprint" from four mockups:
   "Build as per Blueprint. Also add L1, L2, L3, All selection. Also use
   the calendar to show holidays and weekends in a different colour. And
   if while dragging auto move drag to next working day if it stops on a
   non working day." Followed by: "I want both light theme and dark
   theme."

   Blueprint is a DENSITY, not a colour scheme. What it carries over from
   the mockup is the 23px row, the 12/11/10px type ramp, the 3px radii
   and the tight control heights -- roughly forty rows of plan on screen
   before anyone scrolls. The colours come from the app's own tokens, so
   the screen changes with the theme toggle like everything else.

   ── The marks are validated, not chosen ──
   The three colours that carry meaning on the chart -- activity,
   milestone, critical -- were run through the dataviz validator against
   BOTH of this app's real surfaces (--fiori-bg #0B0F14 dark, #F4F6FA
   light) with --pairs all. Every check passes in both: lightness band,
   chroma floor, deuteranope and tritanope separation, normal-vision
   separation, and contrast. They are re-stepped per theme rather than
   flipped, because a palette that passes on white does not automatically
   pass on near-black.

   ── Closed days get two different colours, on purpose ──
   A weekend and a company holiday are both non-working, and the owner
   asked for them to be distinguishable. A weekend is a neutral wash --
   it recurs, it carries no information, and it should read as texture. A
   holiday is amber and named in the header and the tooltip, because it
   is a DECISION somebody made on the project calendar and a PM looking
   at a slipped date needs to see that Diwali is what the bar ran into.
   Colour is not the only carrier: the holiday column also gets a dotted
   top rule, so the two survive greyscale, print and forced-colours.
   ========================================================================== */
.pv {
  --pv-row: 23px;
  --pv-hd: 36px;
  --pv-bar: 12px;
  --pv-sum: 7px;
  --pv-ms: 9px;
  --pv-ctl: 25px;
  --pv-av: 17px;
  --pv-fs: 12px;
  --pv-fs-sm: 11px;
  --pv-fs-xs: 10px;
  --pv-r: 3px;
  --pv-tree: 620px;
  --pv-cell-px: 7px;
  --pv-c-code: 66px;
  --pv-c-date: 78px;
  /* Start and Finish size apart (owner spec 2026-09-22). They were one
     variable used twice, so they could not. */
  --pv-c-start: 78px;
  --pv-c-finish: 78px;
  --pv-c-dur: 46px;
  --pv-c-who: 92px;

  --pv-surface: var(--fiori-surface-solid, #111827);
  --pv-surface2: var(--fiori-surface2-solid, #1F2937);
  --pv-line: var(--fiori-border, rgba(255,255,255,0.12));
  --pv-line-soft: rgba(255, 255, 255, 0.06);
  --pv-line-strong: rgba(255, 255, 255, 0.2);
  --pv-row-hover: rgba(255, 255, 255, 0.045);
  --pv-row-l1: #F59E0B;
  --pv-row-l1-ink: #000000;
  --pv-row-l1-danger: #7F1D1D;
  --pv-row-l1-warn: #5C2708;
  --pv-accent: var(--fiori-brand, #5B8DEF);
  --pv-accent-soft: rgba(91, 141, 239, 0.18);
  /* The FILL behind white button text, which is a different job from
     the accent used for outlines and tints: white on the dark theme's
     periwinkle accent is 3.2:1, under the 4.5:1 a label needs. Stepped
     down until white clears it -- measured 5.17:1 here, 4.88:1 light. */
  --pv-accent-fill: #2563EB;

  /* validated dark set — surface #0B0F14 */
  --pv-bar-c: #3B82F6;
  --pv-bar-done: #93C5FD;
  --pv-on-bar: #08101C;
  --pv-summary: #7C8BA1;
  /* Owner spec 2026-09-24 — the top-level phase band. Orange here,
     black on the light set: each is the one the owner named, and each
     is the one that carries its ink past 4.5:1 on its own canvas. */
  --pv-milestone: #14B183;   /* dark theme: near-black label, 6.50:1 */
  --pv-crit: #E5484D;
  --pv-link: #55657B;
  --pv-today: #F0B429;

  /* the two kinds of closed day */
  --pv-weekend: rgba(255, 255, 255, 0.05);
  --pv-holiday: rgba(240, 180, 41, 0.14);
  --pv-holiday-line: rgba(240, 180, 41, 0.55);
  --pv-warn: #F0B429;

  --pv-si: #3B82F6;
  --pv-cust: #0E8F62;
  --pv-p3: #8A6BD1;

  display: grid;
  /* `auto` on the scroller row, not `1fr`: with a max-height on the card
     and `min(100%, content)` on the scroller itself, a five-row L1 view
     ends at the fifth row instead of leaving a block of empty chart
     below it. A full plan still fills the screen and scrolls. */
  grid-template-rows: auto auto auto auto auto;
  height: auto;
  max-height: calc(100vh - var(--pv-chrome, 132px));
  font-size: var(--pv-fs);
  color: var(--fiori-text);
  background: var(--fiori-bg);
  border: 1px solid var(--pv-line);
  border-radius: 8px;
  overflow: hidden;
}
[data-theme="light"] .pv {
  --pv-surface: #FFFFFF;
  --pv-surface2: #ECEEF1;
  --pv-line: #D8DCE2;
  --pv-line-soft: #E8EAEE;
  --pv-line-strong: #BFC6CF;
  --pv-row-hover: #EFF3F8;
  --pv-row-l1: #000000;
  --pv-row-l1-ink: #FFFFFF;
  --pv-row-l1-danger: #FF8A8A;
  --pv-row-l1-warn: #FBBF24;
  --pv-accent: #2E6FD0;
  --pv-accent-soft: rgba(46, 111, 208, 0.12);
  --pv-accent-fill: #2E6FD0;

  /* validated light set — surface #F4F6FA */
  --pv-bar-c: #2E6FD0;
  --pv-bar-done: #1A4A96;
  --pv-on-bar: #FFFFFF;
  --pv-summary: #3F4854;
  --pv-milestone: #0A6E4B;   /* light theme: white label, 6.28:1 */
  --pv-crit: #C81E1E;
  --pv-link: #8D97A4;
  --pv-today: #B4530A;

  --pv-weekend: rgba(27, 36, 48, 0.055);
  --pv-holiday: rgba(180, 83, 9, 0.12);
  --pv-holiday-line: rgba(180, 83, 9, 0.5);
  --pv-warn: #8A5D04;

  --pv-si: #2E6FD0;
  --pv-cust: #0E8F62;
  --pv-p3: #6D5AB8;
}

/* ── chrome ─────────────────────────────────────────────────────────── */
.pv-hdr, .pv-bar {
  display: flex; align-items: center; gap: 6px;
  background: var(--pv-surface2); border-bottom: 1px solid var(--pv-line);
  padding: 5px 10px; white-space: nowrap;
}
/* The toolbar WRAPS. It used to scroll horizontally with its scrollbar
   hidden (overflow-x:auto + scrollbar-width:none), which put Assign
   resources, Save and Discard past the right edge of a 1440px screen
   with nothing on screen to say they were there -- the owner reported
   it twice on 2026-09-19: "I cant see Save", "Also Assign is going off
   screen".
   This is the same failure the plan's tab strip already records: an
   invisible scrollbar does not look broken, it looks like there is
   nothing more to see. A toolbar of twenty-five controls cannot be one
   line on every screen, so it takes two. */
.pv-bar { flex-wrap: wrap; row-gap: 5px; }
.pv-sep { width: 1px; height: 16px; background: var(--pv-line); flex: none; }

/* ── the ribbon (owner spec 2026-09-20) ──────────────────────────────
   "group together the Menu into sub menu headings like the attached
    example (as is done in excel) to increase the visible space for the
    actual project."

   THE QUICK BAR never folds: Undo, Redo, Save, Discard and the
   keyboard hint. It is the one strip that is always on screen, so it
   is also where the fold control lives -- a Hide button inside the
   thing it hides could not bring it back. */
.pv-quick {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap; row-gap: 4px;
  padding: 4px 10px; background: var(--pv-surface);
  border-bottom: 1px solid var(--pv-line);
}

/* The tabs ride IN the quick bar rather than taking a strip of their
   own -- a separate row is what Excel does and it cost 28px of chart
   on every screen, which is the opposite of what this was asked for.
   They stay when the groups fold, so one click reopens. */
.pv-tabs {
  display: flex; align-items: stretch; gap: 2px; flex: none;
  overflow-x: auto; scrollbar-width: none;
}
.pv-tabs::-webkit-scrollbar { display: none; }
.pv-tabs button {
  border: 0; background: transparent; color: var(--fiori-text-muted);
  font: inherit; font-size: var(--pv-fs-sm); cursor: pointer; white-space: nowrap;
  padding: 2px 11px; border-radius: var(--pv-r);
}
.pv-tabs button:hover { color: var(--fiori-text); }
.pv-tabs button[aria-selected="true"] {
  color: #FFFFFF; background: var(--pv-accent-fill); font-weight: 600;
}

/* The groups. Each is a row of controls with its NAME underneath --
   the Excel arrangement, and the one that makes a group readable at a
   glance without a border around every one of them. */
.pv-ribbon {
  display: flex; align-items: stretch;
  border-bottom: 1px solid var(--pv-line);
  overflow-x: auto;
}
.pv-grp {
  display: flex; flex-direction: column; gap: 3px; flex: none;
  padding: 5px 11px 3px; border-right: 1px solid var(--pv-line);
}
.pv-grp-row { display: flex; align-items: center; gap: 5px; }
/* The chart's gesture hint, at the end of whichever tab is open. It
   wraps rather than pushing the groups off the edge. */
/* nowrap and clipped: squeezed at a narrow width it would wrap to a
   second line and make the whole ribbon row taller, which is the one
   thing this row must not do. */
.pv-hint-end {
  flex: 1 1 auto; min-width: 0; text-align: right; padding: 0 10px; align-self: center;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.pv-grp-n {
  font-size: var(--pv-fs-xs); letter-spacing: .05em; text-transform: uppercase;
  color: var(--fiori-text-muted); text-align: center;
}
/* A group's own separators are redundant once the group has a border
   and a name of its own. */
.pv-grp .pv-sep { display: none; }

/* Only pushes what follows to the right when the row has room. On a
   wrapped toolbar the hint drops to its own line instead, which is
   where it belongs anyway. */
.pv-spacer { margin-left: auto; }
.pv-hint { color: var(--fiori-text-muted); font-size: var(--pv-fs-xs); }

/* Reading an older version, and the version list (owner spec
   2026-09-19). The banner is a STRIP across the chart rather than a
   toast: every gesture below it silently does nothing in this state,
   and a Gantt that ignores a drag looks broken rather than read-only. */
.pv-vbanner {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 5px 10px; font-size: var(--pv-fs-sm);
  background: color-mix(in srgb, var(--fiori-warning) 14%, var(--pv-surface2));
  border-bottom: 1px solid var(--fiori-warning);
}
.pv-vlist {
  padding: 8px 10px; background: var(--pv-surface2);
  border-bottom: 1px solid var(--pv-line); max-height: 240px; overflow: auto;
}

/* A SELECT in the toolbar, matching .pv-btn but laid out as a select.
   It wore .pv-btn at first and rendered as an almost-empty box with a
   few stray glyph tips: .pv-btn is `display: inline-flex`, and a
   select's option text is not a flex item -- the browser lays it out
   as an anonymous one and clips it. Blocks, with room for the label
   next to the 30px the global select rule reserves for its chevron. */
.pv-sel {
  display: inline-block; flex: none; box-sizing: border-box;
  height: var(--pv-ctl); line-height: calc(var(--pv-ctl) - 2px);
  min-width: 165px; max-width: 230px; padding: 0 8px; font: inherit;
  font-size: var(--pv-fs-sm); background: var(--pv-surface);
  color: var(--fiori-text); border: 1px solid var(--pv-line);
  border-radius: var(--pv-r); cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* The native arrow, since data-unstyled opts out of the app's own. */
  -webkit-appearance: menulist; appearance: menulist;
}
.pv-sel:hover:not(:disabled) { border-color: var(--pv-line-strong); }
.pv-sel:disabled { opacity: 0.4; cursor: default; }

/* The count on a level button (owner spec 2026-09-19: "show it as
   L1(5)"). Muted and tabular so the digits do not read as part of the
   label and the buttons do not jump width as the numbers change. */
.pv-n {
  margin-left: 3px; opacity: .6; font-variant-numeric: tabular-nums;
  font-size: var(--pv-fs-xs);
}
.pv-seg button[aria-pressed="true"] .pv-n { opacity: .85; }

/* Dragging a WBS row to reorder it (owner spec 2026-09-19). The drop
   line is on the BOTTOM edge because the drop puts the dragged row
   directly after this one -- a highlight over the whole row would say
   "into", which is not what happens. */
.pv-trow[draggable="true"] { cursor: grab; }
.pv-trow.dragging { opacity: .45; cursor: grabbing; }
.pv-trow.dropafter { box-shadow: inset 0 -2px 0 var(--pv-accent); }

/* Predecessors and people, editable in the drawer (owner spec
   2026-09-19). A row per link: which activity, the relationship, the
   lag, and a way to take it off. */
.pv-preds { display: flex; flex-direction: column; gap: 5px; }
.pv-pred { display: grid; grid-template-columns: 1fr 72px 62px auto; gap: 5px; align-items: center; }
.pv-pred-n {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: var(--pv-fs-sm);
}
.pv-pred-t, .pv-pred-l { min-width: 0; max-width: none; }
.pv-form .pv-sel { width: 100%; max-width: none; }

/* A row whose dates have left the phase above it (owner spec
   2026-09-22: "If any of the dates exceed the parent dates, show the
   child as well as parent WBS in Red Font").

   Both ends of the disagreement are marked, and they are marked
   DIFFERENTLY: the child is the row that is wrong, so it is red
   outright; the parent is red only on its dates, because the parent's
   own name and code are not what is in question -- it is being named
   as the window that was left. Marking a 40-row phase in full red
   would read as "this phase is broken" rather than "something under
   it is".

   Weight as well as colour: red alone fails for the 1 in 12 men who
   cannot separate it from the surrounding grey, and this is the one
   signal on the screen that says a plan does not contain its own
   work. */
.pv-trow.pv-over-child .code,
.pv-trow.pv-over-child .nm > span:last-child,
.pv-trow.pv-over-child .dt {
  color: var(--fiori-negative, #D93025);
  font-weight: 600;
}
.pv-trow.pv-over-parent .dt {
  color: var(--fiori-negative, #D93025);
  font-weight: 600;
}
/* `.pv-trow.odd .dt` paints a row sitting on a non-working day amber,
   and it is declared later in this file at the same specificity -- so
   it won, and sixteen of the fifty-nine marked rows came out amber
   instead of red (measured, not theorised). Red wins here on purpose:
   "this activity is outside its phase" is the stronger statement, and
   the non-working day is still carried by the hatched bar on the
   chart. */
.pv-trow.odd.pv-over-child .code,
.pv-trow.odd.pv-over-child .nm > span:last-child,
.pv-trow.odd.pv-over-child .dt,
.pv-trow.odd.pv-over-parent .dt {
  color: var(--fiori-negative, #D93025);
  font-weight: 600;
}

/* Selection keeps its own foreground -- a selected red row must stay
   readable on the selection fill. */
.pv-trow.sel.pv-over-child .code,
.pv-trow.sel.pv-over-child .nm > span:last-child,
.pv-trow.sel.pv-over-child .dt,
.pv-trow.sel.pv-over-parent .dt {
  color: var(--fiori-negative, #D93025);
}

/* Preferences (owner spec 2026-09-22). Two lines of checkbox in the
   ribbon, each with its label above its consequence -- a preference
   whose effect is only discovered by being refused is a trap. */
/* A Start / Finish / Duration cell being typed into (owner spec
   2026-09-22). The input fills the cell exactly, so the row does not
   change height or reflow the moment it is clicked -- a tree that
   jumps under the pointer is a tree you cannot edit two rows of. */
.pv-cell-edit { padding: 0 2px !important; overflow: visible; }
.pv-cell-edit input {
  width: 100%; min-width: 0; box-sizing: border-box;
  height: calc(var(--pv-row) - 6px);
  padding: 0 4px; font: inherit; font-size: var(--pv-fs-xs);
  color: var(--fiori-text); background: var(--pv-surface2);
  border: 1px solid var(--pv-accent); border-radius: 3px;
  font-variant-numeric: tabular-nums;
}
.pv-cell-edit input::-webkit-calendar-picker-indicator { margin: 0; padding: 0; opacity: .55; }
/* Only hint at editability where it IS editable: the title attribute
   is set only then, so the cursor follows the same condition. */
.pv-trow .dt[title], .pv-trow .du[title] { cursor: text; }

/* ONE LINE (owner spec 2026-09-22). Every other group in this ribbon
   is a single row of controls; a three-line group made Preferences the
   tab that resized the toolbar under it. The explanation lives in each
   label's tooltip now. */
.pv-prefs { flex-direction: row; align-items: center; flex-wrap: wrap; gap: 14px; }
.pv-pref { display: inline-flex; align-items: center; gap: 6px; cursor: pointer; white-space: nowrap; }
.pv-pref input { flex: 0 0 auto; accent-color: var(--pv-accent); }
.pv-pref b { font-size: var(--pv-fs-sm); font-weight: 600; color: var(--fiori-text); }

/* Adding one (owner spec 2026-09-22: "Provide a search bar when
   assigning predecesser. Also provide Level boxes to filter Level of
   WBS"). It was a native <select> holding every activity on the plan
   -- 261 of them on the project this was reported from. A bounded,
   scrolling list with a search box above it and the level boxes beside
   it, so the phase you actually want is two keystrokes away. */
.pv-predpick {
  display: flex; flex-direction: column; gap: 5px;
  margin-top: 6px; padding-top: 6px; border-top: 1px solid var(--pv-line);
}
.pv-predpick-h {
  font-size: var(--pv-fs-xs); letter-spacing: .04em; text-transform: uppercase;
  color: var(--fiori-text-muted);
}
.pv-predpick .pv-in { width: 100%; max-width: none; }
.pv-lvlbox { display: flex; flex-wrap: wrap; gap: 4px; }
.pv-lvl {
  padding: 2px 9px; border-radius: 999px; cursor: pointer;
  font-size: var(--pv-fs-xs); font-weight: 600; line-height: 1.6;
  background: var(--pv-surface); color: var(--fiori-text-muted);
  border: 1px solid var(--pv-line);
}
.pv-lvl:hover { color: var(--fiori-text); border-color: var(--pv-accent); }
.pv-lvl.on { background: var(--pv-accent); border-color: var(--pv-accent); color: #fff; }
/* Bounded: the list scrolls inside the drawer rather than pushing the
   rest of the form -- Complete %, the people, the notes -- off the
   bottom of a 261-row plan. */
.pv-predlist {
  display: flex; flex-direction: column; gap: 1px;
  max-height: 232px; overflow-y: auto; overscroll-behavior: contain;
  border: 1px solid var(--pv-line); border-radius: 6px; padding: 3px;
  background: var(--pv-surface);
}
.pv-predopt {
  display: flex; align-items: center; gap: 7px; width: 100%;
  padding: 4px 6px; border: 0; border-radius: 4px; cursor: pointer;
  background: transparent; color: var(--fiori-text); text-align: left;
  font-size: var(--pv-fs-sm); line-height: 1.35;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pv-predopt:hover, .pv-predopt:focus-visible { background: var(--pv-accent-soft); }
.pv-predopt-l {
  flex: 0 0 auto; min-width: 20px; text-align: center;
  font-size: var(--pv-fs-xs); font-weight: 700; color: var(--fiori-text-muted);
}

.pv-assign { display: flex; flex-direction: column; gap: 5px; }
.pv-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.pv-chip {
  display: inline-flex; align-items: center; gap: 4px;
  padding: 1px 6px; border-radius: 999px; font-size: var(--pv-fs-xs);
  background: var(--pv-surface); border: 1px solid var(--pv-line);
}
.pv-chip button {
  background: none; border: none; padding: 0 1px; cursor: pointer;
  color: var(--fiori-text-muted); font-size: 13px; line-height: 1;
}
.pv-chip button:hover { color: var(--fiori-negative); }

/* A duration Adjust Schedule changed, until it is saved (owner spec
   2026-09-19: "show the duration in Red font where it has been
   changed"). Bold as well as red: the column is small, tabular and
   right-aligned, and colour alone is not something everybody can
   read. */

/* A short select for the level, beside its label. */
.pv-sel-s { min-width: 96px; }

/* The same count on the plan grid's fold buttons, so the Plan tab,
   Manage Project Plan and the Visualizer all read alike. */
.impl-root .impl-lvn {
  margin-left: 3px; opacity: .6; font-variant-numeric: tabular-nums; font-size: 10px;
}
.impl-root .impl-viewtoggle button.on .impl-lvn { opacity: .9; }

.pv-seg { display: flex; background: var(--pv-surface); border: 1px solid var(--pv-line); border-radius: var(--pv-r); padding: 1px; gap: 1px; flex: none; }
.pv-seg button {
  border: 0; background: transparent; font: inherit; cursor: pointer;
  padding: 0 9px; height: calc(var(--pv-ctl) - 4px); border-radius: 2px;
  font-size: var(--pv-fs-sm); color: var(--fiori-text-muted);
}
.pv-seg button:hover { color: var(--fiori-text); }
.pv-seg button[aria-pressed="true"] { background: var(--pv-accent); color: #fff; font-weight: 600; }

/* The build-the-plan group (owner spec 2026-09-19): + Row | Indent |
   Outdent | up | down | delete, the same six Manage Project Plan
   offers. These are the only segmented buttons on this toolbar that
   can be UNAVAILABLE -- every other segment is a view switch that is
   always allowed -- so the disabled state is spelled out here. A
   transparent button left to the browser's own disabled styling looks
   exactly like an enabled one. Placed after the hover rule above
   because both are (0,2,1) and source order is what decides. */
.pv-seg button:disabled { opacity: .38; cursor: default; }
.pv-seg button:disabled:hover { color: var(--fiori-text-muted); }
/* Delete reads in the negative colour, like every other destructive
   control in the app, and fills on hover so the one irreversible
   button in the group cannot be pressed by accident for a tidy one. */
.pv-seg-edit button.danger { color: var(--fiori-negative); }
/* A TINT on hover, not a solid fill. The glyph is an emoji, which
   carries its own colour and ignores `color`, so filling the button
   leaves a red bin on a red pill and reads as a smudge. */
.pv-seg-edit button.danger:hover { background: color-mix(in srgb, var(--fiori-negative) 18%, transparent); }
.pv-seg-edit button.danger:disabled { color: var(--fiori-text-muted); }
.pv-seg-edit button.danger:disabled:hover { background: transparent; color: var(--fiori-text-muted); }

.pv-btn {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  height: var(--pv-ctl); padding: 0 8px; font: inherit; font-size: var(--pv-fs-sm);
  background: var(--pv-surface); color: var(--fiori-text);
  border: 1px solid var(--pv-line); border-radius: var(--pv-r); cursor: pointer;
}
.pv-btn:hover:not(:disabled) { border-color: var(--pv-line-strong); }
.pv-btn:disabled { opacity: 0.4; cursor: default; }
.pv-btn[aria-pressed="true"] { background: var(--pv-accent-soft); border-color: var(--pv-accent); color: var(--pv-accent); }
.pv-btn.primary { background: var(--pv-accent); border-color: var(--pv-accent); color: #fff; font-weight: 600; }
.pv-btn.icon { width: var(--pv-ctl); padding: 0; justify-content: center; }
/* The Download tab's exports are ANCHORS, not buttons: the browser
   then fetches them on the session cookie and a whole workbook never
   passes through a JS string. Which means they arrive wearing the
   link underline, and a toolbar of underlined blue words does not read
   as a row of controls. */
a.pv-btn, a.pv-btn:hover, a.pv-btn:visited, a.pv-btn:focus { text-decoration: none; color: var(--fiori-text); }
a.pv-btn[aria-disabled="true"] { opacity: 0.4; pointer-events: none; }
.pv-chip {
  display: inline-flex; align-items: center; gap: 5px; flex: none;
  padding: 2px 8px; border-radius: 999px; font-size: var(--pv-fs-xs);
  border: 1px solid var(--pv-line); color: var(--fiori-text-muted); background: var(--pv-surface);
}
.pv-chip.warn { color: var(--pv-warn); border-color: var(--pv-holiday-line); background: var(--pv-holiday); cursor: pointer; }
.pv-chip.warn[aria-pressed="true"] { background: var(--pv-warn); color: var(--fiori-bg); }
.pv-chip.dirty { color: var(--pv-warn); border-color: var(--pv-holiday-line); background: var(--pv-holiday); }

.pv-find {
  display: flex; align-items: center; height: var(--pv-ctl);
  background: var(--pv-surface); border: 1px solid var(--pv-line);
  border-radius: var(--pv-r); padding: 0 7px;
}
.pv-find input { border: 0; background: transparent; color: var(--fiori-text); outline: none; font: inherit; font-size: var(--pv-fs-sm); width: 180px; }
.pv-find input::placeholder { color: var(--fiori-text-muted); }

/* ── the scroller ───────────────────────────────────────────────────── */
.pv-wrap { overflow: auto; position: relative; background: var(--fiori-bg); }
.pv-wrap.panning { cursor: grabbing; }
.pv-inner { display: flex; align-items: flex-start; width: max-content; min-width: 100%; }

.pv-tree { width: var(--pv-tree); position: sticky; left: 0; z-index: 30; flex: none; background: var(--pv-surface); border-right: 1px solid var(--pv-line-strong); }
.pv-tree-hd, .pv-time-hd { position: sticky; top: 0; height: var(--pv-hd); }
.pv-tree-hd {
  z-index: 5; background: var(--pv-surface2); border-bottom: 1px solid var(--pv-line-strong);
  display: grid; align-items: center;
  grid-template-columns: var(--pv-c-code) minmax(64px, 1fr) var(--pv-c-start) var(--pv-c-finish) var(--pv-c-dur) var(--pv-c-who);
  font-size: var(--pv-fs-xs); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--fiori-text-muted); font-weight: 600;
}
/* ── several WBS rows / people on one project-table line ───────────
   Owner spec 2026-09-24. Chips plus an "add" dropdown rather than a
   <select multiple>: a multiple select renders as a scrolling BOX,
   cannot be one grid row tall, and needs ctrl-click to add a second
   value -- which nobody discovers. Reading a chip row takes no
   interaction at all. */
.pt-chip {
  display: inline-flex; align-items: center; gap: 2px;
  max-width: 100%; padding: 0 2px 0 6px; border-radius: 10px;
  font-size: 10.5px; line-height: 1.7;
  background: var(--fiori-row-hover);
  border: 1px solid var(--fiori-border);
  color: var(--fiori-text);
}
/* The label clips; the × never does. A remove button that scrolls out
   of its own chip cannot be pressed. */
.pt-chip-txt { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.pt-chip-x {
  all: unset; cursor: pointer; flex-shrink: 0;
  width: 13px; height: 13px; border-radius: 7px;
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 11px; line-height: 1; color: var(--fiori-text-muted);
}
.pt-chip-x:hover { background: var(--fiori-negative); color: #fff; }
.pt-chip-x:focus-visible { outline: 2px solid var(--pv-accent); outline-offset: 1px; }
/* The add control stays narrow so the chips get the width. */
.pt-add { min-width: 62px; max-width: 100%; font-size: 10.5px; }

.pv-tree-hd > span, .pv-trow > * { padding: 0 var(--pv-cell-px); }
/* Every column edge is a handle (owner spec 2026-09-22). The hit area
   is 9px and straddles the boundary, because a 1px line is not a thing
   anybody can aim at; the line it draws is 1px, because a 9px line is
   not a thing anybody wants to look at. */
.pv-tree-hd > span { position: relative; overflow: hidden; }
.pv-colgrip {
  position: absolute; top: 0; bottom: 0; right: -4px; width: 9px;
  cursor: col-resize; z-index: 2;
}
.pv-colgrip::after {
  content: ''; position: absolute; top: 22%; bottom: 22%; left: 4px; width: 1px;
  background: var(--pv-line-strong);
}
.pv-colgrip:hover::after { top: 0; bottom: 0; width: 2px; left: 3px; background: var(--pv-accent); }
/* While a drag is running the pointer is a resize arrow everywhere, and
   nothing on the page selects -- otherwise the gesture paints the whole
   tree blue the moment it leaves the header. */
body.pv-colresizing { cursor: col-resize !important; user-select: none; }
body.pv-colresizing * { cursor: col-resize !important; }
.pv-tree-hd .num, .pv-trow .num { text-align: right; }

.pv-trow {
  display: grid; align-items: center; height: var(--pv-row);
  grid-template-columns: var(--pv-c-code) minmax(64px, 1fr) var(--pv-c-start) var(--pv-c-finish) var(--pv-c-dur) var(--pv-c-who);
  border-bottom: 1px solid var(--pv-line-soft); cursor: pointer; white-space: nowrap;
}
.pv-trow:hover, .pv-grow:hover { background: var(--pv-row-hover); }
.pv-trow.sel, .pv-grow.sel { background: var(--pv-accent-soft); }
/* ── the top-level row band ─────────────────────────────────────────
   Owner spec 2026-09-24: "Let the highest level WBS (1,2,3...) be
   displayed in Solid Black Fill with White Font ... And with Orange
   Fill with Black font in Dark Theme", corrected the same day to mean
   the ROW BACKGROUND rather than the bar -- "the background of the row
   in left as well as right panel".

   One token, both panels: .pv-trow is the row in the tree, .pv-grow is
   the same row's band in the chart, and they must be the same colour
   or a phase reads as two different rows either side of the split.

   The INK has to move with the ground. These rows used to be a 3.5%
   tint, so the muted grey on the code, date and duration columns still
   read; on solid black it does not, and a row whose dates cannot be
   read is worse than an unhighlighted one. Every child that sets its
   own colour is named here, because the ones that do are exactly the
   ones inheritance would not reach. */
.pv-trow.lvl1 { font-weight: 600; background: var(--pv-row-l1); color: var(--pv-row-l1-ink); }
.pv-trow.lvl1 .code,
.pv-trow.lvl1 .dt,
.pv-trow.lvl1 .du,
.pv-trow.lvl1 .nm,
.pv-trow.lvl1 .nm > span:last-child { color: var(--pv-row-l1-ink); }
/* Selection and hover still have to read ON that band. They repaint
   the ground, so the ink goes back to the theme's own. */
.pv-trow.lvl1.sel, .pv-trow.lvl1:hover { color: var(--fiori-text); }
.pv-trow.lvl1.sel .code, .pv-trow.lvl1:hover .code,
.pv-trow.lvl1.sel .dt, .pv-trow.lvl1:hover .dt,
.pv-trow.lvl1.sel .du, .pv-trow.lvl1:hover .du,
.pv-trow.lvl1.sel .nm, .pv-trow.lvl1:hover .nm,
.pv-trow.lvl1.sel .nm > span:last-child,
.pv-trow.lvl1:hover .nm > span:last-child { color: inherit; }
.pv-trow.lvl1.sel { background: var(--pv-accent-soft); }
/* ── status colour on the band ──────────────────────────────────────
   A date the plan cannot meet is red and a non-working date is amber,
   and both were chosen to read on the page background. Neither reads
   on the band: measured, the light theme's #DC2626 on black is 3.5:1
   and the dark theme's #F87171 on #F59E0B is 1.4:1 -- the second is
   not dim, it is gone. Same signal, a value picked for THIS ground.
   Four classes, so these beat the three-class rules they replace. */
.pv-trow.lvl1.pv-over-child .code,
.pv-trow.lvl1.pv-over-child .nm > span:last-child,
.pv-trow.lvl1.pv-over-child .dt,
.pv-trow.lvl1.pv-over-parent .dt { color: var(--pv-row-l1-danger); }
.pv-trow.lvl1 .du.pv-durchg { color: var(--pv-row-l1-danger); }
.pv-trow.lvl1.odd .dt { color: var(--pv-row-l1-warn); }
/* ...and back to the theme's own where the ground goes back to normal. */
.pv-trow.lvl1.sel.pv-over-child .code,
.pv-trow.lvl1.sel.pv-over-child .nm > span:last-child,
.pv-trow.lvl1.sel.pv-over-child .dt,
.pv-trow.lvl1.sel.pv-over-parent .dt,
.pv-trow.lvl1:hover.pv-over-child .dt,
.pv-trow.lvl1:hover.pv-over-parent .dt { color: var(--fiori-negative, #D93025); }
.pv-trow .code { color: var(--fiori-text-muted); font-variant-numeric: tabular-nums; font-size: var(--pv-fs-xs); }
.pv-trow .nm { overflow: hidden; display: flex; align-items: center; gap: 5px; }
.pv-trow .nm > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pv-trow .dt, .pv-trow .du { color: var(--fiori-text-muted); font-size: var(--pv-fs-xs); font-variant-numeric: tabular-nums; }
/* A duration Adjust Schedule changed, until it is saved (owner spec
   2026-09-19: "show the duration in Red font where it has been
   changed").

   THREE classes, and written directly beneath the rule it overrides.
   `.pv-durchg` alone (0,1,0) lost to the muted grey above at (0,2,0);
   matching it at (0,2,0) still lost, because that rule sits eighty
   lines LATER and equal specificity is decided by source order. The
   result rendered grey and bold -- deliberate-looking and wrong,
   which is worse than plainly broken. Matching on `.du.pv-durchg`
   wins wherever either rule ends up.

   Bold as well as red: the column is small and right-aligned, and
   colour alone is not something every reader can use. */
.pv-trow .du.pv-durchg { color: var(--fiori-negative); font-weight: 700; }
.pv-trow.odd .dt { color: var(--pv-warn); }

.pv-caret { width: 13px; height: 13px; flex: none; display: grid; place-items: center; color: var(--fiori-text-muted); cursor: pointer; transition: transform 0.12s ease; }
.pv-caret.closed { transform: rotate(-90deg); }
.pv-caret.leaf { visibility: hidden; }
.pv-msdot { width: 6px; height: 6px; flex: none; transform: rotate(45deg); background: var(--pv-milestone); }

.pv-who { display: flex; align-items: center; }
.pv-av {
  width: var(--pv-av); height: var(--pv-av); border-radius: 50%; flex: none;
  display: grid; place-items: center; font-size: 7.5px; font-weight: 700; color: #fff;
  margin-left: -4px; border: 1.5px solid var(--pv-surface); letter-spacing: -0.02em;
}
.pv-av:first-child { margin-left: 0; }
.pv-av.si { background: var(--pv-si); }
.pv-av.cust { background: var(--pv-cust); }
.pv-av.p3 { background: var(--pv-p3); }
.pv-av.more { background: var(--pv-surface2); color: var(--fiori-text-muted); border-color: var(--pv-line); }

.pv-split { position: sticky; left: var(--pv-tree); z-index: 31; flex: none; width: 5px; margin-left: -5px; cursor: col-resize; }
.pv-split:hover::after, .pv-split.on::after { content: ''; position: absolute; inset: 0 2px; background: var(--pv-accent); }

/* ── timeline ───────────────────────────────────────────────────────── */
.pv-time { position: relative; flex: none; }
.pv-time-hd { z-index: 20; background: var(--pv-surface2); border-bottom: 1px solid var(--pv-line-strong); }
.pv-tier { position: relative; height: calc(var(--pv-hd) / 2); border-bottom: 1px solid var(--pv-line-soft); }
.pv-tier:last-child { border-bottom: 0; }
.pv-tk {
  position: absolute; top: 0; bottom: 0; display: flex; align-items: center; justify-content: center;
  font-size: var(--pv-fs-xs); color: var(--fiori-text-muted);
  border-left: 1px solid var(--pv-line-soft); overflow: hidden; white-space: nowrap;
}
.pv-tier.top .pv-tk { font-weight: 600; color: var(--fiori-text); border-left-color: var(--pv-line-strong); justify-content: flex-start; padding-left: 5px; }
/* The header cell of a closed day is tinted too, so the shading in the
   grid has an explanation at the top of the column rather than looking
   like an unexplained stripe. */
.pv-tk.we { background: var(--pv-weekend); }
.pv-tk.hol { background: var(--pv-holiday); color: var(--pv-warn); border-top: 2px dotted var(--pv-holiday-line); font-weight: 600; }

.pv-canvas { position: relative; }
.pv-bg { position: absolute; inset: 0; z-index: 0; pointer-events: none; }
.pv-vline { position: absolute; top: 0; bottom: 0; width: 1px; background: var(--pv-line-soft); }
.pv-vline.strong { background: var(--pv-line); }
.pv-closed { position: absolute; top: 0; bottom: 0; }
.pv-closed.we { background: var(--pv-weekend); }
/* Amber AND a dotted edge. Two carriers, so a holiday is still a
   holiday in greyscale, in print and under forced-colours. */
.pv-closed.hol { background: var(--pv-holiday); border-left: 1px dotted var(--pv-holiday-line); border-right: 1px dotted var(--pv-holiday-line); }
.pv-today-line { position: absolute; top: 0; bottom: 0; width: 2px; background: var(--pv-today); z-index: 6; pointer-events: none; }

.pv-grow { position: absolute; left: 0; right: 0; z-index: 1; height: var(--pv-row); border-bottom: 1px solid var(--pv-line-soft); }
/* The same band, in the chart, from the same token -- so the two
   panels cannot drift apart. */
.pv-grow.lvl1 { background: var(--pv-row-l1); }

.pv-barel {
  position: absolute; height: var(--pv-bar); border-radius: var(--pv-r);
  background: var(--pv-bar-c); cursor: grab; z-index: 3; display: flex; align-items: center;
}
.pv-barel .fill { position: absolute; left: 0; top: 0; bottom: 0; border-radius: inherit; background: var(--pv-bar-done); pointer-events: none; }
.pv-barel .lbl { position: absolute; left: 6px; white-space: nowrap; font-size: var(--pv-fs-xs); color: var(--pv-on-bar); pointer-events: none; overflow: hidden; }
.pv-barel.sel { outline: 2px solid var(--pv-accent); outline-offset: 1px; z-index: 5; }
.pv-barel.crit { background: var(--pv-crit); }
.pv-barel.odd { background: repeating-linear-gradient(45deg, var(--pv-warn), var(--pv-warn) 4px, transparent 4px, transparent 8px), var(--pv-bar-c); }
/* A summary is the SAME HEIGHT as an activity (owner spec 2026-09-20:
   heights "should be uniform"). What tells it apart is its darker fill
   and its pointed end caps, not its size.

   The caps are triangles whose height is the bar's, so their WIDTH has
   to scale with it too -- a fixed 6px against a 46px bar at the L step
   is a sliver, not a point. A third of the height keeps the same wedge
   at every step, but NEVER more than half the bar: this element does
   not clip, and on a one-day summary in Month view an eleven-pixel cap
   hung seven pixels past a four-pixel bar and sat under the first
   letter of the label (owner report 2026-09-20). Only the component
   knows how wide the bar is, so it sets --pv-cap; the fallback is the
   old fixed point, for anything that renders without it. */
/* ── a summary has no fill (owner spec 2026-09-20) ───────────────────
   "show the higher level WBS with no fill but font color = fill color
    of the lower level WBS and Bold font to differentiate it."

   An outline in its own branch colour rather than a block of it, so a
   phase reads as a bracket over its children instead of competing with
   them for the same ink. It still has to be a grabbable shape -- it is
   draggable and resizable like any other bar -- which is what the
   border is for. */
.pv-barel.summary {
  height: var(--pv-sum); border-radius: 2px;
  background: transparent;
  border: 1px solid var(--pv-tint, var(--pv-summary));
}
/* The end caps take the branch colour too, at a low opacity: they are
   the half of "this is a summary" that survives at a width where the
   outline is thinner than the gap between two bars. */
.pv-barel.summary::before, .pv-barel.summary::after {
  content: ''; position: absolute; top: 0; width: 0; height: 0;
  border-top: var(--pv-sum) solid var(--pv-tint, var(--pv-summary)); opacity: .45;
}
.pv-barel.summary::before { left: 0; border-right: var(--pv-cap, 6px) solid transparent; }
.pv-barel.summary::after { right: 0; border-left: var(--pv-cap, 6px) solid transparent; }

.pv-msel { position: absolute; width: var(--pv-ms); height: var(--pv-ms); z-index: 4; background: var(--pv-milestone); transform: rotate(45deg); cursor: grab; border-radius: 1px; }
.pv-msel.sel { outline: 2px solid var(--pv-accent); outline-offset: 2px; }
.pv-msel.crit { background: var(--pv-crit); }
.pv-msel.odd { background: var(--pv-warn); }

/* Width and offset come from the component -- they depend on how wide
   the bar is at the current zoom. */
.pv-hnd { position: absolute; top: -1px; bottom: -1px; cursor: ew-resize; z-index: 8; }
/* A summary that has been dragged off its children. Owner spec
   2026-09-19 allows that; this is what stops it being silent. Hatched
   rather than recoloured, because colour on a bar already means which
   L1 branch it belongs to, and reusing it would make two things one
   signal. The tooltip carries what the children actually span.

   Drawn on a PSEUDO-ELEMENT, not on the bar. The bar carries an inline
   `background:` shorthand for its branch tint, and a shorthand resets
   background-image to none -- so a `background-image` rule here loses
   every time the bar is tinted, which is almost always. Measured
   computing to `none` before this was moved off the element. */
/* A summary that has been dragged off its children. Owner spec
   2026-09-19 allows that; this is what stops it being silent. Hatched
   rather than recoloured, because colour on a bar already means which
   L1 branch it belongs to, and reusing it would make two things one
   signal. The tooltip carries what the children actually span.

   A CHILD ELEMENT, not a pseudo-element: `.pv-barel.summary::before`
   and `::after` are both taken above, drawing the pointed end caps a
   summary bar has -- so a hatch on ::after silently replaces the right
   cap. Not on the bar's own background either, because the bar
   carries an inline `background:` shorthand for its branch tint and a
   shorthand resets background-image. A child is the only layer left
   that nothing else owns. */
.pv-barel.summary .pv-detach {
  position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: repeating-linear-gradient(135deg,
    transparent 0 3px, var(--pv-warn) 3px 5px);
  opacity: 0.8;
}
/* The grip is drawn ON the bar's edge, wherever the zone happens to
   sit, so a narrow bar still shows where to pull. */
.pv-barel:hover .pv-hnd::after {
  content: ''; position: absolute; top: 15%; bottom: 15%; width: 2px; border-radius: 2px;
  background: var(--pv-accent); opacity: 0.9;
}
.pv-barel:hover .pv-hnd.l::after { right: 0; }
.pv-barel:hover .pv-hnd.r::after { left: 0; }
.pv-knob { position: absolute; top: 50%; width: 9px; height: 9px; margin-top: -4.5px; border-radius: 50%; background: var(--pv-surface); border: 2px solid var(--pv-accent); opacity: 0; cursor: crosshair; z-index: 7; }
.pv-barel:hover .pv-knob { opacity: 1; }
.pv-knob.l { left: -21px; } .pv-knob.r { right: -21px; }

.pv-links { position: absolute; inset: 0; z-index: 2; pointer-events: none; overflow: visible; }
.pv-links path { fill: none; stroke: var(--pv-link); stroke-width: 1.2px; opacity: 0.34; }
.pv-links polygon { fill: var(--pv-link); opacity: 0.34; }
/* The arrowhead is filled; the CONNECTOR never is. A shared `.hot`
   rule that set `fill` on both put a filled polygon where the elbow
   should be -- a 1,200-pixel blue wedge lying across a third of the
   plan, which is what it looked like on screen. Path and polygon are
   addressed separately for that reason. */
.pv-links path.hot { stroke: var(--pv-accent); fill: none; opacity: 1; stroke-width: 2px; }
.pv-links polygon.hot { fill: var(--pv-accent); stroke: none; opacity: 1; }
.pv-links path.bad { stroke: var(--pv-crit); fill: none; opacity: 1; stroke-dasharray: 4 3; }
.pv-links polygon.bad { fill: var(--pv-crit); stroke: none; opacity: 1; }


/* ── the Links view (owner spec 2026-09-20) ──────────────────────────
   "Provide a menu option called 'Links' next to Focus. Here only the
    links will show and will be editable."

   `only the links` is done by EMPHASIS, not by removing the bars: the
   gesture the owner asked for is dragging one activity onto another, so
   both have to be visible and grabbable. The bars fade back, the arrows
   come forward in their type's colour, and every arrow becomes a
   control. */
.pv.links-view .pv-barel, .pv.links-view .pv-msel { opacity: 0.38; }
.pv.links-view .pv-trow { cursor: grab; }
.pv.links-view .pv-trow.dragging { cursor: grabbing; }
/* The drop target is the whole ROW, not its bottom edge: a link is made
   WITH this activity, not positioned after it, and reusing the reorder
   underline would say the wrong thing about what is about to happen. */
.pv-trow.droplink { background: var(--pv-accent-soft); box-shadow: inset 0 0 0 1px var(--pv-accent); }

/* In the Links view the layer takes the pointer, so the fat invisible
   hit paths below can be clicked. Everywhere else it stays inert. */
.pv-links.live { pointer-events: none; }
.pv-links.live path.pv-linkhit { pointer-events: stroke; }
.pv-linkhit { fill: none; stroke: transparent; stroke-width: 11px; cursor: pointer; }
/* Colour by TYPE, and only in this view: elsewhere red means
   out-of-sequence and the accent means selected, and colouring by type
   would bury the two things somebody is actually hunting for. */
.pv-links path.t-FS { stroke: var(--neon-cyan); opacity: 0.9; stroke-width: 1.6px; }
.pv-links polygon.t-FS { fill: var(--neon-cyan); opacity: 0.9; }
.pv-links path.t-SS { stroke: var(--fiori-positive); opacity: 0.9; stroke-width: 1.6px; }
.pv-links polygon.t-SS { fill: var(--fiori-positive); opacity: 0.9; }
.pv-links path.t-FF { stroke: var(--fiori-warning); opacity: 0.9; stroke-width: 1.6px; }
.pv-links polygon.t-FF { fill: var(--fiori-warning); opacity: 0.9; }
.pv-links path.t-SF { stroke: var(--fiori-negative); opacity: 0.9; stroke-width: 1.6px; }
.pv-links polygon.t-SF { fill: var(--fiori-negative); opacity: 0.9; }
/* The one being edited, thicker so it is findable again after the panel
   has covered half the screen. Last, so it wins over the type colours
   on equal specificity. */
.pv-links path.chosen { stroke-width: 3px; opacity: 1; }
.pv-links polygon.chosen { opacity: 1; }

/* ── the four type buttons ───────────────────────────────────────────
   Owner spec 2026-09-20: "Do not show FF, FS etc as dropdowns, but as
   different relevant color buttons." Each carries the colour its arrow
   is drawn in, so the control and the chart read as one thing. Unchosen
   they are outlines; the chosen one fills. */
.pv-lt { display: flex; gap: 4px; flex-wrap: wrap; }
.pv-ltb {
  flex: none; min-width: 42px; height: var(--pv-ctl); padding: 0 9px;
  font: inherit; font-size: var(--pv-fs-sm); font-weight: 700; letter-spacing: .02em;
  background: transparent; border: 1px solid currentColor; border-radius: var(--pv-r);
  cursor: pointer;
}
.pv-lt.compact .pv-ltb { min-width: 30px; height: 22px; padding: 0 5px; font-size: var(--pv-fs-xs); }
.pv-ltb:disabled { opacity: .45; cursor: default; }
.pv-ltb.t-FS { color: var(--neon-cyan); }
.pv-ltb.t-SS { color: var(--fiori-positive); }
.pv-ltb.t-FF { color: var(--fiori-warning); }
.pv-ltb.t-SF { color: var(--fiori-negative); }
/* The fill uses the SAME hue as the outline, so a button does not
   change colour when it is pressed -- it changes weight. The ink is
   near-black rather than white because three of the four hues are light
   enough that white on them is under 3:1. */
.pv-ltb.on { color: #0B0F14; border-color: transparent; }
.pv-ltb.t-FS.on { background: var(--neon-cyan); }
.pv-ltb.t-SS.on { background: var(--fiori-positive); }
.pv-ltb.t-FF.on { background: var(--fiori-warning); }
.pv-ltb.t-SF.on { background: var(--fiori-negative); }

/* The explanation under the buttons. Owner spec 2026-09-20: "display
   the description of these as well to make it easier for user to
   understand." */
/* A plan usually carries one Go Live; a second is unusual, not wrong
   (owner, 2026-09-21: "though exceptions may exist"). So this is a
   note, not an error state -- it names the others and leaves the
   decision to the person setting it. */
.pv-warn-note{
  margin: 4px 0 0; font-size: 11px; line-height: 1.45;
  color: var(--fiori-accent-amber);
}

.pv-ltnote {
  margin: 6px 0 2px; padding: 8px 10px; border-radius: var(--pv-r);
  background: var(--pv-surface); border-left: 3px solid var(--pv-line);
}
.pv-ltnote b { display: block; margin-bottom: 4px; }
.pv-ltnote p { margin: 0 0 4px; }
.pv-ltnote p:last-child { margin-bottom: 0; }
.pv-ltnote.t-FS { border-left-color: var(--neon-cyan); }
.pv-ltnote.t-SS { border-left-color: var(--fiori-positive); }
.pv-ltnote.t-FF { border-left-color: var(--fiori-warning); }
.pv-ltnote.t-SF { border-left-color: var(--fiori-negative); }

/* ── the right-click menu (owner spec 2026-09-20) ────────────────────
   "Use Mouse right click for this." Fixed, not absolute: it is placed
   from clientX/clientY, which are viewport coordinates, and inside a
   scroller an absolute box would be positioned against the scrolled
   canvas and land somewhere else entirely. */
.pv-ctx {
  position: fixed; z-index: 9102; min-width: 210px; padding: 4px;
  background: var(--pv-surface); border: 1px solid var(--pv-line);
  border-radius: var(--pv-r); box-shadow: 0 8px 28px rgba(0, 0, 0, 0.38);
  display: flex; flex-direction: column; gap: 1px;
}
.pv-ctx-h {
  padding: 5px 9px 6px; font-size: var(--pv-fs-xs); color: var(--fiori-text-muted);
  border-bottom: 1px solid var(--pv-line); margin-bottom: 3px;
}
.pv-ctx button {
  text-align: left; border: 0; background: transparent; font: inherit;
  font-size: var(--pv-fs-sm); color: var(--fiori-text);
  padding: 7px 9px; border-radius: 2px; cursor: pointer;
}
.pv-ctx button:hover:not(:disabled) { background: var(--pv-accent-soft); }
.pv-ctx button:disabled { color: var(--fiori-text-muted); cursor: default; }
.pv-ctx button.danger { color: var(--fiori-negative); }
.pv-ctx button.danger:hover:not(:disabled) { background: color-mix(in srgb, var(--fiori-negative) 18%, transparent); }
.pv-ctx-sep { height: 1px; background: var(--pv-line); margin: 3px 0; }

/* ── the ℹ panel (owner spec 2026-09-20) ────────────────────────────
   "However the Modal should be of screen height." A drawer, not a
   centred dialog: the plan stays visible beside it, and a full-height
   column is the right shape for a description that runs to paragraphs
   with a comment thread underneath. The BODY is what scrolls, so the
   title and the Close stay put however long the thread gets. */
/* Two classes, not one: `.pv-drawer` sets its own width later in
   this sheet and they tie on specificity, so a bare `.pv-notes` lost
   on source order and the panel came out at the drawer's 360px. */
.pv-drawer.pv-notes { width: min(460px, 96vw); display: flex; flex-direction: column; }
.pv-drawer.pv-notes .dh { display: flex; align-items: flex-start; gap: 10px; flex: none; }
.pv-notes-t { flex: 1; min-width: 0; }
.pv-notes-t h2 { margin: 0; }
.pv-notes-t p { margin: 2px 0 0; }
.pv-drawer.pv-notes .dh .pv-btn { flex: none; }
.pv-notes-body { flex: 1; min-height: 0; overflow-y: auto; padding: 10px 14px 18px; }
/* The panel's contents are the grid's components, which are styled
   under `.impl-root`; the wrapper carries that class so they look the
   same here as they do there rather than needing a second set of
   rules that drifts. */
.pv-notes-body .impl-plan-idesc { width: 100%; min-height: 170px; }

/* The ℹ at the end of a WBS description. Its colour is the tone the
   row carries, set inline from the shared rule. */
.pv-i {
  flex: none; margin-left: 6px; border: 0; background: transparent;
  font: inherit; font-size: var(--pv-fs-sm); line-height: 1; cursor: pointer;
  padding: 0 3px; border-radius: 2px; opacity: 0.85;
}
.pv-i:hover { opacity: 1; background: var(--pv-accent-soft); }

/* One comment, with the delete the owner asked for. The × only
   appears on hover: a delete on every row of a thread is an invitation
   to press one by accident. */
.impl-cmt { margin-bottom: 8px; }
.impl-cmt-x {
  border: 0; background: transparent; color: var(--fiori-negative);
  cursor: pointer; padding: 0 4px; font-size: 13px; line-height: 1; opacity: 0;
}
.impl-cmt:hover .impl-cmt-x, .impl-cmt-x:focus-visible { opacity: 1; }
.impl-cmt-x:disabled { opacity: 0.35; cursor: default; }

/* ── the Project Plan Header (owner spec 2026-09-20) ─────────────────
   The three dates that lay the plan out, above the plan they lay out.
   One row that wraps, so on a narrow screen the fields stack rather
   than the sentence at the end pushing Save off the edge. */
.impl-planhd {
  display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
  padding: 9px 12px; margin-bottom: 10px;
  background: var(--fiori-surface); border: 1px solid var(--fiori-border);
  border-radius: 6px;
}
.impl-planhd-t { font-weight: 600; align-self: center; }
.impl-planhd label { display: flex; flex-direction: column; gap: 3px; }
.impl-planhd label > span { font-size: 11px; letter-spacing: .04em; color: var(--fiori-text-muted); }
.impl-planhd .impl-field { width: 150px; }

.pv-drawer.pv-addtbl { width: min(420px, 96vw); }
.pv-tblpick {
  display: flex; align-items: flex-start; gap: 8px; padding: 7px 0;
  border-bottom: 1px solid var(--pv-line); cursor: pointer;
}
.pv-tblpick > span { min-width: 0; }

.pv-drawer.pv-tpl { width: min(440px, 96vw); }
.pv-tpl-row {
  display: flex; align-items: center; gap: 8px; padding: 5px 0;
  border-bottom: 1px solid var(--pv-line);
}
/* The chequered flag on the activity that IS go-live (owner spec
   2026-09-20). Inside the WBS, before the name, because it says what
   the row is rather than something about it. */
.pv-golive { flex: none; margin-right: 4px; font-size: var(--pv-fs-sm); line-height: 1; }
/* Owner spec 2026-09-23 — the Meeting / Workshop indicator. Same metrics
   as the go-live flag so a row carrying both keeps one baseline. */
.pv-meeting { flex: none; margin-right: 4px; font-size: var(--pv-fs-sm); line-height: 1; }

.pv-drawer.pv-bulkdates { width: min(400px, 94vw); }
/* Nothing in a drawer may push its own footer off the bottom: the
   body scrolls, the header and the footer stay put. Without this a
   long people list on a short window takes Apply with it. */
.pv-drawer > .pv-plist, .pv-drawer > .pv-notes-body, .pv-drawer > .pv-dbody { min-height: 0; overflow-y: auto; }
.pv-drawer > .pv-dfoot { position: sticky; bottom: 0; background: var(--pv-surface); }
.pv-drawer.pv-linkdrawer { width: min(420px, 94vw); }
/* Which way round it runs, in words. "1.02 → 1.07" leaves the reader to
   work out which end waits. */
.pv-linkpair { display: grid; gap: 6px; margin-bottom: 8px; }
.pv-linkpair > div { display: flex; flex-direction: column; gap: 1px; }
.pv-btn.danger { color: var(--fiori-negative); border-color: var(--fiori-negative); }
.pv-btn.danger:hover { background: color-mix(in srgb, var(--fiori-negative) 18%, transparent); }

.pv-lasso { position: absolute; z-index: 40; pointer-events: none; border: 1px solid var(--pv-accent); background: var(--pv-accent-soft); border-radius: 2px; }
.pv-rubber { position: absolute; inset: 0; z-index: 41; pointer-events: none; overflow: visible; }
.pv-rubber path { fill: none; stroke: var(--pv-accent); stroke-width: 2; stroke-dasharray: 5 4; }

/* ── overview + status ──────────────────────────────────────────────── */
.pv-ov { height: 38px; position: relative; background: var(--pv-surface2); border-top: 1px solid var(--pv-line); overflow: hidden; cursor: pointer; }
.pv-ov canvas { display: block; width: 100%; height: 100%; }
.pv-ov .vp { position: absolute; top: 0; bottom: 0; border: 1px solid var(--pv-accent); background: var(--pv-accent-soft); }
.pv-status { display: flex; align-items: center; gap: 12px; padding: 0 10px; height: 26px; background: var(--pv-surface2); border-top: 1px solid var(--pv-line); font-size: var(--pv-fs-xs); color: var(--fiori-text-muted); }
.pv-status b { color: var(--fiori-text); }
.pv-legend { display: flex; align-items: center; gap: 10px; margin-left: auto; }
.pv-legend span { display: inline-flex; align-items: center; gap: 4px; }
.pv-sw { width: 11px; height: 7px; border-radius: 2px; }

.pv-selbar {
  position: fixed; left: 50%; bottom: 80px; transform: translateX(-50%) translateY(180%);
  display: flex; align-items: center; gap: 6px; z-index: 9090;
  background: var(--fiori-elevated-solid, var(--pv-surface)); border: 1px solid var(--pv-line-strong);
  border-radius: 6px; padding: 5px 8px; box-shadow: 0 10px 28px rgba(0,0,0,0.35);
  transition: transform 0.18s cubic-bezier(0.2, 0.9, 0.3, 1.2), opacity 0.18s linear;
  white-space: nowrap;
}
/* ── It is HIDDEN when nothing is selected ──────────────────────
   The slide-away was doing that job on its own -- translateY(180%)
   was supposed to push it off the bottom of the window. It never
   did: the bar sits at `bottom: 80px` and is about 44px tall, so
   180% of its own height moves it 79px down and it comes to rest one
   pixel above the bottom edge, fully visible, reading "0 selected"
   over the status line and the legend for the whole session.

   Opacity and visibility do it properly, and pointer-events keep the
   invisible bar from swallowing clicks meant for the chart beneath
   it. The transform stays, so it still slides in. */
.pv-selbar:not(.on) { opacity: 0; visibility: hidden; pointer-events: none; }
.pv-selbar.on { transform: translateX(-50%) translateY(0); opacity: 1; visibility: visible; }
/* ── Moved by hand (owner spec 2026-09-21: "Make this a floating menu
   which can be moved around") ───────────────────────────────────
   Once dragged it is positioned from the top-left, so the centring
   translate has to go -- otherwise every drop lands half a bar's width
   to the left of the pointer. The slide-in transform goes with it: a
   bar that animates from 180% below its new home is a bar that appears
   from off-screen somewhere it was never parked. */
.pv-selbar.moved { transform: none; transition: none; }
.pv-selbar.moved:not(.on) { display: none; }
/* The grip. Its cursor is what says the bar is movable at all. */
.pv-selbar > .pv-drag {
  cursor: grab; user-select: none; letter-spacing: -1px;
  color: var(--fiori-text-muted); font-size: 13px; line-height: 1;
  padding: 0 2px; opacity: 0.7;
}
.pv-selbar > .pv-drag:hover { opacity: 1; }
/* The whole bar is the drag surface, so it wants the grabbing cursor
   everywhere except on the controls, which are still controls. */
.pv-selbar { cursor: grab; }
.pv-selbar button, .pv-selbar input, .pv-selbar select { cursor: pointer; }
body.pv-dragging-bar, body.pv-dragging-bar .pv-selbar { cursor: grabbing; user-select: none; }
/* Two button pairs in the selection bar that look alike and do
   different things -- move vs duration. The caption underneath is what
   tells them apart at a glance; the titles carry the detail. Laid out
   as a column so the bar keeps its height and does not grow a second
   row of controls. */
.pv-selgrp { display: inline-flex; flex-direction: column; align-items: center; gap: 1px; }
.pv-selgrp-row { display: inline-flex; gap: 4px; }
/* ── Maximise (owner spec 2026-09-21) ───────────────────────────
   "In View provide a full screen toggle button which should hide the
    page header ... Allow this to be toggled back."

   A body class rather than props: `.page-header` is the application
   shell's breadcrumb, three components above this page, and threading
   a boolean up through the shell so one screen can hide one strip
   would make every other page carry a prop it has no use for.

   `display: none`, not `visibility: hidden` -- the point is the ~110px
   of vertical space, and a hidden element still occupies it. The class
   is removed when the Visualizer unmounts, so it cannot follow
   somebody to another screen. */
/* The app topbar and the breadcrumb, gone for as long as the
   Visualizer is on screen (owner spec 2026-09-24: "Hide the header in
   all views just like it is hidden in Full Screen"). Full Screen was
   already doing this and no other view was, so the same plan was two
   different heights depending on which layout button was pressed. */
body.pv-topless .topbar,
body.pv-topless .page-header,
body.pv-chromeless .topbar,
body.pv-chromeless .page-header { display: none; }
/* `!important` on this one alone, and it is not laziness. The element
   carries `display: flex` INLINE -- it is a flex row of title, blurb
   and project picker -- and an inline declaration beats a stylesheet
   rule. So this selector matched, lost, and the page head stayed on
   screen in Full Screen for as long as the rule has existed while the
   two above it worked; the owner reported it on 2026-09-24 as the
   header not hiding. The alternative is moving the layout out of the
   JSX into a class, which is the tidier fix and a bigger change to a
   row that is otherwise fine. */
body.pv-chromeless .pv-page-head { display: none !important; }

/* ── The Messages tab (owner spec 2026-09-21) ────────────────────
   One list, everything the plan disagrees with itself about. Tone on
   the left edge rather than on the whole row: a panel of coloured
   blocks is hard to read, and the thing being read is the text. */
.pv-msglist { padding: 8px 10px 12px; max-height: 42vh; overflow: auto; }
.pv-msglist ul { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.pv-msg {
  display: flex; align-items: center; gap: 8px;
  padding: 5px 8px; font-size: 11.5px; line-height: 1.4;
  border: 1px solid var(--pv-line); border-radius: var(--pv-r);
  background: var(--pv-surface); color: var(--fiori-text);
  border-left: 3px solid var(--fiori-text-muted);
}
.pv-msg.warn { border-left-color: var(--pv-warn, #c77700); }
.pv-msg.err  { border-left-color: var(--pv-crit, #d9534f); }
.pv-msg-k {
  flex: 0 0 auto; min-width: 74px;
  font-size: 9px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--fiori-text-muted);
}
.pv-msg-t { flex: 1 1 auto; overflow-wrap: anywhere; }
.pv-msg .pv-btn { flex: 0 0 auto; }
.pv-msg-count { font-size: 11px; color: var(--fiori-text-muted); padding: 0 4px; align-self: center; }
.pv-msg-count.on { color: var(--pv-warn, #c77700); font-weight: 600; }

/* The RACI picker in the assign drawer. Letter, name and a line on
   what it means: the difference between Consulted and Informed is the
   one people get wrong, and it belongs where the choice is made rather
   than in a help page nobody opens. */
.pv-raci-pick {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  font-size: 10px; color: var(--fiori-text-muted); margin-bottom: 6px;
}
.pv-raci-pick b { color: var(--fiori-text); font-size: 10.5px; }
.pv-raci-pick .pv-raci-note { flex: 1 1 100%; opacity: 0.85; }

/* ── RACI on a face (owner spec 2026-09-21) ──────────────────────
   A letter in the corner of the avatar, drawn only when it is NOT R.
   R is the default, so badging it would put a letter on every face and
   the one that matters -- the single A -- would stop standing out.
   Colour carries the same thing the letter does, for the glance; the
   letter carries it for everybody who cannot tell the colours apart. */
.pv-av { position: relative; }
.pv-av > .pv-raci {
  position: absolute; right: -3px; bottom: -3px;
  width: 11px; height: 11px; border-radius: 50%;
  font-size: 7.5px; font-weight: 800; font-style: normal; line-height: 11px;
  text-align: center; letter-spacing: 0;
  border: 1px solid var(--pv-surface); color: #fff;
}
.pv-av > .pv-raci.r-A { background: var(--pv-crit, #d9534f); }
.pv-av > .pv-raci.r-C { background: var(--fiori-warning, #c77700); }
.pv-av > .pv-raci.r-I { background: var(--fiori-text-muted, #6b7280); }

.pv-selgrp-n {
  font-size: 8.5px; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--fiori-text-muted); line-height: 1;
}

/* ── assign drawer ──────────────────────────────────────────────────── */
/* ── ABOVE THE APPLICATION CHROME ───────────────────────────────
   The Visualizer's floating layers used z-indexes in the 60-71 range,
   which is a local scale that means nothing outside this screen. The
   shell's top bar sits at 9000, so the modal drawer -- the one that
   holds Apply and Close -- was painted UNDER it: the header of the
   drawer, and on a short window most of the drawer, disappeared
   behind the search bar. The screen dimmed and nothing appeared to
   open.

   These four are the layers that must beat the chrome: the scrim and
   the drawer it dims for, the right-click menu, and the selection bar
   that floats over the chart. Everything else on this screen stays on
   its own local scale, because everything else lives INSIDE the
   chart. */
.pv-scrim { position: fixed; inset: 0; background: rgba(0,0,0,0.38); z-index: 9100; }
.pv-drawer {
  /* ── It can never be wider than the window ──────────────────
     This was a flat `360px`. On any window narrower than that the
     panel ran off the right-hand edge with no way to reach its
     footer, and the two buttons that matter -- Apply and Close --
     are in the footer. Every other drawer here already clamped
     itself with min(); this one, the oldest, never did. */
  position: fixed; top: 0; right: 0; bottom: 0; width: min(360px, 96vw); z-index: 9101;
  background: var(--pv-surface); border-left: 1px solid var(--pv-line-strong);
  display: grid; grid-template-rows: auto auto auto 1fr auto; color: var(--fiori-text);
  box-shadow: -8px 0 28px rgba(0,0,0,0.35);
}
.pv-drawer h2 { margin: 0 0 2px; font-size: 14px; font-weight: 700; }
.pv-drawer .dh { padding: 12px 14px 9px; border-bottom: 1px solid var(--pv-line); }
.pv-drawer .dh p { margin: 0; font-size: var(--pv-fs-xs); color: var(--fiori-text-muted); }
.pv-tabs { display: flex; gap: 3px; padding: 8px 14px 0; }
.pv-tabs button { border: 0; background: transparent; font: inherit; cursor: pointer; padding: 5px 9px; font-size: var(--pv-fs-sm); color: var(--fiori-text-muted); border-bottom: 2px solid transparent; }
.pv-tabs button[aria-pressed="true"] { color: var(--pv-accent); border-bottom-color: var(--pv-accent); font-weight: 600; }
.pv-dsearch { padding: 8px 14px; }
.pv-dsearch input { width: 100%; height: var(--pv-ctl); background: var(--pv-surface2); color: var(--fiori-text); border: 1px solid var(--pv-line); border-radius: var(--pv-r); padding: 0 8px; font: inherit; font-size: var(--pv-fs-sm); outline: none; }
.pv-plist { overflow: auto; padding: 0 7px; }
.pv-prow { display: flex; align-items: center; gap: 8px; padding: 6px 7px; border-radius: var(--pv-r); cursor: pointer; }
.pv-prow:hover { background: var(--pv-row-hover); }
.pv-prow.on { background: var(--pv-accent-soft); }
.pv-prow .meta { min-width: 0; flex: 1; }
.pv-prow .meta b { display: block; font-weight: 500; font-size: var(--pv-fs-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pv-prow .meta span { color: var(--fiori-text-muted); font-size: var(--pv-fs-xs); }
.pv-check { width: 14px; height: 14px; border-radius: 3px; flex: none; border: 1.5px solid var(--pv-line-strong); display: grid; place-items: center; }
.pv-prow.on .pv-check { background: var(--pv-accent); border-color: var(--pv-accent); }
.pv-prow.on .pv-check::after { content: ''; width: 7px; height: 3px; border-left: 2px solid #fff; border-bottom: 2px solid #fff; transform: rotate(-45deg) translate(1px, -1px); }
.pv-dfoot { padding: 10px 14px; border-top: 1px solid var(--pv-line); display: grid; gap: 7px; }

/* ── tooltip ────────────────────────────────────────────────────────── */
.pv-tip {
  position: fixed; z-index: 80; pointer-events: none;
  background: var(--fiori-elevated-solid, var(--pv-surface)); border: 1px solid var(--pv-line-strong);
  border-radius: var(--pv-r); padding: 7px 9px; box-shadow: 0 8px 22px rgba(0,0,0,0.35);
  font-size: var(--pv-fs-xs); max-width: 300px; color: var(--fiori-text);
}
.pv-tip b { display: block; margin-bottom: 3px; font-size: var(--pv-fs-sm); }
.pv-tip dl { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 1px 9px; }
.pv-tip dt { color: var(--fiori-text-muted); }
.pv-tip dd { margin: 0; font-variant-numeric: tabular-nums; }


/* ── Zoom scales BOTH panes, and the Full screen view ────────────────
   Owner report 2026-09-18: "When zooming in or out, zoom the left panel
   as well as right panel without changing the size of the panel
   (currently only the right pane is zooming). Also resize the WBS boxes
   in height also (only the width is currently getting changed)."

   Zoom used to mean one thing only -- pixels per day. So zooming in made
   every bar wider and left the rows the same 23px they always were: the
   chart grew sideways and the plan did not actually get any easier to
   read, which is what the report is describing.

   The scale variables now live on `.pv-wrap` rather than on `.pv`, and
   that placement is the whole trick. Everything inside the scroller --
   the tree rows, the row height, the bar height, the milestones, the
   avatars, the header tiers -- reads them and grows together. The
   toolbar, the status bar, the drawer and the tooltip sit OUTSIDE the
   scroller, keep the values from `.pv`, and stay put: chrome that
   resized itself every time somebody pressed + would be a different and
   worse screen.

   The panel's WIDTH is deliberately untouched. It is the one dimension
   the user sets directly, by dragging the splitter, and a zoom that
   moved it would fight them. The COLUMNS inside it scale with the type
   instead, on a gentler curve than the rows -- at full zoom the fixed
   columns take about a third more width, not twice as much, so the
   activity name keeps its share of a panel that did not grow. */
/* The Full screen label. Free-positioned -- its own absolute element
   in the canvas, not a child of the bar -- for two reasons: a milestone
   is a 45-degree-rotated square and anything inside it inherits the
   rotation, and a label parented to a bar cannot be given a different
   placement from the bar it belongs to without fighting the bar's own
   overflow. Placement is computed in the component and comes in as
   left/top. */
/* ── the label on a bar, in two layers ───────────────────────────────
   Owner spec 2026-09-19: the description starts just inside the bar's
   LEFT border, runs past its right border when it is longer than the
   bar, and there is NO white plate behind it.

   Two elements carry one string. `.pv-lbl-over` is the base, in
   ordinary canvas ink, never clipped -- it is what you read where the
   label has run off the end of the bar. `.pv-lbl-on` is the same
   string at the same left with the same padding, clipped to the bar's
   width and inked for the bar, sitting on top. Every pixel of text is
   therefore coloured for whatever is behind it, and the changeover
   lands exactly on the bar's right edge.

   The two MUST keep identical font, padding and box sizing or the
   glyphs fall out of register and the join shows as a stutter. */
.pv-lbl-over, .pv-lbl-on {
  position: absolute; white-space: nowrap; pointer-events: none;
  display: flex; align-items: center;
  font-size: var(--pv-fs-xs); padding: 0 5px;
  box-sizing: border-box;
}
.pv-lbl-over { z-index: 5; color: var(--fiori-text); overflow: visible; }
/* Bold, as asked -- the colour alone is not enough on a chart that is
   already coloured by phase. */
.pv-lbl-over.sum { font-weight: 700; }
/* Clipped to the bar, and to nothing else: this layer exists to stop
   exactly at the bar's right border. */
.pv-lbl-on { z-index: 6; overflow: hidden; }
/* The progress region sits OVER the bar layer, because it is painted
   over the bar. Same string, same left, same padding -- only the clip
   width and the ink differ, so the join is invisible. */
.pv-lbl-on.done { z-index: 7; }

/* ── the Focus breadcrumb ────────────────────────────────────────────
   Owner spec 2026-09-19. Focus removes the WBS panel, so nothing else
   on the screen says which branch you are looking at or how to get
   back up -- this is both. Every step is a button; the last one is the
   branch you are in. */
.pv-crumb {
  display: flex; align-items: center; flex-wrap: wrap; gap: 2px;
  padding: 5px 10px; font-size: var(--pv-fs-sm);
  border-bottom: 1px solid var(--pv-line-soft); background: var(--pv-surface);
}
.pv-crumb-i {
  border: 0; background: none; cursor: pointer; font: inherit;
  color: var(--fiori-text-muted); padding: 2px 6px; border-radius: 6px;
  max-width: 320px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.pv-crumb-i:hover { background: var(--pv-row-hover); color: var(--fiori-text); }
.pv-crumb-i.on { color: var(--fiori-text); font-weight: 650; }
.pv-crumb-i .c {
  color: var(--fiori-text-muted); font-variant-numeric: tabular-nums;
  margin-right: 6px; font-weight: 400;
}
.pv-crumb-s { color: var(--pv-line-strong); padding: 0 1px; }

/* Full screen: no tree, no splitter, and the timeline starts at the
   left edge. Nothing is hidden with `display: none` -- the panes are
   not rendered at all, so their rows are not built either. */
.pv.fullscreen .pv-time { margin-left: 0; }

/* ── the activity details panel, opened by double-clicking a bar ────── */
.pv-detail { margin: 0; display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; font-size: var(--pv-fs-sm); }
.pv-detail dt { color: var(--fiori-text-muted); }
.pv-detail dd { margin: 0; font-variant-numeric: tabular-nums; }
.pv-swatches { display: grid; grid-template-columns: repeat(10, 1fr); gap: 5px; }
.pv-sw-btn {
  height: 20px; border-radius: 3px; border: 1px solid var(--pv-line); cursor: pointer; padding: 0;
}
.pv-sw-btn:hover:not(:disabled) { outline: 2px solid var(--pv-line-strong); outline-offset: 1px; }
.pv-sw-btn.on { outline: 2px solid var(--pv-accent); outline-offset: 1px; }
.pv-sw-btn:disabled { cursor: default; opacity: 0.5; }
/* A summary's end caps are drawn with border-colour, so they need the
   tint too or a recoloured phase keeps grey arrowheads. */
.pv-barel.summary[style*="--pv-sum-tint"]::before { border-top-color: var(--pv-sum-tint); }
.pv-barel.summary[style*="--pv-sum-tint"]::after { border-top-color: var(--pv-sum-tint); }

/* ── the activity editor ────────────────────────────────────────────── */
.pv-drawer.wide { width: 420px; grid-template-rows: auto 1fr auto; }
.pv-form { overflow: auto; padding: 12px 14px; display: grid; gap: 10px; align-content: start; }
.pv-f { display: grid; gap: 3px; min-width: 0; }
.pv-f-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.pv-f-l { font-size: var(--pv-fs-xs); color: var(--fiori-text-muted); text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600; }
.pv-f-n { font-size: var(--pv-fs-xs); color: var(--pv-warn); }
.pv-in {
  width: 100%; height: 28px; padding: 0 8px; font: inherit; font-size: var(--pv-fs-sm);
  background: var(--pv-surface2); color: var(--fiori-text);
  border: 1px solid var(--pv-line); border-radius: var(--pv-r); outline: none;
}
.pv-in:focus { border-color: var(--pv-accent); }
.pv-in:disabled { opacity: 0.55; cursor: not-allowed; }
.pv-tog { display: flex; align-items: center; gap: 7px; height: 28px; font-size: var(--pv-fs-sm); color: var(--fiori-text-muted); }
.pv-tog input { accent-color: var(--pv-accent); width: 14px; height: 14px; }
.pv-form .pv-detail { padding: 8px 0 0; border-top: 1px solid var(--pv-line-soft); }


/* ── button colours, asserted over the shell's own reset ─────────────
   Owner report 2026-09-18: "the font on the buttons should be white
   when fill is dark blue." They were dark-on-blue, and not because the
   rules above are wrong -- because

       .impl-root button:not(.btn) { color: inherit; }

   sits in this same stylesheet at (0,2,1) and every rule above is
   (0,2,0). One class more specific and it loses; the page's own text
   colour was winning on every button, chip and segment inside the
   screen. Re-asserted here under `.pv` so the PV buttons out-specify
   it, rather than with !important, which would only move the argument
   to whoever writes the next reset.

   The fill is `--pv-accent-fill`, not `--pv-accent`: white on the dark
   theme's accent is 3.2:1. A label has to clear 4.5:1. */
.pv .pv-btn { color: var(--fiori-text); }
.pv .pv-btn:disabled { color: var(--fiori-text-muted); }
.pv .pv-btn[aria-pressed="true"] { color: var(--pv-accent); }
.pv .pv-btn.primary,
.pv .pv-btn.primary[aria-pressed="true"] {
  background: var(--pv-accent-fill); border-color: var(--pv-accent-fill); color: #FFFFFF;
}
.pv .pv-btn.primary:disabled { color: rgba(255, 255, 255, 0.75); opacity: 0.45; }
.pv .pv-seg button { color: var(--fiori-text-muted); }
.pv .pv-seg button:hover { color: var(--fiori-text); }
.pv .pv-seg button[aria-pressed="true"] { background: var(--pv-accent-fill); color: #FFFFFF; }
/* Light theme: `.pv .pv-seg button:hover` above is (0,3,1) and would
   beat the (0,2,1) disabled rules, so they are restated at the same
   weight and after it. */
.pv .pv-seg button:disabled,
.pv .pv-seg button:disabled:hover { color: var(--fiori-text-muted); }
.pv .pv-seg-edit button.danger { color: var(--fiori-negative); }
.pv .pv-seg-edit button.danger:hover { background: color-mix(in srgb, var(--fiori-negative) 18%, transparent); }
.pv .pv-seg-edit button.danger:disabled,
.pv .pv-seg-edit button.danger:disabled:hover { background: transparent; color: var(--fiori-text-muted); }
/* The same trap, and the same fix. `.impl-root button:not(.btn) {
   color: inherit; }` is (0,2,1); every `.pv-ltb.t-XX` above is (0,2,0),
   so all four type buttons came out in the page's text colour and the
   whole point of colouring them was lost. `.pv .pv-btn.danger` is here
   for the matching reason: it ties with `.pv .pv-btn` on specificity
   and loses on source order. Re-asserted under `.pv` rather than with
   !important, which would only move the argument to whoever writes the
   next reset. */
.pv .pv-ltb.t-FS { color: var(--neon-cyan); }
.pv .pv-ltb.t-SS { color: var(--fiori-positive); }
.pv .pv-ltb.t-FF { color: var(--fiori-warning); }
.pv .pv-ltb.t-SF { color: var(--fiori-negative); }
/* Near-black ink on the fill: three of the four hues are light enough
   that white on them is under 3:1. Last of the five, so it wins over
   the hue rules above on equal specificity. */
.pv .pv-ltb.on { color: #0B0F14; }
/* `.impl-root button:not(.btn) { color: inherit; }` is (0,2,1) and
   `.pv-ctx button` is (0,1,1), so the menu items came out in the
   page's text colour rather than their own. Same trap, same fix. */
/* The same app-wide reset again: `.impl-root button:not(.btn)` is
   (0,2,1) and these are (0,1,0) and (0,1,1). */
.pv .pv-i { color: inherit; }
.pv .impl-cmt-x { color: var(--fiori-negative); }
.pv .pv-tabs button { color: var(--fiori-text-muted); }
.pv .pv-tabs button:hover { color: var(--fiori-text); }
.pv .pv-tabs button[aria-selected="true"] { color: #FFFFFF; }
.pv .pv-ctx button { color: var(--fiori-text); }
.pv .pv-ctx button:disabled { color: var(--fiori-text-muted); }
.pv .pv-ctx button.danger { color: var(--fiori-negative); }
.pv .pv-btn.danger { color: var(--fiori-negative); border-color: var(--fiori-negative); }
.pv .pv-btn.danger:hover { background: color-mix(in srgb, var(--fiori-negative) 18%, transparent); }
.pv .pv-chip { color: var(--fiori-text-muted); }
.pv .pv-chip.warn { color: var(--pv-warn); }
.pv .pv-chip.warn[aria-pressed="true"] { background: var(--pv-warn); color: var(--fiori-bg); }
.pv .pv-chip.dirty { color: var(--pv-warn); }
.pv .pv-tabs button { color: var(--fiori-text-muted); }
.pv .pv-tabs button[aria-pressed="true"] { color: var(--pv-accent); }

/* ════════════════════════════════════════════════════════════════
   Dashboards › Delivery (owner spec 2026-09-19).

   Scoped under .impl-root so it inherits the Implementation module's
   cards, KPI tiles and pills rather than growing a second visual
   language for the same data.
   ════════════════════════════════════════════════════════════════ */
.impl-root.dlv-root{ padding: 16px; display: flex; flex-direction: column; gap: 14px; }

.impl-root .dlv-top{
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
}
.impl-root .dlv-jump{ display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.impl-root .dlv-jump a{ text-decoration: none; }

.impl-root .dlv-section{ display: flex; flex-direction: column; gap: 12px; }
.impl-root .dlv-h{ margin: 0; font-size: 14px; letter-spacing: .2px; }
.impl-root .dlv-h4{ margin: 4px 0 6px; font-size: 12px; color: var(--fiori-text-muted);
  text-transform: uppercase; letter-spacing: .5px; }

.impl-root .dlv-kpis{ display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.impl-root .dlv-ragrow{ display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); }
.impl-root .dlv-ragrow .impl-kpi{ text-align: left; font: inherit; }

/* "Not captured" must not read as a number. Lighter, smaller and
   italic, so a tile with no data is distinguishable from a tile
   reading zero at a glance across the page. */
.impl-root .dlv-blank{
  font-size: 13px !important; font-weight: 600 !important; font-style: italic;
  color: var(--fiori-text-muted) !important;
}

.impl-root .dlv-mix{
  display: flex; height: 8px; border-radius: 5px; overflow: hidden;
  background: var(--fiori-row-hover);
}
.impl-root .dlv-mix i{ display: block; height: 100%; }

.impl-root .dlv-gaps{ border-left: 3px solid var(--fiori-warning); }
.impl-root .dlv-gaps-h{ display: flex; justify-content: space-between; align-items: center; gap: 10px; }
.impl-root .dlv-gaplist{ margin: 8px 0 0; padding-left: 18px; font-size: 12.5px; }
.impl-root .dlv-gaplist li{ margin-bottom: 3px; }
.impl-root .dlv-gaplist em{ color: var(--fiori-text-muted); font-style: normal; }
.impl-root .dlv-gaplist em::before{ content: '— '; }

.impl-root .dlv-two{ display: grid; gap: 16px; grid-template-columns: 1fr 1fr; align-items: start; }
@media (max-width: 900px){ .impl-root .dlv-two{ grid-template-columns: 1fr; } }

.impl-root .dlv-tablewrap{ overflow-x: auto; }
.impl-root .dlv-table{ width: 100%; font-size: 12.5px; }
/* Headers left by default. The app-wide .table centres them, which
   on a portfolio table puts every column head out of line with the
   text under it -- the columns read as unrelated to their data. */
.impl-root .dlv-table th{ text-align: left; }
.impl-root .dlv-table th, .impl-root .dlv-table td{ padding: 6px 10px; vertical-align: top; }
/* The first column carries the project name AND its RAG reasons, so
   left alone it takes half the table and squeezes every figure into
   the right edge. Capped, and the reasons wrap under the name. */
.impl-root .dlv-table td:first-child, .impl-root .dlv-table th:first-child{ max-width: 340px; }
.impl-root .dlv-table td:first-child .impl-tiny{ white-space: normal; }
.impl-root .dlv-table th.r, .impl-root .dlv-table td.r{ text-align: right; white-space: nowrap; }
.impl-root .dlv-link{ color: var(--fiori-brand); text-decoration: none; font-weight: 600; }
.impl-root .dlv-link:hover{ text-decoration: underline; }

.impl-root .dlv-bars{ list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 5px; }
.impl-root .dlv-bars li{ display: grid; grid-template-columns: 1fr 2fr 48px; gap: 8px; align-items: center; font-size: 12px; }
.impl-root .dlv-bname{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.impl-root .dlv-btrack{ height: 8px; border-radius: 5px; background: var(--fiori-row-hover); overflow: hidden; }
.impl-root .dlv-btrack i{ display: block; height: 100%; border-radius: 5px; }
.impl-root .dlv-bval{ text-align: right; font-variant-numeric: tabular-nums; }
.impl-root .dlv-chips{ display: flex; flex-wrap: wrap; gap: 6px; }

/* ════════════════════════════════════════════════════════════════
   Deliverables tab (owner spec 2026-09-19).
   Sits inside .impl-root, so it inherits the module's cards, tables
   and pills.
   ════════════════════════════════════════════════════════════════ */
.impl-root .wd-root{ display: flex; flex-direction: column; gap: 10px; }
.impl-root .wd-top{ display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.impl-root .wd-chips{ display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }

.impl-root .wd-assign-grid{
  display: grid; gap: 12px; align-items: start;
  grid-template-columns: minmax(240px, 2fr) minmax(150px, 1fr) auto;
}
/* .impl-lbl is an inline span elsewhere in the module, so used as a
   field label it let FORMAT and MODULE run onto one line with the
   select wedged between them. Blocked HERE rather than on .impl-lbl
   itself, which would reflow every other screen that uses it inline. */
.impl-root .wd-assign-grid .impl-lbl{ display: block; margin-bottom: 4px; }
.impl-root .wd-assign-grid textarea.impl-field{ font-family: inherit; }
@media (max-width: 860px){ .impl-root .wd-assign-grid{ grid-template-columns: 1fr; } }

.impl-root .wd-table{ width: 100%; font-size: 12.5px; }
.impl-root .wd-table th, .impl-root .wd-table td{ padding: 6px 9px; vertical-align: top; }
.impl-root .wd-code{ font-family: ui-monospace, monospace; white-space: nowrap; }
.impl-root .wd-err{ color: var(--fiori-negative); font-size: 11.5px; }

/* An activity whose deliverables are all approved. A left border
   rather than a row fill: a filled row competes with the selected-row
   highlight, and on a 300-row plan the page turns into a colour chart
   nobody can read a single row out of. */
.impl-root .wd-done td:first-child{ box-shadow: inset 3px 0 0 var(--fiori-positive); }

.impl-root .wd-dlist{ display: flex; flex-direction: column; gap: 4px; }
.impl-root .wd-d{ display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
.impl-root .wd-dname{
  background: none; border: none; padding: 0; cursor: pointer; text-align: left;
  color: var(--fiori-brand); font: inherit; font-weight: 600;
}
.impl-root .wd-dname:hover{ text-decoration: underline; }
.impl-root .wd-x{ padding: 0 6px; line-height: 1.6; }

.impl-root .wd-filesrow > td{ background: var(--fiori-row-hover); }
.impl-root .wd-files{ padding: 4px 2px 8px; }
.impl-root .wd-paste textarea{ min-height: 90px; }


/* ══════════════════════════════════════════════════════════════════
   THE TASK PICKER ON A MASTER (owner 2026-09-26)
   ══════════════════════════════════════════════════════════════════
   *"Attaching a task to master task looks very complex. Simplify the
   UI to a proper task searchable modal."*

   The first version borrowed impl-col / impl-row / impl-field, which
   are scoped to `.impl-root` -- and My Tasks is not inside it. Not one
   rule applied, so a hundred tasks rendered as running text with bare
   checkboxes strewn through it. These classes are this screen's own.
   ══════════════════════════════════════════════════════════════════ */
.mt-pick{ display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.mt-pick-sum{ font-size: 11px; color: var(--fiori-text-muted); white-space: nowrap; }
.mt-pick-top{ display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.mt-pick-search{
  display: flex; align-items: center; gap: 6px;
  flex: 1 1 240px; min-width: 0;
  padding: 7px 10px; border-radius: 8px;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface2-solid);
  color: var(--fiori-text-muted);
}
.mt-pick-search:focus-within{ border-color: var(--fiori-brand); }
.mt-pick-input{
  flex: 1 1 auto; min-width: 0;
  border: 0; background: none; outline: none;
  font: inherit; font-size: 13px; color: var(--fiori-text);
}
.mt-pick-clear{
  border: 0; background: none; cursor: pointer; padding: 0 2px;
  color: var(--fiori-text-muted); font-size: 12px;
}
.mt-pick-clear:hover{ color: var(--fiori-text); }
.mt-pick-new{
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 12px; border-radius: 8px; cursor: pointer;
  border: 1px solid rgba(139,92,246,0.55);
  background: rgba(139,92,246,0.12);
  color: var(--fiori-text); font-size: 12px; font-weight: 600;
}
.mt-pick-new:hover{ background: rgba(139,92,246,0.24); }
.mt-pick-note{ font-size: 11px; color: var(--fiori-text-muted); }
/* A fixed height with its own scroll. A thousand rows growing the
   modal is how the old one ran off the bottom of the screen; here the
   list scrolls and the search box stays where it was put. */
.mt-pick-list{
  display: flex; flex-direction: column;
  max-height: 46vh; overflow-y: auto;
  border: 1px solid var(--fiori-border); border-radius: 8px;
  background: var(--fiori-surface-solid);
}
.mt-pick-empty{ padding: 14px; font-size: 12px; color: var(--fiori-text-muted); }
.mt-pick-row{
  display: flex; align-items: center; gap: 10px;
  padding: 8px 12px; cursor: pointer; min-width: 0;
  border-bottom: 1px solid var(--fiori-border);
}
.mt-pick-row:last-child{ border-bottom: 0; }
.mt-pick-row:hover{ background: var(--fiori-surface2); }
/* The ticked ones are at the top AND marked, so the boundary between
   "in this master" and "everything else" is visible without counting. */
.mt-pick-row.is-on{ background: rgba(139,92,246,0.10); }
.mt-pick-row input[type="checkbox"]{ flex: 0 0 auto; width: 15px; height: 15px; cursor: pointer; }
.mt-pick-main{ display: flex; flex-direction: column; gap: 1px; flex: 1 1 auto; min-width: 0; }
.mt-pick-title{
  font-size: 13px; color: var(--fiori-text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.mt-pick-meta{ font-size: 11px; color: var(--fiori-text-muted); }
.mt-pick-flag{
  flex: 0 0 auto; font-size: 10px; font-weight: 700;
  padding: 2px 7px; border-radius: 999px; white-space: nowrap;
}
.mt-pick-flag.is-moved{
  color: var(--ct-amber-500); border: 1px solid var(--ct-amber-500);
}
.mt-pick-flag.is-late{ color: var(--fiori-negative); border: 1px solid var(--fiori-negative); }

/* A sub-task's type is settled before the form opens, so the picker
   is replaced by a sentence rather than left offering a choice the
   server would refuse. */
.mt-sub-of{
  display: inline-block; padding: 6px 0;
  font-size: 12px; color: var(--fiori-text-muted);
}


/* ══════════════════════════════════════════════════════════════════
   MASTERS, SUB-TASKS AND WHAT IS WAITING (owner 2026-09-26)
   ══════════════════════════════════════════════════════════════════ */

/* *"Show the master task in a different shade or a different box line
   so as to distinguish."* Both: the shade separates it from the rows
   around it, the edge survives a hover and a print. !important
   because the row's background and border are written inline by the
   component and an inline style beats a class. */
.mt-row.is-master{
  background: var(--task-master-bg) !important;
  border-color: var(--task-master-edge) !important;
  border-left-width: 3px !important;
}
/* A sub-task is indented by the component (a margin, so its rail and
   its actions still line up with every other row); this is only the
   quieter ground that says it belongs to the row above. */
.mt-row.is-sub{ background: color-mix(in srgb, var(--task-surface) 92%, var(--task-master-bg)) !important; }
/* Waiting on another task: readable, obviously not startable. Not
   faded to the point of being unreadable -- its assignee still has to
   be able to see what is coming and when. */
.mt-row.is-waiting{ background: var(--task-wait-bg) !important; }
.mt-row.is-waiting .mt-row-actions{ opacity: .5; }

.mt-wait{
  display: inline-flex; align-items: center; gap: 4px; margin-left: 8px;
  padding: 1px 8px; border-radius: 999px;
  font-size: 10px; font-weight: 700; white-space: nowrap;
  background: rgba(148,163,184,0.15); color: var(--fiori-text-muted);
  border: 1px solid var(--fiori-border);
}
/* The one that finishes the objective. */
.mt-final{
  display: inline-flex; align-items: center; gap: 4px; margin-left: 8px;
  padding: 1px 8px; border-radius: 999px;
  font-size: 10px; font-weight: 700; white-space: nowrap;
  background: rgba(16,185,129,0.15); color: var(--ct-emerald-500);
  border: 1px solid rgba(16,185,129,0.55);
}
/* *"show the master task as incomplete by showing its description in
   Red font and with an information text that one task needs to be
   marked as final task."* The sentence itself, on the row -- a
   tooltip is not an information text, it is one nobody reads. */
.mt-nofinal{
  margin-left: 8px; font-size: 10.5px; font-weight: 600;
  color: var(--fiori-negative); white-space: nowrap;
}
/* !important: the title's colour is written inline by the component,
   and an inline style beats a class, so without this the red would
   never appear. */
.mt-title-nofinal{ color: var(--fiori-negative) !important; }

/* *"provide an option 'Master' next to 'Automated' button"* -- its own
   toggle rather than a fourth entry inside the source segment. Manual
   / Automated / Calendar answer where a task came from; this answers
   what kind it is, and one control cannot honestly answer both. */
.mt-only-master{
  padding: 5px 12px; font-size: 12px; font-weight: 700;
  border-radius: 8px; cursor: pointer;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-row-hover); color: var(--fiori-text);
}
.mt-only-master:hover{ border-color: var(--task-master-edge); }
.mt-only-master.on{
  background: var(--task-master-edge); border-color: var(--task-master-edge); color: #fff;
}
.mt-only-master:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }

/* The "Dependent on" picker's note, and the final-task checkbox.
   A dropdown that silently changes when a task can be started is one
   people are surprised by later, so it says what it does. */
.mt-fld-why{
  display: block; margin-top: 4px;
  font-size: 11px; line-height: 1.4; color: var(--fiori-text-muted);
}
.mt-final-pick{
  display: inline-flex; align-items: flex-start; gap: 7px;
  padding: 7px 0; cursor: pointer;
  font-size: 12px; line-height: 1.35; color: var(--fiori-text);
}
.mt-final-pick input[type="checkbox"]{ flex: 0 0 auto; width: 15px; height: 15px; margin-top: 1px; cursor: pointer; }

/* ══════════════════════════════════════════════════════════════════
 * THE BUSINESS DRIVEN CONFIGURATION QUESTIONNAIRE
 *
 * Owner 2026-09-27: *"Create a 3rd tab called - Questionnaire
 * (Business Driven Configuration) ... After this is saved, I want this
 * to appear in the Project, when a button (create an icon for
 * Questionnaire) is clicked, open a full screen modal with questions."*
 *
 * Two screens: the tab on Scope Item Master that loads the workbooks,
 * and the full-screen modal in a project that answers them.
 *
 * ── On specificity, which cost this block three rewrites ──
 * Both tables carry `impl-table`, and `.impl-root .impl-table td` is
 * (0,2,1). A rule written as `.pq-table td` is (0,1,1) and loses
 * SILENTLY -- the padding simply never applies. Every rule below that
 * restyles a cell therefore names the row section too
 * (`... tbody td`, `... thead th`), which is (0,2,2) and wins on its
 * own strength rather than on being further down the file. The
 * Scope Item Master page is `<div className="impl-root sim-root">`, so
 * this applies to the sim tables as much as the modal's.
 * ══════════════════════════════════════════════════════════════════ */

/* ── the upload report ────────────────────────────────────────────
 *
 * Not a toast. One upload carries thirteen workbooks writing to ten
 * modules, two of which ship no scope-item column at all, and a toast
 * reading "1,149 added" says none of that. So it is a block that stays
 * on the page until the next upload, with a row per file. */
.sim-root .sim-report{
  margin-top: 12px; padding: 10px 12px; border-radius: 10px;
  background: var(--fiori-surface2-solid); border: 1px solid var(--fiori-border);
  /* Six columns, one of them a sentence explaining why a workbook was
     refused. Measured at a 400px viewport the table came out 419px wide
     and took the whole PAGE sideways with it -- the report is the one
     table on this screen that had no scroll container of its own, which
     is only visible on a narrow window. */
  overflow-x: auto;
}
.sim-root .sim-report-head{
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 8px;
}
.sim-root .sim-report-table{ font-size: 11.5px; }
.sim-root .sim-report-table .num{ text-align: right; font-variant-numeric: tabular-nums; }
/* A file that could not be read is the one row somebody has to act on,
   so it is the one row that is coloured -- and it keeps its colour
   under the cursor, which is why this names tbody: (0,3,2) beats the
   (0,3,1) of `.impl-root .impl-table tbody tr:hover td`. A row that
   loses its warning exactly when you point at it is worse than one
   that never had it. */
.sim-root .sim-report-table tbody tr.sim-report-bad td{
  background: var(--impl-rel-no-bg); color: var(--impl-rel-no-fg);
}

/* ── the questions grid ──────────────────────────────────────────── */
.sim-root .sim-toolbar{
  display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 10px;
}
/* 1,149 questions. The table scrolls inside its own box rather than
   taking the page sideways with it. */
.sim-root .sim-grid-wrap{ overflow: auto; max-height: 62vh; }
.sim-root .sim-q-table{ font-size: 12px; }
/* The module, editable in place. Owner 2026-09-27: *"Allow Module to
   be changed and saved."* Narrow, because a module is at most ten
   characters and the column beside it is the question. */
.sim-root .sim-q-mod{ width: 84px; min-width: 0; height: 26px; font-size: 11px; }
/* Sticky needs a SOLID background: --fiori-surface2 is
   rgba(36,44,56,0.65) in dark, and a translucent header shows the rows
   sliding through it. */
.sim-root .sim-q-table thead th{
  position: sticky; top: 0; z-index: 1;
  background: var(--fiori-surface-solid);
}
/* The question is the column worth the width; the rest are there to
   find it by. min-width only: `max-width` on a table-cell is ignored
   under auto layout, and it was -- declared 620px, measured 902px on a
   1340px window. A rule that does nothing is a rule the next person
   copies, so it is gone rather than left looking like a constraint. The
   modal, where these are actually read rather than scanned, gets its
   measure from the four columns beside it instead. */
.sim-root .sim-q-text{ min-width: 320px; }
.sim-root .sim-q-refs{ white-space: nowrap; }
.sim-root .sim-q-ref{
  display: inline-block; margin: 0 3px 2px 0; padding: 0 6px;
  border-radius: 999px; font-size: 10px; font-weight: 700;
  background: var(--fiori-brand); color: var(--fiori-on-accent);
}
/* An inherited scope ref, marked. Finance writes one ref against the
   first row of a group and leaves the rest blank, so somebody checking
   a row against SAP's own sheet needs to know this row did not carry
   it itself. */
.sim-root .sim-q-carried{
  margin-left: 3px; font-size: 10px; color: var(--fiori-text-muted); cursor: help;
}

/* ── the icon on a module line ───────────────────────────────────── */
.impl-root button.impl-chip.impl-chip-act{
  cursor: pointer; gap: 5px; padding: 2px 9px;
  color: var(--fiori-brand); border-color: var(--fiori-brand);
  background: var(--fiori-surface-solid);
}
.impl-root button.impl-chip.impl-chip-act:hover{
  background: var(--fiori-brand); color: var(--fiori-on-accent);
}
.impl-root button.impl-chip.impl-chip-act:focus-visible{
  outline: 2px solid var(--fiori-brand); outline-offset: 1px;
}
/* The answered/total count. Tabular figures so the chip does not
   twitch as 9/40 becomes 10/40. */
.impl-root button.impl-chip.impl-chip-act b{ font-variant-numeric: tabular-nums; }

/* ── the full-screen modal ────────────────────────────────────────
 *
 * 9600 sits above the shell (9000), the Project Visualizer drawer
 * (9100-9102) and the document panel (9500), and deliberately BELOW
 * the replacement confirm dialog at 10001 -- this modal asks "3
 * answers have not been saved. Close anyway?", and a modal drawn over
 * its own confirmation is a modal nobody can leave. Same ordering the
 * document panel was given on 2026-09-26, and for the same reason. */
.pq-scrim{
  position: fixed; inset: 0; z-index: 9600;
  background: rgba(0,0,0,0.45);
  display: flex; align-items: stretch; justify-content: center;
  padding: 18px;
}
.pq-sheet{
  display: flex; flex-direction: column; min-height: 0;
  width: 100%; max-width: 1500px;
  background: var(--fiori-surface-solid); color: var(--fiori-text);
  border: 1px solid var(--fiori-border); border-radius: 12px;
  box-shadow: 0 18px 50px rgba(0,0,0,0.35); overflow: hidden;
}
.pq-head{
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  flex: 0 0 auto;
  padding: 10px 14px; border-bottom: 1px solid var(--fiori-border);
  background: var(--fiori-surface2-solid);
}
.pq-title{ display: flex; align-items: baseline; gap: 8px; }
.pq-mod{
  padding: 1px 9px; border-radius: 999px; font-size: 11px; font-weight: 700;
  background: var(--fiori-brand); color: var(--fiori-on-accent);
}
.pq-search{ width: 280px; max-width: 40vw; }
/* The BODY scrolls, not the sheet. Save, the search and the answered
   count live in the head and have to stay reachable from question 263
   as much as from question 1. */
.pq-body{ flex: 1 1 auto; min-height: 0; overflow: auto; }
/* FIXED layout, so the column widths on the header cells are the
   widths -- and nothing inside a cell can move them.
 *
 * Under auto layout a <select>'s preferred width is its WIDEST OPTION,
 * and `width: 100%` does not restrain that: the Scope Item ID filter,
 * whose options read "BD9 — Sell from Stock", drove its 150px column
 * to 384px and took the space out of the question beside it. Measured,
 * not guessed. Under fixed layout the first row's widths decide, and
 * the same select fits the column it is in. */
.pq-sheet .pq-table{ table-layout: fixed; }
/* Which means a long unbroken string -- a scope id blob, a URL in an
   answer -- has to be allowed to break, or it overflows its cell
   instead of widening it. */
.pq-sheet .pq-table td, .pq-sheet .pq-table th{ overflow-wrap: anywhere; }
.pq-sheet .pq-table thead th{
  position: sticky; top: 0; z-index: 1;
  background: var(--fiori-surface-solid);
  border-bottom: 1px solid var(--fiori-border);
}
/* Room to read. These are sentences out of SAP's workbooks, not codes:
   the longest question in the thirteen files runs 340 characters. */
.pq-sheet .pq-table tbody td{ padding: 8px 10px; }
/* `.impl-root .impl-table th` is nowrap, which under the fixed layout
   clipped "Relevant for Project" to "RELEVANT FOR PROJE". A heading
   that cannot say its own column's name is worse than two lines. */
/* Bottom, so the five filters sit on one line even though "Relevant
   for Project" needs two lines for its name and the others need one. */
.pq-sheet .pq-table thead th{ white-space: normal; line-height: 1.25; vertical-align: bottom; }
.pq-q{ line-height: 1.45; }
/* SAP's own definition of the topic, under the question. Smaller and
   muted, because it is context FOR the question rather than a second
   question. */
.pq-def{
  display: block; margin-top: 3px;
  font-size: 11px; line-height: 1.4; color: var(--fiori-text-muted);
}
/* The chips WRAP in the modal, and this is not a preference.
 *
 * Under the fixed layout above the Scope Item ID column is 170px and
 * cannot grow, so `nowrap` does not widen it -- it makes ten chips run
 * out of the cell and across the question beside it. Measured at
 * 1400px: chips five to ten sat on top of the words. A cell whose
 * content cannot fit either wraps or overflows, and only one of those
 * is readable. */
.pq-refs{ white-space: normal; }
/* THE SCOPE ITEM CHIP, defined here and not borrowed.
 *
 * The modal wore `sim-q-ref`, whose rule is `.sim-root .sim-q-ref` --
 * and the modal is on the PROJECT page, inside .impl-root, so it
 * matched nothing. Ten chips with no padding and no background render
 * as one unbroken string, which is exactly what the owner saw:
 * *"show each scope ID separately rather than one unbroken
 * concatenated text liek - BD92EQI9IBKABDD1EZ1F11MC1J7J14"*. Ten ids,
 * stored correctly as ten, drawn as thirty characters.
 *
 * check-impl-classes could not catch it: the class IS defined. A class
 * defined under a root the markup is not inside is the same silence
 * one level down. */
.pq-ref{
  display: inline-block; margin: 0 4px 3px 0; padding: 1px 7px;
  border-radius: 999px; font-size: 10.5px; font-weight: 700;
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
  background: var(--fiori-brand); color: var(--fiori-on-accent);
  cursor: help;
}
/* A question with no scope item is here deliberately -- Quality
   Management and Asset Management ship no scope column, and their
   questions are asked of the whole module -- so the cell says so. An
   empty cell among fifty filled ones reads as a fault. */
.pq-whole{
  display: inline-block; padding: 0 7px; border-radius: 999px;
  font-size: 10px; font-style: italic;
  border: 1px dashed var(--fiori-border); color: var(--fiori-text-muted);
}
/* READ-ONLY, and it has to look it. The scope tracker owns this
   answer; a control here would be a second place to set it and the two
   would disagree the first time anyone used them. So: a pill, not a
   dropdown. */
.pq-rel{
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 11px; font-weight: 700;
  background: var(--impl-rel-yes-bg); color: var(--impl-rel-yes-fg);
}
/* `.impl-root .impl-field` is height: 30px with no vertical padding --
   right for an input, wrong for a two-line textarea, where it clips the
   second line. (0,2,1) to beat its (0,2,0). */
.pq-sheet textarea.pq-resp{
  width: 100%; height: auto; min-height: 46px; resize: vertical;
  padding: 6px 9px; line-height: 1.4;
}
.pq-empty{
  display: flex; flex-direction: column; gap: 6px;
  padding: 22px 18px; max-width: 620px;
}

/* ── the per-column filters ───────────────────────────────────────
 *
 * Owner 2026-09-27: *"Provide a dropdown filter for each column and
 * also a search bar."*
 *
 * In the header cell with the column's name, not in a second row
 * beneath it. As two sticky rows, the filter row's `top` had to equal
 * the name row's rendered HEIGHT -- and the moment "Relevant for
 * Project" wrapped to two lines that height went 30px → 40px and the
 * filters were drawn ten pixels under the names. One row cannot come
 * apart that way. */
.pq-th{
  display: block; margin-bottom: 4px;
  font-size: 11px; letter-spacing: .04em; text-transform: uppercase;
}
.pq-sheet .pq-table thead .pq-fcell{
  width: 100%; min-width: 0; height: 26px;
  font-size: 11px; font-weight: 400; text-transform: none; letter-spacing: 0;
}

/* ── the topic band ───────────────────────────────────────────────
 *
 * Owner 2026-09-27: *"Remove this text - "These questions are..." in
 * each line. Let this be show at the top only once common for a
 * module."* SAP writes the topic's definition against every row of
 * that topic, so one 180-word paragraph appeared under four
 * consecutive questions and the questions were hard to find between
 * them. Once, above the topic's first question. */
.pq-sheet .pq-table tbody tr.pq-band td{
  padding: 10px 12px;
  background: var(--fiori-surface2-solid);
  border-top: 1px solid var(--fiori-border);
  border-bottom: 1px solid var(--fiori-border);
}
.pq-band-t{
  display: block; font-size: 12px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--fiori-brand);
}
.pq-band-d{
  display: block; margin-top: 3px; max-width: 105ch;
  font-size: 11.5px; line-height: 1.45; color: var(--fiori-text2);
}
/* A hovered row is a row somebody is reading; a band is not a row. */
.impl-root .pq-table tbody tr.pq-band:hover td{ background: var(--fiori-surface2-solid); }

/* The relevance, now a control rather than a pill. (0,2,1) to beat
   `.impl-root .impl-field`'s width-less 30px default and keep it
   inside a 140px column. */
.pq-sheet select.pq-rel-sel{ width: 100%; min-width: 0; height: 28px; font-size: 11.5px; }

/* ══════════════════════════════════════════════════════════════════
 * WORKING AS ANOTHER ROLE (owner spec 2026-09-27)
 *
 * *"Provide an option for Superadmin to test the application by
 * logging in and the selecting the Role so that menu and
 * authorizations of that role are applied."*
 *
 * A small dialog, not a full-screen modal: it asks one question and
 * the answer is one click. 10050 puts it over the top bar at 9000 and
 * the panels at 9500-9600, and below the replacement confirm at 10001
 * is NOT wanted here -- this dialog asks for a choice, it never raises
 * a confirm of its own, and it must sit over everything that could be
 * open when somebody decides to switch role.
 * ══════════════════════════════════════════════════════════════════ */
.ra-scrim{
  position: fixed; inset: 0; z-index: 10050;
  background: rgba(0,0,0,0.5);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 72px 18px 18px;
}
.ra-box{
  display: flex; flex-direction: column; gap: 10px;
  width: 100%; max-width: 480px; max-height: 76vh;
  padding: 14px; border-radius: 12px;
  background: var(--fiori-surface-solid); color: var(--fiori-text);
  border: 1px solid var(--fiori-border);
  box-shadow: 0 18px 50px rgba(0,0,0,0.35);
}
.ra-head{ display: flex; align-items: baseline; gap: 10px; }
/* Said once, and it has to be read. The owner chose full writes while
   acting, so this is not a preview -- a save made as Employee is a
   real save, by him, with permissions he does not hold. */
.ra-warn{
  padding: 9px 11px; border-radius: 9px;
  font-size: 11.5px; line-height: 1.5;
  background: var(--impl-rel-unsure-bg); color: var(--impl-rel-unsure-fg);
  border: 1px solid var(--impl-rel-unsure-fg);
}
.ra-search{ width: 100%; }
.ra-list{ overflow: auto; display: flex; flex-direction: column; gap: 2px; min-height: 0; }
.impl-root button.ra-role{
  display: flex; align-items: center; gap: 9px;
  padding: 7px 9px; border-radius: 8px; cursor: pointer; text-align: left;
  background: none; border: 1px solid transparent; color: var(--fiori-text);
  font-size: 12.5px;
}
.impl-root button.ra-role:hover{ background: var(--fiori-surface2-solid); border-color: var(--fiori-border); }
.impl-root button.ra-role:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }
/* The level, which is how the owner refers to these roles -- *"select
   as role - L30, L.. etc"* -- so it leads the line rather than
   trailing it. Tabular and fixed width, so a column of them lines up. */
.ra-lvl{
  min-width: 38px; text-align: center;
  padding: 1px 7px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700; font-variant-numeric: tabular-nums;
  background: var(--fiori-brand); color: var(--fiori-on-accent);
}
.ra-name{ flex: 1 1 auto; font-weight: 600; text-transform: capitalize; }

/* ══════════════════════════════════════════════════════════════════
 * THE BPD TEST SCRIPT (owner spec 2026-09-27)
 *
 * *"This is another type of document for testing each step of the
 * Scope ID ... There is one such excel for every scope id."*
 *
 * Organised by STEP, because that is what he asked it to attach to.
 * It reuses the questionnaire's modal shell -- .pq-scrim, .pq-sheet,
 * .pq-head, .pq-body and .pq-ref are written without a page root
 * precisely so a second screen can wear them.
 * ══════════════════════════════════════════════════════════════════ */

/* ── a step, and the activity that tests it ────────────────────── */
.ts-step{ border-bottom: 1px solid var(--fiori-border); }
.ts-step-h{
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 9px 14px;
  background: var(--fiori-surface2-solid);
}
/* The step's place in the scope item. Tabular, so a column of them
   lines up rather than drifting as the numbers get wider. */
.ts-seq{
  min-width: 26px; text-align: right;
  font-size: 11px; font-variant-numeric: tabular-nums; color: var(--fiori-text-muted);
}
/* A step with nothing to test it. Said, not left blank: five of BNX's
   28 have no activity in the document, and that is a fact about the
   document rather than a fault in the load. */
.ts-nolink{
  padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; font-style: italic;
  border: 1px dashed var(--fiori-border); color: var(--fiori-text-muted);
}

/* ── how the link was made ────────────────────────────────────────
 * Four words, not one. A both-keys match and a name-only match are
 * different claims, and collapsing them into "linked" is how a guess
 * becomes a fact between one release and the next. */
.ts-badge{
  padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700; cursor: help;
}
.ts-linked{ background: var(--impl-rel-yes-bg); color: var(--impl-rel-yes-fg); }
.ts-sibling{ background: var(--impl-rel-yes-bg); color: var(--impl-rel-yes-fg); }
.ts-name,
.ts-app,
.ts-near{ background: var(--impl-rel-unsure-bg); color: var(--impl-rel-unsure-fg); }
/* The one that needs a person: the release moved underneath the
   document. */
.ts-conflict{ background: var(--impl-rel-no-bg); color: var(--impl-rel-no-fg); }

/* ── the activity and its actions ─────────────────────────────── */
.ts-act{ padding: 0 14px 4px 40px; }
.impl-root button.ts-act-h{
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  width: 100%; padding: 7px 0; cursor: pointer;
  background: none; border: 0; text-align: left;
  color: var(--fiori-text); font-size: 12.5px;
}
.impl-root button.ts-act-h:hover .ts-act-t{ text-decoration: underline; }
.impl-root button.ts-act-h:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 2px; }
.ts-caret{ color: var(--fiori-text-muted); font-size: 10px; width: 10px; }
.ts-act-t{ font-weight: 600; }
.ts-pre{
  padding: 0 7px; border-radius: 999px; font-size: 10px;
  background: var(--fiori-surface2-solid); color: var(--fiori-text-muted);
  border: 1px solid var(--fiori-border);
}
.ts-actions{ margin: 2px 0 8px; }
/* Instructions come out of SAP's HTML as lines -- a list of field
   values is a list -- so the newlines have to survive into the cell.
   Collapsed to spaces they stop being followable, which is the whole
   point of the document. */
.ts-do, .ts-exp{ white-space: pre-line; line-height: 1.45; }
.ts-exp{ color: var(--fiori-text2); }

/* ── running a cycle, on the project's Test tab ──────────────────
 *
 * Owner 2026-09-27, choosing how results are kept: named cycles, a
 * result per activity, and a tab of its own. */
.impl-root .ts-scope td{ vertical-align: middle; }
.ts-run-bar{
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 10px;
}
.ts-cycle{
  padding: 1px 9px; border-radius: 999px; font-size: 11px; font-weight: 700;
  background: var(--fiori-surface2-solid); border: 1px solid var(--fiori-border);
}
.ts-tallies{ display: inline-flex; gap: 5px; flex-wrap: wrap; align-items: center; }
/* One shape for a count, four colours for what it counts. Written as
   a shared rule plus a colour each, so a fifth outcome cannot be added
   with a different size and shape by accident. */
.ts-t{
  padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700; font-variant-numeric: tabular-nums;
  background: var(--fiori-surface2-solid); color: var(--fiori-text-muted);
}
.ts-t-yes{    background: var(--impl-rel-yes-bg);    color: var(--impl-rel-yes-fg); }
.ts-t-no{     background: var(--impl-rel-no-bg);     color: var(--impl-rel-no-fg); }
.ts-t-unsure{ background: var(--impl-rel-unsure-bg); color: var(--impl-rel-unsure-fg); }
.ts-t-none{   background: var(--fiori-surface2-solid); color: var(--fiori-text-muted); }

/* ── the verdict, four buttons ────────────────────────────────────
 * Buttons rather than a dropdown: a tester sets one of four things
 * hundreds of times, and a dropdown is three interactions where this
 * is one. The chosen one is filled; clicking it again clears it,
 * because a result set by mistake has to be removable and "not run"
 * is a real state, not the absence of a state. */
.ts-act-row{ display: flex; align-items: flex-start; gap: 10px; }
.ts-act-row > .ts-act-h{ flex: 1 1 auto; min-width: 0; }
.ts-verdict{ display: inline-flex; gap: 3px; flex: 0 0 auto; padding-top: 5px; }
.impl-root button.ts-r{
  padding: 2px 9px; border-radius: 7px; cursor: pointer;
  font-size: 11px; font-weight: 700;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface-solid); color: var(--fiori-text-muted);
}
.impl-root button.ts-r:hover:not(:disabled){ border-color: var(--fiori-brand); color: var(--fiori-text); }
.impl-root button.ts-r:disabled{ cursor: default; opacity: .55; }
.impl-root button.ts-r:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }
.impl-root button.ts-r-pass.on{    background: var(--impl-rel-yes-bg);    color: var(--impl-rel-yes-fg);    border-color: var(--impl-rel-yes-fg); }
.impl-root button.ts-r-fail.on{    background: var(--impl-rel-no-bg);     color: var(--impl-rel-no-fg);     border-color: var(--impl-rel-no-fg); }
.impl-root button.ts-r-blocked.on{ background: var(--impl-rel-unsure-bg); color: var(--impl-rel-unsure-fg); border-color: var(--impl-rel-unsure-fg); }
.impl-root button.ts-r-na.on{      background: var(--fiori-surface2-solid); color: var(--fiori-text); border-color: var(--fiori-text-muted); }

/* What happened, and the defect if it did not. */
.ts-record{ display: flex; gap: 10px; flex-wrap: wrap; padding: 0 0 10px; }
.impl-root textarea.ts-actual{
  width: 100%; height: auto; min-height: 44px; resize: vertical;
  padding: 6px 9px; line-height: 1.4;
}

/* ── Test Data: the fields a script asks for ────────────────
 *
 * Owner spec 2026-09-27. The columns are read across, once per field,
 * so a field found in ten scope items wraps its chips rather than
 * pushing the value box off the screen -- which is what the
 * questionnaire's scope column did before it was given
 * table-layout: fixed. */
.ts-fields{ table-layout: fixed; }
.ts-fields td{ overflow-wrap: anywhere; }
.ts-fields .pq-refs{ white-space: normal; }
/* SAP's value, shown as data rather than prose: it is the thing being
   replaced and has to read at a glance beside the box replacing it. */
.ts-sapval{
  /* The literal stack, not a var: there is no --fiori-mono token in
     this file, and a custom property nobody defines renders as
     nothing. master-task-check gates exactly this. */
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px; padding: 1px 6px; border-radius: 5px;
  background: var(--fiori-surface2-solid); color: var(--fiori-text);
}
/* A row with an unsaved answer on it. Marked, because Replace acts on
   exactly these and somebody has to see what they are committing
   without counting. */
.ts-fields tbody tr.ts-row-dirty td{
  background: var(--impl-rel-unsure-bg, rgba(154,107,0,.10));
}
/* ── adding, copying and taking out a step ────────────────
 *
 * Owner spec 2026-09-27: *"Create new step and new sub step or copy or
 * delete such step."* Four controls on a nine-column row, so they are
 * glyphs with names on hover rather than words that would take the
 * width the What-to-do column needs.
 *
 * `.impl-root button.x` on every one of them: `.impl-root
 * button:not(.btn)` weighs (0,2,1) and a colour set by class alone
 * never reaches the element. */
.ts-acts{ white-space: nowrap; text-align: right; }
.impl-root button.ts-act-b{
  padding: 1px 6px; margin-left: 2px; border-radius: 6px; cursor: pointer;
  font-size: 11px; line-height: 1.6;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface-solid); color: var(--fiori-text-muted);
}
.impl-root button.ts-act-b:hover{ border-color: var(--fiori-brand); color: var(--fiori-brand); }
.impl-root button.ts-act-b:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }
/* Delete is the one that cannot be undone by typing again, so it is
   the one that looks different. */
.impl-root button.ts-act-b.ts-act-x:hover{
  border-color: var(--fiori-negative); color: var(--fiori-negative);
}
/* A wide control with words on it. nowrap, because the frame is
   300px and a select beside it can want all of that: without it
   "Add image" breaks across two lines and the frame's controls stop
   being one row. Measured in Chromium at 300px. */
.impl-root button.ts-act-b.ts-act-w{
  padding: 2px 10px; margin-left: 0; white-space: nowrap; flex: 0 0 auto;
}
/* A sub-step: 3.1 under 3. Indented on the number, not on the whole
   row, so the What-to-do column keeps its width down the table. */
.ts-lines tbody tr.ts-sub td:first-child{ padding-left: 16px; }
.ts-lines tbody tr.ts-sub td:first-child::before{
  content: '\21B3'; margin-right: 3px; color: var(--fiori-text-muted);
}
.ts-foot{
  display: flex; align-items: flex-start; gap: 10px; flex-wrap: wrap;
  padding: 8px 2px 4px;
}
.ts-removed{ font-size: 11.5px; color: var(--fiori-text-muted); }
.ts-removed summary{ cursor: pointer; }
.ts-removed-row{
  display: flex; align-items: center; gap: 8px; padding: 3px 0;
  min-width: 320px;
}
/* ── the image tray, on the right ─────────────────────
 *
 * Owner spec 2026-09-27: *"provide a new panel on the right ... This
 * panel should resize the left panel so that together they cover the
 * full page width. The right panel will stretch full height. The
 * images will be displayed as large thumbnails."*
 *
 * A flex row: the strip column takes what is left, the tray takes a
 * fixed share. `min-width: 0` on the column is what lets the strip
 * inside it shrink -- without it a flex child refuses to go below its
 * content's width and the tray is pushed off the screen. */
/* FULL HEIGHT. Owner spec 2026-09-27: *"The right panel will stretch
   full height"*, repeated 2026-09-28. The row stretched its children
   to its own height, and its own height was the strip plus the manual
   panel -- so the tray ended part-way down the sheet with empty space
   under it. `min-height: 100%` makes the row fill the scrolling body
   it sits in, and `align-items: stretch` then carries that to the
   tray. */
.ts-tray-wrap{ display: flex; align-items: stretch; gap: 12px; min-height: 100%; }
.ts-strip-col{ flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.ts-strip-bar{ display: flex; align-items: center; gap: 8px; padding: 6px 2px 0; }
.ts-tray{
  flex: 0 0 300px; width: 300px;
  display: flex; flex-direction: column; gap: 8px;
  padding: 10px; border-radius: 12px;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface2-solid);
  /* Full height of the panel it sits in, and its own scroll: a folder
     of forty screenshots must not make the page scroll. */
  align-self: stretch; max-height: 100%; overflow: hidden;
}
.ts-tray-h{ display: flex; align-items: center; gap: 6px; }
/* The panel's empty state: the action, then what to do with it. */
.ts-tray-empty{
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 28px 16px; text-align: center;
}
.ts-tray-list{
  display: flex; flex-direction: column; gap: 10px;
  overflow-y: auto; min-height: 0; padding-right: 2px;
}
/* LARGE thumbnails, as asked -- a strip thumbnail tells you which
   picture it is, and this has to tell you what is in it. */
.ts-tray-item{
  margin: 0; cursor: grab; border-radius: 8px; overflow: hidden;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface-solid);
}
.ts-tray-item:active{ cursor: grabbing; }
.ts-tray-item img{ display: block; width: 100%; height: 150px; object-fit: cover; }
.ts-tray-item figcaption{
  padding: 3px 6px 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}

@media (max-width: 900px){
  /* No room for two columns on a phone: the tray goes under the
     strip rather than squeezing it to nothing. */
  .ts-tray-wrap{ flex-direction: column; }
  .ts-tray{ flex-basis: auto; width: auto; max-height: 320px; }
}

/* ── the film strip ──────────────────────────────────
 *
 * Owner spec 2026-09-27: *"Show the test procedure in graphical format
 * with blocks (test procedure) like a movie clip and allow user to
 * scroll horizontally and paste the screenshot."*
 *
 * One frame per procedure, running left to right. The frames are a
 * fixed width so the strip reads as a strip rather than as boxes of
 * whatever width their text happened to need, and the STRIP scrolls,
 * never the page. */
.ts-strip-wrap{ position: relative; }
.ts-strip{
  display: flex; gap: 12px; align-items: stretch;
  overflow-x: auto; overflow-y: hidden;
  padding: 10px 2px 14px;
  scroll-snap-type: x proximity;
}
.ts-frame{
  flex: 0 0 300px; width: 300px; scroll-snap-align: start;
  display: flex; flex-direction: column; gap: 7px;
  padding: 10px; border-radius: 12px;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface-solid);
}
.ts-frame:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 2px; }
/* A prerequisite is not a step and must not read as one: it comes
   first because that is the order somebody works in. */
.ts-frame.is-pre{ border-style: dashed; }
/* Dragging a file over it. Said in the frame's own border rather than
   with a tooltip, because the cursor is already carrying the file. */
.ts-frame.is-over{
  border-color: var(--fiori-brand);
  box-shadow: inset 0 0 0 2px var(--fiori-brand);
}
/* THE STEP BEING WORKED ON. Owner spec 2026-09-27: *"mark the current
   step being updated with a strong blue border."* The same weight as
   the drag-over state on purpose -- it is the same statement, "this
   is where the picture goes" -- and it is also where Ctrl-V lands, so
   it has to be unmistakable at a glance down a strip of twenty. */
.ts-frame.is-sel{
  border-color: var(--fiori-brand);
  box-shadow: inset 0 0 0 2px var(--fiori-brand);
}
.ts-frame.is-sel:focus-visible{ outline-offset: 4px; }
.ts-frame-h{ display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
/* THE CHIPS, WHEN THEY ARE THE FILTER.
   Their own classes, not the span's: as a <button> a chip falls under
   `.impl-root button:not(.btn)` at (0,2,1), which beats the (0,1,0)
   `.pq-mod` / `.pq-ref` rules and repaints it. Reusing those classes
   would also have left them looking dead to check-button-colour,
   which is how this was caught -- twice. */
.impl-root button.pq-mod-b,
.impl-root button.pq-ref-b{
  border: 1px solid transparent; cursor: pointer; font: inherit;
  border-radius: 999px; font-weight: 700;
  background: var(--fiori-brand); color: var(--fiori-on-accent);
  font-variant-numeric: tabular-nums; letter-spacing: .02em;
  opacity: .5;
}
.impl-root button.pq-mod-b{ padding: 1px 9px; font-size: 11px; }
.impl-root button.pq-ref-b{ margin: 0 4px 3px 0; padding: 1px 7px; font-size: 10.5px; }
.impl-root button.pq-mod-b:hover,
.impl-root button.pq-ref-b:hover{ opacity: .8; }
.impl-root button.pq-mod-b.on,
.impl-root button.pq-ref-b.on{
  opacity: 1; border-color: var(--fiori-pill-active-border);
  box-shadow: 0 0 0 2px rgba(59,141,248,.22);
}
.impl-root button.pq-mod-b:focus-visible,
.impl-root button.pq-ref-b:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }

/* ── MODULE, OR JUST THIS SCOPE ITEM ───────────────────────────────
 * Owner, 2026-09-28: *"Allow user to select the button on top to see
 * data filtered by Module or by Scope Item."* Two buttons rather than
 * a checkbox: both states are named, so the screen always says which
 * one it is showing instead of leaving it to be worked out from the
 * row count. */
.ts-scope-by{
  display: inline-flex; align-items: center; gap: 0;
  border: 1px solid var(--fiori-border); border-radius: 999px;
  padding: 2px; margin-right: 8px;
}
/* The element is NAMED in every one of these. `.impl-root
   button:not(.btn)` weighs (0,2,1) and sets a colour on any unnamed
   button inside the shell, so `.ts-by` alone at (0,1,0) loses and the
   colour here is never seen. check-button-colour caught exactly this
   when these rules were first written. */
.impl-root button.ts-by{
  border: 0; background: transparent; cursor: pointer;
  font: inherit; font-size: 12px; line-height: 1;
  padding: 5px 12px; border-radius: 999px;
  color: var(--fiori-text2); white-space: nowrap;
}
.impl-root button.ts-by:hover{ color: var(--fiori-text); }
.impl-root button.ts-by.on{
  background: var(--fiori-pill-active-bg);
  border: 1px solid var(--fiori-pill-active-border);
  color: var(--fiori-pill-active-text); font-weight: 600;
}
.impl-root button.ts-by:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }
@media (max-width: 900px){
  .ts-scope-by{ margin: 4px 8px 4px 0; }
  .impl-root button.ts-by{ padding: 5px 9px; }
}

.ts-frame-n{
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--fiori-text-muted);
}
/* SAP's own step code, beside the sequential one. Quieter and not
   uppercased: it is a reference, not the heading. */
.ts-frame-sap{
  font-weight: 500; letter-spacing: 0; text-transform: none;
  color: var(--fiori-text-muted);
}
.ts-frame-t{
  flex: 1 1 100%; font-size: 12.5px; line-height: 1.35;
  overflow-wrap: anywhere;
}
/* ── the selected step, as the manual will read ─────────────
 *
 * Owner spec 2026-09-27. Under the strip, set apart from it: it is not
 * another frame, it is what the frame above will look like when it is
 * handed to the client. */
.ts-manual{
  border-top: 1px solid var(--fiori-border);
  margin-top: 4px; padding-top: 10px;
}
.ts-manual-h{
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding: 0 2px 4px;
}
/* The measure is the manual's, not the strip's: this is a document,
   and a document read at 1400px wide is not read. */
/* `.ts-manual` on the front: this element also wears `.um-page`,
   whose own max-width is 860px and which is declared LATER in this
   file. Two rules of equal weight, and the later one wins -- measured
   at 860 before this was raised. */
.ts-manual .ts-manual-body{ max-width: 760px; padding: 4px 2px 18px; }
/* Bigger here than in the strip's thumbnails -- the point of the
   panel is to see whether the picture actually shows what the step
   says. */
.ts-manual .um-fig img{ max-width: 520px; }

/* ── is this step relevant, on the block ───────────────────
 *
 * Owner spec 2026-09-27: *"it can show 3 buttons like in flow"* — the
 * same three words in the same order as the scope item flow card, so
 * the two screens read alike. The answer store is the same too.
 *
 * `.impl-root button.x` on every one: `.impl-root button:not(.btn)`
 * weighs (0,2,1) and a colour set by class alone never arrives. */
.ts-frame-rel{ display: flex; gap: 4px; flex-wrap: wrap; }
.impl-root button.ts-rel{
  /* 3px, not 1: at 1px these came out 16px tall, which is a small
     target for a control somebody uses on every step of every
     procedure. Measured in Chromium. */
  padding: 3px 10px; border-radius: 999px; cursor: pointer;
  font-size: 11px; font-weight: 600; line-height: 1.45;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface-solid); color: var(--fiori-text-muted);
}
.impl-root button.ts-rel:hover:not(:disabled){ border-color: var(--fiori-brand); color: var(--fiori-text); }
.impl-root button.ts-rel:disabled{ cursor: default; opacity: .55; }
.impl-root button.ts-rel:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }
/* The answer that is ON, in the relevance colours this app already
   uses for yes / no / not sure everywhere else. */
.impl-root button.ts-rel-yes.on{
  background: var(--impl-rel-yes-bg); color: var(--impl-rel-yes-fg); border-color: var(--impl-rel-yes-fg);
}
.impl-root button.ts-rel-no.on{
  background: var(--impl-rel-no-bg); color: var(--impl-rel-no-fg); border-color: var(--impl-rel-no-fg);
}
.impl-root button.ts-rel-not_sure.on{
  background: var(--impl-rel-unsure-bg); color: var(--impl-rel-unsure-fg); border-color: var(--impl-rel-unsure-fg);
}
.ts-frame-tally{ display: flex; gap: 4px; flex-wrap: wrap; }
.ts-frame-target{ display: flex; gap: 6px; align-items: center; }
.ts-frame-target select{ flex: 1 1 auto; min-width: 0; }
.ts-frame-busy{ display: flex; align-items: center; gap: 8px; }
.ts-bar{
  flex: 1 1 auto; height: 5px; border-radius: 999px; overflow: hidden;
  background: var(--fiori-surface2-solid);
}
.ts-bar span{ display: block; height: 100%; background: var(--fiori-brand); }
.ts-shots{ display: flex; flex-wrap: wrap; gap: 6px; }
.ts-shot-line{
  flex: 1 1 100%; display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding-top: 4px; border-top: 1px dashed var(--fiori-border);
}
.ts-shot{ position: relative; margin: 0; }
.ts-shot.is-orphan{ opacity: .72; }
.impl-root button.ts-shot-b{
  display: block; padding: 0; cursor: zoom-in; border-radius: 7px; overflow: hidden;
  border: 1px solid var(--fiori-border); background: var(--fiori-surface2-solid);
  color: var(--fiori-text);
}
.ts-shot img{ display: block; width: 84px; height: 56px; object-fit: cover; }
.impl-root button.ts-act-b.ts-shot-x{
  position: absolute; top: -6px; right: -6px; padding: 0 5px; line-height: 1.5;
  background: var(--fiori-surface-solid);
}
.ts-shot-none{
  flex: 1 1 100%; padding: 14px 8px; border-radius: 8px; text-align: center;
  border: 1px dashed var(--fiori-border);
  font-size: 11.5px; color: var(--fiori-text-muted);
}
/* A screenshot is unreadable at 84px; the point of the thumbnail is to
   find it, and this is where it is looked at. */
.ts-zoom{
  position: fixed; inset: 0; z-index: 9800; cursor: zoom-out;
  display: flex; align-items: center; justify-content: center;
  padding: 24px; background: rgba(0,0,0,.72);
}
.ts-zoom img{ max-width: 100%; max-height: 100%; border-radius: 8px; }

@media (max-width: 900px){
  .ts-frame{ flex-basis: 84vw; width: 84vw; }
}
/* ── the user manual, on the screen ───────────────────────
 *
 * Owner spec 2026-09-27. It is a DOCUMENT, so it is set like one: a
 * measure that stays readable, pictures that sit in the text rather
 * than beside it, and no table anywhere. The Word and PDF versions
 * render the same shape, so what is read here is what is handed over.
 */
.um-page{ max-width: 860px; margin: 0 auto; padding: 18px 20px 40px; }
.um-h1{ font-size: 21px; line-height: 1.25; margin: 0 0 4px; }
.um-meta{ color: var(--fiori-text2); margin: 0 0 4px; font-size: 12.5px; }
.um-note{
  color: var(--fiori-text-muted); font-size: 11.5px;
  margin: 0 0 18px; padding-bottom: 10px;
  border-bottom: 1px solid var(--fiori-border);
}
.um-sec{ margin-top: 22px; }
.um-h2{
  font-size: 15.5px; line-height: 1.3; margin: 0 0 4px;
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
/* "Before you start" is not a step, and a reader who takes it for one
   does the work in the wrong order. */
.um-kind{
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--fiori-text-muted); white-space: nowrap;
}
.um-sub{ display: flex; align-items: center; gap: 8px; margin: 0 0 8px; }
.um-step{ margin: 12px 0; }
.um-h3{ font-size: 12.5px; margin: 0 0 3px; }
.um-page p{ margin: 3px 0; font-size: 12.5px; line-height: 1.55; }
.um-list{ margin: 3px 0 6px 18px; font-size: 12.5px; line-height: 1.55; }
/* ── THE RESULT LINE ───────────────────────────────────────────────
 * Owner, 2026-09-28: *"The field for Result and its content are not
 * aligned. Also make the content field length full panel width as the
 * rest of the fields. And make the Result field and its content in
 * Bold Navy Blue colour."*
 *
 * The label used to sit beside the field. A field that grows to two
 * lines then leaves the label stranded against its last line, which
 * is what "not aligned" was. The label goes above it, and the field
 * takes the full width like every other field in the chapter. */
.um-exp{ display: block; margin: 4px 0 6px; color: var(--um-result); }
.um-exp-k{ display: block; font-weight: 700; color: var(--um-result); }
.um-exp-v{ display: block; font-weight: 700; color: var(--um-result); }
.um-no{ color: var(--fiori-text-muted); }
/* Somebody else's step, kept in the sequence of a role's manual. Set
   apart by a rule rather than a box: it is a signpost, not a card. */
/* The "who is this manual for" picker, in the dialog's header bar. */
.um-by{ display: inline-flex; align-items: center; gap: 6px; margin-right: 6px; }
.um-by-sel{ height: 28px; padding: 0 24px 0 8px; font-size: 12px; max-width: 260px; }
@media (max-width: 720px){
  .um-by{ width: 100%; margin: 4px 0; }
  .um-by-sel{ flex: 1 1 auto; max-width: none; }
}

.um-brief{
  margin: 10px 0; padding: 4px 0 4px 10px; font-size: 12.5px;
  color: var(--fiori-text2); border-left: 3px solid var(--fiori-border);
}

/* ── THE CHAPTER, EDITED WHERE IT IS READ ──────────────────────────
 * Owner, 2026-09-27: *"allow the text of the lower panel to be
 * edited."* The field has to look like the sentence it replaces or
 * the panel stops reading as the manual it is showing, so it carries
 * the surrounding type and no chrome until it is pointed at. */
.um-edit{
  display: block; width: 100%; box-sizing: border-box;
  font: inherit; font-size: 12.5px; line-height: 1.55;
  color: var(--fiori-text); background: transparent;
  border: 1px solid transparent; border-radius: 6px;
  padding: 2px 6px; margin: 1px 0 2px -6px;
  resize: none; overflow: hidden;
}
.um-edit.is-one{ font-weight: 600; font-size: 12.5px; }
.um-edit:hover{ border-color: var(--fiori-border); background: var(--fiori-row-hover); }
.um-edit:focus{
  outline: none; border-color: var(--fiori-brand);
  background: var(--fiori-input-bg); box-shadow: 0 0 0 3px rgba(59,141,248,.18);
}
.um-edit::placeholder{ color: var(--fiori-text-muted); font-style: italic; }
/* Full width, under its label, and in the same bold navy as the text
   it replaces -- a field that restyles the sentence the moment you
   click into it reads as a different sentence. */
.um-exp .um-edit{
  display: block; width: 100%; max-width: 100%;
  font-weight: 700; color: var(--um-result);
}

.um-fig{ margin: 8px 0 10px; }
/* Full width up to the measure, and never wider than the column: a
   1920px screenshot would otherwise take the page sideways. */
.um-fig img{
  display: block; max-width: 100%; height: auto;
  border-radius: 8px; border: 1px solid var(--fiori-border);
}
.um-cap{ display: block; margin-top: 3px; font-size: 11px; color: var(--fiori-text-muted); }
/* ── the role a project grants a team ──────────────────────
 *
 * Owner spec 2026-09-27. A permissions control on a project screen, so
 * it has to read as one: set apart from the team list above it, and
 * carrying its own sentence about how long the grant lasts. */
.ra-bar{
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 10px 0; padding: 8px 10px; border-radius: 10px;
  border: 1px solid var(--fiori-border);
  background: var(--fiori-surface2-solid);
}
.ra-bar-t{ font-size: 12px; font-weight: 700; }
.ra-bar-now{ display: flex; gap: 5px; align-items: center; flex-wrap: wrap; }
.ra-role-chip{
  display: inline-block; padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700;
  background: var(--fiori-brand); color: var(--fiori-on-accent);
}
/* The sentence takes the whole row rather than squeezing between the
   controls, where it would be read as a label for one of them. */
.ra-bar-note{ flex: 1 1 100%; }
/* --fiori-negative is chosen against the page, and this bar sits on
   --fiori-surface2-solid, which is darker: measured 4.31:1 in the
   light theme, under the 4.5 this text has to reach. The relevance
   red is the one that clears it on a tinted ground. */
.ra-bar .impl-negative{ color: var(--impl-rel-no-fg, #B3261E); }
/* A field common to the module rather than to one scope item.
   Owner spec 2026-09-27 -- marked because it changes what saving the
   row does: one answer for the module, applied everywhere it
   appears. */
.ts-agnostic{
  display: inline-block; margin-left: 6px; padding: 0 7px;
  border-radius: 999px; font-size: 10px; font-weight: 700;
  background: var(--impl-rel-unsure-bg); color: var(--impl-rel-unsure-fg);
}
.ts-only{
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11.5px; color: var(--fiori-text-muted); white-space: nowrap;
}
/* The field name, once, with its values under it. Owner, 2026-09-27:
   *"go by the unique field name e.g. Plant and then show variations in
   the value under"*. A band rather than an indent, because Valuation
   Area's three values have to read as three variations of one decision
   and not as three unrelated rows. */
.ts-fields tbody tr.ts-fgroup td{
  background: var(--fiori-surface2-solid);
  border-top: 1px solid var(--fiori-border);
  padding-top: 7px; padding-bottom: 5px;
}
/* The band is a heading; the rows under it are the thing being
   answered, so they carry the left rule that ties them to it. */
.ts-fields tbody tr.ts-fval td:first-child{
  border-left: 2px solid var(--fiori-border);
}

/* ── a tab per test procedure ─────────────────────────────────────
 *
 * Owner spec 2026-09-27: *"Each step will be on a separate tab (link
 * in landscape)."* Sticky under the header, because a procedure runs
 * to a dozen lines and the way to the next one must not be a scroll
 * back to the top. */
.ts-tabs{
  display: flex; gap: 2px; flex-wrap: wrap;
  padding: 6px 10px; flex: 0 0 auto;
  border-bottom: 1px solid var(--fiori-border);
  background: var(--fiori-surface2-solid);
}
/* `.impl-root` on every one of these, and `button` spelled out, so they
   weigh (0,3,1) and clear `.impl-root button:not(.btn)` at (0,2,1).
   Written as `.ts-tab` alone the colours below are dead -- see the note
   on that rule. */
.impl-root button.ts-tab{
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 11px; border-radius: 999px; cursor: pointer;
  font-size: 11.5px; font-weight: 600;
  background: none; border: 1px solid transparent; color: var(--fiori-text-muted);
}
.impl-root button.ts-tab:hover{ background: var(--fiori-surface-solid); color: var(--fiori-text); }
.impl-root button.ts-tab:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }
.impl-root button.ts-tab.on{
  background: var(--fiori-brand); color: var(--fiori-on-accent); border-color: var(--fiori-brand);
}
.impl-root button.ts-tab.on .ts-seq{ color: var(--fiori-on-accent); }
/* The setup tab. It is not a step and must not read as one -- it comes
   first because that is the order a tester works in, not because it is
   step zero. */
.impl-root button.ts-tab.is-pre{ font-style: italic; }

/* ── the procedure's flowchart ────────────────────────────────────
 *
 * Owner spec 2026-09-27: *"Provide the same kind of Stepwise flowchart
 * for solution activity each Test Procedure."* It is drawn by the
 * scope item's own swimlane machinery, so everything here is the frame
 * around it: the room it sits in and the key that says what the
 * colours mean.
 *
 * .sim-root, because the flow's CSS is written under that root and
 * this panel is on the project page -- the same wrapper ScopeItemsTab
 * puts round the flow modal, and the rule check-impl-classes now
 * enforces. */
.ts-chart{
  padding: 8px 0 12px;
  border-bottom: 1px solid var(--fiori-border);
  margin-bottom: 10px;
}
/* Four words, because the boxes are coloured and a colour with no key
   is a colour somebody guesses at. Seventeen statuses collapse to
   three tones plus untouched -- the key says which, rather than
   leaving it to be worked out. */
.ts-key{
  display: flex; gap: 6px; flex-wrap: wrap; align-items: center;
  padding-top: 8px;
}
.ts-k{
  padding: 1px 8px; border-radius: 999px;
  font-size: 10.5px; font-weight: 700;
  background: var(--fiori-surface2-solid); color: var(--fiori-text-muted);
  border: 1px solid var(--fiori-border);
}
.ts-k-yes{    background: var(--impl-rel-yes-bg);    color: var(--impl-rel-yes-fg);    border-color: var(--impl-rel-yes-fg); }
.ts-k-no{     background: var(--impl-rel-no-bg);     color: var(--impl-rel-no-fg);     border-color: var(--impl-rel-no-fg); }
.ts-k-unsure{ background: var(--impl-rel-unsure-bg); color: var(--impl-rel-unsure-fg); border-color: var(--impl-rel-unsure-fg); }

/* ── one procedure ───────────────────────────────────────────────── */
.ts-proc{ padding: 10px 12px 16px; }
.ts-proc-h{
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  padding-bottom: 8px;
}
.ts-lines{ width: 100%; }
/* Every cell is an editable control, and a table of controls needs
   them to fill their column or the grid reads as ragged. */
.pq-sheet .ts-lines td{ padding: 6px 7px; vertical-align: top; }
.impl-root .ts-cell,
.pq-sheet .ts-cell{
  width: 100%; min-width: 0; height: auto; min-height: 34px;
  padding: 5px 7px; font-size: 11.5px; line-height: 1.4; resize: vertical;
}
.pq-sheet select.ts-cell{ min-height: 30px; height: 30px; }
.pq-sheet input.ts-cell{ min-height: 30px; height: 30px; }
/* A line the project wrote, which the master has never heard of. */
.pq-sheet .ts-lines tbody tr.ts-added td{
  background: color-mix(in srgb, var(--fiori-brand) 7%, transparent);
}
/* What SAP shipped, one click away on a line that has been changed --
   so an edit can always be checked against the original rather than
   trusted. */
.impl-root button.ts-sap{
  margin-top: 3px; padding: 0; border: 0; background: none; cursor: pointer;
  font-size: 10.5px; text-decoration: underline;
  color: var(--impl-rel-unsure-fg);
}
.pq-sheet .ts-lines tbody tr.ts-sap-row td{
  background: var(--fiori-surface2-solid);
  border-top: 0; padding: 8px 12px 12px;
}

/* ── the activities that belong to no step ───────────────────── */
.ts-loose{
  display: flex; flex-direction: column; gap: 4px;
  padding: 12px 14px; margin-top: 8px;
  border-top: 2px solid var(--fiori-border);
  background: var(--fiori-surface2-solid);
}
.ts-loose-t{ font-size: 12.5px; }
.ts-act.is-loose{ padding-left: 8px; }

/* A phone is not where 263 questions get answered, but it is where one
   gets checked. Edge to edge, and the search takes the row it needs. */
@media (max-width: 900px){
  .pq-scrim{ padding: 0; }
  .pq-sheet{ border-radius: 0; border: 0; }
  .pq-search{ width: 100%; max-width: none; }
}

/* ── Lead from a document or a link (owner spec 2026-09-30) ────────
   Laid out like the Legal Document Agent's upload card, which is the
   pattern this app already uses for "give me a document and I will read
   it": a tinted bordered box, labelled fields, help text under the
   control, the action on the right.

   These are NOT .impl-* classes. Those are scoped to .impl-root and the
   Leads page is not inside it, so the first version of this panel
   rendered with no styling at all -- an unstyled Close button on its own
   line and a default grey URL box. */
.lead-imp{ padding: 18px; margin-bottom: 16px; }
.lead-imp-head{ display: flex; align-items: center; gap: 12px; margin-bottom: 12px; }
.lead-imp-title{ font-size: 15px; font-weight: 650; flex: 1 1 auto; }
.lead-imp-box{
  background: rgba(20,184,166,0.04);
  border: 1px solid rgba(20,184,166,0.30);
  border-radius: 10px; padding: 14px;
}
/* File, the "or", then the web address. Collapses to one column on a
   narrow screen, where the divider becomes a rule across the middle. */
.lead-imp-cols{
  display: grid; grid-template-columns: 1fr auto 1fr;
  gap: 16px; align-items: start;
}
.lead-imp-lbl{
  display: block; font-size: 12px; font-weight: 600; margin-bottom: 4px;
  color: var(--fiori-text, #0f172a);
}
.lead-imp-or{
  align-self: stretch; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 6px;
  padding-top: 20px; color: var(--fiori-text-muted, #64748b); font-size: 11px;
}
.lead-imp-or::before, .lead-imp-or::after{
  content: ''; flex: 1 1 auto; width: 1px; min-height: 10px;
  background: rgba(148,163,184,0.35);
}
/* A drop target as well as a button: people already have the file in a
   window next to this one. */
.lead-imp-drop{
  display: flex; align-items: center; gap: 10px;
  padding: 12px 14px; border-radius: 8px; cursor: pointer;
  border: 1px dashed rgba(148,163,184,0.55);
  background: rgba(148,163,184,0.06);
  transition: border-color .12s, background .12s;
  min-height: 46px;
}
.lead-imp-drop:hover, .lead-imp-drop:focus-visible{
  border-color: var(--fiori-accent-emerald, #14B8A6);
  background: rgba(20,184,166,0.08);
}
.lead-imp-drop.is-over{
  border-color: var(--fiori-accent-emerald, #14B8A6);
  background: rgba(20,184,166,0.16); border-style: solid;
}
.lead-imp-drop.has-file{ border-style: solid; border-color: rgba(20,184,166,0.55); }
.lead-imp-dropicon{ font-size: 15px; color: var(--fiori-accent-emerald, #14B8A6); flex: 0 0 auto; }
.lead-imp-dropmain{
  font-size: 13px; font-weight: 500; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.lead-imp-urlrow{ display: flex; gap: 8px; }
.lead-imp-urlrow .form-control{ flex: 1 1 auto; min-width: 0; }
.lead-imp-urlrow .btn{ flex: 0 0 auto; white-space: nowrap; }
.lead-imp-help{
  font-size: 11px; line-height: 1.5; margin-top: 4px;
  color: var(--fiori-text-muted, #64748b);
}
.lead-imp-from{ font-size: 12px; margin-bottom: 10px; color: var(--fiori-text-muted, #64748b); }
.lead-imp-busy{
  display: flex; align-items: center; gap: 8px; margin-top: 12px;
  font-size: 12px; color: var(--fiori-text-muted, #64748b);
}
.lead-imp-spin{
  width: 13px; height: 13px; flex: 0 0 auto; border-radius: 50%;
  border: 2px solid rgba(148,163,184,0.35);
  border-top-color: var(--fiori-accent-emerald, #14B8A6);
  animation: lead-imp-spin .7s linear infinite;
}
@keyframes lead-imp-spin{ to{ transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce){
  .lead-imp-spin{ animation-duration: 2.4s; }
}
/* The review list: every field that was filled, editable, with where it
   came from underneath. */
.lead-imp-grid{
  display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 12px 16px; margin-top: 4px;
}
.lead-imp-f{ display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.lead-imp-k{
  font-size: 11px; font-weight: 650; letter-spacing: .02em;
  color: var(--fiori-text-muted, #64748b);
}
.lead-imp-ev{ font-size: 10.5px; color: var(--fiori-text-muted, #64748b); font-style: italic; }
/* Owner spec 2026-09-30: *"If the values determined do not match the
   available dropdowns values show the text in red and allow user to
   select the right dropdown value"*. The reading stays on screen, in
   red, beside an open dropdown -- it is usually a real gap in the
   master rather than a bad reading, and hiding it loses that. */
.lead-imp-f.is-unmatched .form-control{ border-color: var(--fiori-accent-rose, #F43F5E); }
.lead-imp-bad{
  font-size: 10.5px; line-height: 1.45; font-style: normal;
  color: var(--fiori-accent-rose, #F43F5E); font-weight: 600;
}
.lead-imp-actions{ display: flex; justify-content: flex-end; gap: 8px; margin-top: 14px; }
.lead-imp-warn{
  margin-top: 10px; padding: 8px 10px; border-radius: 6px;
  font-size: 12px; line-height: 1.5;
  color: var(--impl-rel-unsure-fg); border: 1px solid var(--impl-rel-unsure-fg);
  background: var(--impl-rel-unsure-bg);
}
@media (max-width: 860px){
  .lead-imp-cols{ grid-template-columns: 1fr; }
  .lead-imp-or{ flex-direction: row; padding-top: 0; }
  .lead-imp-or::before, .lead-imp-or::after{ width: auto; height: 1px; min-height: 0; }
}

/* ── Pointwise legal review + Standard Text (owner spec 2026-09-30) ─
   *"The format of the concerns to discuss is not readable. It should be
   pointwise and allow user to accept so that changes are made in the
   document."*

   One card per point. The severity tone is carried on a custom property
   so the card, its rule and its pills all take it from one place. */
.la-pts{ margin-top: 6px; }
.la-pts-bar{
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  padding: 10px 12px; border-radius: 9px; margin-bottom: 6px;
  background: var(--fiori-surface2); border: 1px solid var(--fiori-border);
}
.la-pts-lbl{ font-size: 11px; color: var(--fiori-text-muted); margin-right: 8px; }
.la-pts-count{ font-size: 12px; color: var(--fiori-text-muted); font-variant-numeric: tabular-nums; }
.la-pts-hint{
  font-size: 12px; line-height: 1.55; color: var(--fiori-text-muted);
  padding: 8px 2px;
}
/* Whose paper. It decides whether a clause in our favour is a win or a
   defect, so it sits above the list it governs. */
.la-paper{ display: inline-flex; gap: 0; border-radius: 7px; overflow: hidden;
  border: 1px solid var(--fiori-border); }
.la-paper-b{
  padding: 5px 11px; font-size: 12px; cursor: pointer;
  background: transparent; border: 0; color: var(--fiori-text-muted);
}
.la-paper-b.is-on{ background: var(--fiori-brand); color: var(--fiori-on-accent, #fff); font-weight: 650; }

.la-pt{
  /* Declared here as well as set inline per card: the inline value is
     the severity tone, this is the default for anything that does not
     set one, and it keeps the property from reading as undefined. */
  --la-tone: #8b5cf6;
  border: 1px solid color-mix(in srgb, var(--la-tone, #8b5cf6) 34%, transparent);
  background: color-mix(in srgb, var(--la-tone, #8b5cf6) 6%, transparent);
  border-left: 3px solid var(--la-tone, #8b5cf6);
  border-radius: 8px; padding: 12px 14px; margin-bottom: 10px;
}
/* A decided point recedes: the eye should land on what is still open. */
.la-pt.la-pt-acc{ border-color: var(--impl-rel-yes-fg, #34D399); background: transparent; opacity: .82; }
.la-pt.la-pt-rej{ opacity: .55; background: transparent; }
.la-pt-h{ display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
.la-pt-t{ font-weight: 650; font-size: 13.5px; }
.la-pt-sev{
  font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em;
  padding: 1px 7px; border-radius: 999px;
  background: color-mix(in srgb, var(--la-tone, #8b5cf6) 22%, transparent);
  color: var(--la-tone, #8b5cf6);
}
.la-pt-fav{
  font-size: 10.5px; padding: 1px 7px; border-radius: 999px;
  background: var(--impl-rel-yes-bg); color: var(--impl-rel-yes-fg);
}
.la-pt-state{ font-size: 11px; font-weight: 700; }
.la-pt-state.la-pt-acc{ color: var(--impl-rel-yes-fg, #34D399); }
.la-pt-state.la-pt-rej{ color: var(--fiori-text-muted); }
.la-pt-c{ font-size: 12.5px; line-height: 1.55; margin-bottom: 6px; }
.la-pt-note{
  font-size: 11.5px; line-height: 1.5; margin: 4px 0;
  color: var(--impl-rel-unsure-fg);
}
/* The wording, quoted. Long clauses scroll rather than stretching the
   card, and the two sides are visually opposed. */
.la-pt-q{
  font-size: 11.5px; line-height: 1.5; margin-top: 5px;
  padding: 5px 9px; border-left: 2px solid var(--fiori-border);
  color: var(--fiori-text-muted); max-height: 8.5em; overflow-y: auto;
}
.la-pt-was{ border-left-color: var(--fiori-accent-rose); }
.la-pt-now{ border-left-color: var(--impl-rel-yes-fg, #34D399); color: var(--fiori-text); }
.la-pt-a{ display: flex; justify-content: flex-end; gap: 8px; margin-top: 10px; }

/* Set aside, not hidden. */
.la-fav{ margin-top: 14px; }
.la-fav-t{
  background: none; border: 0; cursor: pointer; padding: 4px 0;
  font-size: 12px; font-weight: 600; color: var(--impl-rel-yes-fg, #34D399);
}
.la-fav-i{
  font-size: 12px; line-height: 1.55; padding: 7px 10px; margin-bottom: 6px;
  border-radius: 7px; background: var(--fiori-surface2);
  border: 1px solid var(--fiori-border); color: var(--fiori-text-muted);
}

.la-st-list{ display: flex; flex-direction: column; gap: 10px; }
.la-st{
  border: 1px solid var(--fiori-border); border-radius: 9px; padding: 12px 14px;
  background: var(--fiori-surface2);
}
.la-st.is-off{ opacity: .55; }
.la-st-h{ display: flex; align-items: center; gap: 8px; margin-bottom: 6px; flex-wrap: wrap; }
.la-st-n{ font-weight: 650; font-size: 13.5px; }

@media (max-width: 700px){
  .la-pts-bar{ flex-direction: column; align-items: stretch; }
  .la-pt-a{ flex-direction: column-reverse; }
  .la-pt-a .btn{ width: 100%; }
}

/* ── Appraisal: direction A, and the employee history ──────────────
   Owner's choice 2026-09-30. Self and manager side by side, with the
   disagreements named. The gap row is tinted rather than badged: a
   two-point difference is a property of the whole row, not a label on
   one cell. */
.ap-form{ padding: 4px 2px; }
.ap-empty{ font-size: 13px; color: var(--fiori-text-muted); padding: 18px 2px; }
.ap-head{
  display: flex; gap: 18px; flex-wrap: wrap; align-items: flex-start;
  padding-bottom: 14px; border-bottom: 1px solid var(--fiori-border); margin-bottom: 14px;
}
.ap-who{ flex: 1 1 320px; min-width: 0; }
.ap-who h3{ font-size: 19px; font-weight: 650; margin: 0; }
.ap-sub{ font-size: 12.5px; color: var(--fiori-text-muted); margin-top: 3px; }
.ap-chips{ display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
.ap-chip{
  font-size: 11px; font-weight: 600; padding: 2px 9px; border-radius: 999px;
  background: var(--fiori-surface2); border: 1px solid var(--fiori-border);
  color: var(--fiori-text-muted);
}
.ap-chip.ap-role{ background: var(--impl-rel-yes-bg); color: var(--impl-rel-yes-fg); border-color: transparent; }
.ap-chip.ap-auto{ background: var(--impl-rel-unsure-bg); color: var(--impl-rel-unsure-fg); border-color: transparent; }
.ap-st-released, .ap-st-acknowledged{ background: var(--impl-rel-yes-bg); color: var(--impl-rel-yes-fg); border-color: transparent; }
.ap-st-locked{ opacity: .75; }
.ap-score{ flex: 0 0 auto; text-align: right; }
.ap-score-n{
  font-size: 34px; font-weight: 650; line-height: 1; color: var(--fiori-brand);
  font-variant-numeric: tabular-nums;
}
.ap-score-l{ font-size: 11.5px; letter-spacing: .05em; text-transform: uppercase;
  color: var(--fiori-text-muted); margin-top: 5px; }
.ap-score-b{ font-size: 11px; color: var(--fiori-text-muted); margin-top: 2px; }
.ap-weights{ font-size: 12px; color: var(--fiori-text-muted); margin-bottom: 12px; }
.ap-note-inline{ font-style: italic; }
.ap-note{
  font-size: 12px; line-height: 1.5; padding: 8px 11px; border-radius: 7px; margin: 10px 0;
  background: var(--impl-rel-unsure-bg); color: var(--impl-rel-unsure-fg);
}
.ap-sec{
  font-size: 12px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--fiori-text-muted); margin: 22px 0 10px;
  padding-bottom: 6px; border-bottom: 1px solid var(--fiori-border);
}
.ap-tbl-wrap{ overflow-x: auto; }
.ap-tbl{ width: 100%; border-collapse: collapse; font-size: 13px; }
.ap-tbl th{
  font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 600; text-align: left;
  padding: 0 10px 8px; border-bottom: 1px solid var(--fiori-border); white-space: nowrap;
}
.ap-tbl td{ padding: 11px 10px; border-bottom: 1px solid var(--fiori-border); vertical-align: top; }
.ap-tbl .ap-c{ text-align: center; }
.ap-cmp{ font-weight: 600; }
.ap-cmp-d{ font-size: 11.5px; color: var(--fiori-text-muted); margin-top: 2px; line-height: 1.45; }
.ap-w{ font-size: 11.5px; color: var(--fiori-text-muted); font-variant-numeric: tabular-nums; }
.ap-none{ color: var(--fiori-text-muted); }
/* A two-point gap is a property of the row, so the row carries it. */
.ap-gap-row{ background: color-mix(in srgb, var(--fiori-accent-rose) 7%, transparent); }
.ap-gap{
  font-size: 11.5px; line-height: 1.45; margin-top: 6px; font-weight: 600;
  color: var(--fiori-accent-rose);
}
.ap-pill{
  display: inline-grid; place-items: center; width: 30px; height: 30px; border-radius: 7px;
  font-weight: 650; font-size: 14px; font-variant-numeric: tabular-nums;
}
.ap-pill-self{ background: var(--fiori-brand-glow); color: var(--fiori-brand); }
.ap-pill-mgr{ background: var(--impl-rel-yes-bg); color: var(--impl-rel-yes-fg); }
.ap-rate{ display: inline-flex; gap: 3px; flex-wrap: wrap; justify-content: center; }
.ap-rb{
  width: 28px; height: 28px; border-radius: 6px; cursor: pointer;
  border: 1px solid var(--fiori-border); background: transparent;
  color: var(--fiori-text-muted); font-weight: 650; font-size: 12.5px;
  font-variant-numeric: tabular-nums;
}
.ap-rb:hover{ border-color: var(--fiori-brand); }
.ap-rb.on{ background: var(--fiori-brand); border-color: var(--fiori-brand); color: var(--fiori-on-accent, #fff); }
.ap-rb.ap-na{ width: auto; padding: 0 8px; font-size: 11px; }
.ap-ta{ min-height: 46px; font-size: 12.5px; }
.ap-ev{ font-size: 12px; color: var(--fiori-text-muted); line-height: 1.5; }
.ap-goals{ display: flex; flex-direction: column; gap: 10px; }
.ap-goal{
  border: 1px solid var(--fiori-border); border-radius: 8px; padding: 11px 13px;
  background: var(--fiori-surface2);
}
.ap-goal-h{ display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.ap-goal-m{ font-size: 12px; color: var(--fiori-text-muted); margin-top: 5px; line-height: 1.5; }
.ap-goal-s{ display: flex; gap: 6px; margin-top: 8px; }
.ap-ack{ display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.ap-ack-p{ font-size: 12.5px; color: var(--fiori-text-muted); line-height: 1.55; margin: 0; max-width: 62ch; }
.ap-actions{
  display: flex; gap: 8px; align-items: center; margin-top: 20px;
  padding-top: 14px; border-top: 1px solid var(--fiori-border); flex-wrap: wrap;
}

/* ── Employee: appraisal and increment history ─────────────────── */
.eh-tiles{ display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 12px; }
.eh-tile{
  border: 1px solid var(--fiori-border); border-radius: 9px; padding: 12px 14px;
  background: var(--fiori-surface2); min-width: 0;
}
.eh-k{ font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 600; }
.eh-v{ font-size: 21px; font-weight: 650; margin-top: 5px; font-variant-numeric: tabular-nums; }
.eh-d{ font-size: 11.5px; color: var(--fiori-text-muted); margin-top: 3px; line-height: 1.45; }
/* A new joiner has no last increment; "0%" would be a statement about
   their performance rather than about the data. */
.eh-none{ font-size: 13px; color: var(--fiori-text-muted); margin-top: 6px; font-style: italic; }
.eh-since{ font-size: 12px; color: var(--fiori-text-muted); margin-top: 10px; }
.eh-tbl td{ font-size: 12.5px; }
.eh-pct{ font-weight: 650; font-variant-numeric: tabular-nums; }
.eh-sub{ font-size: 11px; color: var(--fiori-text-muted); margin-top: 2px; }
/* Kept in the history, left out of "last increment". */
.eh-nonreview{ opacity: .62; }
.eh-tag{
  display: inline-block; font-size: 10px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; padding: 1px 7px; border-radius: 999px; margin-left: 7px;
  background: var(--fiori-surface2); border: 1px solid var(--fiori-border);
  color: var(--fiori-text-muted);
}
.eh-moves{ display: flex; flex-direction: column; gap: 8px; }
.eh-move{ display: flex; gap: 14px; align-items: flex-start; padding: 9px 0;
  border-bottom: 1px solid var(--fiori-border); }
.eh-move:last-child{ border-bottom: 0; }
.eh-move-d{ flex: 0 0 92px; font-size: 12px; color: var(--fiori-text-muted);
  font-variant-numeric: tabular-nums; }
.eh-move-b{ flex: 1 1 auto; min-width: 0; font-size: 13px; }
.eh-move.eh-promotion .eh-move-b b{ color: var(--impl-rel-yes-fg); }

@media (max-width: 760px){
  .ap-head{ flex-direction: column; }
  .ap-score{ text-align: left; }
  .ap-actions{ flex-direction: column-reverse; align-items: stretch; }
  .ap-actions .btn{ width: 100%; }
  .eh-move-d{ flex-basis: 78px; }
}

/* ── Appraisal: the HR-side screens (forms, rules, assign, compare) ──
   Same vocabulary as the form above, so a weight looks like a weight on
   whichever screen it appears. */
.ap-tabs{ display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 14px; }
.ap-tab{
  padding: 7px 15px; border-radius: 999px; font-size: 13px; font-weight: 600; cursor: pointer;
  border: 1px solid var(--fiori-border); background: transparent; color: var(--fiori-text-muted);
}
.ap-tab:hover{ border-color: var(--fiori-brand); }
.ap-tab.on{
  background: var(--fiori-pill-active-bg); border-color: var(--fiori-pill-active-border);
  color: var(--fiori-pill-active-text);
}
.ap-switches{ display: flex; flex-direction: column; gap: 10px; margin: 14px 0 4px; }
.ap-switch{
  display: flex; gap: 9px; align-items: flex-start; font-size: 12.5px; line-height: 1.5;
  color: var(--fiori-text-muted); cursor: pointer; max-width: 78ch;
}
.ap-switch input{ margin-top: 2px; flex: 0 0 auto; }
.ap-switch b{ color: var(--fiori-text); font-weight: 600; }
.ap-sec-card{
  border: 1px solid var(--fiori-border); border-radius: 9px; padding: 13px 14px; margin-bottom: 12px;
  background: var(--fiori-surface2);
}
.ap-sec-head{
  display: flex; justify-content: space-between; align-items: center; gap: 10px;
  flex-wrap: wrap; margin: 4px 0 10px;
}
/* A competency section whose weights do not add up to 100 still
   produces a number, and that number is wrong in a way nobody sees. */
.ap-w-bad{ font-size: 11.5px; font-weight: 700; color: var(--fiori-accent-rose); }
.ap-fld{
  border: 1px solid var(--fiori-border); border-radius: 8px; padding: 11px 12px; margin-bottom: 9px;
  background: var(--fiori-surface-solid);
}
.ap-fld-foot{ display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-top: 6px; }
.ap-other{
  border: 1px solid var(--fiori-border); border-radius: 8px; padding: 11px 13px; margin-bottom: 9px;
  background: var(--fiori-surface2);
}
.ap-other-h{ display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 5px; }
.ap-list{
  margin: 6px 0 0; padding-left: 20px; font-size: 12.5px; line-height: 1.75;
  color: var(--fiori-text); max-height: 280px; overflow-y: auto;
}
/* The employee's own example, beside the manager's. Set back so the two
   do not read as one note. */
.ap-ev-self{
  margin-top: 6px; padding-left: 9px; border-left: 2px solid var(--fiori-border);
  color: var(--fiori-text-muted);
}
.ap-ev-self b{ color: var(--fiori-text); font-weight: 600; }

/* ── Appraisal: the dry run ─────────────────────────────────────────
   HR trying the form out, as somebody else, at any stage. The banner
   says the one thing that matters about this screen. */
.ap-sim-banner{
  font-size: 12.5px; line-height: 1.55; padding: 9px 13px; border-radius: 8px; margin-bottom: 14px;
  background: var(--impl-rel-unsure-bg); color: var(--impl-rel-unsure-fg);
}
.ap-sim-banner b{ font-weight: 700; }
.ap-sim-ctl{
  border: 1px solid var(--fiori-border); border-radius: 9px; padding: 13px 14px; margin-bottom: 14px;
  background: var(--fiori-surface2);
}
.ap-sim-row{ display: flex; gap: 26px; flex-wrap: wrap; margin-top: 10px; }
.ap-sim-row > div{ min-width: 0; }
.ap-sim-facts{
  display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 12px;
  margin-bottom: 16px;
}
.ap-sim-facts .eh-d b{ color: var(--fiori-text); font-weight: 600; }
@media (max-width: 760px){
  .ap-sim-row{ flex-direction: column; gap: 14px; }
}
/* A multi-value condition: the choices, with how many people each one
   would catch. */
.ap-pick{
  border: 1px solid var(--fiori-border); border-radius: 8px; padding: 6px 4px;
  max-height: 190px; overflow-y: auto; background: var(--fiori-surface-solid);
}
.ap-pick-row{
  display: flex; gap: 8px; align-items: center; padding: 4px 9px;
  font-size: 12.5px; color: var(--fiori-text); cursor: pointer;
}
.ap-pick-row:hover{ background: var(--fiori-row-hover); border-radius: 5px; }

/* The bulk bar over the candidate list. Shown only when something is
   ticked -- a toolbar of disabled buttons is one people stop reading. */
.rr-bulk{
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 8px 12px; margin-bottom: 10px; border-radius: 9px;
  border: 1px solid var(--fiori-brand); background: var(--fiori-brand-glow);
  font-size: 13px; color: var(--fiori-text);
}

/* ── Appraisal: twelve competencies that fit on a screen ───────────
   Owner spec 2026-10-01. The wording sits on the competency's own
   line, and rows are tightened so the form can be read without
   scrolling past the ones already rated. */
.ap-tbl td{ padding: 6px 10px; }
.ap-tbl th{ padding: 0 10px 6px; }
.ap-cmp{ line-height: 1.4; }
.ap-cmp-inline{ font-weight: 400; color: var(--fiori-text-muted); font-size: 12px; }
/* The ones too long for a line: an "i" rather than three wrapped
   lines times twelve. */
.ap-info{
  display: inline-grid; place-items: center; width: 16px; height: 16px; margin-left: 7px;
  border-radius: 50%; border: 1px solid var(--fiori-border); background: transparent;
  color: var(--fiori-text-muted); font-size: 10.5px; font-weight: 700;
  font-style: italic; line-height: 1; cursor: pointer; vertical-align: 1px;
}
.ap-info:hover{ border-color: var(--fiori-brand); color: var(--fiori-brand); }
.ap-info[aria-expanded="true"]{
  background: var(--fiori-brand); border-color: var(--fiori-brand); color: var(--fiori-on-accent, #fff);
}
.ap-tbl .ap-cmp-d{ margin-top: 4px; max-width: 86ch; }
.ap-tbl .ap-rb{ width: 26px; height: 26px; }
.ap-tbl .ap-ta{ min-height: 34px; }
.ap-tbl .ap-gap{ margin-top: 3px; }

/* ── Appraisal: one line per competency ────────────────────────────
   Owner spec 2026-10-01, second pass. The wording sits beside the
   label and is CUT where the line ends; the "i" opens the rest. The
   label never shrinks, because a truncated competency name is not a
   competency name.

   THE COLUMN WIDTHS ARE FIXED, and they have to be. A cell whose text
   must not wrap sizes the column to the whole sentence no matter what
   `overflow: hidden` says -- that is intrinsic width, and only
   `table-layout: fixed` overrides it. Without this the competency
   column took the width of the longest wording on the form, the
   rating column was squeezed to a few pixels, its six buttons stacked
   one per line, and every row stood two hundred pixels tall. */
.ap-rate-tbl{ table-layout: fixed; width: 100%; }
.ap-rate-tbl th:nth-child(1), .ap-rate-tbl td:nth-child(1){ width: 46%; }
.ap-rate-tbl th:nth-child(2), .ap-rate-tbl td:nth-child(2){ width: 8%; }
.ap-rate-tbl th:nth-child(3), .ap-rate-tbl td:nth-child(3){ width: 8%; }
.ap-rate-tbl th:nth-child(4), .ap-rate-tbl td:nth-child(4){ width: 18%; }
.ap-rate-tbl th:nth-child(5), .ap-rate-tbl td:nth-child(5){ width: 20%; }
/* The 1-5 buttons and N/A stay on one line; wrapping them is what made
   the rows tall. */
.ap-rate-tbl .ap-rate{ flex-wrap: nowrap; }
.ap-cmp-row{ display: flex; align-items: baseline; gap: 6px; min-width: 0; }
.ap-cmp-l{ flex: 0 0 auto; }
.ap-cmp-row .ap-cmp-inline{
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.ap-cmp-row .ap-info{ flex: 0 0 auto; margin-left: 2px; }
.ap-tbl td{ padding: 5px 10px; }
.ap-tbl .ap-rb{ width: 25px; height: 25px; font-size: 12px; }
.ap-tbl .ap-pill{ width: 26px; height: 26px; }
/* An expanded wording is the one thing allowed to wrap, and it wraps
   inside its own fixed cell. */
.ap-rate-tbl .ap-cmp-d{ white-space: normal; max-width: none; }
.ap-rate-tbl .ap-ev, .ap-rate-tbl .ap-ta{ white-space: normal; }

@media (max-width: 900px){
  /* Narrow: the wording is the first thing to go, not the ratings. */
  .ap-rate-tbl th:nth-child(1), .ap-rate-tbl td:nth-child(1){ width: 38%; }
  .ap-rate-tbl th:nth-child(5), .ap-rate-tbl td:nth-child(5){ width: 24%; }
}

/* The sheet's salary grid: what they get now, what is proposed, and
   the gap between. Owner spec 2026-10-01. */
.ap-sal{ display: flex; gap: 16px; flex-wrap: wrap; align-items: flex-end; }
.ap-sal-cell{ flex: 0 1 180px; min-width: 0; }
.ap-sal-now{
  font-size: 15px; font-weight: 650; margin-top: 4px;
  font-variant-numeric: tabular-nums; color: var(--fiori-text);
}
.ap-sal-down{ color: var(--fiori-accent-rose); }
@media (max-width: 760px){ .ap-sal-cell{ flex: 1 1 140px; } }
.ap-sal-src{
  flex: 1 0 100%; font-size: 11px; color: var(--fiori-text-muted); margin-top: 2px;
}
/* The proposal, not the record: tinted so a manager's eye separates it
   from the current figure beside it at a glance. */
.ap-sal-rev{ color: var(--fiori-accent-emerald); }

/* ── The sub-task grid (owner spec 2026-10-02) ──────────────────────
   *"The sub tasks need to be compact and easy to create."* So: one
   line per task whatever is on it, hairline rules rather than boxes,
   and the only colour on the grid is the thing that needs attention. */
.sg{ display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.sg-loading{ padding: 24px; color: var(--fiori-text-muted); }
.sg-tools{ display: flex; gap: 6px; align-items: center; flex-wrap: wrap; }
.sg-sp{ flex: 1 1 auto; }
.sg-hint{ font-size: 11.5px; color: var(--fiori-text-muted); }

.sg-paste{ display: flex; flex-direction: column; gap: 6px;
  border: 1px solid var(--fiori-border); border-radius: 8px; padding: 10px; }
.sg-paste-acts{ display: flex; gap: 6px; }

/* A grid is the one place a sideways scroll is right: the alternative
   is wrapping a task onto two lines, which is what "compact" rules
   out. The page itself never scrolls sideways. */
.sg-scroll{ overflow-x: auto; border: 1px solid var(--fiori-border); border-radius: 8px; }
.sg-tbl{ border-collapse: collapse; width: 100%; min-width: 820px; font-size: 13px; }
.sg-tbl thead th{
  text-align: left; font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 650; padding: 7px 8px; white-space: nowrap;
  border-bottom: 1px solid var(--fiori-border);
}
.sg-tbl tbody tr{ border-bottom: 1px solid var(--fiori-border); }
.sg-tbl tbody tr:last-child{ border-bottom: none; }
.sg-tbl td{ padding: 2px 6px; vertical-align: middle; }
.sg-n{ width: 30px; text-align: right; color: var(--fiori-text-muted);
  font-size: 11.5px; font-variant-numeric: tabular-nums; padding-right: 2px; }
.sg-c-who{ width: 178px; }
.sg-c-after{ width: 86px; }
.sg-c-date{ width: 132px; }
.sg-c-fin{ width: 46px; text-align: center; }
.sg-c-x{ width: 28px; }

.sg-tbl input[type=text], .sg-tbl input[type=date],
.sg-tbl input[type=number], .sg-tbl select{
  font: inherit; font-size: 12.5px; width: 100%; background: transparent;
  color: var(--fiori-text); border: 1px solid transparent; border-radius: 6px;
  padding: 5px 6px; min-width: 0;
}
.sg-tbl input:hover, .sg-tbl select:hover{ border-color: var(--fiori-border); }
.sg-tbl input:focus, .sg-tbl select:focus{
  outline: none; border-color: var(--fiori-brand); background: var(--fiori-surface-solid);
}
.sg-tbl input[type=date]{ font-variant-numeric: tabular-nums; }
/* The task box carries the look on its OWN class rather than only on a
   descendant selector: check-unstyled-fields refuses a className that
   looks like a field class and matches no rule, and it is right to --
   the same input rendered outside .sg-tbl would come out as a raw
   browser control, white on the dark theme. */
.sg-title{
  font: inherit; font-size: 12.5px; width: 100%; background: transparent;
  color: var(--fiori-text); border: 1px solid transparent; border-radius: 6px;
  padding: 5px 6px; min-width: 0;
}
.sg-title:hover{ border-color: var(--fiori-border); }
.sg-title:focus{ outline: none; border-color: var(--fiori-brand);
  background: var(--fiori-surface-solid); }

.sg-onfile{
  font-size: 9.5px; letter-spacing: .06em; color: var(--fiori-accent-violet); font-weight: 700;
  border: 1px solid var(--fiori-accent-violet); border-radius: 3px; padding: 0 3px; margin-left: 4px;
  opacity: .75;
}

/* Owners: chips with a +n, so a task with five people on it is still
   one line high. */
.sg-who{ position: relative; }
.sg-who-btn{
  width: 100%; text-align: left; border: 1px solid transparent; background: transparent;
  padding: 4px 6px; border-radius: 6px; font-size: 12.5px; color: var(--fiori-text);
  display: flex; gap: 4px; align-items: center; flex-wrap: nowrap; overflow: hidden;
  min-height: 28px; cursor: pointer;
}
.sg-who-btn:hover{ border-color: var(--fiori-border); }
.sg-who-btn.open{ border-color: var(--fiori-brand); background: var(--fiori-surface-solid); }
.sg-ph{ color: var(--fiori-text-muted); }
.sg-pill{
  font-size: 11px; padding: 1.5px 6px; border-radius: 999px; white-space: nowrap;
  border: 1px solid var(--fiori-brand); color: var(--fiori-brand); font-weight: 600;
}
.sg-more{ font-size: 11px; color: var(--fiori-text-muted); font-weight: 600; white-space: nowrap; }
.sg-picker{
  position: absolute; z-index: 20; top: calc(100% + 3px); left: 0;
  min-width: 220px; background: var(--fiori-surface-solid);
  border: 1px solid var(--fiori-border); border-radius: 8px; padding: 5px;
  max-height: 240px; overflow: auto;
  box-shadow: 0 14px 40px rgba(0,0,0,0.45);
}
.sg-picker label{
  display: flex; gap: 7px; align-items: center; padding: 5px 7px;
  border-radius: 6px; font-size: 12.5px; cursor: pointer;
}
.sg-picker label:hover{ background: var(--fiori-border); }
.sg-picker label input{ width: auto; margin: 0; }
/* The search box stays put while the names scroll under it: a filter
   that scrolls away is one you cannot correct without scrolling back. */
.sg-picker-q{
  font-size: 12px; padding: 5px 7px; margin-bottom: 4px;
  position: sticky; top: 0; z-index: 1;
}
.sg-picker-list{ display: flex; flex-direction: column; }
.sg-picker-none{ padding: 7px; font-size: 12px; color: var(--fiori-text-muted); }

.sg-waits{ font-size: 11.5px; color: var(--fiori-accent-cyan); white-space: nowrap; padding: 5px 6px;
  display: inline-block; }
.sg-days{ display: flex; align-items: center; gap: 4px; font-size: 11.5px;
  color: var(--fiori-text-muted); white-space: nowrap; }
.sg-days input{ width: 50px; text-align: right; }

.sg-fin{ appearance: none; width: 15px; height: 15px; border-radius: 50%;
  border: 1.5px solid var(--fiori-border-light); cursor: pointer; margin: 0; vertical-align: middle; }
.sg-fin:checked{ border-color: var(--fiori-accent-emerald); background: var(--fiori-accent-emerald);
  box-shadow: inset 0 0 0 3px var(--fiori-bg); }

.sg-x{ border: none; background: none; color: var(--fiori-text-muted);
  padding: 3px 5px; font-size: 15px; line-height: 1; cursor: pointer; }
.sg-x:hover{ color: var(--fiori-accent-rose); }

.sg-msg{ font-size: 12px; border-radius: 7px; padding: 6px 10px; }
.sg-err{ color: var(--fiori-accent-rose); border: 1px solid var(--fiori-accent-rose); }
.sg-warn{ color: var(--fiori-accent-amber); border: 1px solid var(--fiori-accent-amber); }
.sg-foot{ display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.sg-offer{ display: flex; flex-direction: column; gap: 8px; }
.sg-offer-t{ font-size: 14px; font-weight: 640; }
.sg-offer-s{ font-size: 12.5px; color: var(--fiori-text-muted); }
.sg-offer-a{ display: flex; gap: 8px; margin-top: 6px; flex-wrap: wrap; }

/* ── News dashboard (owner spec 2026-10-07) ─────────────────────────
   Cards in a responsive grid: a full dashboard, not the 352px panel
   the first mockup drew, because the owner asked for a screen of its
   own. The only colour on a card is the thing that needs attention. */
.nw-wrap{ padding: 18px; display: flex; flex-direction: column; gap: 12px; min-width: 0; }
.nw-head{ display: flex; gap: 10px; align-items: flex-end; flex-wrap: wrap; }
.nw-head-l{ flex: 1 1 auto; min-width: 0; }
.nw-head h1{ margin: 0; font-size: 21px; font-weight: 680; }
.nw-sub{ font-size: 12px; color: var(--fiori-text-muted); margin-top: 3px; }

.nw-grp{ font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 680; margin-top: 4px; }
.nw-grid{ display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 10px; }

.nw-card{
  display: block; text-decoration: none; color: inherit;
  border: 1px solid var(--fiori-border); border-radius: 10px;
  background: var(--fiori-surface); padding: 12px 13px; min-width: 0;
}
.nw-card:hover{ border-color: var(--fiori-brand); }
/* Attention is a left edge, not a filled card: a wall of red reads as
   an outage rather than as news. */
.nw-card.nw-hot{ border-left: 2.5px solid var(--fiori-accent-rose); }
.nw-t{ margin: 0 0 6px; font-size: 13.5px; line-height: 1.38; font-weight: 620; }

/* The AI sentence is badged and set apart. The headline and the link
   are the publisher's; this line is Claude's, and the reader must
   never be in doubt which is which. */
.nw-why{
  margin: 0 0 7px; font-size: 12px; line-height: 1.45; color: var(--fiori-text-muted);
  padding-left: 8px; border-left: 2px solid var(--fiori-accent-violet);
}
.nw-ai{
  font-size: 9.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--fiori-accent-violet); border: 1px solid var(--fiori-accent-violet);
  border-radius: 3px; padding: 0 3px; margin-right: 5px; vertical-align: 1px;
}
.nw-meta{ display: flex; gap: 5px; align-items: center; flex-wrap: wrap;
  font-size: 10.5px; color: var(--fiori-text-muted); }
.nw-dot{ opacity: .5; }
.nw-kw{ font-size: 10px; padding: 1px 6px; border-radius: 999px;
  border: 1px solid var(--fiori-border); color: var(--fiori-accent-cyan); white-space: nowrap; }

.nw-warn{ font-size: 12px; color: var(--fiori-accent-amber); border-radius: 7px; padding: 7px 11px;
  border: 1px solid var(--fiori-accent-amber); }
.nw-note{ font-size: 12px; color: var(--fiori-text-muted); border-radius: 7px; padding: 7px 11px;
  border: 1px solid var(--fiori-border); }
.nw-empty{ padding: 40px 20px; text-align: center; color: var(--fiori-text-muted); font-size: 13px; }
.nw-empty b{ display: block; color: var(--fiori-text); font-size: 14px; margin-bottom: 6px; font-weight: 640; }
.nw-foot{ font-size: 11px; color: var(--fiori-text-muted); margin-top: 6px; }

/* ── Configuration › Information ────────────────────────────────── */
.ic-wrap{ padding: 18px; max-width: 900px; }
.ic-wrap h1{ margin: 0 0 4px; font-size: 20px; font-weight: 670; }
.ic-hint{ color: var(--fiori-text-muted); font-size: 12.5px; margin: 0 0 16px; }
.ic-block{ border: 1px solid var(--fiori-border); border-radius: 10px; margin-bottom: 14px; overflow: hidden; }
.ic-block-h{ display: flex; gap: 8px; align-items: baseline; padding: 9px 12px; flex-wrap: wrap;
  border-bottom: 1px solid var(--fiori-border); }
.ic-block-h b{ font-size: 13px; font-weight: 640; }
.ic-block-h span{ font-size: 11.5px; color: var(--fiori-text-muted); }
.ic-rows{ display: flex; flex-direction: column; }
.ic-r{ display: flex; gap: 8px; align-items: center; padding: 6px 12px; flex-wrap: wrap;
  border-bottom: 1px solid var(--fiori-border); }
.ic-r:last-child{ border-bottom: none; }
.ic-val{ flex: 1 1 240px; min-width: 0; font-size: 12.5px; padding: 5px 7px; }
.ic-sw{ width: 30px; height: 17px; border-radius: 999px; border: 1px solid var(--fiori-border);
  background: transparent; position: relative; flex: none; cursor: pointer; padding: 0; }
.ic-sw::after{ content: ''; position: absolute; top: 1.5px; left: 2px; width: 11px; height: 11px;
  border-radius: 50%; background: var(--fiori-text-muted); transition: .15s; }
.ic-sw.on{ background: var(--fiori-brand); border-color: var(--fiori-brand); }
.ic-sw.on::after{ left: 15px; background: #fff; }
.ic-sw:disabled{ opacity: .45; cursor: not-allowed; }
.ic-st{ font-size: 10.5px; white-space: nowrap; padding: 1px 7px; border-radius: 999px; border: 1px solid; }
.ic-st.ok{ color: var(--fiori-accent-emerald); border-color: var(--fiori-accent-emerald); }
.ic-st.bad{ color: var(--fiori-accent-rose); border-color: var(--fiori-accent-rose); }
.ic-st.off{ color: var(--fiori-text-muted); border-color: var(--fiori-border); }
.ic-x{ border: none; background: none; color: var(--fiori-text-muted); font-size: 15px;
  line-height: 1; padding: 3px 5px; cursor: pointer; }
.ic-x:hover{ color: var(--fiori-accent-rose); }
.ic-note{ font-size: 11.5px; color: var(--fiori-accent-amber); padding: 7px 12px;
  border-top: 1px solid var(--fiori-border); }
.ic-add{ padding: 7px 12px; }
.ic-ai{ display: flex; gap: 12px; flex-wrap: wrap; padding: 11px 12px; }
.ic-fld{ flex: 1 1 170px; min-width: 0; }
.ic-wide{ flex: 1 1 100%; }
.ic-fld label{ display: block; font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 650; margin-bottom: 3px; }
.ic-fld textarea{ resize: vertical; font-size: 12.5px; }
.ic-foot{ display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }

/* ── My Minutes (owner spec 2026-10-07) ─────────────────────────── */
.mm-wrap{ padding: 18px; max-width: 980px; display: flex; flex-direction: column; gap: 12px; }
.mm-head{ display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
.mm-head h1{ margin: 0; font-size: 21px; font-weight: 680; }
.mm-empty{ padding: 40px 20px; text-align: center; color: var(--fiori-text-muted); font-size: 13px; }
.mm-empty b{ display: block; color: var(--fiori-text); font-size: 14px; margin-bottom: 6px; font-weight: 640; }

.mm-tbl{ border-collapse: collapse; width: 100%; font-size: 13px; }
.mm-tbl thead th{ text-align: left; font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 650; padding: 8px 10px; white-space: nowrap;
  border-bottom: 1px solid var(--fiori-border); }
.mm-tbl tbody tr{ border-bottom: 1px solid var(--fiori-border); cursor: pointer; }
.mm-tbl tbody tr:hover{ background: var(--fiori-surface-solid); }
.mm-tbl td{ padding: 9px 10px; vertical-align: top; }
.mm-subj{ font-weight: 620; }
/* Refer-to row: the chip (or "not linked") and the Choose button. */
.mm-ref { display: flex; align-items: center; gap: 8px; min-height: 30px;
          padding: 3px 6px 3px 8px; border: 1px solid var(--fiori-border);
          border-radius: 6px; background: var(--fiori-row-hover); }
/* .mm-none is already defined below for the draft table; only the
   refer-to row italicises it. */
.mm-ref .mm-none { font-size: 11.5px; font-style: italic; }
.mm-sub2{ font-size: 11.5px; color: var(--fiori-text-muted); margin-top: 2px; }
.mm-chip{ font-size: 10.5px; padding: 1px 7px; border-radius: 999px; font-weight: 600;
  border: 1px solid var(--fiori-accent-violet); color: var(--fiori-accent-violet); }
.mm-st{ font-size: 10.5px; padding: 1px 7px; border-radius: 999px; border: 1px solid; white-space: nowrap; }
.mm-st.sent{ color: var(--fiori-accent-emerald); border-color: var(--fiori-accent-emerald); }
.mm-st.draft{ color: var(--fiori-text-muted); border-color: var(--fiori-border); }

.mm-row{ display: flex; gap: 12px; flex-wrap: wrap; }
.mm-fld{ flex: 1 1 200px; min-width: 0; }
.mm-wide{ flex: 1 1 100%; }
.mm-fld label{ display: block; font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 650; margin-bottom: 4px; }
.mm-opt{ text-transform: none; letter-spacing: 0; font-weight: 500; }
.mm-hint{ font-size: 11.5px; color: var(--fiori-text-muted); margin-top: 4px; }
.mm-notes{ resize: vertical; min-height: 150px; line-height: 1.55; font-size: 13px; }
.mm-person{ font-size: 13px; }
.mm-pills{ display: flex; gap: 5px; flex-wrap: wrap; margin-top: 6px; }
.mm-pill{ font-size: 11px; padding: 2px 8px; border-radius: 999px; display: inline-flex; gap: 5px;
  align-items: center; border: 1px solid var(--fiori-brand); color: var(--fiori-brand); }
.mm-pill b{ cursor: pointer; opacity: .7; font-weight: 700; }
.mm-acts2{ display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }

.mm-ai{ font-size: 9.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: var(--fiori-accent-violet); border: 1px solid var(--fiori-accent-violet);
  border-radius: 3px; padding: 0 4px; margin-right: 6px; }
.mm-aihint{ font-size: 12px; color: var(--fiori-text-muted); }
.mm-mail{ border: 1px solid var(--fiori-border); border-radius: 10px; overflow: hidden; }
.mm-mail-h{ padding: 9px 12px; background: var(--fiori-surface-solid);
  border-bottom: 1px solid var(--fiori-border); font-size: 12px; }
.mm-mail-h span{ color: var(--fiori-text-muted); display: inline-block; min-width: 54px; }
.mm-mail-b{ padding: 14px 16px; font-size: 13px; line-height: 1.6; }
.mm-mail-b h4{ margin: 16px 0 8px; font-size: 13.5px; font-weight: 660; }
.mm-mail-b p{ margin: 0 0 10px; }
.mm-acts{ border-collapse: collapse; width: 100%; font-size: 12.5px; }
.mm-acts th{ text-align: left; font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fiori-text-muted); padding: 0 10px 5px 0; border-bottom: 1px solid var(--fiori-border); }
.mm-acts td{ padding: 6px 10px 6px 0; border-bottom: 1px solid var(--fiori-border); vertical-align: top; }
.mm-none{ color: var(--fiori-text-muted); }
.mm-diff{ border-left: 2px solid var(--fiori-accent-violet); padding-left: 10px;
  font-size: 12px; color: var(--fiori-text-muted); }
.mm-warn{ font-size: 12px; color: var(--fiori-accent-amber); border: 1px solid var(--fiori-accent-amber);
  border-radius: 7px; padding: 7px 11px; }
.mm-foot{ font-size: 11.5px; color: var(--fiori-text-muted); }

/* ── "Refer to", inline ────────────────────────────────────────────
   Owner 2026-10-07: a type dropdown, then a searchable dropdown over
   that type's records. The results panel is absolutely positioned, so
   .ref-in is the positioning context and nothing in the chain may
   clip it. */
.ref-in{ position: relative; display: flex; flex-direction: column; gap: 6px; }
.ref-in-type{ width: 100%; }
.ref-in-search{ position: relative; }
.ref-in-q{ width: 100%; box-sizing: border-box; padding-right: 26px; }
.ref-in-x{ position: absolute; right: 6px; top: 50%; transform: translateY(-50%);
  border: none; background: transparent; cursor: pointer; padding: 0 2px;
  font-size: 15px; line-height: 1; color: var(--fiori-text-muted); }
.ref-in-x:hover{ color: var(--fiori-accent-rose); }
.ref-in-list{ position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 60;
  max-height: 260px; overflow-y: auto; padding: 4px;
  background: var(--fiori-surface2-solid); border: 1px solid var(--fiori-border);
  border-radius: 8px; box-shadow: 0 14px 34px rgba(0,0,0,0.38); }
.ref-in-msg{ padding: 14px 10px; text-align: center; font-size: 12.5px;
  color: var(--fiori-text-muted); }
.ref-in-row{ display: flex; align-items: center; gap: 9px; width: 100%; text-align: left;
  cursor: pointer; padding: 7px 9px; margin-bottom: 2px; border-radius: 6px;
  border: 1px solid transparent; background: transparent; color: var(--fiori-text); }
.ref-in-row.on{ background: var(--fiori-row-hover); border-color: var(--fiori-border); }
.ref-in-dot{ width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; }
.ref-in-txt{ min-width: 0; flex: 1; }
.ref-in-txt b{ display: block; font-size: 13px; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ref-in-txt i{ display: block; font-size: 11px; font-style: normal;
  color: var(--fiori-text-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ref-in-st{ font-size: 10px; padding: 1px 7px; border-radius: 999px; flex-shrink: 0;
  white-space: nowrap; background: var(--fiori-row-hover);
  border: 1px solid var(--fiori-border); color: var(--fiori-text-muted); }
.ref-in-warn{ font-size: 11.5px; color: var(--fiori-accent-amber, #F59E0B); }
.ref-in-ok{ display: flex; }

/* ── My Minutes: the editable draft ────────────────────────────────
   Owner 2026-10-07: *"the generated draft should be editable including
   any persons who are part of the meeting invitation mail"*. The
   layout stays the shape of an email so what is on screen reads like
   what will arrive. */
.mm-to{ display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 4px; }
.mm-to > span{ color: var(--fiori-text-muted); display: inline-block; min-width: 54px; font-size: 12px; }
.mm-addp{ width: 220px; font-size: 12.5px; padding: 3px 7px; }
.mm-subj2{ flex: 1 1 260px; font-size: 13px; font-weight: 600; padding: 3px 7px; }
.mm-sum{ width: 100%; box-sizing: border-box; min-height: 130px; resize: vertical;
  font-size: 13px; line-height: 1.6; }
.mm-acts-ed td{ padding: 4px 6px 4px 0; vertical-align: top; }
.mm-acts-ed th:last-child{ width: 26px; }
.mm-what{ width: 100%; box-sizing: border-box; font-size: 12.5px; padding: 4px 7px; }
.mm-owner{ width: 100%; box-sizing: border-box; font-size: 12.5px; padding: 4px 7px; }
.mm-ownertext{ width: 100%; box-sizing: border-box; font-size: 12.5px; padding: 4px 7px; margin-top: 4px; }
.mm-due{ font-size: 12.5px; padding: 4px 7px; }
.mm-del{ border: none; background: transparent; cursor: pointer; font-size: 15px; line-height: 1;
  padding: 4px 2px; color: var(--fiori-text-muted); }
.mm-del:hover{ color: var(--fiori-accent-rose); }

/* The Outlook handoff panel — owner 2026-10-08. The email is not gone
   until they press Send in their own Outlook, so this says so. */
.mm-handoff{ border: 1px solid var(--fiori-accent-emerald); border-radius: 10px;
  padding: 12px 14px; display: flex; flex-direction: column; gap: 8px; font-size: 13px;
  background: var(--fiori-row-hover); }
.mm-handoff b{ font-size: 13.5px; font-weight: 650; }
.mm-handoff.warn{ border-color: var(--fiori-accent-amber); }
.mm-warn2{ font-size: 12.5px; color: var(--fiori-accent-amber); }

/* ── RACI Report — owner spec 2026-10-08 ──────────────────────────
   One row per activity, the people inside it. Colour carries the
   side (ours / theirs) and the letter carries the involvement; the
   dot is the My Tasks indicator, filled when the task exists. */
.raci-wrap{ padding: 2px 0 18px; display: flex; flex-direction: column; gap: 12px; }
.raci-head{ display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap; }
.raci-head h1{ margin: 0; font-size: 21px; font-weight: 680; }
.raci-sub{ font-size: 12.5px; color: var(--fiori-text-muted); }
/* `.card` carries background, radius, border and shadow and NOTHING
   else -- padding comes from .card-body or from the page's own rule,
   and this page never wrote one. Every RACI card therefore had its
   content sitting on its own corner (owner 2026-10-10: *"Look at the
   left and top margin of Project"*). 16px is the gutter every card on
   this screen now uses, including the first table column. */
.raci-filters{ display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start;
  padding: 14px 16px; }
.raci-fld{ flex: 0 1 230px; min-width: 0; }
.raci-fld.raci-wide{ flex: 1 1 420px; }
.raci-fld label{ display: block; font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 650; margin-bottom: 4px; }
.raci-opt{ text-transform: none; letter-spacing: 0; font-weight: 500; }
.raci-none{ font-size: 12.5px; color: var(--fiori-text-muted); font-style: italic; padding: 6px 0; }
.raci-people{ display: flex; flex-wrap: wrap; gap: 5px; max-height: 118px; overflow-y: auto; }
.raci-chip{ font-size: 11.5px; padding: 3px 9px; border-radius: 999px; cursor: pointer;
  background: transparent; color: var(--fiori-text-muted);
  border: 1px solid var(--fiori-border); }
.raci-chip.si.on{ background: var(--fiori-row-hover); color: var(--fiori-accent-cyan);
  border-color: var(--fiori-accent-cyan); font-weight: 650; }
.raci-chip.client.on{ background: var(--fiori-row-hover); color: var(--fiori-accent-amber);
  border-color: var(--fiori-accent-amber); font-weight: 650; }
.raci-totals{ display: flex; gap: 16px; flex-wrap: wrap; align-items: center; font-size: 12.5px;
  color: var(--fiori-text-muted); padding: 10px 16px; }
.raci-tot b{ font-size: 15px; color: var(--fiori-text); font-weight: 680; margin-right: 4px; }
.raci-tot.good b{ color: var(--fiori-accent-emerald); }
.raci-tot.warn b{ color: var(--fiori-accent-amber); }
.raci-q{ width: 240px; font-size: 12.5px; padding: 4px 8px; }
.raci-empty{ padding: 40px 20px; text-align: center; color: var(--fiori-text-muted); font-size: 13px; }
.raci-empty b{ display: block; color: var(--fiori-text); font-size: 14px; margin-bottom: 6px; }
.raci-tblwrap{ overflow-x: auto; padding: 4px 0 2px; }
.raci-tbl{ border-collapse: collapse; width: 100%; font-size: 12.5px; }
.raci-tbl thead th{ text-align: left; font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--fiori-text-muted); font-weight: 650; padding: 7px 14px;
  border-bottom: 1px solid var(--fiori-border); white-space: nowrap; }
.raci-tbl tbody tr{ border-bottom: 1px solid var(--fiori-border); }
.raci-tbl tbody tr.sum{ font-weight: 640; background: var(--fiori-surface-solid); }
/* Owner 2026-10-10: *"Here also the margins are not proper."* The
   cells carried `7px 10px 7px 0` -- no LEFT padding at all, so every
   column's text started hard against the previous column's and the
   whole gutter sat on one side. A wrapped activity title ended four
   or five pixels from the workstream beside it. Symmetric padding,
   and only the first column stays flush so it lines up with the card
   edge above it. */
.raci-tbl td{ padding: 7px 14px; vertical-align: top; }
/* The gutter lives on the end CELLS, not on .raci-tblwrap: a
   container that scrolls sideways drops its right padding as soon as
   it is scrolled, and the last column ends up against the border. */
.raci-tbl thead th:first-child,
.raci-tbl tbody td:first-child{ padding-left: 16px; }
.raci-tbl thead th:last-child,
.raci-tbl tbody td:last-child{ padding-right: 16px; }
.raci-code{ font-family: ui-monospace, Menlo, Consolas, monospace; color: var(--fiori-text-muted);
  white-space: nowrap; }
.raci-dates{ white-space: nowrap; color: var(--fiori-text-muted); }
/* Owner 2026-10-10: *"Increase the width of the WHO columns as longer
   names are getting word wrapped. And dont wordwrap."* The CHIPS may
   wrap against each other -- four people on one activity has to go
   somewhere -- but a name inside a chip never breaks across two lines.
   The column carries a floor so it stops being squeezed by the long
   task titles beside it; it is a min-width, not a width, so a narrow
   screen still lets the table scroll rather than overflow the page. */
.raci-on{ display: flex; flex-wrap: wrap; gap: 5px; }
.raci-tbl th.raci-who, .raci-tbl td.raci-who{ min-width: 190px; white-space: nowrap; }
.raci-person{ display: inline-flex; align-items: center; gap: 5px; font-size: 11.5px;
  padding: 2px 8px 2px 4px; border-radius: 999px; white-space: nowrap;
  border: 1px solid var(--fiori-border); color: var(--fiori-text-muted); }
.raci-person.sel{ color: var(--fiori-text); border-color: var(--fiori-accent-cyan); }
.raci-person.client.sel{ border-color: var(--fiori-accent-amber); }
.raci-l{ font-size: 9.5px; font-weight: 800; width: 15px; height: 15px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center; color: #0B1220; }
.raci-l.lR{ background: var(--fiori-accent-cyan); }
.raci-l.lA{ background: var(--fiori-accent-amber); }
.raci-l.lC{ background: var(--fiori-accent-violet); }
.raci-l.lI{ background: var(--fiori-text-muted); }
.raci-dot{ width: 7px; height: 7px; border-radius: 50%; display: inline-block;
  border: 1px solid var(--fiori-text-muted); }
.raci-dot.has{ background: var(--fiori-accent-emerald); border-color: var(--fiori-accent-emerald); }
/* RACI: the three-way Activities toggle, and staffing from the report
   (owner 2026-10-08). */
.raci-seg{ display: inline-flex; border: 1px solid var(--fiori-border); border-radius: 8px;
  overflow: hidden; }
.raci-segb{ border: none; background: transparent; cursor: pointer; font-size: 12px;
  padding: 6px 12px; color: var(--fiori-text-muted); }
.raci-segb + .raci-segb{ border-left: 1px solid var(--fiori-border); }
.raci-segb.on{ background: var(--fiori-row-hover); color: var(--fiori-accent-cyan); font-weight: 650; }
.raci-segb:disabled{ opacity: .5; cursor: default; }
.raci-nobody{ font-size: 11.5px; font-style: italic; color: var(--fiori-text-muted); }
.raci-x{ cursor: pointer; font-weight: 700; opacity: .55; padding-left: 2px; }
.raci-x:hover{ opacity: 1; color: var(--fiori-accent-rose); }
.raci-plus{ border: 1px dashed var(--fiori-border); background: transparent; cursor: pointer;
  color: var(--fiori-text-muted); border-radius: 999px; width: 22px; height: 22px;
  font-size: 13px; line-height: 1; padding: 0; }
.raci-plus:hover{ border-color: var(--fiori-accent-cyan); color: var(--fiori-accent-cyan); }
.raci-add{ font-size: 12px; padding: 2px 6px; max-width: 220px; }
.raci-dirty{ font-size: 11.5px; color: var(--fiori-accent-amber); font-weight: 650; }

/* ==================================================================
   BPML — the Business Process Master List (owner spec 2026-10-10).
   Every colour a theme token; the amber is the SAME instruction the
   workbook writes, so the preview and the sheet agree. */
.bpml-wrap{ padding: 2px 0 18px; display: flex; flex-direction: column; gap: 12px; }
.bpml-head{ display: flex; gap: 12px; align-items: baseline; flex-wrap: wrap; }
.bpml-head h1{ margin: 0; font-size: 21px; font-weight: 680; }
.bpml-sub{ font-size: 12.5px; color: var(--fiori-text-muted); }
.bpml-filters{ display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start;
  padding: 14px 16px; }
.bpml-fld{ flex: 0 1 260px; min-width: 0; }
.bpml-fld.bpml-wide{ flex: 1 1 360px; }
.bpml-fld label{ display: block; font-size: 10.5px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--fiori-text-muted); font-weight: 650;
  margin-bottom: 4px; }
.bpml-opt{ text-transform: none; letter-spacing: 0; font-weight: 500; }
.bpml-seg{ display: inline-flex; border: 1px solid var(--fiori-border); border-radius: 8px;
  overflow: hidden; }
.bpml-segb{ border: none; background: transparent; cursor: pointer; font: inherit;
  font-size: 12px; padding: 7px 14px; color: var(--fiori-text-muted); }
.bpml-segb + .bpml-segb{ border-left: 1px solid var(--fiori-border); }
.bpml-segb.on{ background: var(--fiori-row-hover); color: var(--fiori-accent-cyan); font-weight: 650; }
.bpml-segb:disabled{ opacity: .5; cursor: default; }

/* The three-position switch (owner 2026-10-10). A sliding THUMB under
   the labels, not three lit buttons: the movement is the whole point,
   and it is one element so the browser animates a transform rather
   than repainting two backgrounds. Three equal columns, so the thumb
   can be translated by whole multiples of its own width and never
   needs to know how wide a label is. */
.bpml-sw{ position: relative; display: grid; grid-template-columns: repeat(3, 1fr);
  border: 1px solid var(--fiori-border); border-radius: 10px; padding: 3px;
  background: var(--fiori-surface-solid); min-width: 320px; }
.bpml-swthumb{ position: absolute; z-index: 0; top: 3px; bottom: 3px; left: 3px;
  width: calc((100% - 6px) / 3); border-radius: 8px;
  background: var(--fiori-row-hover);
  box-shadow: inset 0 0 0 1px var(--fiori-accent-cyan);
  transition: transform .18s cubic-bezier(.2,.9,.3,1); }
@media (prefers-reduced-motion: reduce){ .bpml-swthumb{ transition: none; } }
.bpml-swb{ position: relative; z-index: 1; border: 0; background: transparent;
  cursor: pointer; font: inherit; font-size: 12px; line-height: 1.25;
  padding: 7px 8px; color: var(--fiori-text-muted); border-radius: 8px; }
.bpml-swb.on{ color: var(--fiori-accent-cyan); font-weight: 650; }
.bpml-swb:disabled{ opacity: .45; cursor: default; }
.bpml-swb:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: -2px; }
.bpml-swfld{ flex: 0 1 340px; }

.bpml-totals{ display: flex; gap: 16px; flex-wrap: wrap; align-items: center;
  font-size: 12.5px; color: var(--fiori-text-muted); padding: 10px 16px; }
.bpml-tot b{ font-size: 15px; color: var(--fiori-text); font-weight: 680; margin-right: 4px; }
.bpml-tot.amber b{ color: var(--fiori-accent-amber); }
.bpml-tot.blue b{ color: var(--fiori-brand); }
.bpml-tot.muted b{ color: var(--fiori-text-muted); }
.bpml-gap{ flex: 1; }
.bpml-rel{ font-size: 11.5px; color: var(--fiori-text-muted); }
.bpml-rel.warn{ color: var(--fiori-accent-amber); }
.bpml-dot{ color: var(--fiori-text-muted); opacity: .6; }

.bpml-empty{ padding: 40px 20px; text-align: center; color: var(--fiori-text-muted);
  font-size: 13px; }
.bpml-empty b{ display: block; color: var(--fiori-text); font-size: 14px; margin-bottom: 6px; }
.bpml-cap{ padding: 10px 16px 0; font-size: 11.5px; color: var(--fiori-text-muted); }
.bpml-tblwrap{ overflow-x: auto; padding: 0 0 2px; }
.bpml-tbl{ border-collapse: collapse; width: 100%; font-size: 12.5px; }
.bpml-tbl thead th{ text-align: left; font-size: 10.5px; letter-spacing: .07em;
  text-transform: uppercase; color: var(--fiori-text-muted); font-weight: 650;
  padding: 8px 14px; border-bottom: 1px solid var(--fiori-border); white-space: nowrap; }
.bpml-tbl tbody tr{ border-bottom: 1px solid var(--fiori-border); }
.bpml-tbl td{ padding: 7px 14px; vertical-align: top; max-width: 320px; }
.bpml-tbl thead th:first-child, .bpml-tbl tbody td:first-child{ padding-left: 16px; }
.bpml-tbl thead th:last-child, .bpml-tbl tbody td:last-child{ padding-right: 16px; }
/* The same instruction the workbook writes: the CELL, not the row.
   Two fills, meaning opposite things: amber is a decision somebody has
   to finish, blue is a question nobody has started. */
.bpml-amber{ background: color-mix(in srgb, var(--fiori-accent-amber) 22%, transparent);
  color: var(--fiori-text); font-weight: 650; }
.bpml-blue{ background: color-mix(in srgb, var(--fiori-brand) 20%, transparent);
  color: var(--fiori-text); font-weight: 650; }

/* ==================================================================
   GLASS SELECT — the dropdown list, drawn by us (owner spec
   2026-10-10: *"Wherever this is used in the entire app through CSS,
   convert this to liquid glass kind of UI"*).

   The CLOSED control is deliberately unchanged: it keeps the exact
   metrics `select.form-control` already had, so swapping a call site
   changes nothing on screen until somebody clicks it. That is the
   safest shape for a change this wide.
   ================================================================== */
.gsel{ position: relative; width: 100%; min-width: 0; }

/* STRUCTURE ONLY. The trigger wears whatever class the <select> wore
   -- form-control, impl-field, inline-input, cs-input, impl-cell,
   pv-sel -- and every one of those has an element-agnostic base rule
   in this stylesheet, so the closed control keeps exactly the box it
   had. The element-SCOPED select rules are the appearance reset and
   the caret sprite, neither of which a button wants: we draw our own
   caret as a flex child. */
.gsel-btn{
  display: flex; align-items: center; gap: 8px;
  width: 100%; font-family: inherit; text-align: left; cursor: pointer;
}
/* The fallback box, for a trigger carrying no appearance class at all
   -- 187 of the swapped selects had none and leaned on the bare
   `select:not(.form-input)` rule. Written at :where(), which has ZERO
   specificity, so any real class on the same element still wins no
   matter where it sits in the file. */
:where(.gsel-btn){
  height: 36px; font-size: 13px; color: var(--fiori-text);
  /* The same pair the bare `select` rule used, as tokens:
     --fiori-row-hover IS rgba(255,255,255,0.04) on dark, and light
     wants a solid white field rather than a tint of its own ground. */
  background-color: var(--fiori-row-hover);
  border: 1px solid var(--fiori-border); border-radius: var(--fiori-radius);
  padding: 8px 12px;
}
:where([data-theme="light"] .gsel-btn){ background-color: var(--fiori-surface-solid); }
.gsel-btn:disabled{ cursor: not-allowed; }
.gsel-btn:hover:not(:disabled){ border-color: var(--fiori-text-muted); }
.gsel-btn:focus-visible{ outline: 2px solid var(--fiori-brand); outline-offset: 1px; }
.gsel-btn.open{ border-color: var(--fiori-brand); }
.gsel-btn:disabled{ opacity: .6; }
.gsel-v{ flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gsel-v.ph{ color: var(--fiori-text-muted); }
.gsel-car{ flex: none; width: 12px; height: 12px; opacity: .6; transition: transform .16s ease; }
.gsel-btn.open .gsel-car{ transform: rotate(180deg); }

/* FIXED and portalled to <body>: `.card` sets backdrop-filter, which
   creates a stacking context, so a panel rendered inside a card is
   trapped in it and the next card paints over the top however high
   its z-index is. The component sets left/top (or bottom) in viewport
   coordinates and recomputes them on scroll. */
.gsel-pop{
  /* 10000 sits in the app's existing ladder: ABOVE the shell header
     (9000), the Project Visualizer drawer (9100-9102) and the
     attachment panel (9500), and BELOW the confirm dialog (10001) --
     "discard this?" has to be readable over the panel that asked it.
     It was 1400, which is under the shell header, so a panel opened
     near the top of a page was painted through by it.
     SearchableSelect had worked this out already and carried 10000
     inline; now one rule covers every glass panel. */
  position: fixed; z-index: 10000;
  width: max-content; max-width: min(92vw, 420px);
  padding: 5px; border-radius: 14px; overflow: hidden;
  background: color-mix(in srgb, rgb(var(--gsel-base)) var(--gsel-fill), transparent);
  backdrop-filter: blur(var(--gsel-blur)) saturate(var(--gsel-sat));
  -webkit-backdrop-filter: blur(var(--gsel-blur)) saturate(var(--gsel-sat));
  border: 1px solid var(--gsel-edge);
  box-shadow: var(--gsel-shadow);
  transform-origin: top left;
  animation: gselIn .14s cubic-bezier(.2,.9,.3,1);
}
.gsel-pop.up{ transform-origin: bottom left; }
@keyframes gselIn{ from{ opacity: 0; transform: translateY(-4px) scale(.985); } to{ opacity: 1; transform: none; } }
/* The specular edge. Without it the panel is a blurred rectangle; with
   it, it reads as a pane of glass catching the light. */
.gsel-pop::before{
  content: ""; position: absolute; inset: 0 0 auto; height: 42%; pointer-events: none;
  background: linear-gradient(to bottom, var(--gsel-spec), transparent);
  opacity: .6; border-radius: 14px 14px 0 0; mix-blend-mode: var(--gsel-spec-blend);
}

/* THREE FALLBACKS, none of them optional. A translucent panel with no
   blur is unreadable text over a dashboard, so where the blur cannot
   run the panel goes fully opaque. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))){
  .gsel-pop{ background: var(--fiori-surface-solid); }
  .gsel-pop::before{ display: none; }
}
@media (prefers-reduced-transparency: reduce){
  .gsel-pop{ background: var(--fiori-surface-solid); backdrop-filter: none; -webkit-backdrop-filter: none; }
  .gsel-pop::before{ display: none; }
}
@media (prefers-reduced-motion: reduce){
  .gsel-pop{ animation: none; }
  .gsel-car{ transition: none; }
}

.gsel-search{ padding: 3px 3px 6px; }
.gsel-sinput{
  width: 100%; height: 30px; font: inherit; font-size: 12.5px; color: var(--fiori-text);
  background: rgba(var(--gsel-base), .28); border: 1px solid var(--gsel-edge);
  border-radius: 9px; padding: 5px 9px;
}
.gsel-sinput::placeholder{ color: var(--fiori-text-muted); }
.gsel-sinput:focus{ outline: none; border-color: var(--fiori-brand); }

.gsel-list{ max-height: 286px; overflow-y: auto; scrollbar-width: thin; }
.gsel-grp{
  font-size: 10px; letter-spacing: .07em; text-transform: uppercase; font-weight: 750;
  color: var(--fiori-text-muted); padding: 8px 10px 4px;
}
.gsel-opt{
  display: flex; align-items: center; gap: 9px; width: 100%;
  font: inherit; font-size: 12.5px; text-align: left; cursor: pointer;
  color: var(--fiori-text); background: transparent; border: 0;
  border-radius: 9px; padding: 7px 10px; line-height: 1.35;
}
.gsel-tick{ flex: none; width: 14px; height: 14px; opacity: 0; }
.gsel-opt[aria-selected="true"] .gsel-tick{ opacity: 1; color: var(--fiori-accent-cyan); }
.gsel-opt[aria-selected="true"]{ font-weight: 650; }
.gsel-t{ flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.gsel-meta{ flex: none; font-size: 11px; color: var(--fiori-text-muted); }
/* A TINT, never a solid bar. A solid fill inside a glass panel reads
   as a hole punched through it -- which is exactly what the native
   popup's blue block looked like against this theme. */
.gsel-opt.active,
.gsel-opt:hover:not([aria-disabled="true"]){
  background: color-mix(in srgb, var(--fiori-brand) 26%, transparent);
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fiori-brand) 38%, transparent);
}
.gsel-opt[aria-disabled="true"]{ opacity: .42; cursor: default; }
.gsel-none{ padding: 14px 10px; text-align: center; color: var(--fiori-text-muted); font-size: 12px; }
.gsel-foot{
  padding: 6px 10px 3px; margin-top: 4px; border-top: 1px solid var(--gsel-edge);
  font-size: 10.5px; color: var(--fiori-text-muted);
}

/* ── MultiSearchableSelect's own parts ─────────────────────────────
   Everything this component shares with the other two dropdowns comes
   from the .gsel-* rules above. These four are the parts only a
   MULTI-select has: the chips on the trigger, the checkbox in the
   list, and the bulk row. Colours are tokens on both sides of the
   theme -- the inline versions these replaced were a hard-coded
   rgba(0,224,255,…) that read as a bright cyan on a light ground. */
.msel-chip{
  display: inline-flex; align-items: center; gap: 4px;
  padding: 2px 6px 2px 8px; border-radius: 999px; font-size: 11.5px; max-width: 160px;
  background: color-mix(in srgb, var(--fiori-accent-cyan) 16%, transparent);
  color: var(--fiori-accent-cyan);
  border: 1px solid color-mix(in srgb, var(--fiori-accent-cyan) 42%, transparent);
}
.msel-chip-t{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msel-chip-x{ cursor: pointer; font-size: 13px; line-height: 1; opacity: .8; }
.msel-chip-x:hover{ opacity: 1; }
.msel-more{
  padding: 2px 7px; border-radius: 999px; font-size: 11px;
  background: var(--fiori-row-hover); color: var(--fiori-text-muted);
  border: 1px solid var(--fiori-border);
}
/* The checkbox. A single-select marks the chosen row with a tick; a
   multi-select has to show the UNCHOSEN rows as empty boxes too, or
   nothing tells the operator the list takes more than one answer. */
.msel-box{
  width: 14px; height: 14px; border-radius: 3px; flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1.5px solid var(--fiori-border-light); background: transparent;
  color: var(--fiori-on-accent);
}
.msel-box[data-on="1"]{
  border-color: var(--fiori-accent-cyan); background: var(--fiori-accent-cyan);
}
.msel-bulk{ display: flex; gap: 8px; margin-top: 6px; font-size: 11.5px; }
.msel-mini{
  padding: 4px 9px; border-radius: 7px; cursor: pointer; font: inherit;
  font-size: 11.5px; font-weight: 600;
  background: transparent; border: 1px solid var(--gsel-edge); color: var(--fiori-text);
}
.msel-mini:hover{ border-color: var(--fiori-text-muted); }
.msel-mini.danger{ color: var(--fiori-accent-pink); }
.msel-count{ color: var(--fiori-text-muted); align-self: center; margin-left: auto; }

/* ==================================================================
   RACI — THE LANES VIEW and its gap panel (owner spec 2026-10-10:
   *"Add Option C in addition to current assignment option ... And
   build the left selection panel as shown in B - Gap Queue into
   Option C."*).

   Every colour is a theme token. The view was designed against the
   dark theme, which is exactly how the last three panels ended up
   invisible in light mode. */
.raci-board{ display: grid; grid-template-columns: 232px minmax(0, 1fr); align-items: stretch; }
@media (max-width: 900px){ .raci-board{ grid-template-columns: 1fr; } }

.raci-gaps{ border-right: 1px solid var(--fiori-border); padding: 12px 0; }
@media (max-width: 900px){
  .raci-gaps{ border-right: 0; border-bottom: 1px solid var(--fiori-border); }
}
.raci-gap{ display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 16px 8px 13px; background: transparent; border: 0; border-left: 3px solid transparent;
  cursor: pointer; font: inherit; font-size: 12.5px; color: var(--fiori-text); text-align: left; }
.raci-gap:hover{ background: var(--fiori-row-hover); }
.raci-gap.on{ background: var(--fiori-row-hover); border-left-color: var(--fiori-accent-cyan); font-weight: 650; }
.raci-gapn{ min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.raci-gapc{ margin-left: auto; font-weight: 800; font-size: 13px; font-variant-numeric: tabular-nums;
  color: var(--fiori-text); }
.raci-gapc.bad{ color: var(--fiori-accent-rose); }
.raci-gapc.warn{ color: var(--fiori-accent-amber); }
.raci-gapc.good{ color: var(--fiori-accent-emerald); }
.raci-gapnote{ padding: 10px 16px 0; font-size: 11px; line-height: 1.5; color: var(--fiori-text-muted); }

.raci-lanewrap{ min-width: 0; }
.raci-lanehint{ padding: 12px 16px 0; font-size: 11.5px; color: var(--fiori-text-muted); }
.raci-lanes{ display: flex; gap: 10px; padding: 10px 16px 16px; overflow-x: auto; align-items: flex-start; }
.raci-lane{ flex: 0 0 218px; display: flex; flex-direction: column; max-height: 540px;
  border: 1px solid var(--fiori-border); border-radius: 12px; background: var(--fiori-surface-solid); }
.raci-lane.leftover{ border-style: dashed; border-color: var(--fiori-accent-rose); }
.raci-lane.over{ border-color: var(--fiori-accent-cyan);
  box-shadow: 0 0 0 2px var(--fiori-row-hover); }
.raci-laneh{ display: flex; align-items: center; gap: 7px; padding: 9px 11px 0; }
.raci-lanename{ font-weight: 650; font-size: 12.5px; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.raci-lanetag{ margin-left: auto; font-size: 9.5px; font-weight: 800; border-radius: 999px;
  padding: 1px 7px; border: 1px solid var(--fiori-border); color: var(--fiori-text-muted); }
.raci-lanetag.si{ color: var(--fiori-accent-cyan); border-color: var(--fiori-accent-cyan); }
.raci-lanetag.client{ color: var(--fiori-accent-amber); border-color: var(--fiori-accent-amber); }
.raci-lanesub{ padding: 1px 11px 0; font-size: 10.5px; color: var(--fiori-text-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.raci-lanebar{ height: 4px; border-radius: 3px; background: var(--fiori-row-hover);
  margin: 7px 11px 0; overflow: hidden; }
.raci-lanebar i{ display: block; height: 100%; background: var(--fiori-accent-emerald); }
.raci-lanebar i.hot{ background: var(--fiori-accent-amber); }
.raci-lanem{ display: flex; gap: 8px; flex-wrap: wrap; padding: 5px 11px 8px;
  font-size: 10.5px; color: var(--fiori-text-muted); }
.raci-laneb{ display: flex; flex-direction: column; gap: 6px; padding: 0 9px 9px; overflow-y: auto; }
.raci-lanenone{ font-size: 11px; color: var(--fiori-text-muted); font-style: italic; padding: 2px 2px 6px; }

.raci-card{ display: block; width: 100%; text-align: left; font: inherit; cursor: pointer;
  background: var(--fiori-surface-solid); color: var(--fiori-text);
  border: 1px solid var(--fiori-border); border-radius: 9px; padding: 7px 8px; }
.raci-card:hover{ border-color: var(--fiori-accent-cyan); }
.raci-card.on{ border-color: var(--fiori-accent-cyan); background: var(--fiori-row-hover); }
.raci-card.dragging{ opacity: .45; }
.raci-cardt{ font-size: 12px; line-height: 1.35; }
.raci-cardm{ display: flex; align-items: center; gap: 6px; margin-top: 5px;
  font-size: 10.5px; color: var(--fiori-text-muted); }

/* The right-click menu on a lane card (owner 2026-10-10: *"allow user
   to Select and WBS/Task and right click to unassign also"*).
   POSITION: FIXED, at the cursor -- inside the lane it would be
   clipped by the lane's own overflow and by the board's sideways
   scroller, which is where the first version of it went. */
.raci-ctx{ position: fixed; z-index: 1500; min-width: 230px; max-width: 320px; padding: 4px;
  max-height: 70vh; overflow-y: auto;
  border-radius: 11px; border: 1px solid var(--fiori-border);
  background: var(--fiori-surface-solid); box-shadow: var(--fiori-shadow); }
.raci-ctxh{ padding: 6px 10px 4px; font-size: 10.5px; font-weight: 750;
  letter-spacing: .05em; text-transform: uppercase; color: var(--fiori-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 260px; }
.raci-ctxb{ display: block; width: 100%; text-align: left; font: inherit; font-size: 12.5px;
  cursor: pointer; background: transparent; border: 0; border-radius: 8px;
  padding: 7px 10px; color: var(--fiori-text); }
.raci-ctxb:hover:not(:disabled){ background: var(--fiori-row-hover); }
.raci-ctxb:disabled{ opacity: .55; cursor: default; }
/* The section headings inside it, and the list of who is on the
   activity (owner 2026-10-10). A person row is a REMOVE, so it
   reddens on hover; Done and the Add picker must not. */
.raci-ctxs{ padding: 8px 10px 3px; font-size: 10px; letter-spacing: .07em;
  text-transform: uppercase; font-weight: 750; color: var(--fiori-text-muted); }
.raci-ctxnone{ padding: 3px 10px 6px; font-size: 11.5px; font-style: italic;
  color: var(--fiori-text-muted); }
.raci-ctxp{ display: flex; align-items: center; gap: 7px; }
.raci-ctxp.me{ background: var(--fiori-row-hover); }
.raci-ctxp:hover:not(:disabled){ color: var(--fiori-accent-rose); }
.raci-ctxn{ flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.raci-ctxx{ flex: none; font-size: 11px; font-weight: 700; opacity: .55; }
.raci-ctxp:hover:not(:disabled) .raci-ctxx{ opacity: 1; }
.raci-ctxadd{ margin: 2px 4px 4px; width: calc(100% - 8px); }
.raci-ctxbulk{ border-top: 1px solid var(--fiori-border); margin-top: 4px;
  color: var(--fiori-accent-rose); }
.raci-ctxdone{ color: var(--fiori-text-muted); }

/* RACI: the Task view (owner spec 2026-10-09). */
.raci-tdone{ text-decoration: line-through; opacity: .62; }
.raci-late{ color: var(--fiori-accent-rose); }
.raci-pct{ font-variant-numeric: tabular-nums; white-space: nowrap; }

/* RACI: the Workstream / Role multi-selects, bulk selection and the
   resource picker (owner spec 2026-10-09). Every colour is a theme
   token, so the panel reads in both themes rather than only the dark
   one it was built in. */
/* The Workstream and Role filters are MultiSearchableSelect, the
   shared component, not a local dropdown.

   The local one was unfixable in place. A panel inside .raci-filters
   is inside a .card, and .card carries backdrop-filter -- which makes
   it the CONTAINING BLOCK for position:fixed descendants. So the
   panel's viewport coordinates were resolved against the card and it
   landed off screen; absolute positioning before that had it clipped
   at the card's edge. No z-index reaches either problem. The shared
   component portals its panel to document.body, which is the only
   thing that escapes both. Owner report 2026-10-09, twice.          */
.raci-tick{ width: 28px; padding-right: 6px !important; }
.raci-tbl tbody tr.ticked{ background: var(--fiori-row-hover); }
.raci-meta{ font-size: 11.5px; color: var(--fiori-text-muted); max-width: 170px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.raci-modal{ position: fixed; inset: 0; z-index: 1200; display: flex; align-items: center;
  justify-content: center; padding: 20px; background: rgba(0,0,0,.45); }
.raci-mbox{ display: flex; flex-direction: column; gap: 10px; width: min(760px, 100%);
  max-height: 86vh; padding: 16px; border-radius: 14px;
  background: var(--fiori-surface-solid); border: 1px solid var(--fiori-border); }
.raci-mhead{ display: flex; align-items: baseline; gap: 10px; }
.raci-mlist{ overflow-y: auto; display: flex; flex-direction: column; gap: 12px; }
.raci-agh{ font-size: 10.5px; letter-spacing: .07em; text-transform: uppercase;
  color: var(--fiori-text-muted); margin-bottom: 4px; }
.raci-ap{ display: grid; grid-template-columns: 18px 1.3fr 1fr 1fr; gap: 8px; align-items: center;
  padding: 5px 7px; border-radius: 8px; cursor: pointer; font-size: 12.5px; }
.raci-ap:hover{ background: var(--fiori-row-hover); }
.raci-ap.on{ background: var(--fiori-row-hover); box-shadow: inset 2px 0 0 var(--fiori-accent-cyan); }
.raci-apn{ font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  display: flex; align-items: center; gap: 6px; }
.raci-agh{ display: flex; align-items: center; gap: 6px; }
.raci-agall{ display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font: inherit; color: inherit; letter-spacing: inherit; text-transform: inherit; }
/* A role mismatch, in red FILL as asked (owner 2026-10-09) -- a tint
   of the rose token rather than a flat red, so the name stays readable
   in both themes and a ticked row still reads as ticked. */
.raci-ap.off{ background: color-mix(in srgb, var(--fiori-accent-rose) 14%, transparent); }
.raci-ap.off:hover{ background: color-mix(in srgb, var(--fiori-accent-rose) 22%, transparent); }
.raci-ap.off.on{ box-shadow: inset 2px 0 0 var(--fiori-accent-rose); }
.raci-ap.off .raci-apn{ color: var(--fiori-accent-rose); }
.raci-warn{ font-size: 10px; font-weight: 800; width: 14px; height: 14px; flex: 0 0 auto;
  border-radius: 50%; display: inline-flex; align-items: center; justify-content: center;
  background: var(--fiori-accent-rose); color: var(--fiori-surface-solid); }
.raci-mwarn{ font-size: 11.5px; color: var(--fiori-accent-rose); max-width: 460px; }
.raci-apr, .raci-apm{ color: var(--fiori-text-muted); font-size: 11.5px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.raci-mclass{ display: flex; gap: 14px; flex-wrap: wrap; padding: 4px 0 8px; }
.raci-mclass .raci-fld{ flex: 1 1 240px; }
.raci-mhead .raci-seg{ margin-right: 4px; }
.raci-mfoot{ display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  border-top: 1px solid var(--fiori-border); padding-top: 10px; }
@media (max-width: 680px){
  .raci-ap{ grid-template-columns: 18px 1fr; }
  .raci-apr, .raci-apm{ grid-column: 2; }
}
