/* ==========================================================================
   Permits CRM — the whole visual design.
   ==========================================================================

   One stylesheet, no framework, no CDN. index.php sends
   `default-src 'none'; style-src 'self' 'unsafe-inline'`, so an @import or a
   webfont URL would not load at all — it would fail silently and the page
   would render in Times. Everything needed is in this file.

   The palette is the Permits API console's (web/console.html in that repo),
   with one deliberate swap: there, amber is the accent and teal is secondary;
   here it is the other way round. The console mints credentials and wants to
   look like it — the CRM is a place you sit all day, and teal is the quieter
   of the two. Same tokens, same radius, same 14.5px base, so a person moving
   between the two products does not feel the seam.

   ── THE CLASS VOCABULARY ────────────────────────────────────────────────
   Templates are written by other hands, so the names are the contract:

     .wrap                 centred column; the layout's <main class="main"> is
                           already one, so pages rarely need it
     the shell             .shell is a two-column grid: .sidebar down the left,
                           .shell-col holding the flash area and <main> on the
                           right. .sidebar paints and positions; .nav inside it
                           is the stack that scrolls, and holds .brand (+ .tag),
                           .nav-links (.nav-group headings, mark one
                           .is-current) and .nav-right pinned to the floor.
                           Two elements, not one: a scrolling panel scrolls its
                           own background away. Under 950px it becomes a bar
                           across the top and the group headings hide.
     .page  .page-head     a page and its title row (h1 + .actions)
     .card  .panel         a bordered surface; .card-head is its title row
     .grid                 auto-fitting columns for fields or tiles
     .btn                  .btn-primary  .btn-ghost  .btn-danger
                           (.btn.primary / .ghost / .danger also work)
     .pill                 .pill-new .pill-contacted .pill-demo .pill-trial
                           .pill-proposal .pill-won .pill-lost, plus the
                           activity kinds and lead statuses
     .table-wrap           the scroll container a wide table belongs in
     .stats  .stat         tile row; .stat .n is the number, .stat .k the label
     .field                one label + control + .hint + .error; .has-error
                           reddens it. .errors is the summary box.
     .timeline             <ol> of logged activity
     .task  .overdue       one row of the task list; overdue is rose
     .board  .board-col    the pipeline; scrolls sideways, stacks under 700px
     .flash-area           holds one .flash per message, each also carrying
                           .flash-ok / .flash-error / .flash-warn. A .flash is
                           a flex row, so the message goes inside a single
                           child element — app.js appends the dismiss button
                           as the second item.
     .empty                "nothing here yet"
     .muted                de-emphasised text
     .detail               two columns: .detail-main + .detail-side,
                           one column under 900px

   Page-local <style> blocks in views/ (the board, the timeline partial, the
   tasks panel, the Permits banners) come after this file in document order,
   so at equal specificity they win. Where one of them assumed a light page —
   .deal-card's white background, .tk-overdue's #a11 — the fix here is a
   two-class selector that outranks it. Those are marked OVERRIDE. Deleting
   one does not break the layout; it makes the board unreadable.

   Responsive is not a nicety here: this gets used standing in a truck yard.
   Nothing is allowed to make <body> scroll sideways — wide things scroll
   inside their own container.
   ========================================================================== */

:root {
  color-scheme: dark;

  --bg: #0b0e14;
  --bg-raised: #121722;
  --bg-code: #0d1219;
  --bg-input: #0e131c;

  --line: #1e2634;
  --line-soft: #171d28;

  --text: #e4e9f2;
  --muted: #8a97ad;
  --dim: #5d6981;

  --accent: #5eead4;
  --accent-dim: #2dd4bf;
  /* Text on an accent-filled button. Near-black, not white: teal at full
     chroma has nowhere near the contrast for white text. */
  --accent-ink: #04231d;

  /* The sidebar's width. A token because the shell grid and the narrow-screen
     media query both need to agree about it. 236px fits "Companies" and the
     longest group heading without wrapping at the 14px body size. */
  --sidebar-w: 236px;
  --amber: #fbbf24;
  --rose: #fb7185;
  --violet: #c4b5fd;
  --sky: #9ec2ff;
  --green: #34d399;

  /* Washes for pills, banners and focus rings. Kept as tokens so a status
     colour is changed in one place rather than eleven. */
  --wash-accent: rgba(94, 234, 212, .13);
  --wash-amber: rgba(251, 191, 36, .14);
  --wash-rose: rgba(251, 113, 133, .13);
  --wash-violet: rgba(196, 181, 253, .14);
  --wash-sky: rgba(158, 194, 255, .14);
  --wash-green: rgba(52, 211, 153, .14);
  --wash-muted: rgba(138, 151, 173, .12);
  --ring: rgba(94, 234, 212, .18);

  --radius: 10px;
  --radius-sm: 7px;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif;
}

/* ── reset ─────────────────────────────────────────────────────────────── */

*, *::before, *::after { box-sizing: border-box; }

/* An author rule beats the UA's [hidden]{display:none} at any specificity.
   .flash below is display:flex, and app.js dismisses a banner by setting
   .hidden on it — without this line that dismiss silently does nothing, and
   so does the row filter. Same trap as the console. */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  background: var(--bg);
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font: 14.5px/1.55 var(--sans);
  -webkit-font-smoothing: antialiased;
  /* No overflow-x:hidden. It would mask a runaway table instead of fixing it,
     and it breaks position:sticky on the nav in every browser that matters. */
}

h1, h2, h3, h4 { margin: 0 0 .4rem; font-weight: 640; letter-spacing: -.018em; line-height: 1.2; }
h1 { font-size: 25px; font-weight: 670; letter-spacing: -.022em; }
h2 { font-size: 16px; }
h3 { font-size: 14px; }
p { margin: 0 0 .8rem; }
ul, ol { margin: 0 0 .8rem; padding-left: 1.25rem; }
hr { border: 0; border-top: 1px solid var(--line-soft); margin: 1.5rem 0; }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

code, kbd, samp { font-family: var(--mono); font-size: .92em; }
pre {
  font-family: var(--mono);
  font-size: 13px;
  background: var(--bg-code);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin: 0 0 .8rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* Keyboard focus is visible everywhere. An operator working a list with tab
   and enter is the fast path, and an invisible focus ring ends it. */
:focus-visible {
  outline: 2px solid var(--accent-dim);
  outline-offset: 2px;
  border-radius: 4px;
}

::selection { background: rgba(94, 234, 212, .25); color: var(--text); }

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

/* ── shell ─────────────────────────────────────────────────────────────── */

/* Sidebar, not a top bar. Eight sections do not fit across a laptop without
   wrapping to two rows or scrolling sideways, and both make the thing you
   navigate with the least stable element on the page. */
.shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  min-height: 100dvh;
}
/* minmax(0,1fr) above and min-width:0 here are the same fix twice: a grid item
   is min-content wide by default, so one wide table inside would push the
   whole column past the viewport and make <body> scroll sideways. */
.shell-col { min-width: 0; }

.wrap, .main {
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 32px;
}
.main { padding-top: 4px; padding-bottom: 80px; }
/* A page that wraps its own content must not get the padding twice. */
.main > .wrap, .main .wrap { max-width: none; padding: 0; }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  z-index: 60;
}
.skip:focus { left: 12px; top: 12px; }

/* The panel paints and positions; .nav inside is the stack that scrolls.
   One element cannot do both — a scrolling panel scrolls its own background
   away, and the border stops where the links stop rather than at the floor. */
.sidebar {
  position: sticky;
  top: 0;
  align-self: start;
  height: 100dvh;
  background: var(--bg-raised);
  border-right: 1px solid var(--line-soft);
  z-index: 30;
}
.nav {
  display: flex;
  flex-direction: column;
  height: 100%;
  gap: 2px;
  padding: 18px 12px 14px;
  overflow-y: auto;
}

.brand, .nav-brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--text);
  font-weight: 650;
  font-size: 16px;
  letter-spacing: -.02em;
  white-space: nowrap;
  padding: 4px 10px 18px;
}
.brand:hover, .nav-brand:hover { text-decoration: none; }
.nav form { margin: 0; }
.nav-who { color: var(--dim); font-size: 12.5px; }
.tag {
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--accent);
  border: 1px solid rgba(94, 234, 212, .35);
  border-radius: 5px;
  padding: 2px 7px;
}

.nav-links, .nav-right {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 14px;
  min-width: 0;
}
/* Pushed to the floor of the panel rather than placed after the last link, so
   the account sits in the same spot whatever the list above it does. */
.nav-right {
  margin-top: auto;
  gap: 10px;
  padding-top: 14px;
  border-top: 1px solid var(--line-soft);
}
/* The signed-in address can be forty characters. Truncated rather than
   wrapped, or it pushes Sign out out of the panel. */
.nav-right .muted {
  font-size: 12.5px;
  padding: 0 10px;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nav-right form { padding: 0 10px; }
.nav-right .btn { width: 100%; justify-content: center; }

.nav-group {
  margin: 18px 0 6px;
  padding: 0 10px;
  font-family: var(--mono);
  font-size: 10.5px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--dim);
}

.nav-links a, .nav-right a {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--muted);
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  /* The current item is marked with a left bar. Reserving it on every item
     stops the label shifting 3px sideways as you move between sections. */
  border-left: 3px solid transparent;
}
.nav-links a:hover, .nav-right a:hover {
  color: var(--text);
  background: var(--bg);
  text-decoration: none;
}

/* Icons live here rather than in the template: an inline <svg> would be raw
   markup echoed past the guard that stops a company name becoming script, and
   decoration is not worth an exception to that rule. Masked, not drawn, so the
   glyph takes the link's own colour on hover and when current. */
.nav-links a::before {
  content: "";
  width: 17px;
  height: 17px;
  flex: none;
  background: currentColor;
  opacity: .75;
  -webkit-mask: var(--icon) center / contain no-repeat;
  mask: var(--icon) center / contain no-repeat;
}
.nav-links a.is-current::before { opacity: 1; }
/* No --icon set (a section added to the template and not to this file) leaves
   an empty 17px box, which keeps every label on the same left edge rather than
   knocking one out of line. */
.nav-links a[href="/"] {
  --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='3' width='7' height='9' rx='1.5'/%3E%3Crect x='14' y='3' width='7' height='5' rx='1.5'/%3E%3Crect x='14' y='12' width='7' height='9' rx='1.5'/%3E%3Crect x='3' y='16' width='7' height='5' rx='1.5'/%3E%3C/svg%3E");
}
.nav-links a[href="/contacts"] {
  --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2'/%3E%3Ccircle cx='12' cy='7' r='4'/%3E%3C/svg%3E");
}
.nav-links a[href="/companies"] {
  --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='4' y='3' width='11' height='18' rx='1.5'/%3E%3Cpath d='M15 8h4a1.5 1.5 0 0 1 1.5 1.5V21'/%3E%3Cpath d='M8 7h3M8 11h3M8 15h3'/%3E%3C/svg%3E");
}
.nav-links a[href="/deals"] {
  --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 17l6-6 4 4 8-8'/%3E%3Cpath d='M15 7h6v6'/%3E%3C/svg%3E");
}
.nav-links a[href="/tasks"] {
  --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M9 11l2 2 4-4'/%3E%3Crect x='3' y='4' width='18' height='17' rx='2'/%3E%3Cpath d='M8 2v4M16 2v4'/%3E%3C/svg%3E");
}
.nav-links a[href="/templates"] {
  --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='14' rx='2'/%3E%3Cpath d='M3 8l9 6 9-6'/%3E%3C/svg%3E");
}
.nav-links a[href="/clients"] {
  --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='2' y='5' width='20' height='14' rx='2'/%3E%3Ccircle cx='8.5' cy='11' r='2'/%3E%3Cpath d='M5 16c.7-1.6 2-2.4 3.5-2.4S11.3 14.4 12 16'/%3E%3Cpath d='M15 10h4M15 14h4'/%3E%3C/svg%3E");
}
.nav-links a[href="/permits/leads"] {
  --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 13h5l1.5 3h5L16 13h5'/%3E%3Cpath d='M4.5 6.5h15L21 13v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-5z'/%3E%3C/svg%3E");
}
.nav-links a[href="/keys/new"] {
  --icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='1.9' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='7.5' cy='15.5' r='4'/%3E%3Cpath d='M10.5 12.5L20 3'/%3E%3Cpath d='M17 6l2.5 2.5M14.5 8.5L17 11'/%3E%3C/svg%3E");
}

/* Set server-side by the nav partial, or by app.js when a page renders the bar
   without it — whichever gets there first, same result. */
.nav-links a.is-current, .nav-links a[aria-current="page"],
.nav a.is-current, .nav a[aria-current="page"] {
  color: var(--accent);
  background: var(--wash-accent);
  border-left-color: var(--accent);
  font-weight: 600;
}

/* Under a laptop the panel goes back to being a bar across the top. The group
   headings go with it: they earn their space in a column and only crowd a row.
   The bar is the one thing allowed to scroll sideways — it is short, and it is
   not where the content is. */
@media (max-width: 950px) {
  /* auto rows + min-height:100dvh means align-content:stretch hands the
     leftover height to BOTH rows, which parked the content 220px down the page
     with the bar floating above it. Naming the rows spends it all on the
     second one. */
  .shell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
  }
  .sidebar {
    height: auto;
    border-right: 0;
    border-bottom: 1px solid var(--line-soft);
    background: rgba(18, 23, 34, .92);
    backdrop-filter: blur(8px);
  }
  .nav {
    flex-direction: row;
    align-items: center;
    gap: 8px 14px;
    height: auto;
    padding: 8px 20px;
    overflow: visible;
  }
  .brand { flex: none; padding: 4px 0; }
  .nav-group { display: none; }
  /* The LINKS scroll, not the whole bar. Scrolling .nav left no free space in
     it, so .nav-right's margin-left:auto resolved to zero, its width collapsed
     to zero, and Sign out rendered on top of the first two sections. */
  .nav-links {
    flex: 1 1 auto;
    flex-direction: row;
    gap: 2px;
    font-size: 13.5px;
    min-width: 0;
    overflow-x: auto;
    scrollbar-width: none;
  }
  .nav-links::-webkit-scrollbar { display: none; }
  .nav-links a {
    border-left: 0;
    border-bottom: 2px solid transparent;
    border-radius: var(--radius-sm) var(--radius-sm) 0 0;
  }
  .nav-links a.is-current, .nav-links a[aria-current="page"] {
    border-left-color: transparent;
    border-bottom-color: var(--accent);
  }
  .nav-right {
    flex: none;
    flex-direction: row;
    align-items: center;
    margin: 0;
    padding-top: 0;
    border-top: 0;
    gap: 10px;
  }
  .nav-right .muted { max-width: 12rem; padding: 0; }
  .nav-right form { padding: 0; }
  .nav-right .btn { width: auto; }
  .wrap, .main { padding: 0 20px; }
}

footer, .site-footer {
  border-top: 1px solid var(--line-soft);
  margin-top: 44px;
  padding: 22px 0 40px;
  color: var(--dim);
  font-size: 13px;
}

/* ── flash ─────────────────────────────────────────────────────────────── */

/* .flash-area wraps them; each .flash is one banner. The banner is a flex row
   so the dismiss button app.js adds sits at the end of it — which is why the
   message arrives as a single child element with its lines inside, and not as
   loose text nodes that would become flex items of their own. */
.flash-area, .flashes {
  list-style: none;
  max-width: 1180px;
  margin: 16px auto 0;
  padding: 0 24px;
  background: none;
  border: 0;
}

.flash, .flash-ok, .flash-error, .flash-warn, .flash-notice, .flash-success {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-left: 3px solid var(--muted);
  border-radius: var(--radius-sm);
  padding: 11px 14px;
  margin: 0 0 10px;
  font-size: 13.5px;
  transition: opacity .2s ease;
}
/* A standalone banner outside .flash-area still needs the page's gutter. */
body > .flash { max-width: 1180px; width: calc(100% - 48px); margin: 16px auto; }
.flash > * { min-width: 0; }
.flash > :first-child { flex: 1 1 auto; }
/* Each line of the message is its own <p>. They are one message, so the gap
   between them is a line, not a paragraph break. */
.flash p { margin: 0; overflow-wrap: anywhere; }
.flash p + p { margin-top: 2px; }

.flash-ok, .flash.ok, .flash-success {
  border-color: rgba(94, 234, 212, .35);
  border-left-color: var(--accent);
  background: linear-gradient(90deg, var(--wash-accent), transparent 55%), var(--bg-raised);
}
.flash-error, .flash.error {
  border-color: rgba(251, 113, 133, .35);
  border-left-color: var(--rose);
  background: linear-gradient(90deg, var(--wash-rose), transparent 55%), var(--bg-raised);
}
.flash-warn, .flash.warn, .flash-notice {
  border-color: rgba(251, 191, 36, .3);
  border-left-color: var(--amber);
  background: linear-gradient(90deg, var(--wash-amber), transparent 55%), var(--bg-raised);
}
.flash-ok strong, .flash-ok b, .flash-success strong { color: var(--accent); }
.flash-error strong, .flash-error b { color: var(--rose); }
.flash-warn strong, .flash-warn b, .flash-notice strong { color: var(--amber); }

/* The dismiss button app.js adds. Absent without JS, and the banner is then
   simply permanent for that page view, which is fine. */
.flash-x {
  flex: none;
  margin-left: auto;
  background: none;
  border: 0;
  color: var(--dim);
  font: inherit;
  font-size: 16px;
  line-height: 1;
  padding: 0 2px;
  cursor: pointer;
}
.flash-x:hover { color: var(--text); }
.flash.is-going { opacity: 0; }

/* ── page head ─────────────────────────────────────────────────────────── */

.page { padding: 26px 0 0; }
.page-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px 16px;
  margin-bottom: 18px;
}
.page-head h1 { margin: 0; }
.page-head > a:not(.btn) { color: var(--muted); font-size: 13.5px; }
.page-head > a:not(.btn):hover { color: var(--text); }

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin-left: auto;
}
/* Every destructive action is its own <form> for the CSRF token; without this
   each one would take a whole line and the row would be three tall. */
.actions form { margin: 0; display: inline-flex; }

.lede, .sub { color: var(--muted); max-width: 70ch; }

/* ── cards and panels ──────────────────────────────────────────────────── */

.card, .panel {
  background: var(--bg-raised);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px 20px;
  margin: 0 0 16px;
}
/* Borders, not shadows: a drop shadow on a near-black background is a smudge,
   and twelve of them down a list read as dirt on the screen. */
.card > :last-child, .panel > :last-child { margin-bottom: 0; }
.card h2, .panel h2 { font-size: 15.5px; font-weight: 620; }

.card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px 14px;
  margin: 0 0 12px;
}
.card-head h2, .card-head h3 { margin: 0; }
.card-head > a:last-child, .card-head .actions { margin-left: auto; font-size: 13.5px; }

.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(210px, 1fr)); gap: 14px; }
.grid > .wide, .field.wide { grid-column: 1 / -1; }

/* Definition lists carry most of the detail pages. Two columns, label left. */
.meta {
  display: grid;
  grid-template-columns: minmax(9rem, max-content) 1fr;
  gap: 6px 18px;
  margin: 0;
  font-size: 13.5px;
}
.meta dt {
  color: var(--dim);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  font-weight: 600;
  padding-top: 2px;
}
.meta dd { margin: 0; overflow-wrap: anywhere; }

.notes { background: var(--bg-code); }

/* ── detail: main + side ───────────────────────────────────────────────── */

.detail { display: grid; grid-template-columns: minmax(0, 1fr) 340px; gap: 18px; align-items: start; }
.detail-main, .detail-side { min-width: 0; }

/* ── forms ─────────────────────────────────────────────────────────────── */

.form { margin: 0; max-width: 46rem; }
.form.inline, .inline {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  max-width: none;
}
.form.inline .field, .inline .field { margin: 0; }

/* .field is a <p> in the templates, so its default bottom margin is the
   spacing between fields. Do not remove it without giving them one. */
.field { display: flex; flex-direction: column; gap: 6px; min-width: 0; margin: 0 0 14px; }

label {
  font-size: 11.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--dim);
  font-weight: 600;
}

input, select, textarea {
  background: var(--bg-input);
  border: 1px solid var(--line);
  color: var(--text);
  border-radius: var(--radius-sm);
  padding: 9px 11px;
  font: inherit;
  font-size: 14px;
  width: 100%;
  min-width: 0;
  max-width: 100%;
}
/* Checkbox and radio must not stretch to the column. */
input[type="checkbox"], input[type="radio"] { width: auto; min-width: 0; accent-color: var(--accent-dim); }
input[type="date"], input[type="number"], input[type="search"] { width: auto; min-width: 8rem; }
textarea { resize: vertical; min-height: 76px; font-size: 13.5px; line-height: 1.5; }
select {
  /* The UA arrow is invisible on a dark control in Safari, so it gets drawn
     here as a background image (a data URI — an external asset would be
     blocked by the CSP). */
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' fill='none' stroke='%238a97ad' stroke-width='1.6' stroke-linecap='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 11px center;
  background-size: 10px 6px;
  padding-right: 30px;
}
input::placeholder, textarea::placeholder { color: var(--dim); }
input:focus, select:focus, textarea:focus {
  outline: none;
  border-color: var(--accent-dim);
  box-shadow: 0 0 0 3px var(--ring);
}
input:disabled, select:disabled, textarea:disabled { opacity: .55; cursor: not-allowed; }

.check, .checkbox {
  display: flex;
  align-items: center;
  gap: 9px;
  font-size: 13.5px;
  color: var(--muted);
  text-transform: none;
  letter-spacing: normal;
}
.check input, .checkbox input { flex: none; }

.hint { font-size: 12px; color: var(--dim); margin: 0; }

/* A checkbox group: the trades on a company, and on the key forms. Grid rather
   than a run of inline labels so twelve of them line up in columns and the
   ticked ones can be found at a glance. */
.choices {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 6px 14px;
  margin: 6px 0 4px;
}
.choices .check { font-size: 13px; }
.field > .label { display: block; }
.form.inline .trades { flex: 1 0 100%; }
.error { font-size: 12.5px; color: var(--rose); }
.has-error input, .has-error select, .has-error textarea { border-color: rgba(251, 113, 133, .55); }
.has-error label { color: var(--rose); }

/* The summary box above a rejected form. */
.errors {
  background: var(--bg-raised);
  border: 1px solid rgba(251, 113, 133, .4);
  border-left: 3px solid var(--rose);
  border-radius: var(--radius-sm);
  padding: 12px 16px;
  margin: 0 0 18px;
  font-size: 13.5px;
}
.errors p { margin: 0 0 .3rem; color: var(--rose); font-weight: 560; }
.errors ul { margin: 0; padding-left: 1.1rem; }

.search {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
  margin: 0 0 16px;
}
.search label {
  /* Visible to a screen reader, out of the way of the box. */
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.search input[type="search"], .search input[type="text"] { flex: 1 1 18rem; min-width: 0; width: auto; }

/* ── buttons ───────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  border: 1px solid var(--line);
  background: var(--bg-input);
  color: var(--text);
  border-radius: 8px;
  padding: 9px 15px;
  font: inherit;
  font-size: 14px;
  font-weight: 560;
  line-height: 1.2;
  width: auto;
  cursor: pointer;
  white-space: nowrap;
  transition: background .15s, border-color .15s, color .15s;
}
.btn:hover { text-decoration: none; border-color: var(--dim); }
.btn:disabled, .btn[aria-disabled="true"] { opacity: .5; cursor: not-allowed; }

.btn-primary, .btn.primary {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}
.btn-primary:hover, .btn.primary:hover { background: #7df1dd; border-color: #7df1dd; }

.btn-ghost, .btn.ghost { background: transparent; border-color: var(--line); color: var(--text); }
.btn-ghost:hover, .btn.ghost:hover { background: var(--bg-input); border-color: var(--dim); }

.btn-danger, .btn.danger {
  background: transparent;
  border-color: rgba(251, 113, 133, .4);
  color: var(--rose);
}
.btn-danger:hover, .btn.danger:hover { background: var(--wash-rose); border-color: var(--rose); }

.btn-sm { padding: 5px 11px; font-size: 12.5px; }

/* A button that is really a link — used inside dense rows. */
.linkish {
  background: none;
  border: 0;
  color: var(--accent);
  font: inherit;
  padding: 0;
  cursor: pointer;
  text-align: left;
}
.linkish:hover { text-decoration: underline; }

/* OVERRIDE — the small inline buttons in the tasks panel, the task list and
   the board stage-mover are styled in their own templates with a grey border
   and a transparent background. Two classes so this wins, because those rules
   come later in the document. */
.main .tp-btn, .main .tk-btn, .main .deal-move button, .main .deal-lost-row button,
.main .tp-add button, .main .search button, .main .tk-add button {
  background: var(--bg-input);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
  cursor: pointer;
  font: inherit;
  font-size: 12.5px;
  padding: 5px 11px;
  white-space: nowrap;
}
.main .tp-btn:hover, .main .tk-btn:hover, .main .deal-move button:hover,
.main .deal-lost-row button:hover, .main .tp-add button:hover,
.main .search button:hover, .main .tk-add button:hover {
  border-color: var(--accent-dim);
  color: var(--accent);
}
/* OVERRIDE — same story for the inline inputs those forms use: their local
   rules set padding and font only, so the dark treatment has to outrank them
   to keep the box from being a white slab on Safari. */
.main .tp-add input, .main .tk-add input, .main .deal-lost-row input {
  background: var(--bg-input);
  border: 1px solid var(--line);
  border-radius: 6px;
  color: var(--text);
  padding: 7px 10px;
}

/* ── tables ────────────────────────────────────────────────────────────── */

.table-wrap {
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  background: var(--bg-raised);
  overflow-x: auto;
  /* momentum scrolling on iOS, and the container — never the page — is what
     moves sideways. */
  -webkit-overflow-scrolling: touch;
  margin: 0 0 16px;
}
/* app.js drops a .table-wrap around every table.list it finds, which is what
   turns a wide list into a scroller of its own instead of a page that slides
   sideways. The inner table then hands its border to the wrapper — two nested
   1px boxes read as a rendering fault. */
.table-wrap > table { min-width: 640px; margin: 0; border: 0; border-radius: 0; }
.filter-none { padding: 18px 4px; color: var(--muted); font-size: 13px; }

table { border-collapse: collapse; width: 100%; font-size: 13px; margin: 0 0 16px; }
th {
  text-align: left;
  padding: 10px 13px;
  font-size: 11px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--dim);
  font-weight: 600;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
td {
  padding: 10px 13px;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: top;
  /* A pasted URL or a 36-character id must wrap rather than widen the table
     past the viewport. This is what keeps a bare, unwrapped table honest on a
     phone even before app.js gives it a scroll container. */
  overflow-wrap: anywhere;
}
tbody tr:last-child td { border-bottom: none; }
tbody tr:hover td { background: rgba(255, 255, 255, .015); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
td.mono, .mono { font-family: var(--mono); font-size: 12px; }

/* .list is the standing table class in the templates: a bordered card of rows. */
.list {
  background: var(--bg-raised);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  overflow: hidden;
}
.list thead th { background: rgba(255, 255, 255, .02); }

/* ── pills ─────────────────────────────────────────────────────────────── */

.pill, .kind, .deal-kind, .tl-kind {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 20px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: .04em;
  text-transform: uppercase;
  white-space: nowrap;
  background: var(--wash-muted);
  color: var(--muted);
}

/* Deal stages, in pipeline order. Cold to warm, then won green / lost rose —
   the board is read at a glance and the colour has to carry the meaning. */
.pill-new, .pill.new { background: var(--wash-muted); color: var(--muted); }
.pill-contacted, .pill.contacted { background: var(--wash-sky); color: var(--sky); }
.pill-demo, .pill.demo { background: var(--wash-violet); color: var(--violet); }
.pill-trial, .pill.trial { background: var(--wash-accent); color: var(--accent); }
.pill-proposal, .pill.proposal { background: var(--wash-amber); color: var(--amber); }
.pill-won, .pill.won, .pill.active, .pill.converted { background: var(--wash-green); color: var(--green); }
.pill-lost, .pill.lost, .pill.revoked { background: var(--wash-rose); color: var(--rose); }
.pill-qualified, .pill.qualified { background: var(--wash-sky); color: var(--sky); }
.pill.closed, .pill.expired { background: var(--wash-muted); color: var(--dim); }

/* Activity kinds. Quieter than stages: the timeline is prose, not a signal. */
.kind-note, .pill-note { background: var(--wash-muted); color: var(--muted); }
.kind-call, .pill-call { background: var(--wash-sky); color: var(--sky); }
.kind-email, .pill-email { background: var(--wash-violet); color: var(--violet); }
.kind-meeting, .pill-meeting { background: var(--wash-amber); color: var(--amber); }
.kind-key_issued, .pill-key_issued { background: var(--wash-accent); color: var(--accent); }
.kind-system, .pill-system { background: transparent; color: var(--dim); border: 1px solid var(--line); }

.badge {
  display: inline-block;
  min-width: 18px;
  padding: 1px 6px;
  margin-left: 6px;
  border-radius: 9px;
  background: var(--accent);
  color: var(--accent-ink);
  font-size: 11px;
  font-weight: 700;
  line-height: 16px;
  text-align: center;
}

/* ── stat tiles ────────────────────────────────────────────────────────── */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 12px; margin: 0 0 18px; }
.stat {
  background: var(--bg-raised);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius-sm);
  padding: 13px 15px;
  min-width: 0;
}
.stat .n, .stat strong {
  display: block;
  font-size: 20px;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  letter-spacing: -.02em;
  /* A seven-figure pipeline total is wider than a 140px tile on a phone. */
  overflow-wrap: anywhere;
}
.stat .k, .stat small { display: block; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--dim); margin-top: 3px; }
/* The tiles are links. Without this they inherit the teal link colour and the
   dashboard reads as four hyperlinks rather than four numbers. */
a.stat { color: var(--text); }
a.stat:hover { border-color: var(--dim); text-decoration: none; }
a.stat:hover .n { color: var(--accent); }

/* ── the board ─────────────────────────────────────────────────────────── */

.board, .pipe-board {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  padding-bottom: 10px;
  margin: 0 0 8px;
}
.board-col, .pipe-board .pipe-col {
  flex: 1 0 16rem;
  min-width: 16rem;
  background: var(--bg-raised);
  border: 1px solid var(--line-soft);
  border-radius: var(--radius);
  padding: 10px;
}
.board-col > h2, .pipe-board .pipe-col h2 {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: 11px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--dim);
  font-weight: 600;
  margin: 2px 4px 8px;
}
.pipe-board .pipe-col-sum { color: var(--muted); font-weight: 500; font-variant-numeric: tabular-nums; }

/* OVERRIDE — board.php ships `.deal-card{background:rgba(255,255,255,.75)}`,
   written for a light page. Same specificity, later in the document, so it
   wins unless this selector carries two classes. Without it every card on the
   pipeline is a white slab. */
.board .board-card, .pipe-board .deal-card {
  background: var(--bg-code);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 9px 11px;
  margin-bottom: 8px;
}
.pipe-board .deal-card:hover { border-color: var(--dim); }
.pipe-board .deal-card-title { color: var(--text); font-weight: 600; font-size: 13.5px; }
.pipe-board .deal-card-title:hover { color: var(--accent); text-decoration: none; }
.pipe-board .deal-card-row { font-size: 12.5px; margin-top: 5px; }
.pipe-board .deal-card .muted { font-size: 12.5px; }
.pipe-board .deal-move summary { color: var(--dim); font-size: 12px; }
.pipe-board .deal-move summary:hover { color: var(--muted); }
/* An empty column already has a surface; the dashed box would be a card
   inside a card. */
.pipe-board .empty {
  background: none;
  border: 0;
  padding: 10px 4px;
  margin: 0;
  text-align: left;
  font-size: 12.5px;
}

/* The stage next to the deal's title. Its own template draws the outline; this
   fills in the colour so it reads as the same pill as everywhere else. */
.main .deal-stage {
  border-color: var(--line);
  background: var(--wash-muted);
  color: var(--muted);
  vertical-align: middle;
}

.pipe-total { font-size: 22px; font-weight: 660; letter-spacing: -.02em; font-variant-numeric: tabular-nums; }
.pipe-total .muted { font-size: 13.5px; font-weight: 400; }
.pipe-closed > .card { min-width: 0; }
/* Those two summary tables draw their own top rule, so the shared bottom rule
   has to go or every row sits between a doubled line. */
.pipe-closed td { border-top-color: var(--line-soft); border-bottom: 0; }

/* ── timeline ──────────────────────────────────────────────────────────── */

.timeline { list-style: none; margin: 0; padding: 0; }
.timeline > li, .tl-item, .deal-list li {
  border-top: 1px solid var(--line-soft);
  padding: 9px 2px;
}
.timeline > li:first-child, .tl .tl-item:first-child, .deal-list li:first-child { border-top: 0; }
.timeline time, .tl-when, .tl-who { color: var(--dim); font-size: 12.5px; }
.timeline p, .tl-body, .deal-body {
  margin: 3px 0 0;
  /* pre-wrap keeps a pasted email's line breaks without nl2br() building
     markup out of something a stranger typed. */
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-size: 13.5px;
}
.tl-empty, .tp-empty, .tk-empty { color: var(--muted); font-size: 13px; }
.main .tl-system .tl-kind { color: var(--dim); opacity: 1; }

/* ── tasks ─────────────────────────────────────────────────────────────── */

.tasks { list-style: none; margin: 0; padding: 0; }
.task, .tasks > li, .tp-item, .tk-item {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  border-top: 1px solid var(--line-soft);
  padding: 8px 2px;
}
.tasks > li:first-child, .tk-list .tk-item:first-child, .tp-list .tp-item:first-child { border-top: 0; }
.task form, .tp-item form, .tk-item form { margin: 0; }
.tk-title, .tp-title { flex: 1 1 14rem; overflow-wrap: anywhere; }
.tk-due, .tp-due { color: var(--dim); font-size: 12.5px; white-space: nowrap; }
.tk-links { font-size: 12.5px; }
.tk-zone { color: var(--muted); font-size: 13px; }
.tk-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 12px; margin-bottom: 14px; }
.tk-section { margin: 20px 0; }
.tk-section > h2, .main .tk-section h2 {
  font-size: 11px;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--dim);
  font-weight: 600;
  margin: 0 0 4px;
}
.tk-section > summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 13px;
  list-style-position: outside;
}

/* Overdue is the whole point of the list, so it is the one place rose is
   used for emphasis rather than for an error.
   OVERRIDE — tasks/index.php sets #a11 on these, which on #0b0e14 is a dark
   red on near-black and effectively unreadable. Three classes to outrank it. */
.overdue, .overdue .tk-due, .overdue .task-due,
.main .tk-overdue .tk-due, .main .tp-item .tp-late {
  color: var(--rose);
  font-weight: 600;
  opacity: 1;
}
/* The title of an overdue row stays readable; only the date is the alarm. */
.overdue .tk-title, .overdue .task-title { color: var(--text); font-weight: 400; }
.main .tk-section.tk-overdue h2 { color: var(--rose); }
.tp-done .tp-title, .tk-doing, .deal-done { text-decoration: line-through; color: var(--dim); }

/* ── empty states, muted text ──────────────────────────────────────────── */

.empty {
  padding: 26px 20px;
  text-align: center;
  color: var(--muted);
  background: var(--bg-raised);
  border: 1px dashed var(--line);
  border-radius: var(--radius);
  margin: 0 0 16px;
}
/* Inside a card the surface is already there; a second one reads as a bug. */
.card .empty, .panel .empty, .board-col .empty {
  background: none;
  border: 0;
  padding: 12px 0;
  text-align: left;
}
.muted { color: var(--muted); }
.dim { color: var(--dim); }
small { color: var(--muted); }

/* ── Permits API pages ─────────────────────────────────────────────────── */

/* The outage banner is defined in those three templates with an orange border;
   this fills in the surface so it does not float on the page background. */
.main .px-banner {
  background: var(--bg-raised);
  border-color: rgba(251, 191, 36, .45);
  border-left-color: var(--amber);
  border-radius: var(--radius-sm);
  color: var(--text);
}
.main .px-banner h2 { color: var(--amber); }
.px-note { color: var(--muted); }
.px-id { color: var(--muted); }
.px-message { color: var(--muted); }
.px-cell { overflow-wrap: anywhere; }

/* ── responsive ────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  /* The side column goes under the main one rather than squeezing to 200px. */
  .detail { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 700px) {
  /* Columns stack. A 16rem-wide column inside a sideways scroller is fine on a
     tablet; on a phone it means every card is half off screen, and the board
     becomes a thing you scroll twice to read once. */
  .board, .pipe-board { display: block; overflow-x: visible; padding-bottom: 0; }
  .board-col, .pipe-board .pipe-col { min-width: 0; width: auto; margin: 0 0 12px; }
}

@media (max-width: 640px) {
  .wrap, .main, .nav, .flashes, .flash-area { padding-left: 16px; padding-right: 16px; }
  body > .flash { width: calc(100% - 32px); }
  /* Brand and account stay put; the sections scroll between them. This used
     to push .nav-links onto its own row with `order:3; flex:1 0 100%`, which
     the sidebar rewrite left behind — at 430px that rule still won, laying the
     links out AFTER the account block and 398px wide inside a 390px bar, so
     Sign out rendered on top of the first two sections. */
  .nav { padding-top: 6px; padding-bottom: 6px; min-height: 0; }
  .nav-right .muted { max-width: 8rem; }
  h1 { font-size: 21px; }
  .card, .panel { padding: 15px 14px; }
  .page { padding-top: 18px; }
  .actions { margin-left: 0; width: 100%; }
  .actions .btn { flex: 1 1 auto; }
  /* Label above value: a 9rem label column leaves nothing for a long email. */
  .meta { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .meta dd { margin-bottom: 10px; }
  .pipe-closed > .card { flex-basis: 100%; }
  .deal-cols > .card { flex-basis: 100%; }
  /* Comfortable tap targets. Anything under ~34px gets missed one time in
     five with a thumb, and every miss here submits nothing or the wrong row. */
  .main .tp-btn, .main .tk-btn, .main .deal-move button, .main .deal-lost-row button,
  .main .tp-add button, .main .tk-add button, .main .search button { padding: 8px 13px; font-size: 13px; }
  .main .tp-add input, .main .tk-add input, .main .deal-lost-row input { padding: 9px 11px; }
  /* iOS zooms the page when a focused field is under 16px. */
  input, select, textarea { font-size: 16px; }
}

@media print {
  /* .sidebar, not header: the shell has no <header> since the nav moved to the
     side, and printing a page with a navigation column down the left wastes an
     inch of every sheet. */
  .sidebar, .nav, .actions, .flash, footer { display: none; }
  .shell { display: block; }
  body { background: #fff; color: #000; }
  .card, .panel, .list { border-color: #ccc; background: #fff; }
}
