/* public/console/styles.css
   Brand tokens lifted from chandramtech.com: warm near-black ink, gold accent,
   Plus Jakarta Sans, pill primary buttons, tight heading tracking. */

:root {
  /* Brand dark. Every dark fill and all dark type: #120502, never #000.
     Owner decision, confirmed 2026-09-27. */
  --ink: #120502;
  --ink-soft: #241410;     /* raised surface on ink */
  --gold: #e9c977;         /* brand accent */
  --gold-deep: #d4b05a;    /* gold hover */
  --paper: #faf9f7;        /* warm off-white page */
  --surface: #ffffff;
  --text: #120502;
  --text-on-ink: #f5f5f4;
  --muted: #6b6560;        /* warm neutral, not pure gray */
  --muted-on-ink: #a89f97;
  --border: rgba(18, 5, 2, 0.1);
  --border-on-ink: rgba(245, 245, 244, 0.14);
  --danger: #b3261e;
  --ok: #2f6f4f;
  /* Neutral information, the fourth badge tone. Not gold: gold already means
     "needs attention" on .badge.warn, and a reserved invoice number needs
     none. The hue is the blue the lead stage chips already use, darkened to
     clear 4.5:1 on white as type rather than as a 14 percent wash. */
  --info: #2c6a99;

  --radius: 12px;
  --pill: 999px;
  --shadow: 0 1px 2px rgb(18 5 2 / 0.04), 0 4px 12px rgb(18 5 2 / 0.06),
            0 16px 32px rgb(18 5 2 / 0.05);
}

* { box-sizing: border-box; }

/* The browser's [hidden] rule is display:none at UA specificity, so any class
   that sets display, .grid-2, .checks, .panel, silently beats it and the
   element stays visible with the attribute set. This restores the attribute's
   meaning everywhere rather than per element. */
[hidden] { display: none !important; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--paper);
  color: var(--text);
  font: 15px/1.6 "Plus Jakarta Sans", ui-sans-serif, system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
}

a { color: var(--text); text-underline-offset: 3px; }

h1, h2 { letter-spacing: -0.03em; text-wrap: balance; }

/* Page titles get the display face. Space Grotesk is the current geometric
   grotesque look: a wide, slightly mechanical lowercase with a single-storey
   'a' and a flat-topped 'g', which is what reads as contemporary rather than
   corporate. Only h1, so the body stays Plus Jakarta Sans and a page of dense
   tabular data is still read in the face chosen for reading.
   Loaded on all six views; if it has not arrived, the stack falls straight
   back to the body face and only the tracking differs. */
h1 {
  margin: 0;
  font-family: "Space Grotesk", "Plus Jakarta Sans", ui-sans-serif, system-ui, sans-serif;
  font-size: 1.75rem;
  font-weight: 700;
  line-height: 1.15;
  /* -0.03em is tuned for Plus Jakarta Sans. Space Grotesk is wider and its
     counters close up at that value, so h1 loosens back off. */
  letter-spacing: -0.015em;
}
h2 { margin: 0 0 4px; font-size: 1.125rem; font-weight: 600; }

code {
  padding: 2px 6px;
  background: rgba(18, 5, 2, 0.05);
  border-radius: 6px;
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 0.8125rem;
}

.muted { color: var(--muted); }
.small { font-size: 0.8125rem; }

/* ---------------------------------------------------------------- login */

.centered {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--ink);
  /* the brand hero is a dark field with a faint warm glow */
  background-image: radial-gradient(70% 55% at 50% 0%, rgba(233, 201, 119, 0.16), transparent 70%);
}

.card {
  width: 100%;
  max-width: 400px;
  background: var(--surface);
  border-radius: 16px;
  padding: 36px;
  box-shadow: var(--shadow);
}

.brand-name {
  margin: 0 0 6px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.brand-sub { margin: 8px 0 28px; font-size: 0.875rem; color: var(--muted); }

/* ---------------------------------------------------------------- shell */

/* White bar with dark type and a single accent, mirroring the brand nav. */
.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 18px 48px;
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  color: var(--text);
}

.topbar h1 { margin: 2px 0 0; font-size: 1.375rem; }
.topbar-right { display: flex; align-items: center; gap: 14px; font-size: 0.8125rem; }

.back {
  display: inline-block;
  margin-bottom: 6px;
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  text-decoration: none;
}

.back:hover { color: var(--text); }

/* Two back-links sat flush against each other and read as one string. */
.crumbs { display: flex; gap: 16px; margin-bottom: 6px; }
.crumbs .back { margin-bottom: 0; }

/* Full width with gutters wide enough to read against, rather than a narrow
   centred column: these are dense admin tables, not prose. */
.wrap {
  width: 100%;
  margin: 0 auto;
  padding: 20px 48px 72px;
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.panel {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 28px;
}

.panel-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-bottom: 16px;
}

/* ---------------------------------------------------------------- table */

table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }

th {
  padding: 0 12px 10px;
  border-bottom: 1px solid var(--border);
  text-align: left;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

td { padding: 14px 12px; border-bottom: 1px solid var(--border); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: rgba(233, 201, 119, 0.07); }
td a { font-weight: 600; }

.badge {
  display: inline-block;
  padding: 3px 10px;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
}

/* FOUR TONES, and each one means one thing. Anything else on a badge is an ad
   hoc colour and belongs in this block instead:
     (none)  neutral state, no judgement          e.g. Draft
     .ok     settled, approved, nothing to do     green
     .warn   needs attention, late, unpaid        gold
     .info   a fact worth noticing, not a problem blue
     .off    absent, not bought, released         dashed grey */
.badge.ok { border-color: rgba(47, 111, 79, 0.3); color: var(--ok); }
.badge.warn { border-color: var(--gold-deep); color: #8a6d1f; background: rgba(233, 201, 119, 0.16); }
.badge.info { border-color: rgba(44, 106, 153, 0.34); color: var(--info); background: rgba(44, 106, 153, 0.08); }

/* A module the organization has NOT bought. Dashed rather than merely paler,
   because the difference has to survive a greyscale print and a colourblind
   reader: weight and border style carry it, colour only reinforces. */
.badge.off {
  border-style: dashed;
  border-color: rgba(18, 5, 2, 0.16);
  color: rgba(18, 5, 2, 0.34);
}

/* Nine columns overflow a laptop half-screen. Scroll the table, never squeeze
   the organization name into two words per line.

   min-width:0 is the load-bearing half. .panel is a flex item of .wrap, and a
   flex item's default min-width:auto is its MIN-CONTENT width, so the panel
   grew to fit the table and the overflow escaped to the document: the whole
   page scrolled sideways instead of the table scrolling inside its border.
   overflow-x:auto on the child cannot contain anything until the parent is
   allowed to be narrower than its contents. */
.panel { min-width: 0; }
.table-scroll { overflow-x: auto; min-width: 0; }

/* EVERY DATA TABLE IS WRAPPED IN .table-scroll, and that is not decoration.
   A <table> cannot scroll itself: its used width can never fall below its
   min-content, so above the 1280px card breakpoint a table whose content is
   wider than the panel simply paints past the panel's right border and takes
   the document's horizontal scrollbar with it. That is what the owner reported
   on Users, Leads, Clients, Client projects and Resource allocation, and it
   reproduces on any of them the moment a real company name or a fourth action
   button is in the row. It did not reproduce on an empty database, which is
   why it looked like a phantom: with two short rows the min-content fits.
   Eight tables on the tenant page and nine in the workspace had no wrapper.

   The 1080px floor is what keeps the scroller honest rather than letting the
   browser crush a company name to two words per line. It is on panel tables
   only: the payments dialog is four narrow columns and has no use for it. */
.panel .table-scroll table { min-width: 1080px; }

/* THE ACTION CELL. .row-actions lands on a <td>, so it must never set
   display:flex: that replaces display:table-cell, the cell drops out of column
   layout, the browser wraps it in an anonymous cell of its own, and the buttons
   paint past the table's width and past the panel's right border with a stray
   rule underneath them. Every table using this class was losing its action cell
   that way, not only the one it was first noticed on, so the fix belongs here
   rather than in a per-table override.
   width:1% with nowrap is the shrink-to-fit idiom: the cell takes exactly its
   content width and the slack goes to the columns that can use it.
   The buttons are spaced by a margin rather than a flex gap, because a flex
   container on the cell is the thing that broke this in the first place. */
td.row-actions {
  display: table-cell;
  width: 1%;
  white-space: nowrap;
  text-align: right;
  vertical-align: middle;
}
.row-actions > * + * { margin-left: 6px; }

/* THE TITLE COLUMN NEVER COLLAPSES TO ONE WORD PER LINE. Automatic table
   layout hands the slack to whichever column can take it and squeezes the rest
   down to min-content, which for "Data warehouse migration discovery" is the
   word "warehouse", so the first column rendered as a four line stack beside
   five action buttons sitting on one. 170px is roughly three words on this
   face; past that the scroller takes over, which is the trade this stylesheet
   already made for .col-org below and the reason the scroller exists.
   :not(.num) because two tables lead with a row number, which is two digits. */
.panel table :is(th, td):first-child:not(.num) { min-width: 170px; }

.col-org { min-width: 180px; }
.nowrap { white-space: nowrap; }
.dim { color: rgba(18, 5, 2, 0.4); }
.modules { white-space: nowrap; }

/* Zone over offset. Held on one line the column measured 169px and pushed the
   table's min-content to 1346px inside 1288px of panel, so the action link
   fell off the right edge on a 1440 screen. Stacked it is ~100px and the whole
   table fits without a scrollbar. */
.col-zone span { display: block; line-height: 1.35; }

/* THE THREE STATES OF A LIST all land here: loading, empty and failed. A bare
   "No rows" in a table cell is the thing that makes an admin tool feel
   unfinished, and the three were previously improvised per table. One shape,
   one line of consequence, one muted line saying what to do about it. */
.table-empty { padding: 28px 0; text-align: center; }
.table-empty p { margin: 0 0 4px; }

/* Failure is a state, not an empty list. Red on the leading line only: the
   detail under it is the server's own message and shouting it twice does not
   make it clearer. */
.table-empty.is-error > p:first-child { color: var(--danger); }

/* "Add admin" sits in a data column, so it must read as an action without
   shouting louder than the organization name beside it. */
.subtle { color: var(--muted); font-weight: 600; text-decoration: underline; }
.subtle:hover { color: var(--text); }

/* Television and ultrawide. .wrap is width:100% with 48px gutters and no cap,
   so a two-row table stretched to 2464px on a 2560 panel and the eye had to
   travel the whole width to pair a name with its modules. A measure cap costs
   nothing on a laptop, where the viewport is under it anyway. */
@media (min-width: 1800px) {
  .wrap { max-width: 1720px; }
  .tabbar { padding-inline: calc((100vw - 1720px) / 2 + 48px); }
}

/* Below 1281px a table becomes one card per row: phone, tablet portrait, tablet
   landscape at 1024, and the 1280-wide laptop. Seven to ten columns in a
   horizontal scroller means panning back and forth to pair a value with its
   heading, and the heading is off-screen while you read the value. Each cell
   carries its own label from data-label instead, so nothing needs to stay in
   view.

   1280 rather than 1024: the tenant table's min-content is ~1280px and a 1280
   laptop offers 1128px inside the panel, so the action link sat behind a
   sideways scroll on one of the commonest screens there is. Measured, not
   guessed. Between 1281 and ~1430 a table still scrolls inside its own border,
   which is contained and leaves the page still.
   Pure CSS. This client has no build step and no framework.

   SCOPED BY STRUCTURE, not by page and not by table id. Every table in a panel
   in either client now labels every cell, so ".panel table" covers all of them
   and a table added later is included by existing rather than by being
   remembered here. #pay-table is named because it lives in a <dialog> in both
   clients rather than in a panel.

   This used to read "[data-page='tenant'] .panel table" plus three ids,
   because only the console's tenant page wrote data-label. The workspace was
   left half converted: it shares this stylesheet and the action-cell rule, but
   nine of its ten tables stacked nothing and kept a ten column grid at 390px,
   so the whole page scrolled sideways. The labels landed in its renderers and
   the page scoping came off in the same change.

   The FORM tables inside dialogs are deliberately not here: #ms-rows,
   #in-item-rows, #c-email-rows and #c-phone-rows are rows of inputs being
   filled in, not records being read, and they are three or four narrow columns
   that fit a phone as a table. */
@media (max-width: 1280px) {
  .table-scroll { overflow-x: visible; }
  .panel .table-scroll table { min-width: 0; }

  .panel table, #pay-table,
  .panel table tbody, #pay-table tbody,
  .panel table tr, #pay-table tr,
  .panel table td, #pay-table td { display: block; width: auto; }

  /* thead is dropped, not hidden off-screen: display:block has already
     destroyed the table semantics a screen reader would use it for, and
     data-label is read as real content in its place. */
  .panel table thead, #pay-table thead { display: none; }

  .panel table tr, #pay-table tr {
    margin-bottom: 14px;
    padding: 16px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }
  .panel table tr:hover, #pay-table tr:hover { background: transparent; }
  .panel table tr:last-child, #pay-table tr:last-child { margin-bottom: 0; }

  /* Label over value, both block. NOT a two-column grid: a grid <td> makes
     every child its own grid item, so the three module badges became three
     full-width rows and the ACTIVE pill stretched the width of the card. A
     block cell lets the inline children flow as one line, whatever their
     number. */
  /* overflow-wrap:anywhere, and only here. An email address or a URL has no
     break opportunity in it, so a card cell holding
     "anjali.krishnamurthy@globalpaymentsholdings.example" measured 345px inside
     310px of phone and took the whole document's horizontal scrollbar with it.
     A block cell in a card has nowhere else to put it. Above this breakpoint
     the same string is the scroller's problem and it handles it: the table
     scrolls rather than breaking a word, which is the rule the .table-scroll
     floor exists to hold. Applying it at every width instead squeezed "88,000"
     onto two lines and hyphenated a badge down the middle. */
  .panel table td, #pay-table td {
    display: block;
    padding: 6px 0;
    border-bottom: 0;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  /* A pill is a label, not prose. It breaks its own word before it wraps,
     which reads as a rendering fault rather than as a narrow screen. */
  .panel table td .badge, #pay-table td .badge { overflow-wrap: normal; }
  .panel table td::before, #pay-table td::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 3px;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
  }

  /* An empty-state row is one plain cell spanning the table and carries no
     label, so the pseudo element would print a blank block above it. */
  .panel table td[colspan]::before,
  #pay-table td[colspan]::before { content: none; }

  /* Row numbers are a scanning aid for a dense grid. One card at a time does
     not need them, and a "#  2" line is pure noise. */
  .panel table td.num { display: none; }

  #tenant-table td.modules .badge { margin: 0 4px 4px 0; }

  /* Organization is the card's title, so it takes the full width and drops its
     label: nothing else at that size could be mistaken for anything else. */
  #tenant-table td.col-org {
    display: block;
    margin-bottom: 6px;
    font-size: 1.0625rem;
  }
  #tenant-table td.col-org::before { content: none; }

  #tenant-table td.col-zone span { display: inline; }
  #tenant-table td.modules { white-space: normal; }

  /* The actions land at the foot of the card, above a rule, left aligned with
     everything else in it. The shrink-to-fit width is a column-layout device
     and means nothing once the cell is a block. */
  .panel table td.row-actions,
  #pay-table td.row-actions {
    display: block;
    width: auto;
    white-space: normal;
    margin-top: 10px;
    padding-top: 12px;
    border-top: 1px solid var(--border);
    text-align: left;
  }
  .panel table td.row-actions::before,
  #pay-table td.row-actions::before { content: none; }

  /* Several actions wrapping onto a second line need vertical room too, which
     a left margin alone does not give them. */
  .row-actions > * + * { margin-left: 0; }
  .panel table td.row-actions > *,
  #pay-table td.row-actions > * { margin: 0 6px 6px 0; }

  /* No column header to lean on in a card, so the glyph says what it does.
     Selector is scoped rather than bare: the base .icon-action-text rule is
     declared further down this file, and at equal specificity the later rule
     wins regardless of the media query. */
  #tenant-table td.row-actions .icon-action { padding: 7px 10px 7px 7px; }
  #tenant-table .icon-action-text {
    display: inline;
    font-size: 0.6875rem;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
  }

  .topbar { flex-wrap: wrap; }
  .topbar-right { flex-wrap: wrap; gap: 10px; }
  .panel-head { flex-wrap: wrap; gap: 12px; }
  .pager { flex-direction: column; align-items: flex-start; gap: 12px; }
  .profile-grid { gap: 24px; }
}

@media (max-width: 640px) {
  .head-actions { width: 100%; justify-content: space-between; }
}

/* ------------------------------------------------------- account menu */

/* <details> is the widget. These rules only dress it: the open state, the
   keyboard handling and aria-expanded are the element's own. */
.usermenu { position: relative; }

.usermenu > summary {
  list-style: none;          /* Firefox */
  cursor: pointer;
}
.usermenu > summary::-webkit-details-marker { display: none; }   /* Safari */

.avatar {
  display: block;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  overflow: hidden;
  background: rgba(18, 5, 2, 0.06);
  border: 1px solid var(--border);
  transition: box-shadow 150ms, border-color 150ms;
}
.avatar:hover { border-color: var(--gold-deep); }
.usermenu > summary:focus-visible .avatar,
.usermenu[open] > summary .avatar {
  outline: none;
  border-color: var(--gold-deep);
  box-shadow: 0 0 0 3px rgba(233, 201, 119, 0.45);
}

/* object-fit so a non-square upload is cropped to the circle rather than
   squashed into it. The server stores whatever was sent; the crop is here. */
.avatar-img { width: 100%; height: 100%; object-fit: cover; display: block; }

.avatar-lg { width: 96px; height: 96px; }

.usermenu-panel {
  position: absolute;
  top: calc(100% + 10px);
  right: 0;
  z-index: 40;
  min-width: 232px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}

.usermenu-head {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 10px 12px 12px;
  border-bottom: 1px solid var(--border);
  margin-bottom: 6px;
}
/* The email is the one string here that can be arbitrarily long. */
.usermenu-email {
  font-size: 0.8125rem;
  font-weight: 600;
  overflow-wrap: anywhere;
}
.usermenu-role {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.usermenu-item {
  display: block;
  width: 100%;
  min-height: 0;
  padding: 9px 12px;
  border: 0;
  border-radius: 8px;      /* inner radius under the panel's 12px */
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  letter-spacing: 0;
  text-align: left;
  text-transform: none;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 150ms, color 150ms;
}
.usermenu-item:hover { background: rgba(18, 5, 2, 0.05); }
.usermenu-item.danger { color: var(--danger); }
.usermenu-item.danger:hover { background: rgba(179, 38, 30, 0.08); }

/* ----------------------------------------------------------- profile */

.profile-grid {
  display: flex;
  gap: 32px;
  align-items: flex-start;
  flex-wrap: wrap;
  margin-top: 4px;
}

.profile-avatar { display: flex; flex-direction: column; gap: 10px; align-items: flex-start; }
.profile-avatar p { margin: 0; }
.avatar-actions { display: flex; gap: 8px; align-items: center; }

/* The file input is visually hidden, not display:none, so it stays focusable
   and the label keeps working as its button. */
.file-btn {
  display: inline-block;
  margin: 0;
  min-height: 34px;
  padding: 7px 16px;
  border: 1px solid var(--ink);
  border-radius: var(--pill);
  background: var(--ink);
  color: var(--text-on-ink);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 150ms, color 150ms;
}
.file-btn:hover { background: var(--gold); border-color: var(--gold); color: var(--ink); }
#avatar-file:focus-visible + .file-btn {
  outline: none;
  box-shadow: 0 0 0 3px rgba(233, 201, 119, 0.45);
}

/* THE PROFILE IS ONE LAYOUT AND TWO PAGES REACH IT: the superadmin's own
   /workspace/admin/profile and the workspace Account tab that every admin and
   employee sees. These rules used to live in public/app/styles.css, which only
   the workspace loads, written as "corrections for the longer list this panel
   carries than the console's three-fact version". The console page then carried
   its own `.facts` list instead and the two drifted into different alignment,
   different avatar grouping and a different narrow-screen form. So they are here
   now, in the sheet both clients load, and there is no workspace copy.

   .kv is shared with the tenant page's admin card, so every rule below is scoped
   with the child combinator and none of them reach it. */
.profile-grid > .kv {
  /* .kv ships with margin-top: 20px, which inside this flex row pushed the
     first label 20px below the top of the picture beside it. */
  margin: 0;
  flex: 1 1 420px;
  /* dt is 0.75rem and dd is 0.875rem, so without this the label and its value
     sit on different baselines on every row. */
  align-items: baseline;
  grid-template-columns: 132px minmax(0, 1fr);
}

/* A company email is long and a phone has no break points. Without this one
   runs under the panel edge on a narrow screen rather than wrapping. */
.profile-grid > .kv dd { overflow-wrap: anywhere; }

/* The picture, its two buttons and the format caption are one group. Stacked,
   a 96px circle over a 233px button row reads as three loose pieces, so the
   controls sit beside the picture and centre against it wherever they fit. The
   caption stays under the buttons in both layouts, never beside them. */
.profile-grid > .profile-avatar {
  flex-direction: row;
  align-items: center;
  gap: 16px;
  flex-wrap: wrap;
}

.avatar-side { display: flex; flex-direction: column; gap: 8px; align-items: flex-start; }
.avatar-side p { margin: 0; }

/* Below this the row cannot hold 96px of picture plus both buttons, so it
   wraps to a stack. Equal-width buttons give the stacked form a right edge as
   straight as its left one. */
@media (max-width: 460px) {
  .profile-grid > .profile-avatar { gap: 12px; }
  .avatar-side { width: 100%; }
  /* stretch, not centre: the upload control is a <label> and Remove is a
     <button>, and their intrinsic heights differ by about 9px, which put the
     two on visibly different baselines while the row was centred. */
  .avatar-actions { width: 100%; align-items: stretch; }
  .avatar-actions > .file-btn,
  .avatar-actions > .secondary {
    flex: 1 1 0;
    display: inline-flex;
    align-items: center;
    justify-content: center;
  }
}

/* A long list in one column leaves the right half of the panel empty on a
   laptop. The pairs are dt,dd,dt,dd in source order, so a four-track grid flows
   two pairs per row with no markup change. Above six rows only: the superadmin's
   five would flow to three ragged rows and read worse than one clean column. */
@media (min-width: 1100px) {
  .profile-grid > .kv.two-up {
    grid-template-columns: 132px minmax(0, 1fr) 132px minmax(0, 1fr);
    column-gap: 32px;
  }
}

.profile-section {
  margin-top: 28px;
  padding-top: 24px;
  border-top: 1px solid var(--border);
}
.profile-section h3 { margin: 0 0 4px; font-size: 1rem; font-weight: 600; letter-spacing: -0.02em; }

/* ----------------------------------------------------------- cropper */

.crop-stage {
  position: relative;
  width: 280px;
  height: 280px;
  max-width: 100%;
  margin: 14px auto 18px;
  border-radius: var(--radius);
  background: rgba(18, 5, 2, 0.06);
  overflow: hidden;   /* clips the mask's spread shadow to the stage */
}

#crop-canvas {
  display: block;
  width: 280px;
  height: 280px;
  cursor: grab;
  touch-action: none;   /* or a phone scrolls the page instead of panning */
}
#crop-canvas:active { cursor: grabbing; }
#crop-canvas:focus-visible { outline: none; }
#crop-canvas:focus-visible + .crop-mask { box-shadow: 0 0 0 200px rgba(18, 5, 2, 0.55), inset 0 0 0 2px var(--gold); }

/* The circle is cut by a ring of shadow rather than by clipping the canvas, so
   the user can still see the part of the photo they are cropping away and knows
   which way to drag. */
.crop-mask {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  box-shadow: 0 0 0 200px rgba(18, 5, 2, 0.55);
  pointer-events: none;   /* the drag belongs to the canvas underneath */
}

#crop-zoom {
  width: 100%;
  margin-bottom: 8px;
  accent-color: var(--gold-deep);
}

/* ------------------------------------------------------------ pager */

.count-chip {
  display: inline-block;
  min-width: 26px;
  padding: 2px 9px;
  border-radius: var(--pill);
  background: var(--ink);
  color: var(--text-on-ink);
  font-size: 0.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
}

.panel-title { display: flex; align-items: center; gap: 10px; }
.panel-title h2 { margin: 0; }

.btn-plus { font-size: 1.05em; line-height: 1; }

.pager {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}
.pager-status { margin: 0; font-size: 0.8125rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.pager-right { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
.pager-size { display: flex; align-items: center; gap: 10px; }
.pager-controls { display: flex; align-items: center; gap: 10px; }
.pager-label {
  margin: 0;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  /* "Organizations per page" stays on one line. Wrapped it drags the select
     out of line with Prev and Next and the pager row gains a second storey. */
  white-space: nowrap;
}
/* .pager select, NOT .pager-controls select: the size picker was moved out of
   that group so it stays visible at one page, and the old selector stopped
   matching. It fell back to the global form select, which is width:100%,
   44px tall and carries margin-bottom:18px for a stacked form. Next to a 34px
   button and an 11px label that read as oversized and sat 9px high.
   Sized to the inline buttons here, and the padding keeps clear of the shared
   chevron rather than dropping it. */
.pager select {
  width: auto;
  min-height: 34px;
  margin-bottom: 0;
  padding: 6px 32px 6px 12px;
  background-color: var(--surface);
  background-position: right 12px center;
  border-radius: var(--pill);
  font-size: 0.8125rem;
  font-weight: 700;
  line-height: 1.2;
}
.pager select:hover { background-color: rgba(18, 5, 2, 0.035); }

/* Disabled must stay in the layout, or Prev and Next swap places on page 1. */
.pager-controls button:disabled { opacity: 0.4; cursor: not-allowed; }

/* ------------------------------------------------- icon-only actions */

.icon-action {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px;
  border-radius: 8px;
  color: var(--muted);
  transition: background-color 150ms, color 150ms;
}
.icon-action:hover { background: rgba(18, 5, 2, 0.06); color: var(--text); }
.icon-action:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px rgba(233, 201, 119, 0.5);
}
.icon-action .icon {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.75;
  stroke-linecap: round;
  stroke-linejoin: round;
}
/* The label is for the card layout, where there is no column header to lean on
   and an unexplained glyph is a guess. */
.icon-action-text { display: none; }

/* Labels a screen reader needs and the eye does not: the actions column header. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------------------------------------------------------------- forms */

label {
  display: block;
  margin-bottom: 6px;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--muted);
}

/* REQUIRED IS MARKED, and it is marked from the control rather than by hand.
   Thirty-five fields across the two pages carry `required` and not one label
   said so, so the first anyone knew of it was the browser refusing to submit.
   :has(+ control) reads the attribute that is already there, which means a
   field that stops being required stops being starred in the same edit and
   the two cannot drift. Decoration only: `required` is what the accessibility
   tree and the browser both act on, and this adds nothing they do not have. */
label:has(+ input[required])::after,
label:has(+ select[required])::after,
label:has(+ textarea[required])::after {
  content: " *";
  color: var(--danger);
  font-weight: 700;
}

/* Date, time and number were never listed, so they rendered as bare browser
   controls next to styled fields. */
/* tel belongs here for the same reason the rest do. It was missing, so the two
   mobile fields added with type="tel" rendered at the browser default, 22px
   tall with a grey 2px border, beside 48px branded inputs. Keeping the type is
   worth it: it is what puts a numeric keypad under a phone number on a phone. */
input[type="text"], input[type="email"], input[type="password"], input[type="tel"],
input[type="date"], input[type="time"], input[type="number"], input[type="search"] {
  width: 100%;
  margin-bottom: 18px;
  padding: 12px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font: inherit;
  font-size: 0.875rem;
}

/* Dropdowns. Tinted rather than white so a picker is visibly a control you
   open, not a box you type into, and a real SVG chevron instead of two
   overlapping gradients. Defined once here; the workspace loads this sheet. */
select {
  width: 100%;
  margin-bottom: 18px;
  padding: 12px 40px 12px 14px;
  background-color: rgba(18, 5, 2, 0.035);
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%236b6560' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 8px;
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  line-height: 1.3;
  appearance: none;
  cursor: pointer;
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

select:hover { background-color: rgba(18, 5, 2, 0.06); border-color: rgba(18, 5, 2, 0.2); }

select:focus-visible {
  background-color: var(--surface);
  border-color: var(--gold-deep);
  outline: 2px solid var(--gold-deep);
  outline-offset: 1px;
}

select:disabled { opacity: 0.55; cursor: not-allowed; }

/* The open list is drawn by the OS; only its colours are ours to set. */
select option { background: var(--surface); color: var(--text); font-weight: 500; }
select option:checked { font-weight: 700; }

/* Inputs offering a datalist are pickers too, so they carry the same chevron. */
input[list] {
  padding-right: 40px;
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5 6 6.5l5-5' fill='none' stroke='%23b5aea7' stroke-width='1.75' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 14px center;
  background-size: 12px 8px;
}

input[list]::-webkit-calendar-picker-indicator { opacity: 0; }

input::placeholder { color: #b5aea7; }

/* The native calendar and clock glyphs are dark blue on every platform;
   tone them to the brand neutral instead. */
input[type="date"], input[type="time"] { cursor: pointer; }

input[type="date"]::-webkit-calendar-picker-indicator,
input[type="time"]::-webkit-calendar-picker-indicator {
  padding: 2px;
  border-radius: 4px;
  opacity: 0.45;
  cursor: pointer;
}

input[type="date"]::-webkit-calendar-picker-indicator:hover,
input[type="time"]::-webkit-calendar-picker-indicator:hover {
  background: rgba(18, 5, 2, 0.08);
  opacity: 0.8;
}

input:focus-visible, button:focus-visible, a:focus-visible {
  outline: 2px solid var(--gold-deep);
  outline-offset: 2px;
}

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
/* Roles, departments and designations. Its own name rather than .grid-3, which
   the milestone and contact rows further down this file also claim and redefine
   as "2fr 1fr 1fr auto" with align-items:end. The later rule won, so the three
   columns were unequal and each one's contents sank to the bottom of the row:
   the ROLES heading sat below DEPARTMENTS whenever its list was the shorter.
   align-items:start keeps the three headings on one baseline whatever the lists
   below them do. */
/* auto-fit with a floor, not repeat(3, 1fr). A 1fr track is minmax(auto, 1fr)
   and `auto` bottoms out at MIN-CONTENT, so on a 390px phone the three columns
   refused to go below the width of the word "Designations" and the grid hung
   98px past its panel. The min() is what lets the floor itself collapse when
   the container is narrower than 180px, which is the case a bare
   minmax(180px, 1fr) still overflows in. Three columns on a laptop, one on a
   phone, no breakpoint to keep in step with anything. */
.lookup-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(180px, 100%), 1fr));
  align-items: start;
  gap: 24px;
}
.no-hrms .lookup-grid { grid-template-columns: 1fr; }

/* First heading in the column, so it has no sibling above to clear. */
.lookup-grid .subhead { margin-top: 0; }

.checks { display: flex; flex-wrap: wrap; gap: 18px; margin-bottom: 22px; }

.check {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text);
  text-transform: none;
  letter-spacing: 0;
  cursor: pointer;
}

.check input { width: 16px; height: 16px; margin: 0; accent-color: var(--ink); }

button {
  padding: 12px 24px;
  min-height: 44px;
  background: var(--ink);
  border: 1px solid var(--ink);
  border-radius: var(--pill);
  color: var(--text-on-ink);
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}

button:hover { background: var(--gold); border-color: var(--gold); color: var(--ink); }
button:disabled { opacity: 0.45; cursor: not-allowed; }
button:disabled:hover { background: var(--ink); color: var(--text-on-ink); }

.card button { width: 100%; }

button.secondary {
  background: transparent;
  border-color: var(--border);
  color: var(--muted);
}

button.secondary:hover { background: var(--ink); border-color: var(--ink); color: var(--text-on-ink); }

.topbar button.secondary:hover { background: var(--gold); border-color: var(--gold); color: var(--ink); }

button.inline { min-height: 34px; padding: 7px 16px; font-size: 0.6875rem; }

/* HRMS-only fields. One class on <body> gates every one of them. */
.no-hrms .hrms-only { display: none !important; }

.subhead {
  margin: 24px 0 12px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

/* THE EDITABLE LISTS in Settings: roles, departments, designations. One row
   per value with an x on the end.

   These were called .chips and .chip, and so are the invoice filter buttons
   700 lines below. Two unrelated components under one name in one stylesheet:
   the later block won every property they shared, so a settings list rendered
   as a wrapping flex row of pills that paid no attention to the flex column
   layout written for it here, and #designation-items was the one element
   hanging past its panel at 390px. Renamed here rather than there because the
   filter bar is what "chip" means everywhere else in this product, and in the
   two other clients that read this file. */
.taglist { margin: 0; padding: 0; list-style: none; }

.taglist-item {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 4px;
  border-bottom: 1px solid var(--border);
  font-size: 0.875rem;
}

.taglist-item > span:first-child { flex: 1; font-weight: 600; }

.taglist-remove {
  min-height: 0;
  padding: 2px 9px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  color: var(--muted);
  font-size: 0.875rem;
  line-height: 1.2;
  letter-spacing: 0;
}

.taglist-remove:hover { background: var(--danger); border-color: var(--danger); color: #fff; }

/* --------------------------------------------------------------- detail */

.kv { display: grid; grid-template-columns: 120px 1fr; gap: 10px 20px; margin: 20px 0 24px; }
.kv dt { font-size: 0.75rem; font-weight: 600; color: var(--muted); }
.kv dd { margin: 0; font-size: 0.875rem; }

.reveal {
  margin-top: 24px;
  padding: 20px;
  background: var(--ink);
  border-radius: var(--radius);
}

.reveal-label {
  margin: 0 0 10px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gold);
}

.reveal code {
  display: block;
  margin-bottom: 14px;
  padding: 12px 14px;
  background: rgba(245, 245, 244, 0.08);
  color: var(--text-on-ink);
  font-size: 1rem;
  letter-spacing: 0.06em;
}

.reveal button.secondary { border-color: var(--border-on-ink); color: var(--gold); }
.reveal-note { margin: 14px 0 0; font-size: 0.75rem; color: var(--muted-on-ink); }

/* ---------------------------------------------------------- status line */

.message { margin: 18px 0 0; min-height: 20px; font-size: 0.8125rem; color: var(--muted); }
.message.error { color: var(--danger); }
.message.ok { color: var(--ok); }

@media (max-width: 640px) {
  .grid-2 { grid-template-columns: 1fr; }
  .wrap { padding: 20px 16px 48px; }
  .topbar { padding: 14px 20px; }
  .panel { padding: 20px; }
}

input:disabled { background: rgba(18, 5, 2, 0.04); color: var(--muted); cursor: not-allowed; }

.head-actions { display: flex; align-items: center; gap: 12px; }

/* ------------------------------------------------------------- toasts */

.toasts {
  position: fixed;
  top: 20px;
  left: 50%;
  transform: translateX(-50%);
  align-items: center;
  z-index: 50;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: min(380px, calc(100vw - 48px));
  pointer-events: none;
}

.toast {
  padding: 14px 18px;
  background: var(--ink);
  border-left: 3px solid var(--gold);
  border-radius: 10px;
  box-shadow: var(--shadow);
  color: var(--text-on-ink);
  font-size: 0.875rem;
  line-height: 1.45;
  cursor: pointer;
  pointer-events: auto;
  animation: toast-in 0.22s ease;
}

.toast.ok { border-left-color: #6ee7a8; }
.toast.error { border-left-color: #fca5a5; }
.toast.leaving { animation: toast-out 0.28s ease forwards; }

/* Enters from above, since the stack now hangs from the top of the screen. */
@keyframes toast-in {
  from { opacity: 0; transform: translateY(-12px); }
  to { opacity: 1; transform: none; }
}

@keyframes toast-out {
  to { opacity: 0; transform: translateY(-8px); }
}

@media (prefers-reduced-motion: reduce) {
  .toast, .toast.leaving { animation: none; }
}

/* The undo button a toast can carry. An action inside the notification, rather
   than a second notification mechanism beside it. */
.toast { display: flex; align-items: center; gap: 14px; justify-content: space-between; }

.toast-action {
  flex-shrink: 0;
  padding: 6px 14px;
  border: 1px solid var(--border-on-ink);
  border-radius: var(--pill);
  background: transparent;
  color: var(--gold);
  font: inherit;
  font-weight: 600;
  font-size: 0.8125rem;
  cursor: pointer;
}

.toast-action:hover { background: var(--ink-soft); border-color: var(--gold); }
.toast-action:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* Combobox, the control that replaced every <input list="...">. The popup is
   position: fixed and placed from the field's own rectangle by the script, so
   neither a dialog's bounds nor a scrolling panel's overflow can clip it. */
.combo-list {
  position: fixed;
  z-index: 60;
  margin: 0;
  padding: 4px;
  list-style: none;
  overflow-y: auto;
  overscroll-behavior: contain;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: var(--shadow);
}

.combo-option {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 9px 11px;
  border-radius: 7px;
  font-size: 0.875rem;
  cursor: pointer;
}

.combo-option:hover { background: var(--paper); }

/* The active option, moved by the arrow keys and reported through
   aria-activedescendant. Not :focus, because focus never leaves the input. */
.combo-option[aria-selected="true"] {
  background: var(--ink);
  color: var(--text-on-ink);
}

.combo-option[aria-selected="true"] .combo-label { color: var(--gold); }
.combo-label { color: var(--muted); font-size: 0.75rem; text-align: right; }
.combo-empty { padding: 9px 11px; color: var(--muted); font-size: 0.8125rem; }

/* The inline status line is replaced by toasts. */
.message { display: none; }

@media (max-width: 640px) {
  .toasts { top: 12px; left: 12px; right: 12px; transform: none; max-width: none; }
}

/* Cross-link between the two sign-ins. */
.alt-signin {
  margin: 20px 0 0;
  padding-top: 18px;
  border-top: 1px solid var(--border);
  font-size: 0.8125rem;
  color: var(--muted);
  text-align: center;
}

.alt-signin a { font-weight: 700; text-decoration: none; }
.alt-signin a:hover { text-decoration: underline; }

@media (max-width: 900px) {
  .wrap { padding: 24px 20px 56px; }
  .topbar { padding: 16px 20px; }
}

/* THE ONE RULE FOR DESTRUCTIVE ACTIONS, and it has exactly two tiers.

   Red (.danger) means the click deletes a row and nothing in this product
   brings it back: Delete user, Remove holiday, Remove attendance record,
   Remove lead, Delete draft invoice, and the x on a role or department.

   Everything else is a normal action and wears the neutral outline, INCLUDING
   the consequential ones, because consequential is not the same as
   irreversible. Reject a lead is a recorded business outcome with Reopen
   sitting next to it; Deallocate keeps the allocation as history; Void, Issue
   and Approve all write a new state rather than removing a row. Reject used to
   be red and sat beside a red Remove, which made a reversible decision look
   exactly as final as a deletion and left neither reading as the more serious
   of the two.

   "At most one red button in a row" then falls out of the rule instead of
   needing to be policed: a row has at most one thing to delete. Two reds next
   to each other is the signal that something reversible has been miscategorised,
   not a case to design a third tier for. */
button.secondary.danger { border-color: rgba(179, 38, 30, 0.35); color: var(--danger); }
button.secondary.danger:hover { background: var(--danger); border-color: var(--danger); color: #fff; }


/* ------------------------------------------------------- section tabs */
/* A segmented control rather than underlines: it reads as a control you
   operate, and the filled state survives being glanced at. Sticky, because
   the panels below it are long. */

.tabbar {
  position: sticky;
  top: 0;
  z-index: 20;
  padding: 14px 48px;
  background: rgba(250, 249, 247, 0.88);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}

.segmented {
  display: inline-flex;
  gap: 2px;
  padding: 4px;
  background: rgba(18, 5, 2, 0.05);
  border-radius: var(--pill);
  max-width: 100%;
  overflow-x: auto;
}

.panel-tab {
  min-height: 0;
  padding: 9px 20px;
  background: transparent;
  border: 0;
  border-radius: var(--pill);
  color: var(--muted);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  transition: background 0.16s ease, color 0.16s ease;
}

.panel-tab:hover { background: rgba(18, 5, 2, 0.06); color: var(--text); }

.panel-tab.active {
  background: var(--ink);
  color: var(--text-on-ink);
  box-shadow: 0 1px 2px rgb(18 5 2 / 0.18);
}

.panel-tab.active:hover { background: var(--ink); color: var(--text-on-ink); }

.panel-tab .tab-count {
  display: inline-block;
  margin-left: 8px;
  padding: 1px 7px;
  border-radius: var(--pill);
  background: rgba(18, 5, 2, 0.08);
  font-size: 0.6875rem;
  letter-spacing: 0;
}

.panel-tab.active .tab-count { background: rgba(245, 245, 244, 0.18); }

/* Each group gets a one-line lede so a tab is a place, not just a filter. */
.group-lede {
  margin: 0 auto;
  padding-top: 24px;
  /* The lede is a <p class="wrap">, which it has to be to line its left edge up
     with the cards. .wrap's shorthand also carries padding-bottom: 72px, which
     is there to give the BOTTOM OF THE PAGE room and has no business between a
     one-line lede and the first card: 72px here plus main's own 20px top put 92
     empty pixels under every group heading. */
  padding-bottom: 0;
  font-size: 0.8125rem;
  color: var(--muted);
}

@media (max-width: 900px) {
  .tabbar { padding: 12px 20px; }
  .panel-tab { padding: 9px 14px; }
}

/* Row numbers: tabular figures so the column does not jitter as it scrolls. */
.num {
  width: 44px;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* ------------------------------------------------------------- modal */

dialog {
  width: min(560px, calc(100vw - 32px));
  padding: 28px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: 0 24px 64px rgb(18 5 2 / 0.22);
  color: var(--text);
}

dialog::backdrop { background: rgba(18, 5, 2, 0.45); backdrop-filter: blur(2px); }

.modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
}

.modal-head h2 { font-size: 1.25rem; }

.icon-btn {
  min-height: 0;
  padding: 2px 10px;
  background: transparent;
  border: 0;
  color: var(--muted);
  font-size: 1.5rem;
  line-height: 1;
  letter-spacing: 0;
}

.icon-btn:hover { background: transparent; color: var(--text); }

.modal-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 8px;
}

dialog p.muted { margin-top: 4px; margin-bottom: 20px; }

/* The invoice preview and the billing profile form both carry far more than a
   560px column holds, and a document read at half its width tells you nothing
   about how it will land on the client's desk. */
dialog.wide { width: min(940px, calc(100vw - 32px)); }

/* The preview renders the document's own stylesheet, which travels inside the
   fragment so the dialog, the print window and the Word file are one layout.
   White here rather than var(--surface) because that is the page it becomes. */
.doc-preview {
  max-height: 68vh;
  overflow: auto;
  margin: 12px 0 4px;
  background: #fff;
  border: 1px solid var(--border);
  border-radius: 10px;
}

/* ---------------------------------------------------------- calendar */
/* Status lives in the cell's background rather than a legend you have to
   cross-reference, so an unmarked day reads as a gap at a glance. */

.cal-weekdays,
.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 6px;
}

.cal-weekdays {
  margin: 16px 0 8px;
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
}

.cal-cell {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-height: 78px;
  padding: 8px 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  font: inherit;
  letter-spacing: 0;
  text-transform: none;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.14s ease, transform 0.14s ease;
}

.cal-cell:hover:not(:disabled) { border-color: var(--ink); transform: translateY(-1px); background: var(--surface); }
.cal-cell.empty { background: transparent; border: 0; cursor: default; min-height: 0; }
.cal-cell:disabled, .cal-cell.is-future { opacity: 0.4; cursor: not-allowed; }

.cal-num { font-size: 0.875rem; font-weight: 700; font-variant-numeric: tabular-nums; }
.cal-status { font-size: 0.625rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase; }
.cal-who { font-size: 0.625rem; color: var(--muted); }

.cal-cell.is-today { border-color: var(--ink); box-shadow: inset 0 0 0 1px var(--ink); }

/* One hue per status, tinted so the number stays readable on top. */
.s-present       { background: rgba(47, 111, 79, 0.12);  border-color: rgba(47, 111, 79, 0.3); }
.s-wfh           { background: rgba(56, 132, 189, 0.12); border-color: rgba(56, 132, 189, 0.3); }
.s-leave         { background: rgba(233, 201, 119, 0.28); border-color: var(--gold-deep); }
.s-absent        { background: rgba(179, 38, 30, 0.1);   border-color: rgba(179, 38, 30, 0.3); }
.s-holiday       { background: rgba(18, 5, 2, 0.06);     border-color: var(--border); }

.cal-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin: 18px 0 0;
  padding: 0;
  list-style: none;
  font-size: 0.75rem;
  color: var(--muted);
}

.cal-legend li { display: flex; align-items: center; gap: 6px; }

.dot {
  width: 12px;
  height: 12px;
  border-radius: 4px;
  border: 1px solid var(--border);
}

@media (max-width: 640px) {
  .cal-cell { min-height: 58px; padding: 6px; }
  .cal-status, .cal-who { display: none; }
}

/* An absence nobody recorded: same hue as a marked one, lighter, so the two
   are distinguishable without inventing a sixth colour. */
.cal-cell.is-implied { background: rgba(179, 38, 30, 0.05); border-style: dashed; }
.cal-cell.is-implied .cal-status { opacity: 0.65; font-weight: 600; }

/* Before joining: not a gap, and not clickable. */
.cal-cell.is-prejoin { background: transparent; border-style: dotted; opacity: 0.35; }

/* Weekends: not required, still markable, so they read as quiet rather than
   missing. No red, because nothing is wrong with an unmarked Saturday. */
.cal-cell.is-nonworking { background: rgba(18, 5, 2, 0.025); border-style: solid; }
.cal-cell.is-nonworking .cal-num,
.cal-cell.is-nonworking .cal-status { opacity: 0.45; font-weight: 600; }
.dot.s-nonworking { background: rgba(18, 5, 2, 0.025); }

/* Half day: present, but not a whole one, so the same green at half strength. */
.s-half-day { background: rgba(47, 111, 79, 0.06); border-color: rgba(47, 111, 79, 0.3); border-style: dashed; }
.dot.s-half-day { background: rgba(47, 111, 79, 0.06); border-color: rgba(47, 111, 79, 0.3); }

/* On duty: present, but off site. Its own hue so it is not mistaken for remote. */
.s-od { background: rgba(124, 92, 176, 0.12); border-color: rgba(124, 92, 176, 0.35); }
.dot.s-od { background: rgba(124, 92, 176, 0.12); border-color: rgba(124, 92, 176, 0.35); }

.cal-legend code { padding: 1px 6px; font-size: 0.625rem; font-weight: 700; }

/* ------------------------------------------------------------------- leads */
/* A lead is only worth anything if someone chases it, so the date that matters
   is styled like a warning rather than a column of text. */

.is-overdue {
  color: var(--danger);
  font-weight: 600;
}

.badge.stage-new        { background: rgba(18, 5, 2, 0.06); }
.badge.stage-contacted  { background: rgba(56, 132, 189, 0.14); }
.badge.stage-qualified  { background: rgba(233, 201, 119, 0.28); }
.badge.stage-proposal   { background: rgba(233, 201, 119, 0.4); }
/* Stored leads already carry NEGOTIATION even though the create form will not
   set it, and with no tone of its own it was the one untinted pill in a tinted
   column, which read as "no stage" rather than "the stage after proposal". */
.badge.stage-negotiation { background: rgba(233, 201, 119, 0.52); }
.badge.stage-converted  { background: rgba(47, 111, 79, 0.16); }
.badge.stage-rejected   { background: rgba(168, 50, 50, 0.12); }

.timeline { display: flex; flex-direction: column; gap: 12px; max-height: 320px; overflow-y: auto; }

.timeline-item {
  padding: 12px 14px;
  background: rgba(18, 5, 2, 0.02);
  border: 1px solid var(--border);
  border-radius: 12px;
}

.timeline-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}

.timeline-item p { margin: 0; line-height: 1.55; }
.timeline-item p.muted { margin-top: 6px; }

/* The lead modal carries a form and a history list, so it needs more room than
   the create-organization dialog it borrows its styling from. */
#lead-modal { width: min(680px, calc(100vw - 32px)); }

/* ------------------------------------------------------- project pricing */
/* A milestone row is name, amount, date and a remove button, so it needs a
   different split from the two-column form grid around it. */

.grid-3 {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr auto;
  gap: 10px;
  align-items: end;
}

.ms-row { margin-bottom: 10px; }

@media (max-width: 640px) {
  .grid-3 { grid-template-columns: 1fr 1fr; }
}

/* ------------------------------------------------------- attendance approval */
/* An approved day is the admin's record. The employee can still see it, so it
   says so rather than simply refusing the click with no explanation. */

.badge.approved {
  background: rgba(47, 111, 79, 0.16);
  border-color: rgba(47, 111, 79, 0.3);
}

.cal-cell.is-approved { box-shadow: inset 0 0 0 2px rgba(47, 111, 79, 0.35); }
.cal-lock { font-size: 0.625rem; color: var(--muted); }

/* ------------------------------------------------------- client contacts */
/* A phone row is whose number, the country code, the number and which is
   primary, so it needs a narrower column for the code than the others. */

.grid-4 {
  display: grid;
  grid-template-columns: 1.4fr 0.6fr 1.4fr auto auto;
  gap: 10px;
  align-items: end;
}

.inline-check {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0;
  font-size: 0.8125rem;
  color: var(--muted);
  white-space: nowrap;
}

.inline-check input { width: auto; margin: 0; min-height: 0; }

@media (max-width: 720px) {
  .grid-4 { grid-template-columns: 1fr 1fr; }
}

/* ------------------------------------------------------------ sub tabs */
/* Delivery holds four sections, so it gets a second strip. Set apart from the
   first by weight and size rather than another colour: two equally loud tab
   rows read as one confusing row. */

.subbar {
  padding-top: 0;
  margin-top: -6px;
}

.subbar .segmented {
  background: transparent;
  border: 0;
  padding: 0;
  gap: 4px;
}

.subbar .panel-tab {
  font-size: 0.8125rem;
  padding: 6px 12px;
  border-radius: 8px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--muted);
}

.subbar .panel-tab:hover { color: var(--text); background: rgba(18, 5, 2, 0.03); }

.subbar .panel-tab.active {
  color: var(--text);
  background: rgba(233, 201, 119, 0.18);
  border-color: var(--gold-deep);
  font-weight: 600;
}

/* ------------------------------------------------- invoice line items */
/* A line is description, quantity, unit, rate, the computed total and a remove
   button. The description takes the slack because it is the only free text on
   the row; everything else is a number or a two-word unit. */

#inv-modal { width: min(820px, calc(100vw - 32px)); }

.grid-line {
  display: grid;
  grid-template-columns: 2.4fr 0.7fr 0.7fr 1fr 1.1fr auto;
  gap: 10px;
  align-items: center;
}

.line-total {
  font-variant-numeric: tabular-nums;
  text-align: right;
  font-weight: 600;
  white-space: nowrap;
}

/* Below this the row wraps to two columns and the grid order stops explaining
   what each box is, so each field gets its placeholder and the computed total
   gets a printed label. A line items editor that cannot be used on a phone is
   an invoice that has to wait until someone is at a desk. */
@media (max-width: 760px) {
  .grid-line { grid-template-columns: 1fr 1fr; }
  .grid-line input[data-ii="description"] { grid-column: 1 / -1; }
  /* Stacked, the rows run together and the eye cannot tell where one line
     ends. A rule is cheaper than repeating the column headings per row. */
  .grid-line + .grid-line { padding-top: 14px; border-top: 1px solid var(--border); }
  .line-total { text-align: left; }
  .line-total::before {
    content: attr(data-label) ": ";
    color: var(--muted);
    font-weight: 400;
  }
}

.inv-totals {
  margin: 14px 0 4px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
}

.tot-row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 3px 0;
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
}

.tot-row.grand {
  margin-top: 6px;
  padding-top: 8px;
  border-top: 1px solid var(--border);
  font-size: 1rem;
}

/* ------------------------------------------------------- aged debt */
/* One card per currency, never one figure. An organization invoicing in INR
   and USD has no combined outstanding total, and adding them produces a
   plausible wrong number on a screen people make decisions from. */

.aged-strip {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 16px;
}

.aged-card {
  flex: 1 1 320px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.aged-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 10px;
}

.aged-buckets {
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 8px;
}

.aged-bucket {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px 8px;
  border-radius: 8px;
  background: var(--paper);
}

.aged-bucket.late { background: rgba(179, 38, 30, 0.08); }

.aged-label {
  font-size: 0.625rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
}

.aged-value { font-size: 0.875rem; font-weight: 600; font-variant-numeric: tabular-nums; }

@media (max-width: 760px) {
  .aged-buckets { grid-template-columns: repeat(2, 1fr); }
}

/* ------------------------------------------------- filter chips and sortable headers */
/* Loaded by both clients: the workspace links this stylesheet as well as its
   own, which is why the invoice table is named #inv-table in both pages. */

/* Wrapping, not scrolling. Six chips at 390px need three rows, and a
   horizontally scrolled filter bar hides the options somebody came to find. */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}

.chip {
  width: auto;
  min-height: 34px;
  margin: 0;
  padding: 6px 14px;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  background: var(--surface);
  color: var(--muted);
  font-size: 0.8125rem;
  font-weight: 600;
  line-height: 1.2;
  /* The global button is uppercase and letter-spaced, which turns "Partially
     paid" into a two-line chip on a phone. These read as labels, not actions. */
  letter-spacing: 0;
  text-transform: none;
  cursor: pointer;
}

.chip:hover { background: rgba(18, 5, 2, 0.035); color: var(--text); }

/* Pressed is carried by aria-pressed rather than a class, so the styling and
   the accessibility tree cannot drift apart: there is one source of truth and
   a screen reader and a sighted user are told the same thing. */
.chip[aria-pressed="true"] {
  background: rgba(233, 201, 119, 0.24);
  border-color: var(--gold-deep);
  color: var(--text);
}

/* Overdue is a predicate ANDed with the statuses beside it, not a sixth
   status, so it is set apart rather than sitting in the same row of equals. */
.chip-derived { margin-left: 8px; }
.chip-derived[aria-pressed="true"] {
  background: rgba(179, 38, 30, 0.1);
  border-color: rgba(179, 38, 30, 0.4);
}

.chip-clear { margin-left: auto; color: var(--muted); font-weight: 500; }

/* The whole heading is the target, so the click area matches what a person
   aims at. A button rather than a listener on the th: Tab reaches it, Enter
   and Space fire it, and none of that has to be written. */
th[data-sort] { padding: 0; }
.th-sort {
  display: flex;
  align-items: center;
  gap: 6px;
  width: 100%;
  min-height: 0;
  margin: 0;
  padding: 12px 14px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: inherit;
  /* All four, or the global button's own uppercase and letter-spacing survive
     and this heading reads at a different size from the five beside it. */
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  text-align: left;
  cursor: pointer;
}
.th-sort:hover { background: rgba(18, 5, 2, 0.04); }

/* Drawn from aria-sort, so the arrow cannot disagree with what is announced. */
.sort-arrow::after { content: "\2195"; opacity: 0.35; }
th[aria-sort="ascending"] .sort-arrow::after { content: "\2191"; opacity: 1; }
th[aria-sort="descending"] .sort-arrow::after { content: "\2193"; opacity: 1; }

@media (max-width: 480px) {
  /* Three rows of chips at 390px, and the clear control drops onto its own
     line rather than squeezing the last status chip into two words deep. */
  .chip { padding: 6px 11px; font-size: 0.75rem; }
  .chip-derived { margin-left: 0; }
  .chip-clear { margin-left: 0; }
}

/* The employee document viewer. The image is whatever was photographed on a
   phone, so the stage caps it rather than trusting it to be a sensible shape.
   KEEP IN STEP WITH THE WORKSPACE SHEET, which carries the same block for the
   same dialog on the tenant side. */
.doc-stage {
  display: grid;
  place-items: center;
  padding: 12px;
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.doc-stage img { max-width: 100%; max-height: 62vh; width: auto; height: auto; border-radius: 6px; }

/* KEEP IN STEP WITH THE WORKSPACE SHEET. Download is a link styled as a button,
   and it sits away from Approve so a reviewer cannot hit the wrong one. */
.row-actions a.secondary { text-decoration: none; display: inline-flex; align-items: center; }
.modal-actions-group { display: inline-flex; gap: 8px; margin-left: auto; }
/* KEEP IN STEP WITH THE WORKSPACE SHEET. */

/* KEEP IN STEP WITH THE WORKSPACE SHEET. */
/* A link styled as a button, the outline twin of .file-btn in the console
   sheet. An <a download> rather than a <button>, because the browser owns the
   save: the session cookie rides along, the filename comes off the response,
   and no script has to hold two megabytes of somebody Aadhaar card in memory.
   button.secondary cannot be reused here, it is scoped to the element. */
.link-btn {
  display: inline-flex;
  align-items: center;
  min-height: 34px;
  padding: 7px 16px;
  border: 1px solid var(--border);
  border-radius: var(--pill);
  background: transparent;
  color: var(--muted);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.link-btn:hover { background: var(--ink); border-color: var(--ink); color: var(--text-on-ink); }
.link-btn:focus-visible { outline: 2px solid var(--gold-deep); outline-offset: 2px; }

/* A country code beside its number. The SAME 0.6fr to 1.4fr ratio and the same
   10px gap as .grid-4 uses for the client contact rows, so the code field is
   the same proportion of its row wherever a phone number is typed in this
   product. Do not pick new numbers here; change .grid-4 and this together. */
.phone-pair {
  display: grid;
  grid-template-columns: 0.6fr 1.4fr;
  gap: 10px;
  align-items: end;
}
.phone-pair input { margin: 0; }

/* The inferred-country-code note. Sits under its pair, inside the same grid
   cell, so it pushes nothing sideways and wraps with the field it explains. */
.phone-hint { margin: 6px 0 0; }
