/* XINNACE admin interface
   Navy chrome, cool-grey paper, teal for action. Deliberately quiet:
   the colour in this product belongs to the customer's cards, not to
   the software around them. */

/* The palette is declared once, in app/includes/branding.php, and emitted as
   --brand-* custom properties by brand_palette_css(). These map it onto the
   names this stylesheet has always used, with the literal as a fallback so
   app.css still renders standalone.

   The mapping exists rather than a rename because --navy, --paper and --ink
   are the vocabulary of eighty-odd rules below and read better in them than
   --brand-navy would. What matters is that the VALUES have one home. They did
   not: legal.css held its own greys two points away from these, which is
   invisible on either page alone and wrong when you move between them. */

:root {
  --navy:        var(--brand-navy, #0D1C2B);
  --navy-deep:   var(--brand-navy-deep, #081319);
  --paper:       var(--brand-paper, #F5FAFA);
  --surface:     var(--brand-surface, #FFFFFF);
  --ink:         var(--brand-ink, #1A2E3C);
  --ink-soft:    var(--brand-ink-soft, #5F737F);
  --ink-faint:   var(--brand-ink-faint, #93A6AE);
  --rule:        var(--brand-rule, #E4EDEF);
  --rule-strong: #C4D3D7;
  --accent:        var(--brand-accent, #1C6873);
  --accent-strong: var(--brand-accent-strong, #15535C);
  --accent-soft:   #E4F2F0;
  /* Status is reserved and never doubles as a decorative colour. Each is the
     kit's -600 for ink on white, and its -50 for the wash behind it. */
  --danger:       #A32A21;
  --danger-soft:  #FAEBE9;
  --ok:           #16794A;
  --ok-soft:      #E6F3EC;
  --caution:      #B4650B;
  --caution-soft: #FBF0E0;
  /* The kit's two shadows, plus a hairline. A card barely lifts: the border
     does the separating and the shadow only stops it looking pasted on, and
     nothing rises further until it is genuinely floating over the page. */
  --shadow-sm:   0 1px 2px rgba(16,26,34,.04);
  --shadow:      0 1px 2px rgba(16,26,34,.04), 0 1px 3px rgba(16,26,34,.06);
  --shadow-lg:   0 2px 4px rgba(16,26,34,.06), 0 8px 20px -8px rgba(16,26,34,.16);
  --xinnace-teal: var(--brand-teal, #12B5AB);
  /* Wordmark colours for the XINNACE palette. branding_css_vars() overrides
     all four per company; these are the fallback for the signed-out screens,
     which have no company to brand them.

     -paper and -chrome are two answers to the same question, which is why
     both exist: on white the teal has to be walked to #12A59D to be read at
     all (2.55:1 becomes 3.05:1), and on the navy chrome it does not, so the
     mark keeps its own colour there. legal.css makes the same distinction and
     used to get it wrong in both halves. */
  --wordmark-paper:      var(--brand-navy, #0D1C2B);
  --wordmark-ace-paper:  var(--brand-teal-on-paper, #12A59D);
  --wordmark-chrome:     var(--brand-surface, #FFFFFF);
  --wordmark-ace-chrome: var(--brand-teal, #12B5AB);
  /* One radius was enough while everything was a rectangle with a hairline.
     The house radii, from the Reports kit: 5px on a control, 6px on a card.
     Tight rather than soft — a large radius reads friendly and consumer, and
     this is an instrument somebody uses all day. --radius keeps its name, so the eighty-odd rules already using
     it move together rather than needing eighty edits. */
  --r-xs:        4px;
  --r-sm:        5px;
  --r-md:        6px;
  --r-lg:        8px;
  --r-pill:      999px;
  --radius:      var(--r-sm);

  /* A spacing step, so panels and stacks stop each choosing their own. */
  --gap-sm:      10px;
  --gap:         16px;
  --gap-lg:      24px;

  /* Overridden per company by the branding block in the layout. --chrome-ink
     is computed from the chosen chrome colour, so a company that picks a pale
     brand colour still gets readable navigation. */
  --chrome-ink:  #FFFFFF;
  /* Matches BRAND_FONTS['system'] in branding.php, which is what actually
     lands on a signed-in page. The literal here is the standalone fallback,
     and the two disagreeing is how a stylesheet starts lying about itself. */
  --app-font:    system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;

  /* Poppins is the face the wordmark is set in, so in the Reports kit it names
     things — page titles, panel titles, table heads — and never carries running
     text. Falls back to the reading face, so nothing is left unset. */
  --display-font: "Poppins", var(--app-font);
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 16px/1.6 var(--app-font);
  -webkit-font-smoothing: antialiased;
}

/* ---------- brand ---------- */

.brand {
  font-weight: 700;
  font-size: 1.05rem;
  letter-spacing: .16em;
  text-transform: uppercase;
}
/* No `.brand span` rule here, and none wanted.
   It once carried a trademark symbol's small-and-raised treatment. The symbol
   is gone — the brand kit's wordmark does not have one — and the rule went
   before it did, because a selector that broad caught XINN and ACE as well and
   compounded through the nesting: the whole word rendered at a third of its
   size, superscripted and faded. */

/* ---------- signed-in chrome ----------
   A fixed sidebar on desktop; the same element becomes an off-canvas drawer
   below 960px. Widths live in a custom property so the main column and the
   drawer transform can never drift apart. */

:root {
  --nav-w:  264px;
  --gutter: 28px;
}

.app { background: var(--paper); }

.app-frame {
  display: grid;
  grid-template-columns: var(--nav-w) minmax(0, 1fr);
  min-height: 100vh;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--accent);
  color: #fff;
  padding: 10px 16px;
  border-radius: 0 0 var(--radius) 0;
  text-decoration: none;
}
.skip-link:focus { left: 0; }

/* A link's colour, once, at the lowest specificity there is.
 *
 * There was no rule for a bare <a> anywhere in this file, so any link nobody
 * had styled fell back to the browser's own — blue, then purple once visited,
 * underlined — on screens with no other link colour, where it reads as a fault
 * rather than as a link. It has turned up on the custom cards list, in the
 * order panel and in the companies table, each time fixed one selector at a
 * time. An element selector loses to every class rule in the file, so this
 * changes nothing that was already deliberate.
 */
a { color: var(--accent); }

/* --- the sidebar --- */

.sidenav {
  grid-column: 1;
  position: sticky;
  /* --imp-h is set by nav.js only while impersonating, and is 0 otherwise. */
  top: var(--imp-h, 0px);
  align-self: start;
  height: calc(100vh - var(--imp-h, 0px));
  display: flex;
  flex-direction: column;
  background: var(--navy);
  color: var(--chrome-ink);
  border-right: 1px solid rgba(0, 0, 0, .18);
}

.sidenav-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 20px 18px 16px;
  min-height: 68px;
}

/*
 * The hexagons behind the rail, built the way the marketing site builds them.
 *
 * The five that were here did not touch each other. Flat-top on the site,
 * pointy-top here; and where the site's share edges, these left gaps of 6, 19,
 * 19 and 14 pixels between neighbours — four different gaps, which is what made
 * it read as a mistake rather than a pattern. A honeycomb is recognisable
 * because the cells share walls; five loose hexagons at four different removes
 * are just five hexagons.
 *
 * Same construction as the About page: flat-top, side s, columns stepping 1.5s
 * with every other column dropped half a row, which tiles exactly. Twelve cells
 * here, twenty-three shared edges between them, none meeting at a corner alone.
 * Brand teal rather than the #3493a7 that was never one of the palette's.
 *
 * A background image rather than markup, so no view has to carry decoration,
 * and inline rather than a file so it costs no request and cannot 404. Pinned
 * to the top and not repeated: a watermark under the lockup, not a texture down
 * the whole column, which at any opacity would read as noise on a long rail.
 */
.sidenav::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 300px;
  pointer-events: none;
  opacity: .3;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='240' height='300' viewBox='0 0 240 300'><g fill='none' stroke='%2312B5AB' stroke-width='1'><polygon points='240.0,-32.5 266.0,-32.5 279.0,-10.0 266.0,12.5 240.0,12.5 227.0,-10.0'/><polygon points='240.0,12.5 266.0,12.5 279.0,35.0 266.0,57.5 240.0,57.5 227.0,35.0'/><polygon points='240.0,57.5 266.0,57.5 279.0,80.1 266.0,102.6 240.0,102.6 227.0,80.1'/><polygon points='240.0,102.6 266.0,102.6 279.0,125.1 266.0,147.6 240.0,147.6 227.0,125.1'/><polygon points='201.0,-10.0 227.0,-10.0 240.0,12.5 227.0,35.0 201.0,35.0 188.0,12.5'/><polygon points='201.0,35.0 227.0,35.0 240.0,57.5 227.0,80.1 201.0,80.1 188.0,57.5'/><polygon points='201.0,80.1 227.0,80.1 240.0,102.6 227.0,125.1 201.0,125.1 188.0,102.6'/><polygon points='162.0,-32.5 188.0,-32.5 201.0,-10.0 188.0,12.5 162.0,12.5 149.0,-10.0'/><polygon points='162.0,12.5 188.0,12.5 201.0,35.0 188.0,57.5 162.0,57.5 149.0,35.0'/><polygon points='123.0,-10.0 149.0,-10.0 162.0,12.5 149.0,35.0 123.0,35.0 110.0,12.5'/><polygon points='123.0,35.0 149.0,35.0 162.0,57.5 149.0,80.1 123.0,80.1 110.0,57.5'/><polygon points='84.0,-32.5 110.0,-32.5 123.0,-10.0 110.0,12.5 84.0,12.5 71.0,-10.0'/></g></svg>");
  background-repeat: no-repeat;
  background-position: top right;
  /* The patch runs off the top and right of its own box on purpose, so it
     crops on the rail's edge instead of ending on a whole shape. */
  -webkit-mask-image: linear-gradient(to bottom, #000 58%, transparent 100%);
          mask-image: linear-gradient(to bottom, #000 58%, transparent 100%);
}
/* No position:relative here. .sidenav is already position:sticky, which is a
   positioned element and therefore the containing block ::before needs —
   declaring relative afterwards silently overrode the sticky and let the whole
   rail scroll away with the page. */
.sidenav > * { position: relative; z-index: 1; }
.sidenav-close { display: none; }

.rail-toggle {
  flex: none;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border: 0;
  border-radius: 8px;
  background: rgba(255, 255, 255, .08);
  color: var(--chrome-ink);
  opacity: .7;
  cursor: pointer;
}
.rail-toggle:hover { opacity: 1; background: rgba(255, 255, 255, .16); }
.rail-toggle:focus-visible { outline: 2px solid var(--chrome-ink); outline-offset: 2px; opacity: 1; }
.rail-toggle .nav-icon { width: 18px; height: 18px; }
.rail-icon-out { display: none; }

.sidenav-nav {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 6px 12px 18px;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, .28) transparent;
}
.sidenav-nav::-webkit-scrollbar { width: 8px; }
.sidenav-nav::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, .26);
  border-radius: 4px;
}
.sidenav-nav::-webkit-scrollbar-track { background: transparent; }

/* --- groups, which fold away --- */

.nav-section + .nav-section { margin-top: 4px; }

.nav-group {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  margin: 16px 0 4px;
  padding: 6px 12px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  font: inherit;
  font-family: var(--display-font);
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  text-align: left;
  color: var(--chrome-ink);
  opacity: .5;
  cursor: pointer;
}
.nav-group:hover { opacity: .85; background: rgba(255, 255, 255, .06); }
.nav-group:focus-visible { outline: 2px solid var(--chrome-ink); outline-offset: -2px; opacity: 1; }
.nav-group .nav-icon {
  width: 14px;
  height: 14px;
  transform: rotate(90deg);
  transition: transform .16s ease;
}
.nav-group[aria-expanded="false"] .nav-icon { transform: rotate(0deg); }
.nav-group[aria-expanded="false"] + .nav-list { display: none; }

.sidenav-nav > .nav-section:first-child > .nav-list { margin-top: 2px; }

.nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 2px;
}
.nav-list form { margin: 0; }

.nav-link {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 8px 12px;
  border-radius: var(--r-sm);
  color: var(--chrome-ink);
  opacity: .8;
  text-decoration: none;
  font-size: .875rem;
  font-weight: 500;
  line-height: 1.25;
  transition: background-color .12s ease, opacity .12s ease;
}
.nav-link:hover { background: rgba(255, 255, 255, .07); opacity: 1; }
.nav-link:focus-visible {
  outline: 2px solid var(--chrome-ink);
  outline-offset: -2px;
  opacity: 1;
}
.nav-link.is-current {
  /* The page you are on is a solid block of the accent, not a wash of white
     with a marker down its edge. The wash sat only fourteen percent above the
     chrome, which on the navy is a difference you have to look for — and the
     marker was doing the work the background should have been doing. */
  background: var(--accent);
  color: #fff;
  opacity: 1;
  font-weight: 600;
}
.nav-link.is-current:hover { background: var(--accent); }
.nav-link-button {
  background: none;
  border: 0;
  font: inherit;
  font-size: .93rem;
  text-align: left;
  cursor: pointer;
}
.nav-icon { width: 20px; height: 20px; flex: none; }
.nav-link.is-current .nav-icon { opacity: 1; }

/* --- the person, and their two account actions --- */

.sidenav-foot {
  border-top: 1px solid rgba(255, 255, 255, .12);
  padding: 12px;
}
.whoami {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 12px 12px;
  min-width: 0;
}
.whoami-avatar {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background: rgba(255, 255, 255, .16);
  color: var(--chrome-ink);
  font-size: .8rem;
  font-weight: 700;
  letter-spacing: .02em;
}
.whoami-text { min-width: 0; line-height: 1.3; }
.whoami-text strong {
  display: block;
  font-size: .89rem;
  font-weight: 600;
  color: var(--chrome-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.whoami-text em {
  display: block;
  font-style: normal;
  font-size: .78rem;
  color: var(--chrome-ink);
  opacity: .6;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* --- brand mark --- */

.brand-mark {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  text-decoration: none;
  color: var(--chrome-ink);
}
.brand-mark img { max-height: 32px; max-width: 150px; display: block; }
.brand-mark .brand {
  /* A flex item will not shrink below its content width without this, and a
     long company name then pushes the whole app bar past the viewport. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: .95rem;
  letter-spacing: .12em;
}
.brand-name {
  min-width: 0;
  font-weight: 600;
  font-size: .96rem;
  letter-spacing: .04em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* In the sidebar the name wraps rather than truncating.
   "MERIDIAN FREIG…" is the customer's own name, cut off, in the one place on
   every page that is supposed to tell them whose account they are in. There is
   vertical room there and no horizontal room, so it uses the room it has: two
   lines, and the tracking eased off so two lines are needed less often. The app
   bar keeps the ellipsis, because on a phone the alternative is a brand lockup
   that pushes the menu button off the screen. */
.sidenav-head .brand-mark .brand,
.sidenav-head .brand-mark .brand-name {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
  overflow-wrap: anywhere;
  letter-spacing: .08em;
  line-height: 1.25;
}
.sidenav-head .brand-mark .brand-name { letter-spacing: .02em; }
/* The wordmark is a wordmark: it never breaks, whoever it sits inside. */
.sidenav-head .brand-mark .wordmark { white-space: nowrap; }

.inline { display: inline; margin: 0; }
.linkish {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
  text-decoration: underline;
}

/* --- the mobile top bar, hidden on desktop --- */

/* The dark app bar is gone as an element: on a phone its two jobs — the menu
   button and being the dark chrome at the top — moved into .topbar, so there
   is one bar there instead of two stacked. The button keeps the .appbar-
   prefix because that is what nav.js and the no-script rules address it by.
   The drawer only exists below 60rem, so neither does its button. */
.appbar-button { display: none; }

.nav-scrim { display: none; }

/* --- the content column --- */

/* The column is now the grid item, and it holds two things stacked: the top
   bar and the page. .shell used to be the grid item itself, which is why the
   content area had nowhere to put a header. */
.shell-column {
  grid-column: 2;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.shell {
  min-width: 0;
  padding: 28px var(--gutter) 88px;
  max-width: 1280px;
}

@media (min-width: 1500px) {
  :root { --nav-w: 280px; --gutter: 44px; }
}

/* --- the collapsed rail ---
   Icons only, at the width the icons actually need. The label is kept in the
   markup rather than removed — a screen reader still reads it, and it comes
   back as a hover tooltip so the rail stays usable by someone who has not
   memorised nine glyphs. */

@media (min-width: 961px) {
  html.rail-collapsed { --nav-w: 72px; }

  html.rail-collapsed .sidenav-head {
    justify-content: center;
    padding: 20px 8px 16px;
  }
  html.rail-collapsed .sidenav-head .brand-mark { display: none; }
  html.rail-collapsed .rail-icon-in  { display: none; }
  html.rail-collapsed .rail-icon-out { display: grid; }

  html.rail-collapsed .sidenav-nav { padding: 6px 8px 18px; }
  html.rail-collapsed .nav-group { display: none; }
  /* A folded group would hide its icons with no heading to explain why. */
  html.rail-collapsed .nav-list { display: grid !important; }
  html.rail-collapsed .nav-section + .nav-section {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid rgba(255, 255, 255, .12);
  }

  html.rail-collapsed .nav-link {
    justify-content: center;
    padding: 11px 0;
    position: relative;
  }
  /* The label is hidden from the eye but NOT from the accessibility tree.
     display:none would take it out of both, leaving every item in the rail
     with no accessible name at all — nine unlabelled icons to a screen
     reader. This is the clip technique instead. */
  html.rail-collapsed .nav-link > span {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  html.rail-collapsed .nav-link.is-current { box-shadow: none; background: rgba(255, 255, 255, .18); }

  html.rail-collapsed .sidenav-foot { padding: 10px 8px; }
  html.rail-collapsed .whoami { justify-content: center; padding: 6px 0 12px; }
  html.rail-collapsed .whoami-text { display: none; }
}

.rail-tip {
  position: fixed;
  z-index: 80;
  padding: 6px 11px;
  border-radius: 6px;
  background: var(--navy-deep);
  color: var(--chrome-ink);
  font-size: .82rem;
  font-weight: 500;
  line-height: 1.3;
  white-space: nowrap;
  pointer-events: none;
  box-shadow: 0 8px 24px -8px rgba(0, 0, 0, .65);
  opacity: 0;
  transition: opacity .12s ease;
}
.rail-tip.is-visible { opacity: 1; }
.rail-tip[hidden] { display: none; }

/* Widths change on a class, so the main column has to be told to follow. */
.app-frame { transition: grid-template-columns .18s ease; }
@media (prefers-reduced-motion: reduce) {
  .app-frame { transition: none; }
}

/* --- drawer --- */

@media (max-width: 960px) {
  /* minmax(0, 1fr), not 1fr: an "auto" grid minimum takes its size from the
     track's min-content, and a long brand name in the app bar was widening
     the whole page by a few pixels because of it. */
  .app-frame { grid-template-columns: minmax(0, 1fr); }

  /* The gutter is a variable so a full-bleed element can cancel it exactly;
     hard-coding the padding here would leave the sticky preview overhanging. */
  /* Zero, and deliberately still declared: the designer's sticky preview and
     .topbar's own offset are both measured from it, and a variable that
     quietly stops existing takes those with it. There is one bar now, and it
     is .topbar. */
  :root { --appbar-h: 0px; --gutter: 16px; }

  .appbar-button {
    flex: none;
    display: grid;
    place-items: center;
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: 8px;
    background: rgba(255, 255, 255, .1);
    color: var(--chrome-ink);
    cursor: pointer;
  }
  .appbar-button:hover { background: rgba(255, 255, 255, .18); }

  .sidenav {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 60;
    width: min(300px, 84vw);
    height: 100%;
    transform: translateX(-102%);
    transition: transform .22s ease;
    box-shadow: 0 0 40px rgba(0, 0, 0, .45);
  }
  .sidenav.is-open { transform: none; }
  .sidenav-close {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    flex: none;
    border: 0;
    border-radius: 8px;
    background: rgba(255, 255, 255, .1);
    color: var(--chrome-ink);
    cursor: pointer;
  }

  .nav-scrim {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 55;
    background: rgba(6, 14, 28, .55);
    opacity: 0;
    transition: opacity .22s ease;
  }
  .nav-scrim.is-open { opacity: 1; }
  .nav-scrim[hidden] { display: none; }

  .shell-column { grid-column: 1; }
  .shell { padding: 22px var(--gutter) 72px; }

  .topbar { top: calc(var(--appbar-h, 0px) + var(--imp-h, 0px)); }

  /* The drawer is fixed, so the page behind it must not scroll with it. */
  body.nav-open { overflow: hidden; }
}

@media (max-width: 480px) {
  :root { --gutter: 13px; }
  .shell { padding: 18px var(--gutter) 64px; }
}

/* No script, no drawer — so keep the sidebar in the flow rather than
   off-canvas where its links would be unreachable. */
@media (max-width: 960px) {
  html.no-js .sidenav { position: static; width: auto; transform: none; box-shadow: none; height: auto; }
  html.no-js .sidenav-nav { overflow: visible; }
  html.no-js .appbar-button { display: none; }
}

/* ---------- the XINNACE wordmark ----------
   XINN takes the colour of whatever it sits on, ACE is always the brand teal.
   --wordmark-ink defaults to currentColor so the mark can never disappear:
   on the navy chrome the primary colour would be invisible against itself,
   so there it simply inherits the text colour. */

/* letter-spacing is inherited, and the mark sits inside things that have
   their own: .brand carries .16em for the sidebar lockup, an eyebrow may
   carry more. XINNACE is a wordmark, not a tracked-out label, so it declares
   its own spacing and ignores whatever it is sitting in. */
.wordmark { white-space: nowrap; letter-spacing: normal; }
.wordmark-xinn { color: var(--wordmark-ink, var(--wordmark-paper, currentColor)); }
.wordmark-ace  { color: var(--wordmark-ace-ink, var(--wordmark-ace-paper, var(--xinnace-teal))); }

/* On the navigation the background is the brand's own primary colour, so both
   halves use the values computed against that instead.

   This used to name .appbar as well. There is no .appbar any more — its two
   jobs moved into .topbar — but the topbar is only dark below 60rem, and above
   it the mark would be sitting on white, so the override is scoped to the
   width where the bar is actually navy rather than applied unconditionally. */
.sidenav .wordmark {
  --wordmark-ink: var(--wordmark-chrome);
  --wordmark-ace-ink: var(--wordmark-ace-chrome);
}
@media (max-width: 60rem) {
  .topbar .wordmark {
    --wordmark-ink: var(--wordmark-chrome);
    --wordmark-ace-ink: var(--wordmark-ace-chrome);
  }
}


/* ---------- signed-out chrome ---------- */

body.plain {
  background: linear-gradient(160deg, var(--navy-deep), var(--navy) 55%, #1B3557);
  min-height: 100vh;
}
.plain-shell {
  min-height: 100vh;
  display: grid;
  place-content: center;
  justify-items: center;
  gap: 18px;
  padding: 40px 20px;
}
.plain-card {
  background: var(--surface);
  border-radius: 12px;
  padding: 40px 36px;
  width: min(420px, 92vw);
  box-shadow: 0 24px 60px -24px rgba(0,0,0,.6);
}
/* The same shell holding a document instead of a form.
 *
 * An invoice opened from an email lands here, and 420 points is the width of a
 * sign-in box rather than of something with a table in it. Top-aligned as well
 * as wider: a tall document centred vertically starts below the fold, so the
 * first thing the reader sees is the middle of their own bill. */
.plain-shell-wide { place-content: start center; }
.plain-card-wide  { width: min(860px, 94vw); padding: 34px 30px; }

@media (max-width: 560px) {
  .plain-card-wide { padding: 22px 16px; }
}

/* Nothing but the document when it goes to paper. The shell's dark ground and
   its padding are chrome, and chrome is what the print rules already strip
   everywhere else this document is shown. */
@media print {
  .plain-shell-wide { padding: 0; min-height: 0; }
  .plain-card-wide  { width: auto; padding: 0; box-shadow: none; border-radius: 0; }
}

.plain-card .brand { color: var(--navy); margin-bottom: 26px; }
.plain-card .brand-mark { color: var(--navy); justify-content: center; margin-bottom: 24px; }
/* The secondary way out of a signed-out screen: forgot password, back to
   sign in. Quiet, but never so quiet it reads as decoration. */
.plain-alt {
  margin: 22px 0 0;
  padding-top: 18px;
  border-top: 1px solid var(--rule);
  font-size: .88rem;
  color: var(--ink-soft);
  text-align: center;
}
.plain-alt a { color: var(--accent); }
.plain-card .btn { display: block; width: 100%; text-align: center; }

/* The cross-sell under the enquiry form. Set apart by a rule rather than a
   panel, so it reads as an afterthought to the page rather than a rival to
   the form above it. */
.cross-sell {
  margin: 26px 0 0;
  padding-top: 20px;
  border-top: 1px solid var(--rule);
  text-align: center;
}
.cross-sell p {
  margin: 0 0 12px;
  font-size: .9rem;
  color: var(--ink-soft);
}
.cross-sell .btn { margin-top: 0; }

.plain-foot {
  color: rgba(255,255,255,.55);
  font-size: .85rem;
  margin: 0;
  letter-spacing: .04em;
}

/* ---------- type ---------- */

h1 {
  font-size: 1.6rem;
  line-height: 1.2;
  letter-spacing: -.015em;
  margin: 0 0 8px;
  text-wrap: balance;
}
/* ix-page-subtitle: the small grey line under a title, not body copy. */
.lede { color: var(--ink-soft); font-size: .875rem; margin: 0 0 24px; max-width: 68ch; }

/* ix-page-title. Poppins names the page; the reading face never does. */
.topbar h1 {
  font-family: var(--display-font);
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: -.015em;
  color: var(--ink);
  margin: 0;
}

/* ix-eyebrow: the small tracked-out label that sits above a title. The panel
   headings used to be set in this and had nothing above them; it is a label
   for a section, which is a different job. */
.eyebrow {
  font-family: var(--display-font);
  font-size: .6875rem;
  font-weight: 600;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 6px;
}
.hint { color: var(--ink-faint); font-size: .85rem; margin: -6px 0 4px; }

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

.stack { display: grid; gap: var(--gap-sm); }
.stack label {
  font-size: .8125rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--ink-soft);
  margin-top: 10px;
}
/*
 * Any text field in a panel, not only the ones inside a .stack.
 *
 * The custom-cards screen lays its term form out on its own grid, so those
 * fields sat outside .stack and got no styling at all: raw browser controls,
 * square and grey, beside buttons that had been restyled. A rule that dresses
 * one layout is a rule the next layout will miss.
 *
 * Tick boxes, radios, colours, ranges and file pickers are excluded — they are
 * not text fields, and sizing them like one is what made a checkbox render as
 * a large empty square.
 */
.stack input,
.panel input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="color"]):not([type="range"]) {
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  font-size: .875rem;
  min-height: 40px;
  padding: 9px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  width: 100%;
  transition: border-color .14s ease, box-shadow .14s ease;
}
.stack input:hover,
.panel input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="color"]):not([type="range"]):hover { border-color: var(--ink-faint); }
/* A ring rather than an outline, so the field itself lights up instead of
   growing a second rectangle a pixel outside its own border. */
.stack input:focus-visible,
.panel input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="color"]):not([type="range"]):focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 26%, transparent);
}

a.btn {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  line-height: 1.4;
}
.btn {
  font: inherit;
  font-weight: 600;
  letter-spacing: -.005em;
  font-size: .875rem;
  /* 36px, the kit's control height. Tighter than the 44px thumb minimum on
     purpose: these sit in dense rows in a desktop tool, and the phone layouts
     give their own controls room instead. */
  min-height: 36px;
  padding: 8px 16px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  cursor: pointer;
  margin-top: 20px;
  transition: background-color .14s ease, border-color .14s ease,
              color .14s ease, box-shadow .14s ease, transform .14s ease;
}
.btn-primary {
  background: var(--accent); color: #fff;
  box-shadow: var(--shadow-sm);
}
.btn-primary:hover { background: var(--accent-strong); box-shadow: var(--shadow); }
/* Pressed moves. Nothing else on the page does, so it is unambiguous. */
.btn:active { transform: translateY(1px); }
.btn:disabled, .btn[disabled] {
  opacity: .45; cursor: not-allowed; transform: none; box-shadow: none;
}
/* The accent, not the navy: on a company that has chosen a dark chrome the
   navy ring was invisible against its own buttons. */
.btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* ---------- messages ---------- */

.alert {
  padding: 14px 16px;
  border-radius: var(--r-sm);
  border-left: 3px solid;
  font-size: .93rem;
  margin: 0 0 var(--gap);
}
.alert-error { background: var(--danger-soft); border-color: var(--danger); color: #6E2013; }
.alert-ok    { background: var(--ok-soft);     border-color: var(--ok);     color: #124D34; }
/* Amber, for a thing that has not gone wrong and is going to happen. A closure
   waiting out its ninety days is not an error and is not routine either: it is
   a deletion somebody can still stop, and the last week of it is the only week
   in which reading the screen changes anything. */
.alert-caution { background: var(--caution-soft); border-color: var(--caution); color: #6B3D06; }

/* ---------- stat tiles ---------- */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 16px;
  margin: 0 0 32px;
}
/* auto-fit drops to one very tall column on a phone; two up reads better
   and keeps the numbers above the fold. */
@media (max-width: 560px) {
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; margin-bottom: 24px; }
  .tile { padding: 13px 14px; }
  .tile-label { font-size: .66rem; letter-spacing: .08em; }
  .tile-value { font-size: 1.5rem; }
}
.tile {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 18px 20px;
  box-shadow: var(--shadow);
  display: grid;
  gap: 6px;
}
.tile-label {
  font-size: .75rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.tile-value {
  font-size: 2rem;
  font-weight: 700;
  line-height: 1;
  font-variant-numeric: tabular-nums;
  color: var(--navy);
}
/* Still legible, still the same size — it has to stay comparable with the
   numbers beside it — but no longer the loudest thing on a quiet page. */
.tile-value.is-zero { color: var(--ink-faint); font-weight: 600; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* ---------- page furniture ---------- */

.page-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  margin-bottom: 26px;
}
.page-head .lede { margin-bottom: 0; }
.page-head .btn { margin-top: 0; }
.page-head h1 { margin-bottom: 6px; }
.head-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.head-actions .btn { margin-top: 0; }

@media (max-width: 560px) {
  .page-head { gap: 14px; margin-bottom: 20px; }
  .head-actions { width: 100%; }
  .head-actions .btn { flex: 1 1 auto; }
}

.empty {
  background: var(--surface);
  border: 1px dashed var(--rule-strong);
  border-radius: var(--radius);
  padding: 40px 24px;
  text-align: center;
  color: var(--ink-soft);
}

.btn-quiet {
  background: transparent;
  color: var(--ink-soft);
  border-color: var(--rule-strong);
}
.btn-quiet:hover {
  color: var(--ink); border-color: var(--ink-faint);
  background: var(--brand-surface-tint, #F5FAFA);
}

/* ---------- forms in panels ---------- */

.form-panels { display: grid; gap: 18px; max-width: 640px; }
.panel {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
  padding: 22px 24px 26px;
  box-shadow: var(--shadow);
}
.panel + .panel { margin-top: var(--gap); }
.panel h2 {
  /* A header band across the top of the card, not a line of text with a rule
     under it. The negative margins pull it back out to the panel's own edges,
     so the split reads as structure — which is what ix-card-header does in the
     Reports kit, and it is the single thing that most made a panel there look
     built rather than typed. Done here rather than by wrapping every panel's
     contents in a body div across forty views. */
  font-family: var(--display-font);
  font-size: .9375rem;
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--ink);
  margin: -22px -24px 20px;
  padding: 14px 24px;
  border-bottom: 1px solid var(--rule);
  border-radius: var(--r-md) var(--r-md) 0 0;
  background: var(--brand-surface-tint, #F5FAFA);
}
.form-actions { display: flex; gap: 12px; align-items: center; }
.form-actions .btn { margin-top: 0; }

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

.table-scroll {
  overflow-x: auto;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);

  /* On a phone the row actions sit off the right edge and nothing said so.
     The two "local" layers scroll with the content and cover the shadows;
     the two fixed ones show through only while there is more to reach, so
     the cue appears and disappears on its own with no script. */
  background-image:
    linear-gradient(to right, var(--surface) 30%, rgba(255, 255, 255, 0)),
    linear-gradient(to left,  var(--surface) 30%, rgba(255, 255, 255, 0)),
    linear-gradient(to right, rgba(18, 35, 63, .16), rgba(18, 35, 63, 0)),
    linear-gradient(to left,  rgba(18, 35, 63, .16), rgba(18, 35, 63, 0));
  background-position: 0 0, 100% 0, 0 0, 100% 0;
  background-repeat: no-repeat;
  background-size: 34px 100%, 34px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}
table.data {
  border-collapse: collapse;
  width: 100%;
  min-width: 640px;
  font-size: .94rem;
}
table.data th, table.data td {
  text-align: left;
  padding: 14px 18px;
  border-bottom: 1px solid var(--rule);
}
/* Rows respond to the pointer. On a wide table it is the cheapest way to keep
   your eye on one row while reading across it. */
table.data tbody tr { transition: background-color .12s ease; }
/* The brand wash rather than a grey, so a hovered row reads as warm rather
   than merely dimmer than the one beside it. */
table.data tbody tr:hover { background: color-mix(in srgb, var(--accent) 6%, var(--surface)); }
table.data thead th {
  font-family: var(--display-font);
  font-size: .6875rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
  background: var(--brand-surface-tint, #F5FAFA);
}
table.data tbody tr:last-child td { border-bottom: 0; }
table.data .num { text-align: right; font-variant-numeric: tabular-nums; }
table.data .mono {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: .85em;
  color: var(--ink-soft);
}

/* A count of nothing is not the same kind of fact as a count of something.
   Printed at full strength it competes with the figures that matter, and a
   column of dark zeros is what makes a quiet week look like a broken page. */
table.data .num.is-zero { color: var(--ink-faint); font-weight: 400; }

/* ---------- tables become cards on a phone ---------- */

/* A six-column table on a 390px screen is a table you scroll sideways through
   one column at a time, and the row-actions column — the reason most people
   opened the page — is the one furthest off the edge. Below this width each
   row becomes its own small card, with every cell labelled by the heading it
   sat under.
   The labels come from data-label on the cell, not from the <th>, because
   nothing in CSS can read across to another element; a cell without one shows
   its value alone, which is right for the name at the top of the card. */
@media (max-width: 720px) {
  .table-scroll {
    overflow-x: visible;
    background-image: none;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
  table.data { min-width: 0; display: block; font-size: 1rem; }
  table.data thead {
    /* Not display:none — the headings are the labels, and removing them from
       the accessibility tree as well would leave a screen reader reading six
       unlabelled values per person. */
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  table.data tbody { display: grid; gap: 12px; }
  table.data tbody tr {
    display: block;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    box-shadow: var(--shadow);
    padding: 4px 16px;
  }
  table.data td, table.data tbody tr:last-child td {
    display: grid;
    grid-template-columns: minmax(0, 108px) minmax(0, 1fr);
    gap: 4px 14px;
    align-items: baseline;
    /* Grid items stretch by default, which turned every status pill into a
       full-width bar — a rounded lozenge is a label, and a label the width of
       the screen reads as a progress bar. */
    justify-items: start;
    padding: 10px 0;
    border-bottom: 1px solid var(--rule);
    text-align: left;
  }
  table.data tbody tr td:last-child { border-bottom: 0; }
  table.data td[data-label]::before {
    content: attr(data-label);
    font-size: .72rem;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-faint);
    font-weight: 600;
  }
  /* The first cell is the card's heading: full width, no label. */
  table.data tbody tr td:first-child { grid-template-columns: 1fr; padding-top: 14px; }
  table.data tbody tr td:first-child::before { display: none; }
  table.data td.num { text-align: left; }
  table.data td.actions { text-align: left; }
  table.data td.actions .action-group { justify-content: flex-start; }
  .action-group > .sub { text-align: left; }
  td .person + .person-email { margin-left: 45px; }
  .rowmenu-panel { right: auto; left: 0; }
}

/* ---------- a person in a list ---------- */

.person { display: flex; align-items: center; gap: 11px; min-width: 0; }
.person-face {
  flex: none;
  width: 34px; height: 34px;
  border-radius: 50%;
  object-fit: cover;
  background: var(--rule);
}
.person-initial {
  display: grid;
  place-items: center;
  font-weight: 700;
  font-size: .95rem;
  color: var(--ink-soft);
  /* Not the accent: thirty of these down a page would be thirty teal dots
     shouting over the one button on the page that is meant to be teal. */
  background: linear-gradient(160deg, #E8EDF4, #DCE4EE);
}
.person-text { min-width: 0; display: block; }
.person-name { font-weight: 600; display: block; overflow-wrap: anywhere; }
.person-name .pill { margin-left: 6px; vertical-align: 2px; }
.person-email { display: block; }
/* Belongs to the text column, not to the row, or it aligns under the face. */
td .person + .person-email { margin-left: 45px; }

/* Removed and erased rows keep their faces, dimmed with the rest of the row. */
tr.is-removed .person-face, tr.is-erased .person-face { opacity: .55; }

/* ---------- the row overflow menu ---------- */

/* <details>, not a scripted popover: it opens with no JavaScript at all, it is
   keyboard-reachable and announced as a disclosure for free, and there is no
   inline handler for the content security policy to have to allow. rowmenu.js
   only adds the two conveniences the element does not have on its own —
   closing on Escape, and closing when you click somewhere else. */
.rowmenu { position: relative; flex: none; }
.rowmenu > summary {
  list-style: none;
  cursor: pointer;
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  border-radius: 8px;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  color: var(--ink-soft);
  font-size: 1.1rem;
  line-height: 1;
  user-select: none;
}
.rowmenu > summary::-webkit-details-marker { display: none; }
.rowmenu > summary::marker { content: ''; }
.rowmenu > summary:hover { background: var(--paper); color: var(--ink); }
.rowmenu > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.rowmenu[open] > summary { background: var(--paper); color: var(--ink); }

.rowmenu-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 30;
  min-width: 250px;
  display: grid;
  gap: 8px;
  justify-items: stretch;
  padding: 12px;
  text-align: left;
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  border-radius: 12px;
  box-shadow: 0 16px 34px rgba(18, 35, 63, .18);
}
.rowmenu-panel form { display: block; margin: 0; }
/* Menu items, so they read down a left edge like a list rather than sitting
   centred like a row of buttons. */
.rowmenu-panel .btn {
  width: 100%;
  justify-content: flex-start;
  text-align: left;
  white-space: nowrap;
}
.rowmenu-note {
  margin: 0;
  font-size: .82rem;
  color: var(--ink-soft);
  line-height: 1.4;
}
/* The last row's menu would open below the bottom of the scroll container and
   be clipped by it. It opens upwards instead. */
tbody tr:nth-last-child(-n+2) .rowmenu-panel { top: auto; bottom: calc(100% + 6px); }

/* .table-scroll scrolls, which on a long table would cut an open menu off at
   the container's edge. Only while a menu is open, because that clipping is
   what draws the scroll shadows the rest of the time.

   Honest note: with a short table an open menu fits inside the container
   anyway, so this rule has no visible effect on the test fixture and no test
   here proves it. It is kept for the long list this fixture is not — a company
   with thirty staff, where a menu opened two thirds of the way down has
   somewhere to be cut off. */
.table-scroll:has(.rowmenu[open]) { overflow: visible; }

.pill {
  display: inline-block;
  font-size: .72rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  font-weight: 600;
  padding: 4px 11px;
  border-radius: var(--r-pill);
}
.pill-active    { background: var(--ok-soft);     color: #124D34; }
.pill-suspended { background: #FDF3E0;            color: #7A4C09; }
.pill-inactive  { background: var(--rule);        color: var(--ink-soft); }

/* ---------- one-time credential handover ---------- */

/* The handover panel means something went wrong: an invitation could not be
   sent, so a password has to be passed on by hand after all. It used to be the
   normal outcome and wore the calm accent colour to match. Now it is the
   exception, and it should look like one. */
.handover {
  background: var(--danger-soft);
  border: 1px solid #E8C4BC;
  border-left: 3px solid var(--danger);
  border-radius: var(--radius);
  padding: 22px 24px;
  margin-bottom: 26px;
}

/* The ordinary outcome: everyone was emailed and there is nothing to keep. */
.invited {
  background: var(--accent-soft);
  border: 1px solid #B9DDD9;
  border-left: 3px solid var(--accent);
  border-radius: var(--radius);
  padding: 22px 24px;
  margin-bottom: 26px;
}
.invited h2 { font-size: 1.05rem; margin: 0 0 8px; }
.invited p  { margin: 0 0 14px; max-width: 60ch; color: var(--ink-soft); }
.handover h2 { font-size: 1.05rem; margin: 0 0 8px; }
.handover p { margin: 0 0 14px; max-width: 60ch; color: var(--ink-soft); }
.handover dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 8px 20px;
  margin: 0 0 14px;
}
.handover dt {
  font-size: .75rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding-top: 2px;
}
.handover dd { margin: 0; }
.handover code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  background: #fff;
  border: 1px solid #B9DDD9;
  border-radius: 4px;
  padding: 3px 8px;
  font-size: .95em;
  letter-spacing: .04em;
  user-select: all;
}
.handover .hint { margin: 0; }

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

td .sub {
  display: block;
  font-size: .82rem;
  color: var(--ink-faint);
  margin-top: 2px;
}
/* A row can carry more than one action now — "Send invitation" alongside
   "Activate" — and each is its own form. Left as blocks they stacked flush
   against each other with no gap and ragged edges, because this was written
   when there was only ever one. */
/* nowrap was right when a row had one control and two were a squeeze. A row
   can now carry an invitation, an activate, a remove, and — once somebody has
   been removed — a restore, a confirmation field and an erase. Six controls on
   one unbreakable line pushed the last of them off the edge of the table,
   where it was still clickable by anyone who thought to scroll sideways and
   invisible to everyone else. They wrap now, and the row grows instead. */
td.actions { text-align: right; vertical-align: middle; }

/* The flex container is INSIDE the cell, not the cell itself. Setting
   display:flex on a <td> takes it out of the table layout algorithm: the
   column stops sharing the row's height and its borders drift away from
   everybody else's, which looked like the table had come apart. */
.action-group {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  align-items: center;
  gap: 8px;
}
td.actions form { margin: 0; display: inline-block; vertical-align: middle; }

/* The status line above the controls — "Removed 3 June · erased in 74 days" —
   takes the full width so the buttons start on a line of their own rather than
   trailing off the end of a sentence. */
.action-group > .sub { flex-basis: 100%; text-align: right; margin: 0 0 2px; }
.btn-small {
  margin-top: 0;
  padding: 6px 12px;
  font-size: .84rem;
}

.stack select,
.panel select {
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  font-size: .875rem;
  min-height: 40px;
  padding: 9px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--r-sm);
  width: 100%;
  transition: border-color .14s ease, box-shadow .14s ease;
}
.stack select:hover,
.panel select:hover { border-color: var(--ink-faint); }
/* A ring rather than an outline, so the field itself lights up instead of
   growing a second rectangle a pixel outside its own border. */
.stack select:focus-visible,
.panel select:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--accent) 26%, transparent);
}

/* ---------- tick boxes ----------
   .stack input styles every input in a stack as a full-width field, which is
   right for the text ones and absurd for a tick box: width:100% and a 44px
   minimum turned a checkbox into a large empty square with its label stranded
   on the line above. The two selectors below take it back out of that rule.

   accent-color brands the native control, so it follows a company's chosen
   colour without anyone building a custom one out of divs — which is the usual
   way a tick box stops being reachable by keyboard. */
.stack input[type="checkbox"],
.stack input[type="radio"],
.panel input[type="checkbox"],
.panel input[type="radio"],
.checkbox input[type="checkbox"],
.checkbox input[type="radio"] {
  width: 1.15rem;
  height: 1.15rem;
  min-height: 0;
  padding: 0;
  margin: 0;
  flex: 0 0 auto;
  accent-color: var(--accent);
  cursor: pointer;
}

/* The label wraps the control, so the words are part of the target rather than
   a caption beside it. */
.checkbox,
.stack label.checkbox {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: .94rem;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--ink);
  cursor: pointer;
  margin-top: 6px;
  text-transform: none;
}

/* The live-card banner: a real link, plus the two things anyone actually
   wants to do with it. */
.card-live {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 14px 20px;
  flex-wrap: wrap;
}
.card-live-text { min-width: 0; }
.card-live-url {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: .92rem;
  word-break: break-all;
}
.card-live-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.card-live-actions .btn { margin-top: 0; }

/* ---------- my card ---------- */

.alert-quiet {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--rule-strong);
  color: var(--ink-soft);
}

.photo-row { display: flex; gap: 22px; align-items: flex-start; flex-wrap: wrap; }
.photo-input { flex: 1 1 240px; }
.photo-current img,
.photo-current .photo-empty {
  width: 96px;
  height: 96px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  object-fit: cover;
  border: 1px solid var(--rule);
}
.photo-current .photo-empty {
  background: var(--surface-alt, #EEF2F6);
  color: var(--ink-faint);
  font-size: 2rem;
  font-weight: 600;
  text-transform: uppercase;
}
.stack input[type="file"] {
  padding: 9px 11px;
  background: var(--paper);
}

.stack textarea {
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  padding: 11px 13px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  width: 100%;
  resize: vertical;
}
.stack textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
.stack input:disabled,
.stack textarea:disabled {
  background: var(--paper);
  color: var(--ink-faint);
  cursor: not-allowed;
}

.lock-tag {
  font-weight: 400;
  font-size: .72rem;
  letter-spacing: .04em;
  text-transform: none;
  color: var(--ink-faint);
  margin-left: 8px;
}
.locked { color: var(--ink-faint); font-size: .9rem; margin: 0; }

/* The line above the submit button that says what happens to what you type.
   POPIA wants the notification at the point of collection, so it has to be
   on the form rather than only behind a link — but it is supporting text, not
   the form, so it reads quieter than the fields and louder than nothing. */
.form-notice {
  margin: 4px 0 0;
  padding: 12px 14px;
  background: var(--paper);
  border-radius: var(--radius);
  font-size: .84rem;
  line-height: 1.55;
  color: var(--ink-soft);
}
.form-notice a {
  color: var(--accent-strong);
  font-weight: 600;
  text-underline-offset: .2em;
}
.form-notice a:hover { text-decoration-thickness: 2px; }

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

.photo-caption {
  display: block;
  margin-top: 8px;
  font-size: .72rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  text-align: center;
}
.photo-current { text-align: center; }

.cropper {
  margin-top: 22px;
  padding-top: 22px;
  border-top: 1px solid var(--rule);
}
.cropper[hidden] { display: none; }
.cropper-lede {
  color: var(--ink-soft);
  font-size: .92rem;
  margin: 0 0 16px;
  max-width: 52ch;
}
/* Crop a square out of the photo, or use the whole thing letterboxed into
   one — a headshot wants the first, a logo or a group shot the second, and
   nothing about the photo itself says which until a person looks at it. */
.cropper-fit {
  display: flex;
  gap: 8px;
  margin: 0 0 16px;
}
.cropper-fit .btn[aria-pressed="true"] {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}
.cropper.is-contain .cropper-zoom-row { display: none; }

.cropper-stage {
  display: flex;
  gap: 26px;
  align-items: center;
  flex-wrap: wrap;
}
.cropper-frame {
  position: relative;
  width: 260px;
  height: 260px;
  flex: 0 0 auto;
  overflow: hidden;
  border-radius: var(--radius);
  background: #0F1620;
  /* Not grab. The crop starts as the largest square the photo contains, so it
     starts with no slack to drag into — and for a square photo it has none in
     either direction until you zoom in. A grab cursor over a drag that clamps
     to zero reads as a broken control. The script adds .is-pannable when there
     is somewhere to go. */
  cursor: default;
  touch-action: none;
  user-select: none;
}
.cropper.is-contain .cropper-frame { cursor: default; touch-action: auto; }
.cropper-frame.is-pannable { cursor: grab; }
.cropper-frame.is-pannable.is-dragging { cursor: grabbing; }
.cropper-note {
  flex-basis: 100%;
  margin: 8px 0 0;
  font-size: .8125rem;
  color: var(--ink-soft);
}
.cropper-frame:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.cropper-frame img {
  position: absolute;
  /* Starts at zero and is sized by the script. Without this an unstyled or
     not-yet-scripted page would lay the raw photo out at its natural size. */
  width: 0;
  height: 0;
  max-width: none;
  pointer-events: none;
  user-select: none;
}
/* The circle is a guide only — the saved image is the whole square. */
.cropper-mask {
  position: absolute;
  inset: 0;
  pointer-events: none;
  box-shadow: 0 0 0 999px rgba(15, 22, 32, .55) inset;
  border-radius: 50%;
}
.cropper-controls {
  flex: 1 1 220px;
  display: grid;
  gap: 10px;
  align-content: center;
}
.cropper-zoom-row { display: grid; gap: 10px; }
.cropper-controls label {
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink-soft);
}
.cropper-controls input[type="range"] { width: 100%; accent-color: var(--accent); }
.cropper-controls .btn { justify-self: start; }

/* ---------- company settings ---------- */

.logo-current {
  width: 150px;
  min-height: 84px;
  display: grid;
  place-items: center;
  padding: 12px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}
.logo-current img { max-width: 100%; max-height: 64px; display: block; }
.logo-empty {
  font-size: .8rem;
  color: var(--ink-faint);
  text-align: center;
  line-height: 1.3;
}

td.link-icon { width: 46px; color: var(--ink-soft); line-height: 0; }
td.url-cell {
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

th.tick, td.tick { text-align: center; width: 150px; }
td.tick input[type="checkbox"] {
  width: 19px; height: 19px;
  accent-color: var(--accent);
  cursor: pointer;
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px;
  margin: -1px; padding: 0; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ---------- my card ---------- */

/* Two columns, the same shape as the company designer: what you are changing
   on the left, what it produces on the right. The page was a narrow form with
   half the viewport empty beside it, and the only way to see your own card was
   to open it in another tab. */
.mycard {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 420px);
  gap: 28px;
  align-items: start;
}
.mycard-preview { position: sticky; top: 24px; min-width: 0; }
.mycard-preview h2 { margin: 0 0 2px; font-size: 1rem; }
.mycard-preview .hint { margin: 0 0 12px; }

/* Not .preview-frame: the company designer already owns that name further
   down this file, where the frame is a scaled navy stage sized by the card
   inside it. Reusing it here meant the designer's rules — which come later and
   therefore win — collapsed this iframe to the height of its empty parent. */
.mycard-frame {
  border-radius: 16px;
  overflow: hidden;
  background: var(--surface);
  border: 1px solid var(--rule);
  box-shadow: var(--shadow);
}
.mycard-frame iframe { display: block; width: 100%; height: 720px; border: 0; }

/* What the company set, as facts rather than as disabled boxes. A greyed-out
   input invites a click and then does nothing. */
.managed-list { margin: 0; display: grid; gap: 12px; }
.managed-list > div {
  display: grid;
  grid-template-columns: minmax(0, 150px) minmax(0, 1fr);
  gap: 12px;
  align-items: baseline;
}
.managed-list dt { font-size: .84rem; color: var(--ink-soft); }
.managed-list dd { margin: 0; font-weight: 500; overflow-wrap: anywhere; }
.managed-empty { color: var(--ink-faint); font-weight: 400; }

/* The save button follows you down a long form instead of waiting at the
   bottom of it.
   Opaque, with a rule along the top: a sticky bar necessarily passes over the
   content above it, and a fading translucent background made that read as a
   rendering fault — the heading of the panel underneath showed through it
   half-erased. An opaque bar reads as a bar. */
.form-actions-sticky {
  position: sticky;
  bottom: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
  margin-top: 4px;
  padding: 14px 2px;
  background: var(--paper);
  border-top: 1px solid var(--rule);
}
.form-actions-sticky .hint { margin: 0; }

@media (max-width: 1100px) {
  /* Below this the preview would be too narrow to be a preview. It goes above
     the form rather than below it: on a phone the first thing you want is to
     see the card, then scroll into the fields that change it. */
  .mycard { grid-template-columns: 1fr; }
  /* Capped rather than stretched. A full-width frame does not make the card
     bigger — the card has its own maximum width — it just surrounds it with
     empty stage, and the height has to stay the same or the card is cut off
     inside a frame that does not scroll. */
  .mycard-preview { position: static; order: -1; max-width: 420px; }
}

/* ---------- file picker ---------- */

.filepick { display: inline-block; cursor: pointer; }
/* Hidden but real: still focusable, still what the form posts, still what the
   server validates. Not display:none, which takes it out of the tab order. */
.filepick input[type="file"] {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%);
  white-space: nowrap; border: 0;
}
.filepick-face {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 10px 16px;
  border-radius: 10px;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  font-weight: 600;
  font-size: .92rem;
  transition: border-color .15s ease, background .15s ease;
}
.filepick:hover .filepick-face { border-color: var(--accent); }
/* :focus-within, because the focus lands on the input the label is hiding. */
.filepick input[type="file"]:focus-visible + .filepick-face {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ---------- phone field ---------- */

/* The country and the number are one control, so they sit on one line. The
   select is sized to its content rather than sharing the width evenly: the
   number is what people read back to check, and it should not be the narrower
   of the two. */
.phone-field {
  display: grid;
  grid-template-columns: minmax(0, auto) minmax(0, 1fr);
  gap: 8px;
  align-items: center;
}
.phone-field select { max-width: 15rem; }

@media (max-width: 520px) {
  /* Two controls at 8px apart on a narrow screen leaves the number unreadably
     short. Stacked, both get the full width. */
  .phone-field { grid-template-columns: 1fr; }
  .phone-field select { max-width: none; }
}

/* ---------- theme picker ---------- */

/* Each tile is a miniature of the card, drawn from the theme's own values.
   Everything that varies arrives as a custom property from the view, so this
   block never needs to know a theme exists. */
.themes .theme-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 16px;
  margin-top: 4px;
}
.theme { display: flex; flex-direction: column; margin: 0; }
.theme h3 {
  margin: 10px 0 2px; font-size: .95rem;
  /* The badge sits beside the name and wraps under it on a narrow tile rather
     than squeezing the name to one character per line. */
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 7px;
}
.theme p  { margin: 0 0 10px; font-size: .82rem; line-height: 1.45; color: var(--ink-soft); }

/* Which theme this draft came from, and whether it has moved since.
   Only ever on one tile: a theme nobody applied has no news for them. */
.theme-flag {
  font-size: .66rem; font-weight: 700; letter-spacing: .04em;
  padding: 2px 7px; border-radius: 999px; white-space: nowrap;
  background: var(--accent-soft); color: var(--accent-strong);
}
/* The one that is asking to be acted on, so it carries the attention colour
   the rest of the interface uses for "this needs you" rather than the accent,
   which here would read as decoration on a panel already full of colour. */
.theme-flag-stale { background: #FDF3E0; color: #7A4C09; }
.theme-stale-note {
  margin: -4px 0 10px !important;
  font-size: .78rem !important; line-height: 1.5 !important;
  color: var(--ink-soft);
  border-left: 2px solid #E8C98A; padding-left: 9px;
}
/* Pushed to the bottom so the buttons line up across tiles whose blurbs run to
   different numbers of lines. */
.theme button { margin-top: auto; align-self: start; }
/* The tiles are a grid with its own row height; without this the hint that
   follows overlapped the last row of buttons. */
.themes .hint { margin-top: 18px; }

.theme-thumb {
  display: block;
  aspect-ratio: 3 / 4;
  border-radius: var(--t-radius, 14px);
  overflow: hidden;
  background: var(--t-card);
  border: 1px solid rgba(0, 0, 0, .12);
  box-shadow: 0 1px 3px rgba(16, 24, 40, .08);
}
.theme-hero {
  display: grid; place-items: center;
  height: 46%;
  background: var(--t-hero);
  color: var(--t-hero-ink);
}
.theme-hero b { font-size: 1.5rem; font-weight: 700; line-height: 1; }
.theme-body { display: block; padding: 10px 10px 0; }

/* Three contact rows and a button, at the proportions the real card uses. */
.theme-body i {
  display: block; height: 7px; margin-bottom: 5px; border-radius: 3px;
  background: color-mix(in srgb, var(--t-ink) 12%, transparent);
}
.theme-body em {
  display: block; height: 11px; margin-top: 9px;
  background: var(--t-accent);
}

.saved-style .saved-meta {
  margin: 0 0 12px;
  font-size: .86rem;
  color: var(--ink-soft);
}
/* One column: label, note field, then the two buttons side by side.
   A two-column grid was the wrong shape — the two actions are not
   symmetrical, so equal columns left the buttons at different heights and a
   column of empty space beside the shorter one. */
.saved-actions {
  display: grid;
  gap: 10px;
}
.saved-actions form { margin: 0; }
.saved-actions > label {
  font-size: .82rem;
  font-weight: 600;
  letter-spacing: .02em;
  color: var(--ink-soft);
}
.saved-actions > input {
  font: inherit;
  width: 100%;
  color: var(--ink);
  background: var(--surface);
  padding: 11px 13px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
}

/* The pair. align-items:start so both buttons sit on the same line whatever
   their hints do underneath, and wrap so a narrow column stacks them rather
   than squeezing two buttons into one row. */
.saved-buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 14px 18px;
  align-items: start;
  margin-top: 4px;
}
.saved-button {
  display: grid;
  gap: 5px;
  justify-items: start;
  /* Wide enough for the hint to read as a caption under its button rather
     than a paragraph, narrow enough that two sit side by side. */
  /* Equal shares of the row, so the pair fills the panel instead of
     leaving a column of nothing to its right. The buttons inside stay
     sized to their own words. */
  flex: 1 1 15rem;
}
.saved-button .hint { margin: 0; }

/* ---------- card designer ---------- */

.designer {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 380px;
  gap: 28px;
  align-items: start;
}

.designer-controls { display: grid; gap: 18px; min-width: 0; }
.designer-controls .panel { padding-bottom: 20px; }
.panel-note {
  margin: -6px 0 16px;
  font-size: .88rem;
  color: var(--ink-faint);
  max-width: 46ch;
}

/* --- individual controls --- */

.control { margin-bottom: 16px; }
.control:last-child { margin-bottom: 0; }
.control > label {
  display: block;
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink-soft);
  margin-bottom: 7px;
}
.control select {
  font: inherit;
  width: 100%;
  padding: 9px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
}

/* .hint carries margin-top:-6px, which is right where it follows a
   .control-row — the slider and its number box leave a gap of their own to
   close up. A select has no such gap, so the same rule pulled the hint up into
   the select's bottom border and the first line of text sat on it. Only a hint
   that directly follows a select is affected, so the slider hints keep the
   spacing they were tuned for. */
.control select + .hint { margin-top: 6px; }
.control-row { display: flex; align-items: center; gap: 14px; }
.control-row input[type="range"] { flex: 1; accent-color: var(--accent); min-width: 0; }
.control-number {
  min-width: 64px;
  text-align: right;
  font-variant-numeric: tabular-nums;
  font-size: .88rem;
  color: var(--ink-soft);
  white-space: nowrap;
}
.control-number i { font-style: normal; opacity: .5; margin-left: 2px; }

/* The number beside a slider is now typeable. It should still read as a
   readout rather than another form field competing with the control it
   belongs to — so no border until you go near it. */
.control-entry {
  width: 4.5ch;
  padding: 2px 4px;
  border: 1px solid transparent;
  border-radius: 5px;
  background: transparent;
  font: inherit;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  text-align: right;
  -moz-appearance: textfield;
}
.control-entry::-webkit-outer-spin-button,
.control-entry::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.control-entry:hover { border-color: var(--rule); }
.control-entry:focus {
  border-color: var(--accent);
  background: var(--surface);
  outline: 2px solid color-mix(in srgb, var(--accent) 30%, transparent);
  outline-offset: 1px;
}

/* A range that can go negative needs room for the sign. */
.control-entry[min^="-"] { width: 5.5ch; }

.swatch-row { display: flex; align-items: center; gap: 12px; }
.swatch-row input[type="color"] {
  inline-size: 46px;
  block-size: 34px;
  padding: 2px;
  border: 1px solid var(--rule-strong);
  border-radius: 6px;
  background: var(--surface);
  cursor: pointer;
}
.swatch-hex {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: .84rem;
  letter-spacing: .05em;
  color: var(--ink-soft);
}

/* --- segmented choice --- */

.segmented { display: flex; gap: 0; border: 1px solid var(--rule-strong); border-radius: var(--radius); overflow: hidden; }
.segmented label { flex: 1; }
.segmented input { position: absolute; opacity: 0; pointer-events: none; }
.segmented span {
  display: block;
  text-align: center;
  padding: 9px 10px;
  font-size: .88rem;
  font-weight: 600;
  color: var(--ink-soft);
  cursor: pointer;
  background: var(--surface);
}
.segmented label + label span { border-left: 1px solid var(--rule); }
.segmented input:checked + span { background: var(--accent-soft); color: var(--accent); }
.segmented input:focus-visible + span { outline: 2px solid var(--accent); outline-offset: -2px; }

/* --- switches --- */

.switch { display: inline-flex; align-items: center; gap: 9px; cursor: pointer; font-size: .88rem; }
.switch input[type="checkbox"] { width: 17px; height: 17px; accent-color: var(--accent); cursor: pointer; }
.switch.standalone { margin: 0 0 16px; color: var(--ink-soft); }

/* --- shape layers --- */

.shape-group {
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0 14px;
  margin-bottom: 12px;
  background: var(--brand-surface-tint, #F5FAFA);
}
.shape-group[open] { padding-bottom: 16px; }
.shape-group summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 12px 0;
  cursor: pointer;
  font-weight: 600;
  font-size: .9rem;
  list-style: none;
}
.shape-group summary::-webkit-details-marker { display: none; }
.shape-group[open] summary { border-bottom: 1px solid var(--rule); margin-bottom: 16px; }

.contrast-note { border-left: 2px solid var(--accent); padding-left: 12px; margin-top: 16px; }

/* --- preview --- */

/* align-items: start on the grid leaves this column exactly as tall as the
   card, and a sticky child with no room to travel simply scrolls away with
   it. Stretching only this column gives the preview the full height of the
   controls to stay pinned against. */
.designer-preview { min-width: 0; align-self: stretch; }

/* Taller than the viewport, a sticky block scrolls until its bottom edge is
   reached and then holds — which parks the card off the top of the screen,
   the opposite of what sticky is for. Capping the column to the viewport and
   letting the frame flex keeps the whole card in view at any window height. */
.preview-sticky {
  position: sticky;
  top: 20px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  max-height: calc(100vh - 40px);
}
.preview-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
.preview-head > div { min-width: 0; }
.preview-head h2 { font-size: 1.05rem; margin: 0 0 2px; }
.preview-head .hint { margin: 0; }
.preview-toggle { display: none; }

/* Which face of the card the preview is showing. Same shape as the switch on
   the public card's QR sheet, so the two read as the same kind of control. */
.preview-view {
  flex: none;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4px;
  padding: 4px;
  border-radius: 999px;
  background: var(--rule);
}
.preview-view button {
  appearance: none;
  border: 0;
  border-radius: 999px;
  padding: 8px 12px;
  font: inherit;
  font-size: .86rem;
  font-weight: 600;
  color: var(--ink-soft);
  background: transparent;
  cursor: pointer;
  transition: background-color .14s ease, color .14s ease;
}
.preview-view button:hover:not(:disabled) { color: var(--ink); }
.preview-view button[aria-pressed="true"] {
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow);
}
.preview-view button:disabled { opacity: .45; cursor: not-allowed; }
.preview-view button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The card is drawn at its true size and then scaled to fit, rather than
   being given a fixed height and left to scroll inside. A preview with its
   own scrollbar is not a preview of anything — the card it shows is not the
   shape the card actually is. design.js measures and sets the scale; this
   only has to centre the result and refuse to scroll. */
.preview-frame {
  /* Sized by the card inside it, not by the space around it. Growing to fill
     the column would leave a tall slab of navy with a small card adrift in
     the middle of it on a big screen. */
  flex: 0 1 auto;
  min-height: 0;
  display: grid;
  place-items: center;
  background: var(--navy-deep);
  border-radius: 20px;
  padding: 12px;
  box-shadow: var(--shadow);
  overflow: hidden;
}
.preview-stage {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  background: #fff;
  /* Until the script has measured, fall back to something sensible rather
     than collapsing to nothing. */
  width: 100%;
  height: 100%;
}
.preview-frame iframe {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  background: #fff;
  transform-origin: top left;
}

.preview-actions { flex: none; display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.preview-actions .btn { margin-top: 0; }
.designer-preview .hint { margin: 0; }
.preview-sticky > .hint { flex: none; }

/* On a short window the trailing note is the first thing worth dropping —
   the buttons and the card itself are not. */
@media (min-width: 1001px) and (max-height: 820px) {
  .preview-sticky > .hint { display: none; }
  .preview-frame { min-height: 260px; }
}

/* --- the preview on one column ---
   Stacked, the preview would sit under every control and be invisible while
   editing. So it moves above them and sticks to the top of the viewport,
   shorter, with a toggle for anyone who wants the room back. */
@media (max-width: 1000px) {
  .designer { grid-template-columns: 1fr; gap: 18px; }

  .designer-preview {
    order: -1;
    position: sticky;
    top: calc(var(--appbar-h, 0px) + var(--imp-h, 0px));
    z-index: 20;
    margin: 0 calc(var(--gutter) * -1);
    padding: 10px var(--gutter) 12px;
    background: var(--paper);
    box-shadow: 0 14px 20px -18px rgba(18, 35, 63, .8);
  }
  .preview-sticky { position: static; max-height: none; gap: 10px; }

  .preview-frame {
    padding: 8px;
    border-radius: 14px;
    height: min(46vh, 420px);
  }

  .preview-head .hint { display: none; }
  .preview-toggle {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    flex: none;
    background: none;
    border: 1px solid var(--rule-strong);
    border-radius: 999px;
    padding: 5px 12px;
    font: inherit;
    font-size: .8rem;
    font-weight: 600;
    color: var(--ink-soft);
    cursor: pointer;
  }
  .preview-toggle:hover { color: var(--ink); border-color: var(--ink-faint); }

  .designer-preview.is-collapsed .preview-frame { display: none; }
  .designer-preview.is-collapsed .preview-actions { margin-top: 2px; }
  .preview-sticky > .hint { display: none; }
}

@media (max-width: 1000px) and (max-height: 620px) {
  .preview-frame { height: 40vh; }
}

/* On a phone the pinned preview has to leave room to actually edit below it. */
@media (max-width: 560px) {
  .preview-frame { height: min(38vh, 340px); }
  .designer-preview { padding-top: 8px; }
  .preview-head h2 { font-size: .98rem; }
}

.discard { margin-top: 22px; }
.linkish-quiet {
  background: none;
  border: 0;
  padding: 0;
  font: inherit;
  font-size: .88rem;
  color: var(--ink-faint);
  cursor: pointer;
  text-decoration: underline;
}
.linkish-quiet:hover { color: var(--danger); }

/* ---------- company links ---------- */

.links-form { display: grid; gap: 18px; max-width: 860px; }
.link-rows { display: grid; gap: 4px; }

.link-row {
  display: grid;
  grid-template-columns: 34px minmax(0, 1fr) auto;
  align-items: start;
  gap: 14px;
  padding: 14px 12px;
  border-radius: var(--radius);
  border: 1px solid transparent;
}
.link-row + .link-row { border-top: 1px solid var(--rule); border-radius: 0; }
.link-row:hover { background: var(--brand-surface-tint, #F5FAFA); }

.link-row-icon {
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
  margin-top: 20px;
}
.link-row-main { min-width: 0; }
.link-row-main > label {
  display: block;
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink-soft);
  margin-bottom: 6px;
}
.link-row-main input {
  font: inherit;
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
}
.link-row-main input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}
.link-row-main .hint { margin: 6px 0 0; }

.link-row-pair { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr); gap: 12px; }
.link-row-pair label {
  display: block;
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink-soft);
  margin-bottom: 6px;
}

.link-row-switch { margin-top: 26px; white-space: nowrap; }
.link-row-side {
  display: grid;
  gap: 8px;
  margin-top: 26px;
  justify-items: start;
  white-space: nowrap;
}
.switch-danger { color: var(--danger); }
.switch-danger input[type="checkbox"] { accent-color: var(--danger); }

/* A rejected row is marked in red; a browser-side hint is amber, because it
   is a warning about something the server has not yet refused. */
.link-row.has-error {
  border-color: var(--danger);
  background: var(--danger-soft);
}
.link-row.has-error input { border-color: var(--danger); }
.link-row.has-warning input { border-color: #B7791F; }

.field-error {
  margin: 7px 0 0;
  font-size: .85rem;
  color: var(--danger);
}
.field-warning {
  margin: 7px 0 0;
  font-size: .85rem;
  color: #8A5A11;
}

.links-actions { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.links-actions .hint { margin: 0; }

@media (max-width: 620px) {
  .link-row {
    grid-template-columns: 34px minmax(0, 1fr);
    row-gap: 10px;
  }
  .link-row-icon { margin-top: 18px; }
  .link-row-switch,
  .link-row-side {
    grid-column: 2;
    margin-top: 0;
    flex-direction: row;
    grid-auto-flow: column;
    justify-content: start;
    gap: 18px;
  }
  .link-row-pair { grid-template-columns: 1fr; gap: 10px; }
}

/* ---------- billing ---------- */

.month-picker { display: flex; gap: 8px; align-items: center; }
.month-picker select {
  font: inherit;
  padding: 9px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
}

.section-heading {
  font-size: .78rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 34px 0 14px;
  font-weight: 600;
}

.tile-foot {
  font-size: .74rem;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}
.tile-accent { border-color: var(--accent); }
.tile-accent .tile-value { color: var(--accent); }

table.data tfoot td {
  border-top: 2px solid var(--rule-strong);
  border-bottom: 0;
  font-weight: 600;
  background: var(--brand-surface-tint, #F5FAFA);
}

.pill-draft   { background: var(--rule);       color: var(--ink-soft); }
.pill-issued  { background: #E7EEF9;           color: #1F3F79; }
.pill-paid    { background: var(--ok-soft);    color: #124D34; }
.pill-overdue { background: var(--danger-soft);color: #6E2013; }
.pill-void    { background: var(--rule);       color: var(--ink-faint); text-decoration: line-through; }

.head-actions { display: flex; gap: 10px; }
.head-actions .btn { margin-top: 0; }

/* ---------- the invoice document ---------- */

.invoice {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 38px 40px 30px;
  box-shadow: var(--shadow);
  max-width: 860px;
}
.invoice-head {
  display: flex;
  justify-content: space-between;
  gap: 24px;
  flex-wrap: wrap;
  padding-bottom: 26px;
  border-bottom: 2px solid var(--ink);
}
.invoice-from {
  margin: 0;
  font-weight: 700;
  letter-spacing: .16em;
  font-size: 1.05rem;
  /* The lockup is one drawing and sets its own spacing, so nothing here has to
     arrange a mark beside a word. */
  line-height: 0;
}
.invoice-from .brand-lockup { display: block; }
.invoice-meta { margin: 6px 0 0; font-size: .85rem; color: var(--ink-faint); line-height: 1.5; }
.invoice-ref { text-align: right; }
.invoice-ref h2 {
  margin: 0;
  font-size: .78rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
}
.invoice-number {
  margin: 4px 0 8px;
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: 1.02rem;
  font-weight: 600;
}

.invoice-parties {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 30px;
  padding: 26px 0;
}
.invoice-parties h3 {
  font-size: .72rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 8px;
  font-weight: 600;
}
.invoice-parties p { margin: 0; line-height: 1.6; font-size: .94rem; }
.invoice-dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 5px 18px;
  margin: 0;
  font-size: .92rem;
}
.invoice-dl dt { color: var(--ink-faint); }
.invoice-dl dd { margin: 0; font-variant-numeric: tabular-nums; }

.invoice-table { width: 100%; border-collapse: collapse; font-size: .93rem; }
.invoice-table th {
  text-align: left;
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 600;
  padding: 10px 12px;
  border-bottom: 1px solid var(--rule-strong);
}
.invoice-table td { padding: 12px; border-bottom: 1px solid var(--rule); vertical-align: top; }
.invoice-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.invoice-total td {
  border-top: 2px solid var(--ink);
  border-bottom: 0;
  font-size: 1.06rem;
  font-weight: 700;
  padding-top: 14px;
}
/* ---------- the invoice on a phone ----------
 *
 * A monthly invoice is five columns — who, days, monthly fee, daily rate,
 * amount — and five columns do not go into 375 points. It was not scrolling
 * sideways either, because the document is not wrapped in a scroller: the
 * columns simply crushed, and the daily rate at four decimal places wrapped
 * onto three lines of its own.
 *
 * Stacked the way the .data tables already stack, because a reader who has met
 * one on the employees screen has met this. Each line becomes a small block
 * with its numbers labelled; the name keeps the full width and needs no label,
 * being the heading of its own block.
 *
 * Not below the print rules, and that matters: paper is never 375 points wide,
 * so the table stays a table when it is printed however small the screen that
 * printed it was.
 */
@media (max-width: 620px) {
  .invoice-table thead {
    /* Not display:none — the headings are the labels for a screen reader, and
       hiding them from the tree would leave four unlabelled numbers per line. */
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip: rect(0 0 0 0); white-space: nowrap; border: 0;
  }
  .invoice-table tbody tr {
    display: block;
    padding: 4px 0 10px;
    border-bottom: 1px solid var(--rule);
  }
  .invoice-table tbody tr:last-child { border-bottom: 0; }
  .invoice-table td {
    display: grid;
    grid-template-columns: minmax(0, 96px) minmax(0, 1fr);
    gap: 4px 12px;
    align-items: baseline;
    padding: 5px 0;
    border-bottom: 0;
    text-align: left;
  }
  .invoice-table td[data-label]::before {
    content: attr(data-label);
    font-size: .7rem;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--ink-faint);
    font-weight: 600;
  }
  /* The person is the block's heading: full width, no label, a little air
     above it so the blocks read as separate lines rather than one list. */
  .invoice-table tbody td:first-child {
    grid-template-columns: 1fr;
    padding-top: 12px;
    font-size: 1rem;
  }
  /* The numbers align left in a stacked block. Right-aligning a value that
     starts at the middle of the row leaves it floating away from its label. */
  .invoice-table .num { text-align: left; }

  /* The total keeps its own line and its weight, but stops trying to sit in a
     column that no longer exists. */
  .invoice-total td { display: block; padding: 12px 0 0; }
  .invoice-total td[colspan] { font-size: .8rem; color: var(--ink-soft); font-weight: 600; }

  /* Two label/value grids that assume room for a max-content column. On a
     narrow screen the label wins and the value is squeezed to nothing. */
  .invoice-dl, .invoice-pay dl { grid-template-columns: 1fr; gap: 2px; }
  .invoice-dl dt, .invoice-pay dt { margin-top: 8px; }
}

/* How to pay. Sits between the total and the note, because it is the thing a
   reader does next — and it prints, since an invoice is often paid off paper. */
.invoice-pay {
  margin: 22px 0 0;
  padding: 16px 18px;
  background: var(--brand-surface-tint, #F5FAFA);
  border: 1px solid var(--rule);
  border-radius: var(--r-md);
}
.invoice-pay h3 {
  font-family: var(--display-font);
  font-size: .8125rem;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 0 0 10px;
}
.invoice-pay dl {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 5px 18px;
  margin: 0;
  font-size: .9rem;
}
.invoice-pay dt { color: var(--ink-soft); }
.invoice-pay dd { margin: 0; color: var(--ink); }
.invoice-pay .hint { margin: 12px 0 0; }

.invoice-foot {
  margin-top: 26px;
  padding-top: 18px;
  border-top: 1px solid var(--rule);
  font-size: .82rem;
  color: var(--ink-faint);
  max-width: 66ch;
}

/* ---------- print ---------- */

@media print {
  .no-print, .sidenav, .nav-scrim, .impersonation-bar { display: none !important; }
  body { background: #fff; color: #000; font-size: 11pt; }
  .app-frame { display: block; }
  .shell-column { display: block; }
  .shell { padding: 0; max-width: none; }
  .invoice {
    border: 0;
    box-shadow: none;
    padding: 0;
    max-width: none;
  }
  .invoice-table { page-break-inside: auto; }
  .invoice-table tr { page-break-inside: avoid; }
  .invoice-table thead { display: table-header-group; }
  .pill { border: 1px solid #999; background: none !important; color: #000 !important; }
  a { color: #000; text-decoration: none; }
}

/* ---------- lead form and leads ---------- */

/* The honeypot. Not display:none — some bots skip hidden fields, and some
   screen readers announce them. Positioned away and taken out of the tab
   order instead, with aria-hidden on the wrapper. */
.trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.leads { display: grid; gap: 16px; }

.lead {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 20px 22px;
  box-shadow: var(--shadow);
}
.lead-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 16px;
  flex-wrap: wrap;
}
.lead-head h2 { font-size: 1.06rem; margin: 0; }
.lead-contact { margin: 3px 0 0; font-size: .9rem; }
.lead-contact a { color: var(--accent); text-decoration: none; }
.lead-contact a:hover { text-decoration: underline; }

.lead-facts {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 4px 18px;
  margin: 16px 0 0;
  font-size: .9rem;
}
.lead-facts dt {
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  padding-top: 3px;
}
.lead-facts dd { margin: 0; }

.lead-message {
  margin: 16px 0 0;
  padding: 12px 16px;
  border-left: 2px solid var(--rule-strong);
  background: var(--paper);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: .93rem;
  color: var(--ink-soft);
}

.lead-form {
  display: flex;
  gap: 10px;
  margin-top: 18px;
  padding-top: 16px;
  border-top: 1px solid var(--rule);
  flex-wrap: wrap;
}
.lead-form select, .lead-form input {
  font: inherit;
  font-size: .9rem;
  padding: 8px 11px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
}
.lead-form input { flex: 1; min-width: 180px; }
.lead-form .btn { margin-top: 0; }

/* The signed-out shell needs room for a longer form than the login box. */
.plain-card { max-height: none; }

/* ---------- charts ---------- */

.chart-card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 22px 24px 18px;
  box-shadow: var(--shadow);
  margin-bottom: 18px;
}
.chart-head { margin-bottom: 16px; }
.chart-head h2 { font-size: 1.02rem; margin: 0 0 2px; }
.chart-head .hint { margin: 0; }
.chart-body { overflow-x: auto; }
.chart-body svg {
  display: block;
  width: 100%;
  height: auto;
  /* Below this the axis labels stop being readable, so the card scrolls
     rather than shrinking the type into illegibility. */
  min-width: 560px;
}

/* --- HTML bars --- */

.bars { list-style: none; margin: 0; padding: 0; display: grid; gap: 14px; }
.bar {
  display: grid;
  grid-template-columns: minmax(120px, 220px) 1fr auto;
  align-items: center;
  gap: 16px;
}
.bar-label { min-width: 0; }
.bar-name {
  display: block;
  font-size: .92rem;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bar-note {
  display: block;
  font-size: .76rem;
  color: var(--ink-faint);
  margin-top: 1px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bar-track {
  height: 14px;
  border-radius: 4px;
  background: var(--rule);
  overflow: hidden;
  min-width: 60px;
}
.bar-fill {
  height: 100%;
  border-radius: 4px;
  /* Matches CHART_SERIES_1 in charts.php. The bars and the area chart are the
     same data in two shapes and have to be the same colour; they were only
     ever separate because one is SVG and the other is HTML. */
  background: var(--accent);
}
.bar-value {
  font-size: .92rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: right;
  min-width: 56px;
}

@media (max-width: 560px) {
  .bar { grid-template-columns: 1fr auto; gap: 6px 12px; }
  .bar-track { grid-column: 1 / -1; }
}

.chart-table { margin-top: 16px; }
.chart-table summary {
  cursor: pointer;
  font-size: .85rem;
  color: var(--ink-soft);
  padding: 6px 0;
}
.chart-table summary:hover { color: var(--ink); }
.chart-table[open] summary { margin-bottom: 12px; }

/* ---------- import ---------- */

tr.row-muted td { opacity: .62; }
tr.row-muted .pill { opacity: 1; }
.handover .table-scroll { background: #fff; margin-top: 6px; }
.handover code { user-select: all; }

/* ---------- plain bullet list ----------
   Named "bullets" rather than "plain": a bare .plain rule for the signed-out
   page shell was matching any element that used the same word, and did — a
   column list on the import page inherited a full-height navy gradient. */

ul.bullets {
  padding-left: 20px;
  margin: 0 0 16px;
  max-width: 66ch;
  color: var(--ink-soft);
  font-size: .93rem;
}
ul.bullets li { margin-bottom: 7px; }
ul.bullets li::marker { color: var(--ink-faint); }
ul.bullets strong { color: var(--ink); }
ul.bullets code {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  font-size: .88em;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 4px;
  padding: 1px 5px;
}

/* ---------- impersonation ---------- */

/* Deliberately loud and deliberately fixed. A support engineer acting as a
   customer should never be able to forget that they are. */
.impersonation-bar {
  position: sticky;
  top: 0;
  z-index: 50;
  background: #7A3E00;
  color: #FFF3E4;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
  padding: 10px 24px;
  font-size: .88rem;
  line-height: 1.45;
}
.impersonation-bar strong { color: #fff; }
.impersonation-bar form { margin: 0; }
.impersonation-bar button {
  font: inherit;
  font-weight: 600;
  white-space: nowrap;
  background: #FFF3E4;
  color: #7A3E00;
  border: 0;
  border-radius: 999px;
  padding: 7px 16px;
  cursor: pointer;
}
.impersonation-bar button:hover { background: #fff; }
body.impersonating .sidenav,

/* ---------- destructive actions ---------- */

.danger-panel { border-color: #E8C4BC; }
.danger-panel .panel-note { color: var(--ink-soft); }

.btn-danger {
  background: var(--danger);
  color: #fff;
  border-color: transparent;
}
.btn-danger:hover { background: #8A2817; }

/* ---------- filters, pager, health ---------- */

.filter-row {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
.filter-row select {
  font: inherit;
  font-size: .9rem;
  padding: 8px 12px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  max-width: 260px;
}
.filter-row .btn { margin-top: 0; }

.pager {
  display: flex;
  align-items: center;
  gap: 14px;
  margin-top: 18px;
}
.pager .btn { margin-top: 0; }
.pager-position { font-size: .86rem; color: var(--ink-faint); }

td.nowrap { white-space: nowrap; }

td.health-state { width: 34px; text-align: center; }
.dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
}
.dot-ok   { background: var(--ok); }
.dot-warn { background: #B8860B; }
.dot-fail { background: var(--danger); }

/* ---------- removal, restoration and erasure ----------
   The removed rows sit at the bottom of the employee table, dimmed but still
   legible: a leaver who disappears from the screen is a leaver nobody can
   restore after a misclick, and is also a person whose data is still here. */

tr.is-removed td { opacity: .62; }
tr.is-removed td.actions { opacity: 1; }
tr.is-erased  td { opacity: .45; }

/* The typed confirmation. Narrow on purpose — it is not a field anybody
   should be able to fill in without having read the word next to it. */
.erase-now {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-left: 8px;
}
.erase-now input {
  width: 7.5em;
  padding: 5px 8px;
  font-size: .82rem;
  border: 1px solid var(--rule);
  border-radius: 7px;
  background: #fff;
  color: var(--ink);
}
.erase-now input::placeholder { color: var(--ink-faint); letter-spacing: .04em; }
.erase-now input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* ---------- what we hold about you ---------- */

.data-section { margin: 0 0 26px; }
.data-section > h2 {
  font-size: 1.02rem;
  margin: 0 0 4px;
}
.data-section > .hint { margin: 0 0 12px; }

.data-list {
  display: grid;
  grid-template-columns: minmax(140px, 15em) 1fr;
  gap: 0;
  margin: 0;
  border: 1px solid var(--rule);
  border-radius: 12px;
  overflow: hidden;
}
.data-list dt,
.data-list dd {
  margin: 0;
  padding: 10px 14px;
  border-top: 1px solid var(--rule);
}
.data-list dt:first-of-type,
.data-list dt:first-of-type + dd { border-top: 0; }
.data-list dt { background: var(--paper); font-weight: 600; color: var(--ink-soft); }
.data-list dd { overflow-wrap: anywhere; }
.data-list dd.is-empty { color: var(--ink-faint); }

@media (max-width: 34rem) {
  .data-list { grid-template-columns: 1fr; }
  .data-list dd { border-top: 0; padding-top: 0; }
  .data-list dt:first-of-type + dd { border-top: 0; }
  .data-list dt { border-top: 1px solid var(--rule); }
}

.data-note {
  border-left: 3px solid var(--accent);
  padding: 2px 0 2px 14px;
  margin: 14px 0 0;
  color: var(--ink-soft);
}

/* A .sub that has to sit on its own line rather than trail the value it
   qualifies. Inline is right in a table cell of stacked spans; here it made
   "Not published at the moment" read as part of the address. */
.data-list .sub.is-block { display: block; margin-top: 2px; }

/* ---------- the top bar ----------

   The page's name on the left, the person signed in on the right — and, below
   60rem, the menu button too, where it takes the chrome's dark treatment and
   is the only bar on the screen.

   Sticky, because the pages most in need of a header are the long ones: an
   employee table scrolled halfway leaves you with no indication of where you
   are or whose account you are in. --imp-h is the impersonation bar's height,
   set by nav.js and zero the rest of the time, so the bar sits under it rather
   than beneath it. */

.topbar {
  position: sticky;
  top: var(--imp-h, 0px);
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  min-height: 68px;
  padding: 10px var(--gutter);
  background: var(--surface);
  border-bottom: 1px solid var(--rule);
}

.topbar-title {
  margin: 0;
  font-size: 1.32rem;
  line-height: 1.25;
  letter-spacing: -.01em;
  min-width: 0;
  overflow-wrap: anywhere;
}

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

.account { position: relative; flex: none; }

/* <summary> draws a disclosure triangle in every browser and it cannot be
   styled, only removed. Both rules are needed: the standard one and WebKit's. */
.account-button::-webkit-details-marker { display: none; }
.account-button {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: 320px;
  padding: 6px 10px 6px 6px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: transparent;
  color: inherit;
  font: inherit;
  cursor: pointer;
  text-align: left;
}
.account-button:hover { background: var(--paper); border-color: var(--rule); }
.account[open] > .account-button { background: var(--paper); border-color: var(--rule-strong); }
.account-button:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* nav_icon('chevron') draws a chevron pointing right, which is why the sidebar
   turns it a quarter turn to point down. Same here: down at rest, up when the
   menu is open. */
.account-button svg {
  flex: none; width: 16px; height: 16px; opacity: .55;
  transform: rotate(90deg);
  transition: transform .15s ease;
}
.account[open] > .account-button svg { transform: rotate(-90deg); }

@media (prefers-reduced-motion: reduce) {
  .account-button svg { transition: none; }
}

.account-avatar {
  flex: none;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: var(--navy);
  color: var(--chrome-ink);
  font-size: .76rem;
  font-weight: 700;
  letter-spacing: .04em;
}

.account-text { min-width: 0; display: grid; line-height: 1.25; }
.account-text strong {
  font-size: .88rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.account-text em {
  font-style: normal;
  font-size: .76rem;
  color: var(--ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.account-menu {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 60;
  min-width: 278px;
  padding: 6px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 12px;
  box-shadow: var(--shadow);
}

.account-menu-head {
  margin: 0;
  padding: 10px 12px 12px;
  border-bottom: 1px solid var(--rule);
  display: grid;
  gap: 2px;
  line-height: 1.3;
}
.account-menu-head strong { font-size: .9rem; }
.account-menu-head em {
  font-style: normal;
  font-size: .78rem;
  color: var(--ink-faint);
  overflow-wrap: anywhere;
}

.account-menu form { margin: 0; }

.account-item {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin-top: 2px;
  padding: 9px 12px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--ink);
  font: inherit;
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.account-item:first-of-type { margin-top: 6px; }
.account-item:hover { background: var(--paper); }
.account-item.is-current { background: var(--accent-soft); color: var(--accent-strong); }
.account-item.is-current svg { opacity: 1; }
.account-item:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.account-item svg { flex: none; width: 17px; height: 17px; opacity: .6; }

/* On a phone this is the whole header: menu, title, account. The account
   shrinks to its avatar — the name and the company beside it are what the
   drawer says on the line under the brand, and there is no room for them
   here. */
@media (max-width: 60rem) {
  /* The chrome's colours. This has to live in the block that comes AFTER the
     base .topbar rule, not in the earlier one: a media query adds no
     specificity, so an identical selector further up the file simply loses,
     and the bar stayed white while every rule that was meant to darken it sat
     there looking correct. */
  .topbar {
    min-height: 56px;
    padding: 8px var(--gutter);
    z-index: 40;
    gap: 12px;
    background: var(--navy);
    color: var(--chrome-ink);
    border-bottom: 0;
    box-shadow: 0 1px 0 rgba(0, 0, 0, .2);
  }
  /* The title takes the room the brand used to and pushes the account to the
     far edge. */
  /* .topbar .topbar-title, not .topbar-title: `.topbar h1` further up sets
     colour too, and one class plus one element beats one class on its own — so
     the intended chrome-ink lost to the page's dark ink and the title was
     painted navy-on-navy. Two classes settles it. */
  .topbar .topbar-title { font-size: 1.14rem; color: var(--chrome-ink); flex: 1; min-width: 0; }
  body.impersonating .topbar { box-shadow: inset 0 3px 0 #7A3E00, 0 1px 0 rgba(0, 0, 0, .2); }
  .account-text { display: none; }
  /* On the dark bar the summary is just the avatar; the name and the chevron
     have nowhere to go and the avatar is the target. */
  .account-button { padding: 4px; background: transparent; border-color: transparent; }
  .account-button:hover { background: rgba(255, 255, 255, .12); }
  .account-button svg { display: none; }
  .account-avatar { background: rgba(255, 255, 255, .16); color: var(--chrome-ink); }
  /* The menu itself stays a light panel — it is a sheet over the page, not
     part of the bar. */
  .account-menu { color: var(--ink); }
}

@media print {
  .topbar { position: static; border-bottom: 1px solid #ccc; }
  .account { display: none; }
}

/* ---------- the dashboard ---------- */

.dash-greeting { margin-bottom: 22px; }

/* The list of things waiting for somebody. Deliberately the first thing on the
   page and deliberately absent when it is empty — a permanent "Needs you"
   heading with nothing under it is how a dashboard trains people to skip it. */
.dash-todo { margin: 0 0 28px; }

.dash-head {
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin: 0 0 12px;
}

.todo-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }

.todo-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-left-width: 3px;
  border-radius: var(--radius);
}
.todo-text { flex: 1; min-width: 0; }
.todo-item .btn { flex: none; margin: 0; }

/* One dot, one border, four meanings. Colour is never the only signal — the
   sentence says what is wrong, so this reads the same to somebody who cannot
   tell the red from the amber. */
.todo-dot { flex: none; width: 8px; height: 8px; border-radius: 50%; }
.todo-danger { border-left-color: var(--danger); }
.todo-danger .todo-dot { background: var(--danger); }
.todo-warn   { border-left-color: #C98A16; }
.todo-warn   .todo-dot { background: #C98A16; }
.todo-accent { border-left-color: var(--accent); }
.todo-accent .todo-dot { background: var(--accent); }
.todo-quiet  { border-left-color: var(--rule-strong); }
.todo-quiet  .todo-dot { background: var(--ink-faint); }

/* A tile whose number came from somewhere you can go and check. */
.tile-link { text-decoration: none; color: inherit; transition: border-color .14s ease, transform .14s ease; }
.tile-link:hover { border-color: var(--rule-strong); transform: translateY(-1px); }
.tile-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.tile-note { font-size: .74rem; color: var(--ink-faint); }

@media (prefers-reduced-motion: reduce) {
  .tile-link { transition: none; }
  .tile-link:hover { transform: none; }
}

/* The chart wants the room; the table and the card do not. auto-fit would give
   all three equal width and leave a thirty-day series squeezed into a third of
   the page. */
.dash-grid {
  display: grid;
  grid-template-columns: minmax(0, 2fr) minmax(260px, 1fr);
  gap: 20px;
  align-items: start;
}
.dash-grid > .dash-panel:first-child { grid-column: 1 / -1; }

@media (max-width: 60rem) {
  .dash-grid { grid-template-columns: minmax(0, 1fr); }
  .dash-grid > .dash-panel:first-child { grid-column: auto; }
}

.dash-panel { padding: 18px 20px; }
.dash-panel .chart-body { margin: 0; }

.dash-empty { margin: 0; color: var(--ink-soft); }

.dash-table { margin: 0; }
.dash-table th, .dash-table td { padding: 8px 10px; }
.dash-table th:first-child, .dash-table td:first-child { padding-left: 0; }
.dash-table .num { text-align: right; font-variant-numeric: tabular-nums; }

.dash-card-name {
  margin: 0 0 10px;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

/* The link is the product, so it is selectable text and not only a button —
   what people do most often with it is paste it into a message by hand. */
.dash-link {
  margin: 0 0 12px;
  padding: 10px 12px;
  background: var(--paper);
  border: 1px solid var(--rule);
  border-radius: 9px;
  font-size: .85rem;
  overflow-wrap: anywhere;
}
.dash-link a { color: var(--accent); text-decoration: none; }
.dash-link a:hover { text-decoration: underline; }

.dash-actions { margin: 0; display: flex; flex-wrap: wrap; gap: 8px; }
.dash-actions .btn { margin: 0; }

/* Removing a listing, behind a disclosure in its own row. Closed it is a
   button; open it is the question that has to be answered before the button
   does anything. The marker comes off because a triangle beside the word
   Remove reads as a fault rather than as an affordance. */
.row-remove > summary{list-style:none; display:inline-flex; cursor:pointer}
.row-remove > summary::-webkit-details-marker{display:none}
.row-remove[open] > summary{margin-bottom:10px}
.row-remove form{min-width:240px}

/* Automatic, or a colour — one row, because they are one decision. */
.ink-row{display:flex; flex-wrap:wrap; align-items:center; gap:10px 14px}
.ink-row label{display:inline-flex; align-items:center; gap:6px; font-size:13px; cursor:pointer}
.ink-row input[type="radio"]{width:auto; margin:0; accent-color:var(--accent)}
.ink-row input[type="color"]{width:44px; height:32px; padding:2px; flex:none}

/* A dialog for editing something without leaving the screen that shows it.
   The browser draws the backdrop and traps focus; this only has to make it
   look like the rest of the product. */
dialog.sheet{
  width:min(520px, calc(100vw - 32px));
  padding:22px 24px;
  border:1px solid var(--rule);
  border-radius:var(--r-md, 12px);
  background:var(--surface);
  color:var(--ink);
  box-shadow:0 24px 60px rgba(6,14,28,.28);
}
dialog.sheet::backdrop{background:rgba(6,14,28,.5)}
dialog.sheet h2{margin:0 0 6px; font-size:1.05rem}
.sheet-actions{display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin-top:6px}

/* The words live listings can be found by, on the platform's directory screen.
   A wrapped row of pills rather than a list: the useful reading is which words
   there are and roughly how many carry each, not their exact order. */
.tag-cloud{display:flex; flex-wrap:wrap; gap:8px}

/* Orders drawn up against a custom card, on the card's own screen. */
.quote-row { padding: 14px 0; border-top: 1px solid var(--rule, #e1e7ee); }
.quote-row:first-child { border-top: 0; }
.quote-row-head { display: flex; flex-wrap: wrap; align-items: center;
                  gap: 8px 12px; margin: 0 0 6px; }
.quote-row-link { display: flex; flex-wrap: wrap; align-items: center;
                  gap: 8px; margin: 10px 0 0; font-size: .8125rem; }

/* The order's own address. It was rendering as the browser's default link —
   underlined, and purple once visited, which on a screen with no other link
   colour reads as a fault rather than a link. Monospaced because the thing
   after /order/ is a token: it is read character by character when somebody is
   checking they are looking at the right one. */
.quote-row-link a {
  font-family: ui-monospace, "Cascadia Mono", Consolas, monospace;
  color: var(--accent);
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
  padding-bottom: 1px;
}
.quote-row-link a:hover { color: var(--accent-strong); border-bottom-color: var(--accent-strong); }

/* Two forms, side by side. This lived in custom_cards.php's inline <style>,
   so on the card's own edit screen — a different view — the class resolved to
   nothing and each button, being a block-level form, took its own line. A
   class used by two views does not belong inside one of them. */
.cc-submit { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin: 12px 0 0; }
.cc-submit form { display: inline; margin: 0; }

/* The sentence the customer ticked, kept exactly as it was shown to them.
   Quoted and set off, because it is evidence rather than commentary — the
   answer to "what did they actually agree to" a year after anybody remembers. */
.quote-statement {
  margin: 8px 0 0;
  padding: 10px 12px;
  border-left: 2px solid var(--rule-strong);
  background: var(--paper);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
}
.quote-statement .sub { display: block; margin-top: 5px; }

/* The anti-fraud line on an invoice. Quiet rather than alarming — it is on
   every invoice, including the ones nobody is being defrauded over, and a red
   warning on all of them would be ignored on the one that mattered. It must
   survive printing, because a PDF of an invoice is what most people actually
   pay from. */
.invoice-fraud{
  margin:14px 0 0;
  padding-top:12px;
  border-top:1px solid var(--rule);
  font-size:.8125rem;
  line-height:1.55;
  color:var(--ink-soft);
}
.invoice-fraud strong{color:var(--ink)}

/* A cancelled invoice, said on the document rather than only in the admin
   panel, and it has to survive printing: a PDF of a voided invoice that looks
   exactly like a live one is how somebody pays a bill that was withdrawn.
   --danger and its wash, because that is what the status colours are for. */
.invoice-void{
  margin:20px 0 0;
  padding:12px 14px;
  border:1px solid var(--danger);
  border-radius:var(--r-sm);
  background:var(--danger-soft);
  color:var(--ink);
  font-size:.875rem;
}
.invoice-void strong{color:var(--danger)}

/* ---------- the welcome pack's numbered steps ----------
 *
 * A list of instructions is not a list of bullets. The number is the point —
 * the steps are in an order and doing the last one first issues a card that
 * looks like nobody's — so it is pulled out where the eye lands rather than
 * left as the small grey marker a browser draws by default.
 */
.welcome-steps{list-style:none; counter-reset:step; margin:0; padding:0}
.welcome-steps li{
  counter-increment:step;
  position:relative;
  padding:0 0 0 42px;
  margin:0 0 18px;
  max-width:62ch;
  line-height:1.55;
}
.welcome-steps li::before{
  content:counter(step);
  position:absolute; left:0; top:1px;
  width:26px; height:26px;
  display:grid; place-items:center;
  border-radius:50%;
  background:var(--accent-soft);
  color:var(--accent-strong);
  font-size:.8125rem; font-weight:700;
}
.welcome-steps li:last-child{margin-bottom:0}
.welcome-steps strong{display:block; margin:0 0 2px}
/* The caveat under a step, not a step of its own. */
.welcome-steps .sub{display:block; margin:6px 0 0}
