/*
 * KD-Plant — Copyright (c) 2026 Kevin Dennis. All rights reserved.
 * Proprietary and confidential. Not open source. See LICENSE.
 */
/* =====================================================================
   The shared components.

   Everything here is built from the tokens in tokens.css and nothing here
   contains a colour of its own, so both themes come out of one definition
   rather than two. Where a component already existed and was good - the
   table in interaction.css, the popup in modal.css, the empty state in
   empty.css - it stays where it is and is not copied here.

   These are additive. No existing class is removed or overridden, so a
   page can move onto them one control at a time.
   ===================================================================== */

/* ---- Focus ---------------------------------------------------------------
   Twelve classes in the whole product declared a focus style. Everything
   else - the header's bell and theme buttons, every tab, every row action,
   the plant switcher - showed the browser default or, where a rule had
   removed outlines, nothing at all. Keyboard use was possible and invisible.

   :where() keeps the specificity at zero, so any page that wants its own
   focus treatment still wins without !important. */
:where(a[href], button, [role="button"], [role="tab"], [role="menuitem"],
       [role="option"], summary, [tabindex]:not([tabindex="-1"])):focus-visible {
  outline: 2px solid var(--db-focus, #4f46e5);
  outline-offset: 2px;
  border-radius: var(--db-r-sm, 6px);
}

/* ---- Badge ---------------------------------------------------------------
   One chip for every status, priority, machine state and stock level. These
   were drawn per page: the work order list, the request queue, the parts
   table, the asset register and the dispatch board each had their own, at
   their own size, with their own hand-picked colours.

   Colour is never the only channel. Every badge carries a word, and the
   ones that mean "something is wrong" carry a mark as well, so they survive
   a monochrome print, a sun-struck screen and colour blindness. */
.kd-badge {
  display: inline-flex; align-items: center; gap: .3em;
  font-size: var(--db-fs-cap, .68rem); font-weight: 700;
  letter-spacing: .02em; line-height: 1.35;
  padding: .2em .55em; border-radius: var(--db-r-sm, 6px);
  white-space: nowrap;
  background: var(--db-surface-3, #f1f5f9); color: var(--db-text-2, #475569);
  /* The edge is the chip's own text colour, thinned. A pale tint alone -
     amber-50 on white - is nearly invisible on a light row, and deepening
     every tint instead would also darken the warning panels that share those
     tokens. Taking the border from currentColor gives each chip a defined
     edge in its own hue without touching anything else. */
  border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
}
.kd-badge::before { font-weight: 800; }
.kd-badge.is-sm { font-size: .62rem; padding: .15em .45em; }

/* Lifecycle: what state the record is in. Deliberately not the validation
   colours - a completed job is not a "success" and a cancelled one is not
   a "danger", and reusing those made a closed work order read as an alarm. */
.kd-badge[data-life="open"] { background: var(--db-life-open-soft); color: var(--db-life-open); }
.kd-badge[data-life="in_progress"] { background: var(--db-life-progress-soft); color: var(--db-life-progress); }
.kd-badge[data-life="on_hold"] { background: var(--db-life-hold-soft); color: var(--db-life-hold); }
.kd-badge[data-life="on_hold"]::before { content: "\23F8"; }
.kd-badge[data-life="completed"] { background: var(--db-life-done-soft); color: var(--db-life-done); }
.kd-badge[data-life="completed"]::before { content: "\2713"; }
.kd-badge[data-life="cancelled"] { background: var(--db-life-cancelled-soft); color: var(--db-life-cancelled); }
.kd-badge[data-life="cancelled"]::before { content: "\2717"; }

/* Severity: how much attention this needs now. */
.kd-badge[data-tone="good"] { background: var(--db-good-soft); color: var(--db-good-fg); }
.kd-badge[data-tone="warn"] { background: var(--db-warn-soft); color: var(--db-warn-fg); }
.kd-badge[data-tone="warn"]::before { content: "\25B2"; }
.kd-badge[data-tone="bad"] { background: var(--db-bad-soft); color: var(--db-bad-fg); }
.kd-badge[data-tone="bad"]::before { content: "\25CF"; }
.kd-badge[data-tone="accent"] { background: var(--db-accent-soft); color: var(--db-accent-fg); }

/* Priority: how much attention this needs relative to everything else. It is
   its own scale, not the severity one - a low-priority job is not "good" and
   a medium one is not a warning - and it is ordered, so the four steps run
   from quiet to loud rather than being four unrelated colours. */
.kd-badge[data-prio="low"] { background: var(--db-surface-3); color: var(--db-text-2); }
.kd-badge[data-prio="medium"] { background: var(--db-accent-soft); color: var(--db-accent-fg); }
.kd-badge[data-prio="high"] { background: var(--db-warn-soft); color: var(--db-warn-fg); }
.kd-badge[data-prio="high"]::before { content: "\25B2"; }
.kd-badge[data-prio="urgent"] { background: var(--db-bad-soft); color: var(--db-bad-fg); }
.kd-badge[data-prio="urgent"]::before { content: "\25CF"; }
/* A fifth step the data carries and this scale did not name. 110 jobs are
   marked critical, and with no rule for them they printed as the flat grey
   chip a LOW priority job gets - the loudest thing in the plant drawn as the
   quietest. It shares urgent's pairing, which is the only red that clears
   4.5:1 in both themes (white on --db-bad measures 3.7:1 in light and 2.7:1
   in dark), and is told apart from it by a full-strength edge and its own
   mark - so the difference survives a monochrome print as well as a glance. */
.kd-badge[data-prio="critical"] { background: var(--db-bad-soft); color: var(--db-bad-fg); border-color: currentColor; }
.kd-badge[data-prio="critical"]::before { content: "\203C"; }

/* A badge that is also the control that changes the thing it names. The work
   order list ships 786 of these instead of 786 form controls; clicking one
   swaps a select in place. It has to look clickable and take a keyboard. */
.kd-badge.is-btn { cursor: pointer; }
.kd-badge.is-btn:hover { border-color: color-mix(in srgb, currentColor 65%, transparent); }
.kd-badge.is-btn:focus-visible { outline: 2px solid var(--db-focus); outline-offset: 2px; }

/* ---- Segmented control ---------------------------------------------------
   List / Calendar / Templates, Table / Hierarchy / Cards, 7d / 14d / 30d,
   Month / Week / Day. Eleven families of this existed, four of which looked
   different from one another on pages a planner uses in the same minute. */
.kd-seg {
  display: inline-flex; align-items: stretch;
  background: var(--db-surface-3, #f1f5f9);
  border: 1px solid var(--db-border, #e9edf3);
  border-radius: var(--db-r, 10px);
  padding: 2px; gap: 2px;
}
.kd-seg > * {
  display: inline-flex; align-items: center; gap: .35rem;
  padding: 0 .7rem; min-height: calc(var(--db-ctl-h, 32px) - 6px);
  font-size: var(--db-fs-sm, .78rem); font-weight: 600; line-height: 1;
  color: var(--db-text-2, #475569); text-decoration: none;
  background: transparent; border: 0; border-radius: calc(var(--db-r, 10px) - 3px);
  cursor: pointer; white-space: nowrap;
  transition: background var(--db-dur-fast, 120ms), color var(--db-dur-fast, 120ms);
}
.kd-seg > *:hover { color: var(--db-text, #0f172a); }
.kd-seg > .is-on, .kd-seg > [aria-selected="true"], .kd-seg > [aria-pressed="true"] {
  background: var(--db-surface, #fff); color: var(--db-text, #0f172a);
  box-shadow: var(--db-shadow, 0 1px 2px rgba(15, 23, 42, .06));
}

/* ---- Count bar -----------------------------------------------------------
   The row of numbers above a list, where every number is also the filter that
   shows you those records.

   It was six cards, each with a label, a 1.5rem number and a line of
   explanation stacked inside it, and the block was 100px tall before a single
   row of work appeared. On the work order list that was one of four bands of
   chrome above the table: 305px on a 900px screen, so nine of 197 jobs were
   visible. The explanation lines said things the label already said - "Open /
   not yet complete", "Overdue / past their due date".

   Laid on one line the same six numbers take 44px and read as one control
   rather than six cards, which is what they are: a filter with the count
   built in. Nothing is lost but the restating. */
.kd-countbar {
  display: flex; align-items: stretch; gap: 2px;
  padding: 3px; margin-bottom: var(--db-sp-3, 12px);
  background: var(--db-surface-3, #f1f5f9);
  border: 1px solid var(--db-border, #e9edf3);
  border-radius: var(--db-r, 10px);
  overflow-x: auto; scrollbar-width: none;
}
.kd-countbar::-webkit-scrollbar { display: none; }
.kd-countbar > * {
  display: inline-flex; align-items: baseline; gap: .4rem;
  padding: .3rem .7rem; border-radius: calc(var(--db-r, 10px) - 3px);
  color: var(--db-text-2, #475569); text-decoration: none;
  white-space: nowrap; border: 0; background: transparent; cursor: pointer;
  transition: background var(--db-dur-fast, 120ms), color var(--db-dur-fast, 120ms);
}
.kd-countbar > *:hover { background: var(--db-surface, #fff); color: var(--db-text, #0f172a); }
.kd-countbar > .is-on {
  background: var(--db-surface, #fff); color: var(--db-text, #0f172a);
  box-shadow: var(--db-shadow, 0 1px 2px rgba(15, 23, 42, .06));
}
.kd-cb-l { font-size: var(--db-fs-sm, .78rem); font-weight: 600; }
.kd-cb-v {
  font-size: var(--db-fs-lg, 1rem); font-weight: 800; line-height: 1;
  letter-spacing: -.01em; font-variant-numeric: tabular-nums;
  color: var(--db-text, #0f172a);
}
/* A count that means something is wrong keeps its colour when it is not the
   one selected, because that is the one the eye is meant to find. */
.kd-countbar > [data-tone="bad"] .kd-cb-v { color: var(--db-bad-fg); }
.kd-countbar > [data-tone="warn"] .kd-cb-v { color: var(--db-warn-fg); }
.kd-countbar > [data-tone] .kd-cb-v[data-zero="1"] { color: var(--db-text-2); font-weight: 600; }

/* ---- Page tabs -----------------------------------------------------------
   The underline tabs on a record. Seven families existed; this one is the
   asset page's, which was the cleanest, with the job page's count bubble. */
.kd-tabs2 {
  display: flex; align-items: stretch; gap: .25rem;
  border-bottom: 1px solid var(--db-border, #e9edf3);
  overflow-x: auto; scrollbar-width: none;
}
.kd-tabs2::-webkit-scrollbar { display: none; }
.kd-tabs2 > * {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .6rem .75rem; margin-bottom: -1px;
  font-size: var(--db-fs-md, .86rem); font-weight: 600; line-height: 1.2;
  color: var(--db-text-2, #475569); text-decoration: none;
  background: transparent; border: 0; border-bottom: 2px solid transparent;
  cursor: pointer; white-space: nowrap;
  transition: color var(--db-dur-fast, 120ms), border-color var(--db-dur-fast, 120ms);
}
.kd-tabs2 > *:hover { color: var(--db-text, #0f172a); }
.kd-tabs2 > .is-on, .kd-tabs2 > [aria-selected="true"] {
  color: var(--db-accent-fg, #4f46e5); border-bottom-color: var(--db-accent, #4f46e5);
}
.kd-tabs2 .kd-tab-n {
  font-size: .68rem; font-weight: 700; font-variant-numeric: tabular-nums;
  padding: .05rem .35rem; border-radius: var(--db-r-pill, 999px);
  background: var(--db-surface-3, #f1f5f9); color: var(--db-text-2, #475569);
}
.kd-tabs2 > .is-on .kd-tab-n { background: var(--db-accent-soft); color: var(--db-accent-fg); }

/* ---- Stat tile -----------------------------------------------------------
   The row of numbers above a list. Six markups existed at six different
   value sizes, and half put the label under the number and half above it.
   `.kd-kpi` in interaction.css is the same idea and is left alone; this is
   the version that can also be a filter, which is what most of them are. */
.kd-stats {
  display: grid; gap: var(--db-sp-3, 12px);
  grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
}
.kd-stat {
  display: flex; flex-direction: column; gap: .15rem;
  padding: var(--db-sp-3, 12px) var(--db-sp-4, 16px);
  background: var(--db-surface, #fff);
  border: 1px solid var(--db-border, #e9edf3);
  border-radius: var(--db-r, 10px);
  text-decoration: none; text-align: left; min-width: 0;
}
.kd-stat-l {
  font-size: var(--db-fs-cap, .68rem); font-weight: 700;
  letter-spacing: var(--db-track-cap, .07em); text-transform: uppercase;
  color: var(--db-text-3, #64748b);
}
.kd-stat-v {
  font-size: var(--db-fs-2xl, 1.9rem); font-weight: 700; line-height: 1.05;
  letter-spacing: -.02em; color: var(--db-text, #0f172a);
  font-variant-numeric: tabular-nums;
}
.kd-stat-s { font-size: var(--db-fs-sm, .78rem); color: var(--db-text-3, #64748b); line-height: 1.35; }
.kd-stat[data-tone="warn"] .kd-stat-v { color: var(--db-warn-fg); }
.kd-stat[data-tone="bad"] .kd-stat-v { color: var(--db-bad-fg); }
.kd-stat[data-tone="good"] .kd-stat-v { color: var(--db-good-fg); }

/* A tile that is also a filter says so: it is clickable, and it shows when
   it is the one being applied. */
a.kd-stat, button.kd-stat { cursor: pointer; transition: border-color var(--db-dur-fast, 120ms), box-shadow var(--db-dur-fast, 120ms); }
a.kd-stat:hover, button.kd-stat:hover { border-color: var(--db-border-strong, #cbd5e1); }
.kd-stat.is-on, .kd-stat[aria-pressed="true"] {
  border-color: var(--db-accent, #4f46e5);
  box-shadow: 0 0 0 1px var(--db-accent, #4f46e5);
}

/* ---- Skeleton ------------------------------------------------------------
   What a card shows while its data is on the way. The dashboard's widgets,
   the work order side panel, the search results and the asset context rail
   all fetched after paint and showed empty space until the answer arrived. */
.kd-skel {
  background: linear-gradient(90deg,
    var(--db-surface-3, #f1f5f9) 25%,
    var(--db-surface-2, #f8fafc) 37%,
    var(--db-surface-3, #f1f5f9) 63%);
  background-size: 400% 100%;
  border-radius: var(--db-r-sm, 6px);
  animation: kd-skel 1.4s ease infinite;
}
.kd-skel-line { height: .7rem; margin-bottom: .5rem; }
.kd-skel-line:nth-child(3n) { width: 70%; }
.kd-skel-line:nth-child(3n + 2) { width: 88%; }
@keyframes kd-skel { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
@media (prefers-reduced-motion: reduce) { .kd-skel { animation: none; } }

/* ---- Dark mode for the tints assembled at runtime -------------------------
   tailwind.config.js safelists bg-<hue>-50/100 and text-<hue>-500/600/700
   because the report cards and the asset categories build those class names
   from data, so the content scan cannot see them. dashboard.css remaps the
   neutral utilities for dark mode but never these, so every pastel tint -
   the report icons, the safety cards on a job, the PPE chips - stayed a
   light-mode pastel on a dark page. */
html[data-theme="dark"] :is(
  .bg-violet-50, .bg-amber-50, .bg-emerald-50, .bg-indigo-50, .bg-rose-50,
  .bg-orange-50, .bg-teal-50, .bg-blue-50, .bg-sky-50, .bg-lime-50,
  .bg-red-50, .bg-purple-50, .bg-fuchsia-50, .bg-cyan-50, .bg-green-50, .bg-yellow-50) {
  background-color: var(--db-surface-3);
}
html[data-theme="dark"] :is(
  .bg-violet-100, .bg-amber-100, .bg-emerald-100, .bg-indigo-100, .bg-rose-100,
  .bg-orange-100, .bg-teal-100, .bg-blue-100, .bg-sky-100, .bg-lime-100,
  .bg-red-100, .bg-purple-100, .bg-fuchsia-100, .bg-cyan-100, .bg-green-100, .bg-yellow-100) {
  background-color: color-mix(in srgb, var(--db-surface-3) 70%, var(--db-surface));
}
/* The text keeps its hue - that is the whole point of a tint - but takes it
   from the token rather than from Tailwind's fixed scale, so it reads on
   whichever ground the theme puts behind it.

   This used to be a dark-mode-only patch, which fixed the wrong half. On a
   dark page these utilities were merely dull; on a white page text-emerald-600
   measures 3.8:1 and text-rose-600 4.3:1, both under what body text needs,
   and light is the default theme. The -fg tokens read in both, so one rule
   now does what two half-rules did.

   The sidebar and the drawer are excluded. They are dark navy in both themes,
   so a colour chosen for the page's ground is wrong on them. */
.text-rose-500,
.text-rose-600,
.text-rose-700,
.text-rose-800,
.text-rose-900,
.text-rose-950,
.text-red-500,
.text-red-600,
.text-red-700,
.text-red-800,
.text-red-900,
.text-red-950 {
  color: var(--db-bad-fg);
}
.text-emerald-500,
.text-emerald-600,
.text-emerald-700,
.text-emerald-800,
.text-emerald-900,
.text-emerald-950,
.text-green-500,
.text-green-600,
.text-green-700,
.text-green-800,
.text-green-900,
.text-green-950,
.text-teal-500,
.text-teal-600,
.text-teal-700,
.text-teal-800,
.text-teal-900,
.text-teal-950 {
  color: var(--db-good-fg);
}
.text-amber-500,
.text-amber-600,
.text-amber-700,
.text-amber-800,
.text-amber-900,
.text-amber-950,
.text-yellow-500,
.text-yellow-600,
.text-yellow-700,
.text-yellow-800,
.text-yellow-900,
.text-yellow-950 {
  color: var(--db-warn-fg);
}
.text-orange-500,
.text-orange-600,
.text-orange-700,
.text-orange-800,
.text-orange-900,
.text-orange-950 {
  color: var(--st-serious-fg);
}
.text-indigo-500,
.text-indigo-600,
.text-indigo-700,
.text-indigo-800,
.text-indigo-900,
.text-indigo-950,
.text-violet-500,
.text-violet-600,
.text-violet-700,
.text-violet-800,
.text-violet-900,
.text-violet-950,
.text-purple-500,
.text-purple-600,
.text-purple-700,
.text-purple-800,
.text-purple-900,
.text-purple-950,
.text-blue-500,
.text-blue-600,
.text-blue-700,
.text-blue-800,
.text-blue-900,
.text-blue-950,
.text-sky-500,
.text-sky-600,
.text-sky-700,
.text-sky-800,
.text-sky-900,
.text-sky-950,
.text-cyan-500,
.text-cyan-600,
.text-cyan-700,
.text-cyan-800,
.text-cyan-900,
.text-cyan-950 {
  color: var(--db-accent-fg);
}
:is(.kd-sidebar, .kd-drawer) :is(.text-rose-500, .text-rose-600, .text-rose-700, .text-rose-800, .text-rose-900, .text-rose-950, .text-red-500, .text-red-600, .text-red-700, .text-red-800, .text-red-900, .text-red-950, .text-emerald-500, .text-emerald-600, .text-emerald-700, .text-emerald-800, .text-emerald-900, .text-emerald-950, .text-green-500, .text-green-600, .text-green-700, .text-green-800, .text-green-900, .text-green-950, .text-teal-500, .text-teal-600, .text-teal-700, .text-teal-800, .text-teal-900, .text-teal-950, .text-amber-500, .text-amber-600, .text-amber-700, .text-amber-800, .text-amber-900, .text-amber-950, .text-yellow-500, .text-yellow-600, .text-yellow-700, .text-yellow-800, .text-yellow-900, .text-yellow-950, .text-orange-500, .text-orange-600, .text-orange-700, .text-orange-800, .text-orange-900, .text-orange-950, .text-indigo-500, .text-indigo-600, .text-indigo-700, .text-indigo-800, .text-indigo-900, .text-indigo-950, .text-violet-500, .text-violet-600, .text-violet-700, .text-violet-800, .text-violet-900, .text-violet-950, .text-purple-500, .text-purple-600, .text-purple-700, .text-purple-800, .text-purple-900, .text-purple-950, .text-blue-500, .text-blue-600, .text-blue-700, .text-blue-800, .text-blue-900, .text-blue-950, .text-sky-500, .text-sky-600, .text-sky-700, .text-sky-800, .text-sky-900, .text-sky-950, .text-cyan-500, .text-cyan-600, .text-cyan-700, .text-cyan-800, .text-cyan-900, .text-cyan-950) {
  color: inherit;
}

/* The four dark slate buttons had no dark-mode rule either, so they became
   a near-black button on a near-black page. */
html[data-theme="dark"] :is(.bg-slate-900, .bg-slate-800) { background-color: var(--db-accent); color: var(--db-on-accent, #fff); }
html[data-theme="dark"] :is(.hover\:bg-slate-800:hover, .hover\:bg-slate-700:hover) { background-color: var(--db-accent); filter: brightness(1.1); }

/* ---- The muted label utility ---------------------------------------------
   text-slate-400 is #94a3b8, which is 2.6:1 on white. Fourteen views use it
   for the quiet line under a heading - a requester's email, a job's field
   labels, an asset's Location and Category - so a large share of the small
   print in the product was below what body text needs, in the default theme.

   It becomes the muted text token instead, which is a step darker in light
   mode and a step lighter in dark. The drawer is the exception: it is dark
   navy in both themes and #94a3b8 reads at 5.9:1 on it, so it keeps what it
   has. */
.text-slate-400 { color: var(--db-text-3); }
.kd-drawer .text-slate-400, .kd-sidebar .text-slate-400 { color: var(--db-on-dark-2); }
.kd-drawer .hover\:text-white:hover, .kd-sidebar .hover\:text-white:hover { color: var(--db-on-dark); }

/* ---------------------------------------------------------------------
   A page fills the screen it is on.

   The builder, the two forms and the preview were each centred inside a
   Tailwind max-w-* box - 7xl, 5xl, 4xl, 3xl - so on a 1920 monitor a third
   of the width was empty on either side of the thing being worked on, and
   on a phone the cap did nothing at all because a phone was never that
   wide. One class, so the next page cannot pick a fifth width.
   --------------------------------------------------------------------- */
.kd-fill { width: 100%; max-width: none; margin-left: 0; margin-right: 0; }

/* ---------------------------------------------------------------------
   The mark.

   It was the same string of Tailwind utilities in nine places — a
   bg-gradient-to-br from indigo-500 to violet-600 — on the sidebar logo,
   the phone header, the sign-in pages, the request portal and the avatar.
   An indigo-to-violet gradient is the single most recognisable tell of a
   generated interface, and it was the first thing a buyer saw on the
   sign-in screen.

   What replaces it is a machine nameplate: a flat graphite tile, the
   letters set in the mono, and one accent rule along the bottom edge. Flat
   because a plant is not a startup, mono because the rest of the product
   sets its identifiers that way, and the rule so the accent is still in the
   mark without being the whole of it.

   Size and shape stay with whatever utilities the element already carries,
   so the sidebar's 36px and the sign-in page's 44px are unchanged.
   --------------------------------------------------------------------- */
.kd-mark {
  position: relative;
  overflow: hidden;
  background: var(--db-text, #0f172a);
  color: var(--db-surface, #ffffff);
  font-family: var(--db-mono, ui-monospace, monospace);
  font-weight: 600;
  letter-spacing: .01em;
}
.kd-mark::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: 0;
  height: 2px;
  background: var(--db-accent, #4f46e5);
}
/* On a dark ground a near-black tile disappears, so it lifts to the raised
   surface and takes the ordinary text colour. */
html[data-theme="dark"] .kd-mark {
  background: var(--db-surface-3, #1e293b);
  color: var(--db-text, #e2e8f0);
}

/* The avatar is a person, not the product, so it keeps the accent and drops
   only the gradient. */
.kd-avatar {
  background: var(--db-accent, #4f46e5);
  color: var(--db-on-accent, #ffffff);
  font-weight: 600;
}

/* ---------------------------------------------------------------------
   Said, but not shown.

   Eight pages inside the layout carried no <h1> at all — the dashboard, the
   dispatch board, the four purchasing screens, the scanner and the spend
   breakdown. They say what they are in the top bar, which a sighted person
   reads and a screen reader does not announce as the page's heading, so
   those pages had no title in the document structure and no landmark to jump
   to.

   A visible heading would have meant redesigning eight page tops that are
   working; this gives the structure without moving a pixel. Tailwind's
   sr-only is purged out of the build, so the utility lives here.
   --------------------------------------------------------------------- */
.kd-sr {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}

/* ---------------------------------------------------------------------
   The value most rows carry is not news.

   Measured on the demo plant: 175 of 201 live jobs are open, and 88 of 201
   are medium priority. So the Status column was 175 identical filled chips
   and 26 that meant something, and Priority was much the same. A column
   that shouts the same word at you 175 times is not telling you anything —
   it is charging you attention to find the 26 rows that need a decision.

   The default states keep the chip's shape, so the column still lines up,
   and give up the fill. Everything that is NOT the default — in progress,
   on hold, high, urgent, critical — keeps its colour and now has the column
   to itself. The tab above the table already says which stage you are
   looking at, so the word is a label here, not an alarm.
   --------------------------------------------------------------------- */
.kd-badge[data-life="open"],
.kd-badge[data-prio="medium"] {
  background: transparent;
  color: var(--db-text-2, #475569);
  box-shadow: inset 0 0 0 1px var(--db-border, #e9edf3);
}
/* Low is quieter still: it is below the default, not at it. */
.kd-badge[data-prio="low"] {
  background: transparent;
  color: var(--db-text-3, #64748b);
  box-shadow: inset 0 0 0 1px var(--db-border, #e9edf3);
}

/* ---------------------------------------------------------------------
   What a card does when you are on it.

   The thesis is the accent rule, because it is already how this product
   signals attention: the KD mark carries one along its bottom edge, so does
   the sign-in panel, and so does every cell of the KPI strip. Hovering a card
   draws that same rule. It is the product's own mark appearing under whatever
   you are pointing at, rather than the fade-and-rise every dashboard does.

   The rule wipes in from the left over 220ms on an exponential curve — fast
   enough to feel like a response, slow enough to read as drawn rather than
   switched on. It is inset from both corners so it never runs into the radius
   the way the strip cell's border-top used to.

   Two treatments, because a lift is a promise:

     a card you can open   rises 1px and deepens its shadow
     a card that holds     warms its border and draws the rule, and stays put

   Lifting a container that does nothing when clicked is a small lie, and the
   dashboard is full of containers.
   --------------------------------------------------------------------- */
.db-widget, .card, .tp-card, .pd-tile, .kd-kpi,
a.wo-card, a.as-card, .db-strip-cell {
  position: relative;
  transition: border-color .18s ease, box-shadow .18s ease, transform .18s cubic-bezier(.22, 1, .36, 1);
}
.db-widget::after, .card::after, .tp-card::after, .pd-tile::after, .kd-kpi::after,
a.wo-card::after, a.as-card::after, .db-strip-cell::after {
  content: '';
  position: absolute; left: .8rem; right: .8rem; bottom: 0; height: 2px;
  background: var(--db-accent, #4f46e5);
  border-radius: 2px 2px 0 0;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform .22s cubic-bezier(.22, 1, .36, 1);
  pointer-events: none;
}
.db-widget:hover::after, .card:hover::after, .tp-card:hover::after,
.pd-tile:hover::after, .kd-kpi:hover::after,
a.wo-card:hover::after, a.as-card:hover::after, .db-strip-cell:hover::after,
a.wo-card:focus-visible::after, a.as-card:focus-visible::after,
.db-strip-cell:focus-visible::after {
  transform: scaleX(1);
}

/* Every card warms its edge — but never the left one.

   A left border is load-bearing in this product: it is how a work order card
   says what type of job it is, and how a dozen other cards carry their status.
   Warming all four sides repainted that signal away on hover, so the colour
   telling you this was a reactive job vanished exactly when you pointed at it.
   Three sides warm; the left keeps whatever it was saying. */
.db-widget:hover, .card:hover, .tp-card:hover, .pd-tile:hover, .kd-kpi:hover,
a.wo-card:hover, a.as-card:hover, .db-strip-cell:hover {
  border-top-color: color-mix(in srgb, var(--db-accent, #4f46e5) 40%, var(--db-border, #e9edf3));
  border-right-color: color-mix(in srgb, var(--db-accent, #4f46e5) 40%, var(--db-border, #e9edf3));
  border-bottom-color: color-mix(in srgb, var(--db-accent, #4f46e5) 40%, var(--db-border, #e9edf3));
}

/* Only the ones that open rise. */
a.wo-card:hover, a.as-card:hover, .db-strip-cell:hover {
  transform: translateY(-1px);
}

/* Somebody who has asked for less movement still gets the answer, just
   without the travel: the rule is there, it simply does not draw itself. */
@media (prefers-reduced-motion: reduce) {
  .db-widget::after, .card::after, .tp-card::after, .pd-tile::after, .kd-kpi::after,
  a.wo-card::after, a.as-card::after, .db-strip-cell::after { transition: none; }
  a.wo-card:hover, a.as-card:hover, .db-strip-cell:hover { transform: none; }
}

/* ---------------------------------------------------------------------
   The hover card for a work order.

   Three screens described a job on hover in three different ways, two of
   them through the browser's own tooltip — a grey system box, in the system
   font, that ignores the theme and arrives a second late. On the dark board
   it turned up as a slab of light.

   This is the description once, in the product's own surface, type and
   tokens. See public/js/kd-jobcard.js for what goes in it.
   --------------------------------------------------------------------- */
/* ---------------------------------------------------------------------
   Every popup that appears under the pointer wears this.

   There were six, in four different costumes: two dark slabs that ignored the
   theme, a white card that turned up as a hole in the dark board, and the job
   card. A person hovering across the calendar, the board and the job list met
   three different objects doing the same thing. One surface now, in the
   product's own tokens, so a popup reads as the same object wherever it
   appears — and public/js/kd-pop.js puts all of them in the same place.
   --------------------------------------------------------------------- */
.kd-pop, .kd-jobcard {
  position: fixed; z-index: 90; max-width: 21rem; min-width: 13rem;
  padding: .6rem .7rem;
  border-radius: var(--db-r, 10px);
  background: var(--db-surface, #fff);
  border: 1px solid var(--db-border, #e9edf3);
  box-shadow: 0 12px 32px -12px rgba(15, 23, 42, .45);
  font-size: .76rem; line-height: 1.35; color: var(--db-text, #0f172a);
  pointer-events: auto;
}
/* A popup listing several things scrolls rather than growing off the screen. */
.kd-pop { max-height: min(26rem, 70vh); overflow-y: auto; }
.kd-pop[hidden] { display: none !important; }
/* The heading a list-shaped popup carries: what day, whose crew, how many. */
.kd-pop-h {
  font-weight: 700; color: var(--db-text, #0f172a);
  padding-bottom: .35rem; margin-bottom: .4rem;
  border-bottom: 1px solid var(--db-border, #e9edf3);
}
.kd-pop-h small { display: block; font-weight: 500; font-size: .7rem; color: var(--db-text-3, #64748b); }
/* A popup that is only read, never reached into, lets the pointer through. */
.kd-pop-quiet { pointer-events: none; }
/* Out of the way while something is being dragged: a popup opens beside a day,
   which is the ground you are about to drop onto. Its own switch, so that
   putting it back cannot fight with however each popup shows itself. */
.kd-pop-away { display: none !important; }
.kd-jobcard-h { display: flex; align-items: center; gap: .4rem; margin-bottom: .25rem; }
.kd-jobcard-num { font-family: var(--db-mono, ui-monospace, monospace); font-size: .7rem; font-weight: 600; color: var(--db-text-2, #475569); }
.kd-jobcard-late {
  margin-left: auto; font-size: .6rem; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  padding: .05rem .35rem; border-radius: var(--db-r-sm, 6px);
  background: var(--db-bad-soft, #fff1f2); color: var(--db-bad-fg, #be123c);
}
.kd-jobcard-t { font-weight: 650; color: var(--db-text, #0f172a); margin-bottom: .35rem; }
/* Label and value in two columns, so the values line up and the eye can run
   down them instead of hunting through a sentence. */
.kd-jobcard-d { display: grid; grid-template-columns: auto 1fr; gap: .15rem .6rem; margin: 0; }
.kd-jobcard-d dt { color: var(--db-text-3, #64748b); }
.kd-jobcard-d dd { margin: 0; color: var(--db-text, #0f172a); font-variant-numeric: tabular-nums; }
