/* =====================================================
   WorkforceHR — Editorial
   ─────────────────────────────────────────────────────
   One token layer owns every colour, size, space, radius
   and shadow. No component below this block may contain a
   raw hex value; see docs/specs/SPEC-004-design-system.md.

   Direction: D-007 (Editorial, warm) settled by D-023 —
   Newsreader on a warm paper ground, deep ink blue accent,
   hairlines and whitespace instead of cards and shadows.
   ===================================================== */

@import url('https://fonts.googleapis.com/css2?family=Newsreader:opsz,wght@6..72,400;6..72,500;6..72,600;6..72,700&family=Inter+Tight:wght@400;500;600;700&display=swap');

:root {
  color-scheme: light;

  /* =========================================================
     TYPE
     Newsreader carries the editorial premise but a serif goes
     muddy at small sizes, so it is confined to display roles —
     never below --ps-text-lg, never inside a grid cell (D-023).
     ========================================================= */
  --ps-font-display: 'Newsreader', 'Iowan Old Style', Georgia, 'Times New Roman', serif;
  --ps-font-ui:      'Inter Tight', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --ps-font-mono:    ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;

  /* 14px UI base: this is a data tool read all day, not a marketing page. */
  --ps-text-2xs:  0.6875rem;  /* 11 */
  --ps-text-xs:   0.75rem;    /* 12 */
  --ps-text-sm:   0.8125rem;  /* 13 */
  --ps-text-base: 0.875rem;   /* 14 */
  --ps-text-md:   0.9375rem;  /* 15 */
  --ps-text-lg:   1.125rem;   /* 18 — the floor for display type */
  --ps-text-xl:   1.375rem;   /* 22 */
  --ps-text-2xl:  1.75rem;    /* 28 */
  --ps-text-3xl:  2.25rem;    /* 36 */
  --ps-text-4xl:  2.875rem;   /* 46 */

  --ps-leading-tight: 1.2;
  --ps-leading-snug:  1.35;
  --ps-leading-body:  1.55;

  --ps-tracking-tight: -0.011em;
  --ps-tracking-wide:  0.06em;   /* eyebrows and section labels */

  /* =========================================================
     SPACE — a 4px base step
     ========================================================= */
  --ps-space-1:  4px;
  --ps-space-2:  8px;
  --ps-space-3:  12px;
  --ps-space-4:  16px;
  --ps-space-5:  20px;
  --ps-space-6:  24px;
  --ps-space-8:  32px;
  --ps-space-10: 40px;
  --ps-space-12: 48px;
  --ps-space-16: 64px;

  /* =========================================================
     NEUTRALS — warm, not slate. The canvas is paper.
     ========================================================= */
  --ps-n-0:   #FFFFFF;
  --ps-n-25:  #FDFCFA;
  --ps-n-50:  #FAF8F5;
  --ps-n-100: #F3EFE9;
  --ps-n-200: #E8E2D9;
  --ps-n-300: #D6CEC2;
  --ps-n-400: #B0A697;
  --ps-n-500: #736959;   /* AA on all three grounds; worst case is the sunk fill at 4.70:1 */
  --ps-n-600: #6B6255;
  --ps-n-700: #4E463B;
  --ps-n-800: #332E26;
  --ps-n-900: #1F1B15;

  /* =========================================================
     ACCENT — deep ink blue. Used sparingly: action, focus,
     the active nav state. 9.8:1 on the canvas (D-023).
     ========================================================= */
  --ps-a-50:  #EEF2F7;
  --ps-a-100: #D8E1EC;
  --ps-a-200: #B4C4DA;
  --ps-a-300: #7D97BA;
  --ps-a-400: #4A6E9B;
  --ps-a-500: #2C5182;
  --ps-a-600: #1F3A5F;
  --ps-a-700: #172C48;
  --ps-a-800: #101F33;
  --ps-a-900: #0A1420;

  /* =========================================================
     STATUS — mixed for a warm ground. The stock #10b981 /
     #f59e0b / #ef4444 read as plastic against paper.
     ========================================================= */
  --ps-success:      #3F7D5C;
  --ps-success-bg:   #EDF4EF;
  --ps-success-text: #2C5A41;
  --ps-warning:      #B5822C;
  --ps-warning-bg:   #FBF3E4;
  --ps-warning-text: #86601F;
  --ps-error:        #A83E38;
  --ps-error-bg:     #F9EDEC;
  --ps-error-text:   #7E2E29;
  --ps-info:         var(--ps-a-500);
  --ps-info-bg:      var(--ps-a-50);
  --ps-info-text:    var(--ps-a-700);

  /* =========================================================
     SEMANTIC ROLES — what components actually reference.
     ========================================================= */
  --ps-canvas:       var(--ps-n-50);
  --ps-surface:      var(--ps-n-0);
  --ps-surface-sunk: var(--ps-n-100);
  --ps-surface-alt:  var(--ps-n-25);

  --ps-rule:         var(--ps-n-200);   /* the hairline that carries structure */
  --ps-rule-strong:  var(--ps-n-300);
  --ps-rule-soft:    var(--ps-n-100);
  /* A field's outline identifies a control, so WCAG 1.4.11 wants 3:1 against the
     surface — --ps-rule is 1.29:1, which is right for a row separator and wrong
     for an input. */
  --ps-rule-control: #9C9283;

  --ps-ink:          var(--ps-n-900);
  --ps-ink-muted:    var(--ps-n-700);
  --ps-ink-soft:     var(--ps-n-600);
  --ps-ink-faint:    var(--ps-n-500);
  --ps-ink-disabled: var(--ps-n-400);
  --ps-ink-inverse:  var(--ps-n-0);

  --ps-accent:       var(--ps-a-600);
  --ps-accent-hover: var(--ps-a-700);
  --ps-accent-quiet: var(--ps-a-50);
  --ps-accent-text:  var(--ps-a-700);
  --ps-on-accent:    var(--ps-n-0);

  /* =========================================================
     RADII — small. A 2px corner reads as a rule; a 12px one
     reads as a rounded box, which is the look being left behind.
     ========================================================= */
  --ps-radius-xs: 2px;
  --ps-radius-sm: 3px;
  --ps-radius-md: 4px;
  --ps-radius-lg: 6px;
  --ps-radius-pill: 999px;

  /* =========================================================
     ELEVATION — reserved for things that genuinely float:
     dialogs, menus, toasts. Not the default container.
     ========================================================= */
  --ps-shadow-none:  none;
  --ps-shadow-pop:   0 4px 12px -2px rgba(31, 27, 21, 0.10), 0 2px 4px -2px rgba(31, 27, 21, 0.06);
  --ps-shadow-float: 0 16px 32px -12px rgba(31, 27, 21, 0.22), 0 4px 10px -4px rgba(31, 27, 21, 0.10);
  --ps-focus-ring:   0 0 0 3px var(--ps-a-200);

  /* The wash Lumo lays behind a modal and under the mobile drawer. A ramp token rather than a
     literal on the dark selector: the prefers-color-scheme branch redefines the ramp and would
     silently keep the light scrim otherwise, which is how the theme attribute and the OS branch
     drifted apart before. */
  --ps-scrim-soft: rgba(31, 27, 21, 0.06);
  --ps-scrim:      rgba(31, 27, 21, 0.18);

  /* =========================================================
     COMPATIBILITY ALIASES
     The Phase 2 stylesheet is ~1400 lines written against the
     old names. They are re-pointed at the ramp above rather
     than renamed, so every screen keeps working while the
     redesign lands section by section (SPEC-004 scope 3.4a/b).
     These are deleted as each section is rewritten.
     ========================================================= */
  --ps-bg-app:      var(--ps-canvas);
  --ps-bg-surface:  var(--ps-surface);
  --ps-bg-subtle:   var(--ps-surface-sunk);
  --ps-bg-muted:    var(--ps-n-200);
  --ps-border:      var(--ps-rule);
  --ps-border-soft: var(--ps-rule-soft);
  --ps-divider:     var(--ps-rule-soft);

  --ps-text:        var(--ps-ink);
  --ps-text-muted:  var(--ps-ink-muted);
  --ps-text-soft:   var(--ps-ink-soft);
  --ps-text-faint:  var(--ps-ink-faint);

  --ps-primary-50:  var(--ps-a-50);
  --ps-primary-100: var(--ps-a-100);
  --ps-primary-200: var(--ps-a-200);
  --ps-primary-300: var(--ps-a-300);
  --ps-primary-400: var(--ps-a-400);
  --ps-primary-500: var(--ps-a-500);
  --ps-primary-600: var(--ps-a-600);
  --ps-primary-700: var(--ps-a-700);
  --ps-primary-800: var(--ps-a-800);
  --ps-primary-900: var(--ps-a-900);

  --ps-sidebar:            var(--ps-surface);
  --ps-sidebar-hover:      var(--ps-surface-sunk);
  --ps-sidebar-hover-text: var(--ps-ink);
  --ps-sidebar-hover-icon: var(--ps-ink-muted);
  --ps-sidebar-active:     var(--ps-accent);
  --ps-sidebar-active-text:var(--ps-on-accent);
  --ps-sidebar-active-icon:var(--ps-on-accent);
  --ps-sidebar-text:       var(--ps-ink-muted);
  --ps-sidebar-text-muted: var(--ps-ink-faint);
  --ps-sidebar-icon:       var(--ps-ink-faint);
  --ps-sidebar-border:     var(--ps-rule);
  --ps-topbar:             var(--ps-surface);
  --ps-topbar-border:      var(--ps-rule);

  --ps-shadow-xs: var(--ps-shadow-none);
  --ps-shadow-sm: var(--ps-shadow-none);
  --ps-shadow-md: var(--ps-shadow-pop);
  --ps-shadow-lg: var(--ps-shadow-float);
  --ps-shadow-focus: var(--ps-focus-ring);

  /* ---- Lumo ---- */
  --lumo-font-family: var(--ps-font-ui);
  --lumo-primary-color: var(--ps-accent);
  --lumo-primary-color-50pct: var(--ps-a-300);
  --lumo-primary-text-color: var(--ps-accent-text);
  --lumo-primary-contrast-color: var(--ps-on-accent);

  --lumo-base-color: var(--ps-surface);
  --lumo-body-text-color: var(--ps-ink);
  --lumo-secondary-text-color: var(--ps-ink-muted);
  --lumo-tertiary-text-color: var(--ps-ink-faint);
  --lumo-header-text-color: var(--ps-ink);
  /* The whole ramp, not the first three. Lumo's own values are translucent *shades* —
     dark ink at low alpha — so any token left unmapped composites to near-nothing on the
     dark ground. That is invisible placeholder text, field icons and grid affordances,
     and it cannot be fixed downstream: Lumo paints them with -webkit-text-fill-color,
     which beats any color an outer ::part() rule sets. */
  --lumo-contrast-5pct:  var(--ps-n-100);
  --lumo-contrast-10pct: var(--ps-n-200);
  --lumo-contrast-20pct: var(--ps-n-300);
  --lumo-contrast-30pct: var(--ps-n-400);
  --lumo-contrast-40pct: var(--ps-n-500);
  --lumo-contrast-50pct: var(--ps-n-500);
  --lumo-contrast-60pct: var(--ps-n-600);
  --lumo-contrast-70pct: var(--ps-n-700);
  --lumo-contrast-80pct: var(--ps-n-800);
  --lumo-contrast-90pct: var(--ps-n-900);
  --lumo-contrast:       var(--ps-n-900);

  /* A disabled control still has to say what it is — the label and the value it holds are
     the only thing explaining why it is off. --ps-ink-disabled is a fill colour for dots and
     arrows, too weak to read at 2.4:1; the inert signal comes from the flattened border. */
  --lumo-disabled-text-color: var(--ps-ink-faint);

  /* Lumo paints every overlay as base-color plus a lightening wash
     (linear-gradient(--lumo-tint-5pct, --lumo-tint-5pct)) and a 1px --lumo-shade-5pct ring.
     Both are raw primitives the ramp above never claimed, so they fell back to Lumo's light
     values — white at 30% alpha over the near-black surface, which is why menus rendered as a
     flat grey slab. Surfaces here are explicit, so the wash has no job. */
  --lumo-tint-5pct:  transparent;
  /* Shades are washes, not colours: Lumo layers them over whatever is beneath — the modal scrim,
     the mobile drawer backdrop, grid row tint, --lumo-box-shadow-*. They have to stay
     translucent. An opaque ramp value here turns the dialog's scrim into a solid slab that sits
     lighter than the dialog it is meant to sit behind. Warm, to match the ramp. */
  --lumo-shade-5pct:  var(--ps-scrim-soft);
  --lumo-shade-20pct: var(--ps-scrim);

  --lumo-success-color: var(--ps-success);
  --lumo-error-color:   var(--ps-error);
  --lumo-warning-color: var(--ps-warning);

  --lumo-border-radius-s: var(--ps-radius-sm);
  --lumo-border-radius-m: var(--ps-radius-md);
  --lumo-border-radius-l: var(--ps-radius-lg);

  --lumo-font-size-xxs: var(--ps-text-2xs);
  --lumo-font-size-xs:  var(--ps-text-xs);
  --lumo-font-size-s:   var(--ps-text-sm);
  --lumo-font-size-m:   var(--ps-text-base);
  --lumo-font-size-l:   var(--ps-text-md);
  --lumo-font-size-xl:  var(--ps-text-lg);
  --lumo-font-size-xxl: var(--ps-text-xl);

  --lumo-line-height-s: var(--ps-leading-snug);
  --lumo-line-height-m: var(--ps-leading-body);

  --lumo-button-size: 34px;
}

/* =====================================================
   DARK
   Not an inversion. Inverting a warm light palette gives
   something green, so the dark ramp is its own warm scale
   on a warm near-black, and the accent lightens to hold
   contrast on a dark ground.
   ===================================================== */
html[theme~="dark"] {
  color-scheme: dark;

  --ps-n-0:   #191714;
  --ps-n-25:  #1E1B18;
  --ps-n-50:  #14120F;
  --ps-n-100: #24211D;
  --ps-n-200: #322E28;
  --ps-n-300: #423D35;
  --ps-n-400: #5C554A;
  --ps-n-500: #90877B;
  --ps-n-600: #A79E90;
  --ps-n-700: #C4BCAF;
  --ps-n-800: #E0D9CE;
  --ps-n-900: #F5F0E8;

  --ps-canvas:       var(--ps-n-50);
  --ps-surface:      var(--ps-n-0);
  --ps-surface-sunk: var(--ps-n-100);
  --ps-surface-alt:  var(--ps-n-25);

  --ps-accent:       var(--ps-a-300);
  --ps-accent-hover: var(--ps-a-200);
  --ps-accent-quiet: var(--ps-a-900);
  --ps-accent-text:  var(--ps-a-200);
  --ps-on-accent:    #0A1420;
  --ps-rule-control: #6E6558;

  --ps-success:      #6FA987;
  --ps-success-bg:   #1B2A21;
  --ps-success-text: #9CC7AE;
  --ps-warning:      #D6A65A;
  --ps-warning-bg:   #2C2317;
  --ps-warning-text: #E6C48C;
  --ps-error:        #CF7A73;
  --ps-error-bg:     #2C1B1A;
  --ps-error-text:   #E6A29C;
  --ps-info:         var(--ps-a-300);
  --ps-info-bg:      var(--ps-a-900);
  --ps-info-text:    var(--ps-a-200);

  --ps-focus-ring:   0 0 0 3px var(--ps-a-700);
  --ps-shadow-pop:   0 4px 12px -2px rgba(0, 0, 0, 0.55);
  --ps-shadow-float: 0 16px 32px -12px rgba(0, 0, 0, 0.70);

  /* Repeated from :root only to outrank Lumo's own [theme~='dark'] block, which reaches for
     its blue-grey tints. The prefers-color-scheme branch below needs no such copy: with no
     theme attribute set, that block never matches. */
  --lumo-contrast-5pct:  var(--ps-n-100);
  --lumo-contrast-10pct: var(--ps-n-200);
  --lumo-contrast-20pct: var(--ps-n-300);
  --lumo-contrast-30pct: var(--ps-n-400);
  --lumo-contrast-40pct: var(--ps-n-500);
  --lumo-contrast-50pct: var(--ps-n-500);
  --lumo-contrast-60pct: var(--ps-n-600);
  --lumo-contrast-70pct: var(--ps-n-700);
  --lumo-contrast-80pct: var(--ps-n-800);
  --lumo-contrast-90pct: var(--ps-n-900);
  --lumo-contrast:       var(--ps-n-900);
  --lumo-base-color: var(--ps-surface);
  --lumo-disabled-text-color: var(--ps-ink-faint);
  /* A scrim has to darken a dark ground too, so it goes further than the light one. */
  --ps-scrim-soft: rgba(0, 0, 0, 0.20);
  --ps-scrim:      rgba(0, 0, 0, 0.50);
}

/* Follow the operating system until the user chooses explicitly. */
@media (prefers-color-scheme: dark) {
  html:not([theme~="light"]):not([theme~="dark"]) {
    color-scheme: dark;

    --ps-n-0:   #191714;
    --ps-n-25:  #1E1B18;
    --ps-n-50:  #14120F;
    --ps-n-100: #24211D;
    --ps-n-200: #322E28;
    --ps-n-300: #423D35;
    --ps-n-400: #5C554A;
    --ps-n-500: #90877B;
    --ps-n-600: #A79E90;
    --ps-n-700: #C4BCAF;
    --ps-n-800: #E0D9CE;
    --ps-n-900: #F5F0E8;

    --ps-accent:       var(--ps-a-300);
    --ps-accent-hover: var(--ps-a-200);
    --ps-accent-quiet: var(--ps-a-900);
    --ps-accent-text:  var(--ps-a-200);
    --ps-on-accent:    #0A1420;

    --ps-success:      #6FA987;
    --ps-success-bg:   #1B2A21;
    --ps-success-text: #9CC7AE;
    --ps-warning:      #D6A65A;
    --ps-warning-bg:   #2C2317;
    --ps-warning-text: #E6C48C;
    --ps-error:        #CF7A73;
    --ps-error-bg:     #2C1B1A;
    --ps-error-text:   #E6A29C;

    --ps-focus-ring:   0 0 0 3px var(--ps-a-700);
    --ps-shadow-pop:   0 4px 12px -2px rgba(0, 0, 0, 0.55);
    --ps-shadow-float: 0 16px 32px -12px rgba(0, 0, 0, 0.70);
    --ps-scrim-soft:   rgba(0, 0, 0, 0.20);
    --ps-scrim:        rgba(0, 0, 0, 0.50);
  }
}

html, body {
  font-family: var(--ps-font-ui);
  font-size: var(--ps-text-base);
  background: var(--ps-canvas);
  color: var(--ps-ink);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Numbers line up in columns. Every figure in the product is tabular. */
body {
  font-variant-numeric: tabular-nums lining-nums;
}

/* =========================================================
   DISPLAY TYPE
   Newsreader on headings only. The optical-size axis (6..72)
   is set per step so large sizes get the high-contrast cut
   and 18px still holds together.
   ========================================================= */
h1, h2, h3, h4,
.ps-display {
  font-family: var(--ps-font-display);
  font-weight: 600;
  letter-spacing: var(--ps-tracking-tight);
  line-height: var(--ps-leading-tight);
  color: var(--ps-ink);
}

h1 { font-size: var(--ps-text-3xl); font-variation-settings: 'opsz' 72; }
h2 { font-size: var(--ps-text-2xl); font-variation-settings: 'opsz' 60; }
h3 { font-size: var(--ps-text-lg);  font-variation-settings: 'opsz' 24; }
h4 { font-size: var(--ps-text-md);  font-variation-settings: 'opsz' 18; font-family: var(--ps-font-ui); }

/* An eyebrow above a title: small, wide, quiet. */
.ps-eyebrow {
  font-family: var(--ps-font-ui);
  font-size: var(--ps-text-2xs);
  font-weight: 600;
  letter-spacing: var(--ps-tracking-wide);
  text-transform: uppercase;
  color: var(--ps-ink-faint);
}

/* =====================================================
   App layout: white sidebar + white topbar (slate borders)
   ===================================================== */
vaadin-app-layout {
  --vaadin-app-layout-drawer-background: var(--ps-sidebar);
}
vaadin-app-layout::part(navbar) {
  background: var(--ps-topbar);
  border-bottom: 1px solid var(--ps-topbar-border);
  backdrop-filter: saturate(140%) blur(8px);
  /* No fixed height: the navbar is one column now, and it has to grow for the impersonation
     banner underneath the topbar row. The 64px that aligns with .ps-side-logo lives on that
     row instead, so the logo still lines up whether or not the banner is showing. */
  min-height: 64px;
  box-sizing: border-box;
  padding: 0;
}
vaadin-app-layout::part(drawer) {
  background: var(--ps-sidebar);
  border-right: 1px solid var(--ps-sidebar-border);
}

/* The logo and footer already span the full drawer width; make the
   scrolling nav area (Scroller > VerticalLayout > SideNav) do the same,
   so the active pill and section dividers reach the full width instead of
   leaving dead space on the right. The Scroller is slotted light-DOM
   content, so it's a plain descendant of <vaadin-app-layout> here. */
vaadin-app-layout vaadin-scroller,
vaadin-app-layout vaadin-scroller > vaadin-vertical-layout {
  width: 100%;
  box-sizing: border-box;
}

/* Drawer toggle lives in the LIGHT topbar */
vaadin-drawer-toggle {
  color: var(--ps-text-soft);
}

/* Sidebar nav (the parent) — sets vars that all items inherit */
vaadin-side-nav {
  width: 100%;
  --vaadin-side-nav-item-color: var(--ps-sidebar-text);
  --vaadin-side-nav-item-icon-color: var(--ps-sidebar-icon);
  --vaadin-side-nav-item-background: transparent;
  --vaadin-side-nav-item-hover-background: var(--ps-sidebar-hover);
  --vaadin-side-nav-item-hover-color: var(--ps-sidebar-hover-text);
  --vaadin-side-nav-item-active-background: var(--ps-sidebar-active);
  --vaadin-side-nav-item-active-color: var(--ps-sidebar-active-text);
  --vaadin-side-nav-item-focus-ring-color: var(--ps-primary-500);
  color: var(--ps-sidebar-text);
}

/* Sidebar nav items — slate text on white.
   margin-left (8) + ::part(item) padding-left (12) = 20px, so the nav
   icons line up with the 20px gutter used by the logo, section labels,
   footer and topbar. */
vaadin-side-nav-item {
  border-radius: var(--ps-radius-md);
  margin: 2px 8px;
  font-weight: 500;
  font-size: var(--ps-text-base);
  color: var(--ps-sidebar-text);
  transition: background 140ms ease, color 140ms ease;
}
vaadin-side-nav-item::part(item) {
  padding: 10px 12px;
  border-radius: var(--ps-radius-md);
  color: var(--ps-sidebar-text);
}
vaadin-side-nav-item vaadin-icon,
vaadin-side-nav-item iron-icon {
  color: var(--ps-sidebar-icon);
}
vaadin-side-nav-item:hover {
  background: var(--ps-sidebar-hover);
  color: var(--ps-sidebar-hover-text);
}
vaadin-side-nav-item:hover::part(item) {
  color: var(--ps-sidebar-hover-text);
}
vaadin-side-nav-item:hover vaadin-icon,
vaadin-side-nav-item:hover iron-icon {
  color: var(--ps-sidebar-hover-icon);
}
/* Active item = solid blue pill with white text/icon and a soft blue glow.
   (A left-edge ::before rail doesn't work here: SideNavItem paints its
   background in a shadow-DOM [part="item"] that covers the host's ::before.) */
vaadin-side-nav-item[active] {
  background: var(--ps-sidebar-active);
  color: var(--ps-sidebar-active-text) !important;
  font-weight: 600;
  box-shadow: none;
}
/* Paint the solid fill on the shadow part itself — SideNavItem ignores
   --vaadin-side-nav-item-active-background and falls back to a pale Lumo
   primary tint, so we override the part's background directly. */
vaadin-side-nav-item[active]::part(item) {
  background-color: var(--ps-sidebar-active) !important;
  color: var(--ps-sidebar-active-text) !important;
}
vaadin-side-nav-item[active] vaadin-icon,
vaadin-side-nav-item[active] iron-icon {
  color: var(--ps-sidebar-active-icon) !important;
}

/* =====================================================
   Buttons — consistent height, padding, no truncation
   ===================================================== */
vaadin-button {
  font-family: var(--ps-font-ui);
  font-weight: 500;
  font-size: var(--ps-text-base);
  border-radius: var(--ps-radius-sm);
  letter-spacing: 0;
  height: 34px;
  min-height: 34px;
  min-width: auto;
  padding: 0 var(--ps-space-4);
  white-space: nowrap;
  cursor: pointer;
  transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
}
/* Focus is a visible ring, not a colour shift — 3.6 depends on this being one rule. */
vaadin-button:focus-visible {
  outline: none;
  box-shadow: var(--ps-focus-ring);
}
/* The one solid fill in the product. Flat: no shadow under a button that is not floating. */
vaadin-button[theme~="primary"] {
  background: var(--ps-accent);
  color: var(--ps-on-accent);
  box-shadow: none;
}
vaadin-button[theme~="primary"]:hover {
  background: var(--ps-accent-hover);
}
vaadin-button[theme~="tertiary"] {
  color: var(--ps-ink-muted);
  background: transparent;
}
vaadin-button[theme~="tertiary"]:hover {
  background: var(--ps-surface-sunk);
  color: var(--ps-ink);
}
vaadin-button[theme~="error"] {
  color: var(--ps-n-0);
  background: var(--ps-error);
}
vaadin-button[theme~="error"]:hover {
  background: var(--ps-error-text);
}
vaadin-button[theme~="success"] {
  color: var(--ps-n-0);
  background: var(--ps-success);
}

/* Icon-only tertiary buttons used in grid action columns */
vaadin-button[theme~="tertiary"][theme~="icon"],
vaadin-button[theme~="small"][theme~="tertiary"] {
  height: 30px;
  min-width: 30px;
  width: 30px;
  padding: 0;
  border-radius: var(--ps-radius-sm);
}

/* Outlined secondary appearance for the Export-style buttons */
.ps-btn-outline,
vaadin-button.ps-btn-outline {
  background: transparent;
  border: 1px solid var(--ps-rule-strong);
  color: var(--ps-ink-muted);
}
.ps-btn-outline:hover {
  background: var(--ps-surface-sunk);
  border-color: var(--ps-ink-faint);
  color: var(--ps-ink);
}

/* =====================================================
   Inputs
   ===================================================== */
vaadin-text-field,
vaadin-email-field,
vaadin-password-field,
vaadin-number-field,
vaadin-integer-field,
vaadin-text-area,
vaadin-combo-box,
vaadin-multi-select-combo-box,
vaadin-date-picker,
vaadin-time-picker,
vaadin-select {
  --vaadin-input-field-border-radius: var(--ps-radius-md);
  /* Lumo draws the outline as an inset shadow whose width defaults to 0, so a border
     colour on its own paints nothing -- without this every field outside
     .ps-section__body is a fill the same colour as the surface behind it. */
  --vaadin-input-field-border-width: 1px;
  --vaadin-input-field-border-color: var(--ps-rule-control);
  --vaadin-input-field-background: var(--ps-bg-surface);
  --vaadin-input-field-value-color: var(--ps-text);
  --vaadin-input-field-label-color: var(--ps-text-muted);
  --vaadin-input-field-height: 40px;
}
vaadin-text-field:focus-within,
vaadin-email-field:focus-within,
vaadin-password-field:focus-within,
vaadin-number-field:focus-within,
vaadin-integer-field:focus-within,
vaadin-text-area:focus-within,
vaadin-combo-box:focus-within,
vaadin-multi-select-combo-box:focus-within,
vaadin-date-picker:focus-within,
vaadin-time-picker:focus-within,
vaadin-select:focus-within {
  --vaadin-input-field-border-color: var(--ps-primary-500);
  box-shadow: var(--ps-shadow-focus);
}

/* =====================================================
   Grid
   ===================================================== */
vaadin-grid {
  --lumo-row-stripes: rgba(241, 245, 249, 0.6);
  border-radius: 0;
  background: var(--ps-bg-surface);
  border: none;
}
vaadin-grid::part(header-cell) {
  background: var(--ps-bg-subtle);
  color: var(--ps-text-soft);
  font-weight: 600;
  text-transform: uppercase;
  font-size: var(--ps-text-2xs);
  letter-spacing: 0.06em;
  border-bottom: 1px solid var(--ps-border);
}
vaadin-grid::part(row):hover {
  background: var(--ps-primary-50);
}
vaadin-grid::part(cell) {
  border-bottom: 1px solid var(--ps-divider);
  font-size: var(--ps-text-base);
}

/* =====================================================
   Dialogs / Notifications / Badges
   ===================================================== */
vaadin-dialog-overlay::part(overlay) {
  border-radius: var(--ps-radius-md);
  border: 1px solid var(--ps-border);
  box-shadow: var(--ps-shadow-lg);
}
vaadin-dialog-overlay::part(header) {
  border-bottom: 1px solid var(--ps-border-soft);
  padding: 18px 24px;
}
vaadin-dialog-overlay::part(content) {
  padding: 22px 24px;
}
vaadin-dialog-overlay::part(footer) {
  border-top: 1px solid var(--ps-border-soft);
  padding: 14px 20px;
  gap: 8px;
}

/* Dialogs were given a surface, hairline and float shadow above; menus and the field
   overlays never were, so they leaned on Lumo's defaults. Same treatment, one place. */
vaadin-context-menu-overlay::part(overlay),
vaadin-menu-bar-overlay::part(overlay),
vaadin-select-overlay::part(overlay),
vaadin-combo-box-overlay::part(overlay),
vaadin-multi-select-combo-box-overlay::part(overlay),
vaadin-date-picker-overlay::part(overlay),
vaadin-time-picker-overlay::part(overlay) {
  background-color: var(--ps-surface);
  background-image: none;
  border: 1px solid var(--ps-rule);
  border-radius: var(--ps-radius-md);
  box-shadow: var(--ps-shadow-float);
  color: var(--ps-ink);
}

vaadin-notification-card {
  border-radius: var(--ps-radius-md);
  box-shadow: var(--ps-shadow-md);
  font-weight: 500;
}

[theme~="badge"] {
  border-radius: var(--ps-radius-md);
  padding: 4px 11px;
  font-weight: 600;
  font-size: var(--ps-text-2xs);
  letter-spacing: 0.02em;
  display: inline-flex;
  align-items: center;
  line-height: 1.2;
}
[theme~="badge"][theme~="success"]  { background: var(--ps-success-bg); color: var(--ps-success-text); }
[theme~="badge"][theme~="error"]    { background: var(--ps-error-bg);   color: var(--ps-error-text); }
[theme~="badge"][theme~="warning"]  { background: var(--ps-warning-bg); color: var(--ps-warning-text); }
[theme~="badge"][theme~="contrast"] { background: var(--ps-bg-muted);   color: var(--ps-text-muted); }
[theme~="badge"][theme~="primary"]  { background: var(--ps-primary-100); color: var(--ps-primary-700); }

/* =====================================================
   Avatar
   ===================================================== */
vaadin-avatar {
  border: 2px solid var(--ps-bg-surface);
  box-shadow: var(--ps-shadow-xs);
}

/* =====================================================
   Reusable layout / card classes
   ===================================================== */

/* Page wrapper - one consistent container for every view */
.ps-page {
  background: var(--ps-bg-app);
  padding: 24px !important;
  width: 100%;
  box-sizing: border-box;
  min-height: 100%;
  gap: 16px;
  /* The view is sized to the app-layout's content area and nothing above it scrolls — html and
     body are both exactly the viewport. A grid screen gets away with it because its card fills
     the page and the grid scrolls inside itself; a screen of stacked cards simply overflowed,
     leaving whatever sat past the fold unreachable with no scrollbar to say so. */
  overflow-y: auto;
}
.ps-page > * {
  width: 100%;
  box-sizing: border-box;
}

/* Page header: icon badge + title block + actions */
.ps-page-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  width: 100%;
  margin-bottom: 4px;
}
.ps-page-header__title-group {
  display: flex;
  align-items: center;
  gap: 14px;
  min-width: 0;
  flex: 1 1 auto;
}
.ps-page-header__actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
/* Flat, square-ish, hairline. The gradient-with-coloured-glow treatment this
   replaces is the single most template-looking element in the old design. */
/* Outlined, not filled. A solid accent block on every page header is not "one
   accent used sparingly" (D-007) — the accent belongs to actions. */
.ps-icon-badge {
  width: 32px;
  height: 32px;
  min-width: 32px;
  border-radius: var(--ps-radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--ps-rule-strong);
  box-shadow: none;
}
/* Vaadin icons paint with `fill`, not `color` — setting only `color` leaves the
   glyph whatever the Java side last set it to. */
.ps-icon-badge vaadin-icon,
.ps-icon-badge iron-icon {
  width: 17px;
  height: 17px;
  color: var(--ps-ink-soft);
  fill: var(--ps-ink-soft);
}
.ps-icon-badge--slate vaadin-icon   { color: var(--ps-ink-muted) !important; }
.ps-icon-badge--success vaadin-icon { color: var(--ps-success); fill: var(--ps-success); }
.ps-icon-badge--warning vaadin-icon { color: var(--ps-warning); fill: var(--ps-warning); }
.ps-icon-badge--info vaadin-icon    { color: var(--ps-info) !important; }
.ps-page-header__title {
  font-family: var(--ps-font-display);
  font-variation-settings: 'opsz' 60;
  font-size: var(--ps-text-2xl);
  font-weight: 600;
  color: var(--ps-ink);
  margin: 0;
  letter-spacing: var(--ps-tracking-tight);
  line-height: var(--ps-leading-tight);
}
.ps-page-header__avatar {
  width: 40px !important;
  height: 40px !important;
  min-width: 40px;
  border-radius: var(--ps-radius-sm) !important;
  box-shadow: none;
  border: 1px solid var(--ps-rule);
  background: var(--ps-surface-sunk);
  font-family: var(--ps-font-ui);
  font-weight: 600;
  color: var(--ps-accent-text);
}
.ps-page-header__subtitle {
  display: block;
  color: var(--ps-text-soft);
  font-size: var(--ps-text-sm);
  margin-top: 2px;
}

/* Toolbar (filters, search, status pills) */
.ps-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  width: 100%;
  padding: 16px 18px;
  background: var(--ps-bg-surface);
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-md);
  box-shadow: var(--ps-shadow-xs);
  box-sizing: border-box;
}
.ps-toolbar__left,
.ps-toolbar__right {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.ps-toolbar__left { flex: 1 1 auto; min-width: 0; }

/* Stat pills inline with toolbar */
.ps-stat-pill {
  display: inline-flex;
  align-items: baseline;
  gap: var(--ps-space-2);
  padding: 0 0 0 var(--ps-space-4);
  border-left: 1px solid var(--ps-rule);
  border-radius: 0;
  background: transparent;
  font-size: var(--ps-text-2xs);
  font-weight: 600;
  letter-spacing: var(--ps-tracking-wide);
  text-transform: uppercase;
  color: var(--ps-ink-faint);
  white-space: nowrap;
}
.ps-stat-pill__value {
  font-family: var(--ps-font-display);
  font-variation-settings: 'opsz' 18;
  font-size: var(--ps-text-lg);
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ps-ink);
  font-variant-numeric: tabular-nums lining-nums;
}
.ps-stat-pill--primary .ps-stat-pill__value { color: var(--ps-accent-text); }
.ps-stat-pill--success .ps-stat-pill__value { color: var(--ps-success-text); }

/* Card holding a grid */
.ps-grid-card {
  background: var(--ps-surface);
  border: 1px solid var(--ps-rule);
  border-radius: var(--ps-radius-md);
  box-shadow: none;
  overflow: hidden;
  width: 100%;
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.ps-grid-card vaadin-grid {
  flex: 1 1 auto;
  min-height: 380px;
}

/* Generic content card (used in dashboard / settings) */
/* A "card" is now a bounded region, not a floating tile: hairline, no shadow,
   small radius. Elevation is reserved for things that genuinely float. */
.ps-card {
  background: var(--ps-surface);
  border-radius: var(--ps-radius-md);
  border: 1px solid var(--ps-rule);
  box-shadow: none;
  padding: var(--ps-space-6);
  box-sizing: border-box;
}
/* No lift, no shadow — the rule darkens. A tile that jumps on hover is the
   look this direction is leaving behind. */
.ps-card--hover { transition: border-color 140ms ease; }
.ps-card--hover:hover { border-color: var(--ps-rule-strong); }

.ps-card-title {
  font-size: var(--ps-text-md);
  font-weight: 600;
  color: var(--ps-text);
  margin: 0;
  letter-spacing: -0.01em;
}

/* The one place a number gets the display face. Tabular figures keep a row of
   stat tiles optically aligned even at 36px. */
.ps-stat-value {
  font-family: var(--ps-font-display);
  font-variation-settings: 'opsz' 72;
  font-size: var(--ps-text-3xl);
  font-weight: 600;
  color: var(--ps-ink);
  /* Not --ps-leading-tight: a serif's descenders need room at display sizes; 1.2 clips them. */
  line-height: 1.28;
  letter-spacing: var(--ps-tracking-tight);
  font-variant-numeric: tabular-nums lining-nums;
}
.ps-stat-label {
  font-size: var(--ps-text-sm);
  color: var(--ps-text-soft);
  font-weight: 500;
}

/* Section card with title strip (Settings-style) */
.ps-section {
  background: var(--ps-bg-surface);
  border: 1px solid var(--ps-rule);
  border-radius: var(--ps-radius-md);
  box-shadow: none;
  padding: 0;
  width: 100%;
  box-sizing: border-box;
  overflow: visible;
  display: flex;
  flex-direction: column;
}
.ps-section__body {
  flex: 1 1 auto;
}
.ps-section > .ps-section__head:first-child {
  border-top-left-radius: var(--ps-radius-md);
  border-top-right-radius: var(--ps-radius-md);
}
.ps-section__head {
  padding: var(--ps-space-4) var(--ps-space-6);
  border-bottom: 1px solid var(--ps-rule);
  display: flex;
  align-items: center;
  gap: 12px;
}
.ps-section__head h3 {
  margin: 0;
  font-size: var(--ps-text-md);
  font-weight: 600;
  color: var(--ps-text);
}
.ps-section__head .ps-icon-badge { width: 36px; height: 36px; min-width: 36px; border-radius: var(--ps-radius-md); }
/* Plain surface, not a tinted block. A grey fill behind every card body is the
   card-on-grey pattern this direction replaces with hairlines and whitespace. */
.ps-section__body {
  padding: var(--ps-space-5) var(--ps-space-6);
  background: transparent;
}

/* Field separation in edit forms */
.ps-section__body vaadin-form-layout {
  --vaadin-form-layout-column-spacing: 24px;
  --vaadin-form-layout-row-spacing: 20px;
}
.ps-section__body vaadin-form-layout > * {
  box-sizing: border-box;
  max-width: 100%;
}

/* Each field's input area becomes a clean white card on the tinted body. */
.ps-section__body vaadin-text-field::part(input-field),
.ps-section__body vaadin-email-field::part(input-field),
.ps-section__body vaadin-number-field::part(input-field),
.ps-section__body vaadin-password-field::part(input-field),
.ps-section__body vaadin-date-picker::part(input-field),
.ps-section__body vaadin-time-picker::part(input-field),
.ps-section__body vaadin-combo-box::part(input-field),
.ps-section__body vaadin-select::part(input-field),
.ps-section__body vaadin-text-area::part(input-field) {
  background: var(--ps-bg-surface);
  /* --ps-rule-control, not --ps-border. The token block says why: a field's outline identifies a
     control, so WCAG 1.4.11 wants 3:1 against the surface, and --ps-rule is 1.29:1 — right for a
     row separator, wrong for an input. This rule had the separator token, which put every field
     on the busiest form in the product at 1.33:1 in dark. --ps-rule-control gives 3.12:1. */
  border: 1px solid var(--ps-rule-control);
  border-radius: var(--ps-radius-md);
  box-shadow: var(--ps-shadow-xs);
  transition: border-color 140ms ease, box-shadow 140ms ease;
}
.ps-section__body vaadin-text-field:hover::part(input-field),
.ps-section__body vaadin-email-field:hover::part(input-field),
.ps-section__body vaadin-number-field:hover::part(input-field),
.ps-section__body vaadin-password-field:hover::part(input-field),
.ps-section__body vaadin-date-picker:hover::part(input-field),
.ps-section__body vaadin-time-picker:hover::part(input-field),
.ps-section__body vaadin-combo-box:hover::part(input-field),
.ps-section__body vaadin-select:hover::part(input-field),
.ps-section__body vaadin-text-area:hover::part(input-field) {
  border-color: var(--ps-text-faint);
}
.ps-section__body vaadin-text-field[focused]::part(input-field),
.ps-section__body vaadin-email-field[focused]::part(input-field),
.ps-section__body vaadin-number-field[focused]::part(input-field),
.ps-section__body vaadin-password-field[focused]::part(input-field),
.ps-section__body vaadin-date-picker[focused]::part(input-field),
.ps-section__body vaadin-time-picker[focused]::part(input-field),
.ps-section__body vaadin-combo-box[focused]::part(input-field),
.ps-section__body vaadin-select[focused]::part(input-field),
.ps-section__body vaadin-text-area[focused]::part(input-field) {
  border-color: var(--ps-primary-600);
  box-shadow: var(--ps-focus-ring);
}

/* Make the field labels clearly visible */
.ps-section__body vaadin-text-field::part(label),
.ps-section__body vaadin-email-field::part(label),
.ps-section__body vaadin-number-field::part(label),
.ps-section__body vaadin-password-field::part(label),
.ps-section__body vaadin-date-picker::part(label),
.ps-section__body vaadin-time-picker::part(label),
.ps-section__body vaadin-combo-box::part(label),
.ps-section__body vaadin-select::part(label),
.ps-section__body vaadin-text-area::part(label),
.ps-section__body vaadin-checkbox::part(label) {
  color: var(--ps-text);
  font-weight: 600;
  font-size: var(--ps-text-sm);
  letter-spacing: 0.01em;
  margin-bottom: 6px;
}

/* Checkbox sits on tinted body — give it a subtle pill so it doesn't disappear */
.ps-section__body vaadin-checkbox {
  background: var(--ps-bg-surface);
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-md);
  padding: 10px 12px;
  box-shadow: var(--ps-shadow-xs);
  align-self: center;
}
.ps-section__footer {
  padding: 14px 22px;
  border-top: 1px solid var(--ps-border-soft);
  background: var(--ps-bg-subtle);
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

/* Sidebar section label */
.ps-side-section-label {
  color: var(--ps-text-faint);
  font-size: var(--ps-text-2xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  /* Generous top padding gives clear separation between sections. */
  padding: 24px 20px 10px 20px;
  display: block;
  /* vaadin-vertical-layout is align-items:flex-start, so a block child is content-width and
     the divider below stops at the end of the word. vaadin-side-nav sets width:100% for the
     same reason; these labels are the other direct child and need it too. */
  width: 100%;
  box-sizing: border-box;
}
/* The first label sits right under the logo, so it needs less air on top. */
.ps-side-section-label:first-child {
  padding-top: 14px;
}
/* Hairline divider above every section after the first — clear grouping. */
.ps-side-section-label:not(:first-child) {
  border-top: 1px solid var(--ps-sidebar-border);
  margin-top: 8px;
}

/* Sidebar logo (light) — height matches topbar exactly (64px including 1px bottom border) */
.ps-side-logo {
  padding: 0 20px;
  height: 64px;
  min-height: 64px;
  max-height: 64px;
  display: flex;
  align-items: center;
  gap: 12px;
  border-bottom: 1px solid var(--ps-sidebar-border);
  box-sizing: border-box;
  background: var(--ps-sidebar);
  flex: 0 0 auto;
}
.ps-side-logo__icon {
  width: 40px;
  height: 40px;
  min-width: 40px;
  border-radius: var(--ps-radius-sm);
  background: var(--ps-accent);
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: none;
}
.ps-side-logo__name {
  color: var(--ps-text);
  font-size: var(--ps-text-lg);
  font-weight: 700;
  margin: 0;
  letter-spacing: -0.02em;
  line-height: 1.1;
}
.ps-side-logo__tag {
  color: var(--ps-text-faint);
  font-size: var(--ps-text-2xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  margin-top: 2px;
  display: block;
}

/* Sidebar footer (light) */
.ps-side-footer {
  padding: 14px 20px;
  border-top: 1px solid var(--ps-sidebar-border);
  background: var(--ps-bg-subtle);
}
.ps-side-footer__status {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ps-text-soft);
  font-size: var(--ps-text-xs);
}
.ps-side-footer__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ps-success);
  box-shadow: var(--ps-focus-ring);
}
.ps-side-footer__version {
  color: var(--ps-text-faint);
  font-size: var(--ps-text-2xs);
  margin: 6px 0 0 0;
}

/* Topbar */
.ps-navbar-stack {
  width: 100%;
  display: flex;
  flex-direction: column;
}
.ps-topbar-row {
  width: 100%;
  height: 64px;
  flex: none;
  padding: 0 20px;
  display: flex;
  align-items: center;
  gap: 12px;
  box-sizing: border-box;
}
.ps-topbar-title {
  font-size: var(--ps-text-lg);
  font-weight: 600;
  color: var(--ps-text);
  margin: 0;
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.ps-topbar-user {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 4px 6px 4px 10px;
  border-radius: var(--ps-radius-md);
  background: var(--ps-bg-surface);
  border: 1px solid var(--ps-border);
}
.ps-topbar-user__name {
  font-weight: 600;
  font-size: var(--ps-text-sm);
  color: var(--ps-text);
  line-height: 1.2;
  white-space: nowrap;
}
.ps-topbar-user__role {
  font-size: var(--ps-text-2xs);
  color: var(--ps-text-soft);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  line-height: 1.2;
}
.ps-icon-btn {
  width: 36px;
  height: 36px;
  min-width: 36px;
  padding: 0;
  border-radius: var(--ps-radius-md);
  background: transparent;
  border: 1px solid transparent;
  color: var(--ps-text-soft);
}
.ps-icon-btn:hover {
  background: var(--ps-bg-surface);
  border-color: var(--ps-border);
  color: var(--ps-text);
}

/* Status badges (component-level overrides shipped via Span styles) */
/* State is carried by a tinted field with a matching hairline rather than a
   saturated pill — colour still says "approved" without shouting across a grid. */
.ps-status {
  display: inline-flex;
  align-items: center;
  gap: var(--ps-space-1);
  padding: 2px var(--ps-space-2);
  border-radius: var(--ps-radius-xs);
  border: 1px solid transparent;
  font-family: var(--ps-font-ui);
  font-size: var(--ps-text-2xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  white-space: nowrap;
  line-height: 1.5;
}
.ps-status--success {
  background: var(--ps-success-bg); color: var(--ps-success-text); border-color: var(--ps-success);
}
.ps-status--error {
  background: var(--ps-error-bg);   color: var(--ps-error-text);   border-color: var(--ps-error);
}
.ps-status--warning {
  background: var(--ps-warning-bg); color: var(--ps-warning-text); border-color: var(--ps-warning);
}
.ps-status--info {
  background: var(--ps-info-bg);    color: var(--ps-info-text);    border-color: var(--ps-a-300);
}
.ps-status--neutral {
  background: var(--ps-surface-sunk); color: var(--ps-ink-soft);   border-color: var(--ps-rule);
}

/* Action button group in grid */
.ps-actions {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
/* A 32px square is the icon-button shape, so it has to ask for an icon button. Applied to
   every child it also caught PlatformView's labelled actions, and Lumo ellipsizes a button
   label that does not fit — "Open" and "Deactivate" rendered as "O.." and "Dea...".
   [theme~="icon"] is the marker ButtonVariant.LUMO_ICON sets and the one Lumo's own sizing
   rules key off, which every other .ps-actions already passes. */
.ps-actions vaadin-button[theme~="icon"] {
  width: 32px;
  min-width: 32px;
  height: 32px;
  padding: 0;
  border-radius: var(--ps-radius-md);
}
/* Labelled actions size to their text and keep the row's button height. */
.ps-actions vaadin-button:not([theme~="icon"]) {
  height: 32px;
  border-radius: var(--ps-radius-md);
  white-space: nowrap;
}

/* Filters — give text fields and combos a sensible min-width inside flex toolbars */
.ps-toolbar vaadin-text-field { min-width: 240px; }
.ps-toolbar vaadin-combo-box,
.ps-toolbar vaadin-date-picker,
.ps-toolbar vaadin-time-picker { min-width: 160px; }

/* =====================================================
   Login page
   ===================================================== */
.login-view {
  background:
    radial-gradient(1200px 600px at 100% -10%, rgba(37, 99, 235, 0.18), transparent 60%),
    radial-gradient(900px 500px at 0% 110%, rgba(15, 23, 42, 0.18), transparent 60%),
    var(--ps-bg-app);
  min-height: 100vh;
  padding: 0 !important;
}
.login-card {
  width: 420px;
  max-width: 92vw;
  background: var(--ps-bg-surface);
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-md);
  box-shadow: var(--ps-shadow-lg);
  padding: 36px 32px 28px;
}
.login-logo {
  width: 56px;
  height: 56px;
  border-radius: var(--ps-radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--ps-accent);
  box-shadow: none;
  margin-bottom: 18px;
}
.login-title {
  font-size: var(--ps-text-2xl);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--ps-text);
  margin: 0 0 4px;
}
.login-subtitle {
  font-size: var(--ps-text-base);
  color: var(--ps-text-soft);
  margin: 0 0 24px;
}
.login-card vaadin-login-form,
.login-card vaadin-login-form-wrapper {
  background: transparent !important;
  box-shadow: none !important;
  padding: 0 !important;
}
.login-footer {
  margin-top: 22px;
  font-size: var(--ps-text-xs);
  color: var(--ps-text-faint);
  text-align: center;
}

hr {
  border: none;
  border-top: 1px solid var(--ps-divider);
  margin: 16px 0;
}

/* =====================================================
   Employee profile (tabbed view)
   ===================================================== */
.ps-employee-name-link {
  color: var(--ps-text);
  transition: color 120ms ease;
}
.ps-employee-name-link:hover {
  color: var(--ps-primary-600);
  text-decoration: underline;
}

.ps-back-link {
  align-self: flex-start;
  width: auto !important;
  max-width: max-content;
  color: var(--ps-text-soft);
  font-size: var(--ps-text-sm);
  font-weight: 500;
  padding: 4px 8px 4px 4px;
  margin-bottom: -8px;
  cursor: pointer;
  border-radius: var(--ps-radius-lg);
}
.ps-back-link:hover {
  color: var(--ps-primary-600);
  background: var(--ps-primary-50);
}
.ps-back-link vaadin-icon,
.ps-back-link iron-icon {
  width: 14px;
  height: 14px;
}

.ps-profile-tabs {
  background: var(--ps-bg-surface);
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-md);
  box-shadow: var(--ps-shadow-xs);
  padding: 0;
  width: 100%;
  box-sizing: border-box;
}
.ps-profile-tabs vaadin-tabsheet::part(tabs) {
  padding: 4px 12px 0 12px;
  border-bottom: 1px solid var(--ps-border-soft);
}
.ps-profile-tab-pane {
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
/* Setting display:flex on the host does not clear vaadin-vertical-layout's own
   align-items:flex-start, so toolbars, note lists and payslip sections here would be as wide
   as their text. Same reason .ps-page stretches its children; same fix. */
.ps-profile-tab-pane > * {
  width: 100%;
  box-sizing: border-box;
}

.ps-info-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: 18px;
}

.ps-kv-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 12px;
  padding: 8px 0;
  border-bottom: 1px dashed var(--ps-border-soft);
}
.ps-kv-row:last-child { border-bottom: none; }
.ps-kv-row__label {
  font-size: var(--ps-text-sm);
  color: var(--ps-text-soft);
  font-weight: 500;
  flex-shrink: 0;
}
.ps-kv-row__value {
  font-size: var(--ps-text-base);
  color: var(--ps-text);
  font-weight: 500;
  text-align: right;
  word-break: break-word;
}

.ps-note-card {
  background: var(--ps-bg-subtle);
  border: 1px solid var(--ps-border-soft);
  border-radius: var(--ps-radius-md);
  padding: 14px 16px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ps-note-card__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
}
.ps-note-card__title {
  font-weight: 600;
  color: var(--ps-text);
  font-size: var(--ps-text-md);
}
.ps-note-card__body {
  font-size: var(--ps-text-base);
  color: var(--ps-text);
  white-space: pre-wrap;
}
.ps-note-card__meta {
  font-size: var(--ps-text-xs);
  color: var(--ps-text-faint);
}

.ps-empty-state {
  padding: var(--ps-space-16) var(--ps-space-4);
  text-align: center;
  color: var(--ps-ink-soft);
  font-size: var(--ps-text-base);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--ps-space-3);
}
.ps-empty-state__icon {
  width: 56px;
  height: 56px;
  border-radius: 50%;
  background: var(--ps-bg-subtle);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ps-text-faint);
}

/* =====================================================
   Payslips tab (Employee profile)
   ===================================================== */
.ps-payslip-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 0 8px;
}

/* Segmented view toggle */
.ps-view-toggle {
  display: inline-flex;
  background: var(--ps-bg-surface);
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-md);
  padding: 3px;
  gap: 2px;
}
.ps-view-toggle__btn {
  background: transparent !important;
  color: var(--ps-text-muted) !important;
  font-weight: 500 !important;
  height: 32px !important;
  min-width: 0 !important;
  padding: 0 12px !important;
  border-radius: var(--ps-radius-lg) !important;
  box-shadow: none !important;
}
.ps-view-toggle__btn vaadin-icon {
  width: 14px;
  height: 14px;
}
.ps-view-toggle__btn--active {
  background: var(--ps-primary-50) !important;
  color: var(--ps-primary-700) !important;
  box-shadow: inset 0 0 0 1px rgba(37, 99, 235, 0.22) !important;
}

.ps-payslip-views {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* ---- List view: collapsible year sections ---- */
.ps-payslip-section {
  background: var(--ps-bg-surface);
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-md);
  box-shadow: var(--ps-shadow-xs);
  overflow: hidden;
}
.ps-payslip-section__header {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 16px;
  cursor: pointer;
  user-select: none;
  transition: background 120ms ease;
}
.ps-payslip-section__header:hover {
  background: var(--ps-surface-sunk);
}
.ps-payslip-section__chevron {
  width: 14px;
  height: 14px;
  color: var(--ps-text-soft);
  transition: transform 160ms ease;
}
.ps-payslip-section--collapsed .ps-payslip-section__chevron {
  transform: rotate(-90deg);
}
.ps-payslip-section__year {
  font-size: var(--ps-text-md);
  font-weight: 700;
  color: var(--ps-text);
  letter-spacing: 0.01em;
}
.ps-payslip-section__count {
  margin-left: auto;
  font-size: var(--ps-text-xs);
  color: var(--ps-text-soft);
  font-weight: 500;
}
.ps-payslip-section__body {
  border-top: 1px solid var(--ps-border-soft);
  display: flex;
  flex-direction: column;
}
.ps-payslip-section--collapsed .ps-payslip-section__body {
  display: none;
}

.ps-payslip-row {
  display: grid;
  grid-template-columns: 56px minmax(0, 1.6fr) 80px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--ps-border-soft);
  font-size: var(--ps-text-base);
}
.ps-payslip-row:last-child { border-bottom: none; }
.ps-payslip-row:hover { background: var(--ps-surface-sunk); }
.ps-payslip-row__month {
  font-weight: 600;
  color: var(--ps-text);
  letter-spacing: 0.01em;
}
.ps-payslip-row__name {
  color: var(--ps-primary-700);
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ps-payslip-row__size {
  color: var(--ps-text-soft);
  font-variant-numeric: tabular-nums;
  font-size: var(--ps-text-sm);
}
.ps-payslip-row__uploader {
  color: var(--ps-text-muted);
  font-size: var(--ps-text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ps-payslip-row__actions {
  display: flex;
  align-items: center;
  gap: 4px;
  justify-self: end;
}
@media (max-width: 640px) {
  .ps-payslip-row {
    grid-template-columns: 56px minmax(0, 1fr) auto;
  }
  .ps-payslip-row__size,
  .ps-payslip-row__uploader { display: none; }
}

/* ---- Matrix view: year × 12-month grid ---- */
.ps-payslip-matrix {
  background: var(--ps-bg-surface);
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-md);
  box-shadow: var(--ps-shadow-xs);
  padding: 14px 16px;
  overflow-x: auto;
}
.ps-payslip-matrix__row {
  display: grid;
  grid-template-columns: 72px repeat(12, minmax(36px, 1fr));
  align-items: center;
  gap: 6px;
  padding: 6px 0;
}
.ps-payslip-matrix__row--head {
  border-bottom: 1px solid var(--ps-border-soft);
  margin-bottom: 4px;
  padding-bottom: 8px;
}
.ps-payslip-matrix__year-head { /* spacer */ }
.ps-payslip-matrix__month-head {
  text-align: center;
  font-size: var(--ps-text-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ps-text-soft);
  text-transform: uppercase;
}
.ps-payslip-matrix__year {
  font-size: var(--ps-text-base);
  font-weight: 700;
  color: var(--ps-text);
}
.ps-payslip-matrix__cell {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 36px;
  border-radius: var(--ps-radius-md);
  font-weight: 600;
  text-decoration: none;
  transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease;
}
.ps-payslip-matrix__cell--filled {
  background: var(--ps-primary-50);
  color: var(--ps-primary-700);
  border: 1px solid rgba(37, 99, 235, 0.22);
}
.ps-payslip-matrix__cell--filled vaadin-icon {
  width: 14px;
  height: 14px;
}
.ps-payslip-matrix__cell--filled:hover {
  transform: translateY(-1px);
  box-shadow: var(--ps-shadow-sm);
  background: rgba(37, 99, 235, 0.14);
}
.ps-payslip-matrix__cell--empty {
  background: transparent;
  color: var(--ps-ink-faint);
  border: 1px dashed var(--ps-border);
  font-size: var(--ps-text-md);
  line-height: 1;
}

/* =====================================================
   Documents tab — description cell + details dialog
   ===================================================== */
.ps-doc-desc {
  display: inline;
  font-size: var(--ps-text-sm);
  color: var(--ps-text);
  line-height: 1.45;
}
.ps-doc-desc__more {
  margin-left: 2px !important;
  padding: 0 !important;
  height: auto !important;
  min-width: 0 !important;
  font-size: var(--ps-text-xs) !important;
  font-weight: 600 !important;
  color: var(--ps-primary-700) !important;
}
.ps-doc-details {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 4px 0 8px;
}
.ps-doc-details__desc {
  margin-top: 14px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.ps-doc-details__desc-text {
  font-size: var(--ps-text-base);
  color: var(--ps-text);
  background: var(--ps-surface-sunk);
  border: 1px solid var(--ps-border-soft);
  border-radius: var(--ps-radius-md);
  padding: 10px 12px;
  white-space: pre-wrap;
}

/* =====================================================
   Self-service clock-in card (Attendance tab)
   ===================================================== */
.ps-clock-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px 20px;
  background: var(--ps-bg-surface);
  border: 1px solid var(--ps-border);
  border-radius: var(--ps-radius-md);
  box-shadow: var(--ps-shadow-xs);
}
.ps-clock-card__title {
  font-size: var(--ps-text-md);
  font-weight: 700;
  color: var(--ps-text);
  letter-spacing: 0.01em;
}
.ps-clock-card__status {
  font-size: var(--ps-text-base);
  color: var(--ps-text-muted);
}
.ps-clock-card__actions {
  display: flex;
  gap: 8px;
  margin-top: 4px;
}

/* =====================================================
   Notifications (header bell + dropdown)
   ===================================================== */
.ps-notif-wrap {
  position: relative;
  display: inline-flex;
}
.ps-notif-badge {
  position: absolute;
  top: 2px;
  right: 2px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: var(--ps-radius-md);
  background: var(--ps-error);
  color: var(--ps-n-0);
  font-size: var(--ps-text-2xs);
  font-weight: 700;
  display: none;
  align-items: center;
  justify-content: center;
  pointer-events: none;
  box-shadow: 0 0 0 2px var(--ps-surface);
}
.ps-notif-menu vaadin-context-menu-overlay {
  --vaadin-context-menu-item-padding: 0;
}
.ps-notif-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 10px 12px;
  min-width: 320px;
  max-width: 360px;
  border-bottom: 1px solid var(--ps-border-soft);
}
.ps-notif-item--unread {
  background: var(--ps-primary-50);
}
.ps-notif-item__title {
  font-size: var(--ps-text-sm);
  font-weight: 600;
  color: var(--ps-text);
}
.ps-notif-item__msg {
  font-size: var(--ps-text-xs);
  color: var(--ps-text-muted);
  white-space: normal;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.ps-notif-item__time {
  font-size: var(--ps-text-2xs);
  color: var(--ps-text-soft);
}
.ps-notif-empty {
  padding: 18px 16px;
  font-size: var(--ps-text-sm);
  color: var(--ps-text-soft);
  text-align: center;
  min-width: 280px;
}
.ps-notif-mark-all {
  padding: 8px 12px;
  font-size: var(--ps-text-xs);
  font-weight: 600;
  color: var(--ps-primary-700);
  text-align: center;
}

/* =====================================================
   Scrollbars
   ===================================================== */
::-webkit-scrollbar { width: 8px; height: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--ps-border); border-radius: var(--ps-radius-md); }
::-webkit-scrollbar-thumb:hover { background: var(--ps-text-faint); }

/* =====================================================
   DASHBOARD — Editorial
   Rewritten in Phase 3 (SPEC-004, scope 3.4a). Everything
   here used to be getStyle().set(...) in DashboardView.java,
   which is why the old dashboard could not be restyled.
   ===================================================== */

/* ---- Welcome banner: a masthead, not a card ---- */
.ps-banner {
  flex: 0 0 auto;
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--ps-space-6);
  flex-wrap: wrap;
  padding: var(--ps-space-2) 0 var(--ps-space-6);
  border-bottom: 1px solid var(--ps-rule);
  margin-bottom: var(--ps-space-8);
}
.ps-banner__text { min-width: 0; flex: 1 1 auto; }
.ps-banner__greeting {
  font-family: var(--ps-font-display);
  font-variation-settings: 'opsz' 72;
  font-size: var(--ps-text-3xl);
  font-weight: 600;
  color: var(--ps-ink);
  letter-spacing: var(--ps-tracking-tight);
  line-height: var(--ps-leading-tight);
  margin: var(--ps-space-2) 0 var(--ps-space-1);
}
.ps-banner__subtitle {
  margin: 0;
  font-size: var(--ps-text-base);
  color: var(--ps-ink-soft);
}
/* A dateline, set like one — not a coloured chip. */
.ps-datestamp {
  font-size: var(--ps-text-sm);
  color: var(--ps-ink-faint);
  letter-spacing: var(--ps-tracking-wide);
  text-transform: uppercase;
  white-space: nowrap;
  padding-bottom: var(--ps-space-1);
}

/* ---- Stat row: four figures separated by rules, not four floating tiles ---- */
/* flex-shrink:0 matters: the parent is a full-height Vaadin flex column, and
   without it the row is compressed and overflow:hidden crops the hint line. */
.ps-stat-row {
  flex: 0 0 auto;
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  border: 1px solid var(--ps-rule);
  border-radius: var(--ps-radius-md);
  background: var(--ps-surface);
  overflow: hidden;
  margin-bottom: var(--ps-space-6);
}
.ps-stat {
  padding: var(--ps-space-5) var(--ps-space-6);
  border-left: 1px solid var(--ps-rule);
  display: flex;
  flex-direction: column;
  gap: var(--ps-space-1);
  min-width: 0;
}
.ps-stat:first-child { border-left: none; }
.ps-stat__label {
  font-size: var(--ps-text-2xs);
  font-weight: 600;
  letter-spacing: var(--ps-tracking-wide);
  text-transform: uppercase;
  color: var(--ps-ink-faint);
}
.ps-stat__hint {
  font-size: var(--ps-text-sm);
  color: var(--ps-ink-soft);
}

@media (max-width: 1100px) {
  .ps-stat-row { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ps-stat:nth-child(3) { border-left: none; }
  .ps-stat:nth-child(n + 3) { border-top: 1px solid var(--ps-rule); }
}
@media (max-width: 640px) {
  .ps-stat-row { grid-template-columns: 1fr; }
  .ps-stat { border-left: none; border-top: 1px solid var(--ps-rule); }
  .ps-stat:first-child { border-top: none; }
}

/* ---- Two-panel row ---- */
.ps-panel-row {
  flex: 0 1 auto;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
  gap: var(--ps-space-6);
  align-items: start;
}
.ps-panel { padding: 0; overflow: hidden; }
.ps-panel__head {
  display: flex;
  align-items: center;
  gap: var(--ps-space-3);
  padding: var(--ps-space-4) var(--ps-space-5);
  border-bottom: 1px solid var(--ps-rule);
}
.ps-panel__head h3 { margin: 0; font-size: var(--ps-text-lg); }
.ps-panel__body { padding: var(--ps-space-2) 0; }

/* ---- Quick actions: hairline-separated rows, neutral icons ---- */
.ps-action {
  display: flex;
  align-items: center;
  gap: var(--ps-space-3);
  padding: var(--ps-space-3) var(--ps-space-5);
  text-decoration: none;
  border-bottom: 1px solid var(--ps-rule-soft);
  transition: background-color 120ms ease;
}
.ps-action:last-child { border-bottom: none; }
.ps-action:hover { background: var(--ps-surface-sunk); }
.ps-action:focus-visible { outline: none; box-shadow: inset var(--ps-focus-ring); }
.ps-action__icon {
  width: 18px;
  height: 18px;
  color: var(--ps-ink-faint);
  flex-shrink: 0;
}
.ps-action:hover .ps-action__icon { color: var(--ps-accent); }
.ps-action__text { flex: 1; min-width: 0; }
.ps-action__title {
  display: block;
  font-size: var(--ps-text-base);
  font-weight: 500;
  color: var(--ps-ink);
}
.ps-action__desc {
  display: block;
  margin-top: 1px;
  font-size: var(--ps-text-xs);
  color: var(--ps-ink-soft);
}
.ps-action__arrow {
  width: 16px;
  height: 16px;
  color: var(--ps-ink-disabled);
  flex-shrink: 0;
}

/* ---- Recent activity: a dot carries the state, a rule separates the rows ---- */
.ps-activity { max-height: 420px; overflow-y: auto; }
.ps-activity__row {
  display: flex;
  align-items: center;
  gap: var(--ps-space-3);
  padding: var(--ps-space-3) var(--ps-space-5);
  border-bottom: 1px solid var(--ps-rule-soft);
}
.ps-activity__row:last-child { border-bottom: none; }
.ps-activity__dot {
  width: 6px;
  height: 6px;
  border-radius: var(--ps-radius-pill);
  flex-shrink: 0;
  background: var(--ps-ink-disabled);
}
.ps-activity__dot--success { background: var(--ps-success); }
.ps-activity__dot--info    { background: var(--ps-info); }
.ps-activity__dot--warning { background: var(--ps-warning); }
.ps-activity__dot--error   { background: var(--ps-error); }
.ps-activity__dot--neutral { background: var(--ps-ink-disabled); }
.ps-activity__text {
  flex: 1;
  min-width: 0;
  font-size: var(--ps-text-sm);
  color: var(--ps-ink-muted);
}
.ps-activity__time {
  font-size: var(--ps-text-xs);
  color: var(--ps-ink-faint);
  white-space: nowrap;
}

/* =====================================================
   DATA GRIDS — Editorial
   The dense screens are where this direction earns or
   loses its keep, so the rules here are deliberate:
   hairline row separation, a quiet uppercase header,
   tabular figures, and no zebra striping.
   ===================================================== */
vaadin-grid {
  font-family: var(--ps-font-ui);
  font-size: var(--ps-text-base);
  --lumo-font-size-m: var(--ps-text-base);
  background: transparent;
  border: none;
}
vaadin-grid::part(header-cell) {
  background: var(--ps-surface);
  border-bottom: 1px solid var(--ps-rule-strong);
  font-size: var(--ps-text-2xs);
  font-weight: 600;
  letter-spacing: var(--ps-tracking-wide);
  text-transform: uppercase;
  color: var(--ps-ink-faint);
}
vaadin-grid::part(body-cell) {
  border-bottom: 1px solid var(--ps-rule-soft);
  color: var(--ps-ink-muted);
}
vaadin-grid::part(row):hover > [part~="body-cell"] {
  background: var(--ps-surface-sunk);
}
/* Every figure in a grid lines up. */
vaadin-grid-cell-content { font-variant-numeric: tabular-nums lining-nums; }

/* ---- Person cell: avatar + name + code ---- */
.ps-person {
  display: flex;
  align-items: center;
  gap: var(--ps-space-3);
  cursor: pointer;
  min-width: 0;
}
.ps-person__avatar {
  --vaadin-avatar-size: 30px;
  border: 1px solid var(--ps-rule);
  background: var(--ps-surface-sunk);
  color: var(--ps-ink-soft);
  font-family: var(--ps-font-ui);
  font-size: var(--ps-text-2xs);
  font-weight: 600;
  flex-shrink: 0;
}
.ps-person__name {
  display: block;
  font-size: var(--ps-text-base);
  font-weight: 500;
  color: var(--ps-ink);
}
.ps-person:hover .ps-person__name { color: var(--ps-accent); }
.ps-person__code {
  display: block;
  font-size: var(--ps-text-xs);
  color: var(--ps-ink-faint);
  font-variant-numeric: tabular-nums lining-nums;
}

/* ---- Row action icons: neutral until hovered ---- */
.ps-icon-action { color: var(--ps-ink-faint); }
.ps-icon-action:hover { color: var(--ps-accent); background: var(--ps-surface-sunk); }
.ps-icon-action--danger:hover { color: var(--ps-error); }

/* An anchor wrapping a button should not look like a link. */
.ps-bare-link { text-decoration: none; }

/* Topbar avatar: matches the directory's person cell rather than Vaadin's
   saturated default palette. */
.ps-topbar-user__avatar {
  --vaadin-avatar-size: 30px;
  border: 1px solid var(--ps-rule);
  background: var(--ps-surface-sunk);
  color: var(--ps-ink-soft);
  font-family: var(--ps-font-ui);
  font-size: var(--ps-text-2xs);
  font-weight: 600;
}

/* =====================================================
   LAYOUT PRIMITIVES
   Phase 3 scope 3.2: these replace the 97 getStyle().set(...)
   calls that used to live in the Vaadin views. Every visual
   decision is now reachable from this file.
   ===================================================== */
.ps-spacer { flex: 1; }
.ps-muted  { color: var(--ps-ink-soft); }

.ps-flags {
  display: flex;
  gap: var(--ps-space-1);
  align-items: center;
}

/* A tab's own toolbar: one action, pushed right. */
.ps-tab-toolbar {
  display: flex;
  justify-content: flex-end;
  margin-bottom: var(--ps-space-4);
}

/* Filter controls sitting on their baselines, above a grid. */
.ps-filter-bar {
  display: flex;
  gap: var(--ps-space-3);
  align-items: flex-end;
  flex-wrap: wrap;
  margin-bottom: var(--ps-space-4);
}

.ps-form-actions {
  display: flex;
  gap: var(--ps-space-2);
  align-items: center;
  flex-wrap: wrap;
}

.ps-settings-grid {
  display: grid;
  gap: var(--ps-space-4);
}

/* ---- Dialogs ---- */
.ps-dialog-body {
  display: flex;
  flex-direction: column;
  gap: var(--ps-space-3);
}
/* min() rather than a fixed width: the same rule serves the phone widths in 3.5. */
.ps-dialog-form { min-width: min(480px, 82vw); }

/* A control living inside a grid cell. Sized down and stripped of its own chrome so a column of
   them reads as a column, not as a row of boxes competing with the data. */
.ps-inline-select {
  --lumo-text-field-size: var(--ps-space-8);
  font-size: var(--ps-text-sm);
  max-width: 9.5rem;
}

/* A dialog that reports a consequence rather than asking a neutral question -- approving leave
   the employee has not got, for instance. Warning tokens, not error: the action is allowed. */
.ps-dialog-warning {
  font-size: var(--ps-text-sm);
  background: var(--ps-warning-bg);
  color: var(--ps-warning-text);
  border-left: 2px solid var(--ps-warning);
  padding: var(--ps-space-3);
  margin: 0;
}
.ps-dialog-hint {
  font-size: var(--ps-text-sm);
  color: var(--ps-ink-soft);
}

/* ---- Row action icons ---- */
.ps-icon-action--success:hover { color: var(--ps-success); }
.ps-icon-action--warning:hover { color: var(--ps-warning); }
.ps-icon-action--muted { color: var(--ps-ink-disabled); }
.ps-icon-action--muted:hover { color: var(--ps-ink-soft); }

/* ---- Notes list ---- */
.ps-note-list {
  display: flex;
  flex-direction: column;
  gap: var(--ps-space-3);
}

/* ---- Profile tab labels ---- */
.ps-tab-label {
  display: flex;
  align-items: center;
  gap: var(--ps-space-2);
}

/* ---- Empty state ---- */
.ps-empty-state__title {
  font-weight: 600;
  color: var(--ps-ink);
}

/* ---- Company screen cards ---- */
.ps-settings-card {
  padding: var(--ps-space-6);
  margin-bottom: var(--ps-space-4);
  /* These cards also carry .ps-grid-card, which is sized for a grid that scrolls inside
     itself: flex:1 1 auto, min-height:0, overflow:hidden. A form has no internal scroller,
     so those rules shrink it to the leftover flex space and crop it mid-field instead of
     letting the page scroll. Here the content decides the height, which also settles it for
     WorkDayTemplateCard: it carries both classes, and its grid keeps the 380px floor below.
     overflow:hidden stays — a box that never shrinks has nothing to clip, and it is what
     rounds the grid's corners. */
  flex: 0 0 auto;
  min-height: auto;
}
.ps-settings-card__title { margin: 0 0 var(--ps-space-1); }
.ps-settings-card__desc {
  margin: 0 0 var(--ps-space-4);
  color: var(--ps-ink-soft);
  font-size: var(--ps-text-base);
}

/* ---- Chrome ---- */
.ps-topbar-actions {
  display: flex;
  align-items: center;
  gap: var(--ps-space-2);
}
.ps-topbar-user__text {
  display: flex;
  flex-direction: column;
  line-height: var(--ps-leading-tight);
}
.ps-side-logo__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.ps-side-nav-wrapper { padding: var(--ps-space-2) 0 var(--ps-space-4); }
.ps-page-header__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.ps-notif-badge--visible { display: flex; }
.ps-topbar-user { cursor: pointer; }
/* The pill is the button's whole content, so the button contributes nothing of its own. */
.ps-topbar-user-menu vaadin-menu-bar-button {
  padding: 0;
  margin: 0;
  min-width: 0;
  height: auto;
  background: transparent;
  border-radius: var(--ps-radius-md);
}

/* =====================================================
   DENSITY (SPEC-004 scope 3.5)
   A token override, nothing more. Because every component
   sizes itself from --ps-space-* and the type scale, compact
   mode is ~20 declarations rather than a parallel stylesheet.
   ===================================================== */
html[density="compact"] {
  --ps-space-1:  2px;
  --ps-space-2:  6px;
  --ps-space-3:  8px;
  --ps-space-4:  12px;
  --ps-space-5:  14px;
  --ps-space-6:  16px;
  --ps-space-8:  22px;
  --ps-space-10: 28px;
  --ps-space-12: 34px;
  --ps-space-16: 44px;

  --lumo-button-size: 30px;
}
html[density="compact"] vaadin-button { height: 30px; min-height: 30px; }
html[density="compact"] vaadin-grid { --lumo-size-m: 30px; }
html[density="compact"] .ps-stat-value { font-size: var(--ps-text-2xl); }
html[density="compact"] .ps-banner__greeting { font-size: var(--ps-text-2xl); }

/* =====================================================
   RESPONSIVE (SPEC-004 scope 3.5)
   The drawer is Vaadin's job; what needs handling here is
   everything that assumes a wide viewport — toolbars laid
   out in a row, dialogs with a fixed min-width, and grids
   that must scroll rather than squash.
   ===================================================== */
@media (max-width: 1024px) {
  .ps-page { padding: var(--ps-space-4); }
  .ps-panel-row { grid-template-columns: 1fr; }
  .ps-banner { align-items: flex-start; flex-direction: column; gap: var(--ps-space-2); }
  .ps-datestamp { padding-bottom: 0; }
}

@media (max-width: 768px) {
  .ps-page-header { flex-direction: column; align-items: flex-start; }
  .ps-page-header__actions { width: 100%; }

  /* Filters stack and fill the width rather than overflowing sideways. */
  .ps-toolbar,
  .ps-filter-bar { flex-direction: column; align-items: stretch; }
  .ps-toolbar__left,
  .ps-toolbar__right { width: 100%; flex-wrap: wrap; }
  .ps-stat-pill { border-left: none; padding-left: 0; }

  /* A dense grid scrolls horizontally; squashing the columns makes it unreadable. */
  .ps-grid-card { overflow-x: auto; }
  vaadin-grid { min-width: 720px; }

  .ps-info-grid { grid-template-columns: 1fr; }

  /* Dialogs go near-full-width; a 480px min-width does not fit a phone. */
  .ps-dialog-form { min-width: auto; width: 100%; }
  vaadin-dialog-overlay::part(overlay) {
    width: min(96vw, 560px);
    max-width: 96vw;
  }

  .ps-banner__greeting { font-size: var(--ps-text-2xl); }
  .ps-page-header__title { font-size: var(--ps-text-xl); }
}

@media (max-width: 480px) {
  .ps-page { padding: var(--ps-space-3); }
  .ps-banner__greeting { font-size: var(--ps-text-xl); }
  .ps-stat-value { font-size: var(--ps-text-2xl); }
  .ps-payslip-matrix { overflow-x: auto; }
}

/* =====================================================
   FOCUS & MOTION (SPEC-004 scope 3.6)
   Every interactive element shows where the keyboard is.
   :focus-visible rather than :focus so a mouse click does
   not leave a ring behind.
   ===================================================== */
a:focus-visible,
vaadin-button:focus-visible,
vaadin-side-nav-item:focus-visible,
.ps-person:focus-visible,
.ps-action:focus-visible,
[tabindex]:focus-visible {
  outline: none;
  box-shadow: var(--ps-focus-ring);
  border-radius: var(--ps-radius-sm);
}

vaadin-text-field:focus-within,
vaadin-email-field:focus-within,
vaadin-password-field:focus-within,
vaadin-number-field:focus-within,
vaadin-integer-field:focus-within,
vaadin-text-area:focus-within,
vaadin-combo-box:focus-within,
vaadin-multi-select-combo-box:focus-within,
vaadin-date-picker:focus-within,
vaadin-time-picker:focus-within {
  --vaadin-input-field-border-color: var(--ps-accent);
}

/* =====================================================
   Contacts & addresses (Employee profile)
   SPEC-008. Two stacked lists in one pane: contacts, then
   addresses with the superseded ones folded away.
   ===================================================== */
.ps-contact-block { margin-bottom: var(--ps-space-4); }
.ps-contact-block:last-child { margin-bottom: 0; }

/* The head carries an action, so the title has to give up the slack. */
.ps-contact-block .ps-section__head h3 { flex: 1 1 auto; }
.ps-contact-block .ps-section__head .ps-tab-toolbar { margin-bottom: 0; }

.ps-address-card { gap: var(--ps-space-2); }

/* Superseded, not deleted: legible, and visibly no longer in force. */
.ps-address-card--previous {
  background: transparent;
  border-style: dashed;
}
.ps-address-card--previous .ps-note-card__body { color: var(--ps-ink-soft); }

/* The history disclosure. Closed by default, so a long tenancy history does not
   bury the address that actually matters. */
.ps-disclosure {
  margin-top: var(--ps-space-2);
}
.ps-disclosure::part(summary) {
  font-size: var(--ps-text-sm);
  font-weight: 600;
  color: var(--ps-ink-soft);
  padding: var(--ps-space-2) 0;
}
.ps-disclosure::part(content) {
  padding: var(--ps-space-2) 0 0;
}

/* A section that explains where a thing moved to, instead of holding fields. */
.ps-section--note .ps-section__body { padding-top: var(--ps-space-4); }
.ps-section__note {
  display: block;
  max-width: 68ch;
  font-size: var(--ps-text-base);
  color: var(--ps-ink-soft);
  line-height: var(--ps-leading-body);
}

/* =====================================================
   Timesheets (SPEC-009)
   One day at a time: a nav strip, the day's totals against
   the clock, then the spans themselves.
   ===================================================== */
.ps-timesheet-day {
  display: flex;
  flex-direction: column;
  gap: var(--ps-space-4);
  padding: var(--ps-space-2) 0;
}

.ps-timesheet-day__nav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ps-space-3);
  padding-bottom: var(--ps-space-3);
  border-bottom: 1px solid var(--ps-rule);
}
.ps-timesheet-day__title {
  font-family: var(--ps-font-display);
  font-size: var(--ps-text-lg);
  font-weight: 600;
  color: var(--ps-ink);
}

/* Booked / clocked / scheduled side by side. The point of the row is the
   comparison, so they share one baseline and one type scale. */
.ps-timesheet-totals {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: var(--ps-space-3);
}
.ps-timesheet-totals .ps-dialog-warning { grid-column: 1 / -1; }

.ps-timesheet-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--ps-space-3);
  background: var(--ps-bg-subtle);
  border: 1px solid var(--ps-border-soft);
  border-radius: var(--ps-radius-md);
}
.ps-timesheet-stat__label {
  font-size: var(--ps-text-2xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ps-text-soft);
}
.ps-timesheet-stat__value {
  font-family: var(--ps-font-mono);
  font-size: var(--ps-text-lg);
  color: var(--ps-ink);
}

.ps-timesheet-note { padding-top: var(--ps-space-2); }

/* =====================================================
   Performance reviews (SPEC-010)
   A rating is the only kind of question this form asks,
   so the star row is the component that has to carry the
   design. Ink blue rather than gold: this is an editorial
   palette and a row of yellow stars belongs to a different
   product.
   ===================================================== */
.ps-stars {
  display: inline-flex;
  align-items: center;
  gap: 1px;
}

/* The interactive row. Vaadin sizes an icon button for a toolbar; a rating
   wants them tighter than that, and closer to the label they belong to. */
.ps-stars__star {
  min-width: 0;
  width: 28px;
  height: 28px;
  padding: 0;
  margin: 0;
  color: var(--ps-ink-faint);
}
.ps-stars__star vaadin-icon,
.ps-stars__star iron-icon {
  width: 18px;
  height: 18px;
  fill: currentColor;
}
.ps-stars__star:hover { color: var(--ps-accent-hover); }
.ps-stars__star--on { color: var(--ps-accent); }
.ps-stars__star[disabled] { opacity: 1; }
.ps-stars__star[disabled].ps-stars__star--on { color: var(--ps-accent); }

/* The read-only mark, for grids and cards. Not a button: nothing here is
   clickable, and rendering it as one invites a click that does nothing. */
.ps-stars--static { gap: 2px; }
.ps-stars__mark { display: inline-flex; }
.ps-stars__mark vaadin-icon {
  width: 14px;
  height: 14px;
  fill: currentColor;
}
.ps-stars__mark--on { color: var(--ps-accent); }
.ps-stars__mark--off { color: var(--ps-ink-faint); }
.ps-stars--empty {
  font-size: var(--ps-text-sm);
  color: var(--ps-ink-soft);
}

/* ---------------------------------------------------- the form itself */
.ps-review-form {
  display: flex;
  flex-direction: column;
  gap: var(--ps-space-5);
  padding: var(--ps-space-2) 0;
}

.ps-review-form__status {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--ps-space-3);
  padding-bottom: var(--ps-space-3);
  border-bottom: 1px solid var(--ps-rule);
}
.ps-review-form__meta {
  font-size: var(--ps-text-sm);
  color: var(--ps-ink-soft);
}

.ps-review-form__section {
  display: flex;
  flex-direction: column;
  gap: var(--ps-space-3);
}
.ps-review-form__heading {
  font-family: var(--ps-font-display);
  font-size: var(--ps-text-lg);
  font-weight: 600;
  color: var(--ps-ink);
}

/* One competency per row: what is being judged, the judgement, the caveat.
   Three columns on a desktop so the stars line up down the page and the row
   can be read as a column of scores rather than five separate questions. */
.ps-review-form__competency {
  display: grid;
  grid-template-columns: minmax(180px, 1.4fr) auto minmax(160px, 1fr);
  align-items: center;
  gap: var(--ps-space-3);
  padding: var(--ps-space-3) 0;
  border-bottom: 1px solid var(--ps-rule-soft);
}
.ps-review-form__competency:last-child { border-bottom: 0; }

.ps-review-form__competency-label {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ps-review-form__competency-name {
  font-weight: 600;
  color: var(--ps-ink);
}
.ps-review-form__competency-hint {
  font-size: var(--ps-text-sm);
  color: var(--ps-ink-soft);
  line-height: var(--ps-leading-snug);
}
.ps-review-form__competency-comment { width: 100%; }

/* The overall score sits apart from the competencies -- it is a summary of
   them, not another one of them. */
.ps-review-form__overall {
  flex-direction: row;
  align-items: center;
  justify-content: space-between;
  gap: var(--ps-space-4);
  padding: var(--ps-space-4);
  background: var(--ps-bg-subtle);
  border: 1px solid var(--ps-border-soft);
  border-radius: var(--ps-radius-md);
}

.ps-review-form__narrative { gap: var(--ps-space-4); }

/* ---------------------------------------------------- review detail */
.ps-review-card__rating {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ps-space-3);
  padding-top: var(--ps-space-2);
  font-size: var(--ps-text-sm);
  color: var(--ps-ink-soft);
}

.ps-competency-page {
  display: flex;
  flex-direction: column;
  gap: var(--ps-space-4);
}

@media (max-width: 720px) {
  /* Stacked, because a three-column grid at this width puts one word per line
     in the label and hides the stars off the right edge. */
  .ps-review-form__competency {
    grid-template-columns: 1fr;
    align-items: flex-start;
  }
  .ps-review-form__overall {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* Respect the OS setting. Nothing here is load-bearing animation. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Printing an HR record should not print the app chrome around it. */
@media print {
  vaadin-app-layout::part(drawer),
  vaadin-app-layout::part(navbar),
  .ps-page-header__actions,
  .ps-toolbar { display: none !important; }
  .ps-page { padding: 0; }
  .ps-section, .ps-grid-card, .ps-card { border-color: var(--ps-n-400); }
}


/* =========================================================
   Platform: working inside another company
   ---------------------------------------------------------
   A platform administrator with a company open sees that
   company's real data on every screen. The failure mode of
   every "act as" feature is forgetting you are still in it,
   so this sits in the chrome, spans the full width, and
   carries its own way out.
   ========================================================= */
.ps-impersonation {
    display: flex;
    align-items: center;
    gap: var(--lumo-space-s);
    width: 100%;
    padding: var(--lumo-space-xs) var(--lumo-space-m);
    background: var(--ps-accent);
    color: var(--ps-on-accent);
    font-size: var(--lumo-font-size-s);
    font-weight: 500;
}

.ps-impersonation vaadin-icon {
    width: 16px;
    height: 16px;
    flex: none;
}

.ps-impersonation__label {
    flex: 1;
}

.ps-impersonation vaadin-button {
    color: var(--ps-on-accent);
    text-decoration: underline;
}
