/* components.css -- the named pieces. Layer 4 of 4.
 *
 * Each block below is one component: its own class, its own modifiers, no
 * dependency on where it sits. Layout is layout.css's job -- nothing here sets
 * a page-level margin or a column count.
 *
 * Replaces Bootstrap 3 plus the purchased theme (~700 KB of CSS, of which the
 * site used perhaps a tenth) with the ten things the app actually renders.
 */

/* ══ Icon ═══════════════════════════════════════════════════════════════
 * Every icon is a <use> into the sprite inlined once per page by
 * partials/icon-sprite.ejs. currentColor means an icon tints itself from the
 * text colour around it, so a button, a nav item and a service card all get
 * the right icon colour with no per-context rule.
 *
 * 1em sizing ties the icon to its label's font-size -- the pairing stays
 * correct when the heading scale changes underneath it. */
.icon {
  width: 1em;
  height: 1em;
  flex: none;
  vertical-align: -0.125em; /* optical centring against the text baseline */
}

.icon-lg { font-size: 1.5rem; }
.icon-xl { font-size: 2rem; }

/* The sprite element itself. Hidden without display:none, which would stop
 * some browsers resolving the <use> references inside it. */
.icon-sprite {
  position: absolute;
  width: 0;
  height: 0;
  overflow: hidden;
}

/* ══ Button ═════════════════════════════════════════════════════════════ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 0.7em 1.4em;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: var(--surface-3);
  color: var(--ink);
  font-size: var(--text-base);
  font-weight: 600;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease),
    transform var(--dur-fast) var(--ease);
}

.btn:hover {
  background: var(--line);
  color: var(--ink);
}

.btn:active {
  transform: translateY(1px);
}

.btn:disabled,
.btn[aria-disabled='true'] {
  opacity: 0.55;
  cursor: not-allowed;
  transform: none;
}

.btn-primary {
  background: var(--brand-accent);
  border-color: var(--accent-strong);
  color: var(--accent-contrast);
}

.btn-primary:hover {
  background: var(--accent-strong);
  color: var(--accent-contrast);
}

.btn-dark {
  background: var(--ink);
  color: #fff;
}

.btn-dark:hover {
  background: var(--ink-2);
  color: #fff;
}

.btn-ghost {
  background: transparent;
  border-color: var(--line-strong);
}

.btn-ghost:hover {
  background: var(--surface-2);
  border-color: var(--ink-4);
}

/* Destructive actions -- delete a lead, remove a user, clear the blacklist.
 * Outlined rather than filled: a solid red button next to a solid save button
 * is a misclick waiting to happen. */
.btn-danger {
  background: transparent;
  border-color: color-mix(in srgb, var(--danger) 40%, white);
  color: var(--danger);
}

.btn-danger:hover {
  background: var(--danger-fill);
  color: var(--danger);
}

.btn-sm {
  padding: 0.45em 0.9em;
  font-size: var(--text-sm);
}

.btn-lg {
  padding: 0.85em 1.9em;
  font-size: var(--text-lg);
}

.btn-block {
  width: 100%;
}

/* ══ Card ═══════════════════════════════════════════════════════════════ */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  padding: var(--space-5);
}

.card-flush {
  padding: 0;
  overflow: hidden; /* clips a table's corners to the card radius */
}

.card-raised {
  border-color: transparent;
  box-shadow: var(--shadow);
}

/* A card the whole surface of which is a link. The ::after stretches the
 * anchor over the card so the entire block is clickable while the accessible
 * name stays on the heading text -- rather than wrapping the card in an <a>,
 * which would swallow any nested link. */
.card-link {
  position: relative;
  transition: transform var(--dur) var(--ease),
    box-shadow var(--dur) var(--ease),
    border-color var(--dur) var(--ease);
}

.card-link:hover {
  transform: translateY(-3px);
  border-color: var(--accent-muted);
  box-shadow: var(--shadow);
}

.card-link a::after {
  content: '';
  position: absolute;
  inset: 0;
}

.card-title {
  font-size: var(--text-xl);
}

.card-title a {
  text-decoration: none;
}

.card-meta {
  font-size: var(--text-sm);
  color: var(--ink-3);
}

/* The accent mark on a service card. A square tile rather than a bare glyph,
 * so cards with visually light icons still line up. */
.card-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-size: 1.5rem;
}

/* ══ Section heading ════════════════════════════════════════════════════ */
.section-head {
  max-width: var(--measure);
  margin-bottom: var(--space-6);
}

.section-head p {
  color: var(--ink-3);
  font-size: var(--text-lg);
}

.eyebrow {
  display: block;
  margin-bottom: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--accent-strong);
}

/* ══ Site header ════════════════════════════════════════════════════════ */
.topbar {
  background: var(--surface-inverse);
  color: color-mix(in srgb, white 72%, var(--surface-inverse));
  font-size: var(--text-sm);
  padding-block: var(--space-2);
}

.topbar a {
  color: #fff;
  text-decoration-color: var(--brand-accent);
}

.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
}

.site-header > .container {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  min-height: var(--header-height);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3);
  font-size: var(--text-lg);
  font-weight: 700;
  letter-spacing: var(--tracking-tight);
  color: var(--ink);
  text-decoration: none;
}

.brand img {
  height: 2.25rem;
  width: auto;
}

.brand-accent {
  color: var(--accent-strong);
}

/* ── Primary nav ───────────────────────────────────────────────────────
 * Mobile behaviour is a checkbox, not JavaScript: a visually-hidden
 * <input type="checkbox"> before the nav toggles it via :checked. No script,
 * so the menu cannot break under CSP, a failed asset load, or a JS error --
 * which matters more here than usual, because the header is the only route
 * to every other page. */
.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.25rem;
  height: 3.25rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: 1.5rem;
  cursor: pointer;
}

.nav-checkbox {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.nav-checkbox:focus-visible + .nav-toggle {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  box-shadow: 0 0 0 5px var(--accent-ring);
}

.site-nav {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}

.site-nav a {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  font-weight: 600;
  color: var(--ink-2);
  text-decoration: none;
}

.site-nav a:hover {
  background: var(--surface-2);
  color: var(--ink);
}

.site-nav a[aria-current='page'] {
  color: var(--ink);
  box-shadow: inset 0 -2px 0 var(--brand-accent);
}

@media (max-width: 767px) {
  .site-nav {
    display: none;
    position: absolute;
    inset: 100% 0 auto;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: var(--space-3);
    background: var(--surface);
    border-bottom: 1px solid var(--line);
    box-shadow: var(--shadow);
  }
  .nav-checkbox:checked ~ .site-nav {
    display: flex;
  }
  .site-nav a {
    padding: var(--space-3);
  }
}

@media (min-width: 768px) {
  .nav-toggle {
    display: none;
  }
}

/* ══ Hero ═══════════════════════════════════════════════════════════════
 * The banner image and its blur radius are both admin-set, so both arrive as
 * custom properties on the element rather than as a stylesheet rule. The
 * blur goes on a ::before pseudo-element rather than on the section itself,
 * because filter: blur() on a parent blurs its text too -- and blurring the
 * background inside the element's own box would show a soft edge where the
 * blur samples past the boundary, which is what the scale(1.06) prevents. */
.hero {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  background: var(--surface-inverse);
  color: #fff;
  padding-block: var(--space-9);
}

.hero::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -2;
  background-image: var(--hero-image, none);
  background-size: cover;
  background-position: center;
  filter: blur(var(--hero-blur, 0px));
  transform: scale(1.06);
}

.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    180deg,
    rgb(20 22 26 / 78%) 0%,
    rgb(20 22 26 / 62%) 55%,
    rgb(20 22 26 / 82%) 100%
  );
}

.hero h1 {
  color: #fff;
}

.hero p {
  max-width: 56ch;
  font-size: var(--text-lg);
  color: color-mix(in srgb, white 86%, var(--surface-inverse));
}

.hero-logo {
  max-width: 320px;
  margin-inline: auto;
}

/* ══ Breadcrumb / page head ═════════════════════════════════════════════ */
.page-head {
  background: var(--surface-2);
  border-bottom: 1px solid var(--line);
  padding-block: var(--space-6);
}

.breadcrumb {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: 0;
  margin: 0;
  list-style: none;
  font-size: var(--text-sm);
  color: var(--ink-3);
}

.breadcrumb li + li {
  margin-top: 0;
}

.breadcrumb li + li::before {
  content: '/';
  margin-right: var(--space-2);
  color: var(--ink-4);
}

/* ══ Accordion (the /services interaction) ══════════════════════════════
 * Built on <details>/<summary>, which gives keyboard support, the open/closed
 * state, in-page find (a browser expands a closed <details> to reveal a match)
 * and deep-linking into a panel for free -- and, critically, needs no
 * JavaScript at all. The production site spent ~90 lines of script on a
 * button row plus a measured FLIP animation to do less than this, and showed
 * nothing but inert buttons when that script failed to run.
 *
 * Panels are not mutually exclusive here. Forcing one open at a time is the
 * one thing <details> will not do alone, and it is not worth a script: a
 * visitor comparing two services can read both, and closing one they did not
 * ask to close is a worse default than a slightly longer page. */
.accordion {
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
  overflow: hidden;
}

.accordion-item + .accordion-item {
  border-top: 1px solid var(--line);
}

.accordion-summary {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-5);
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--ink);
  cursor: pointer;
  list-style: none; /* hides the default marker in Firefox */
  transition: background var(--dur-fast) var(--ease);
}

.accordion-summary::-webkit-details-marker {
  display: none;
}

.accordion-summary:hover {
  background: var(--surface-2);
}

.accordion-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  flex: none;
  border-radius: var(--radius);
  background: var(--accent-soft);
  color: var(--accent-strong);
  font-size: 1.25rem;
}

.accordion-chevron {
  margin-left: auto;
  color: var(--ink-4);
  transition: transform var(--dur) var(--ease);
}

.accordion-item[open] .accordion-chevron {
  transform: rotate(180deg);
}

.accordion-panel {
  padding: 0 var(--space-5) var(--space-5);
}

.accordion-panel > .prose {
  padding-top: var(--space-2);
  border-top: 1px solid var(--line);
}

/* ══ Field ══════════════════════════════════════════════════════════════ */
.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.field > label {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink-2);
}

.field-hint {
  font-size: var(--text-sm);
  color: var(--ink-3);
}

.input,
.field input[type='text'],
.field input[type='email'],
.field input[type='tel'],
.field input[type='url'],
.field input[type='number'],
.field input[type='password'],
.field input[type='search'],
.field input[type='date'],
.field select,
.field textarea {
  width: 100%;
  padding: 0.65em 0.85em;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  transition: border-color var(--dur-fast) var(--ease),
    box-shadow var(--dur-fast) var(--ease);
}

.input:focus,
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--brand-accent);
  box-shadow: 0 0 0 4px var(--accent-ring);
}

.field textarea {
  min-height: 9rem;
  resize: vertical;
}

.field input[type='color'] {
  width: 4rem;
  height: 2.5rem;
  padding: 2px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  background: var(--surface);
}

.field input[type='file'] {
  font-size: var(--text-sm);
}

/* Server-side validation state. aria-invalid rather than a .is-error class,
 * so the visual and the assistive-technology signal cannot drift apart. */
.field [aria-invalid='true'] {
  border-color: var(--danger);
}

.field-error {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--danger);
}

.checkbox {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  font-size: var(--text-sm);
}

/* A hint under a checkbox label sits on its own line. The hint is a span --
 * a block child of a label is legal, but a p is not, and the label has to
 * stay the click target for the box. */
.checkbox .field-hint {
  display: block;
  margin-top: var(--space-1);
}

.checkbox input {
  margin-top: 0.25em;
  width: 1.1em;
  height: 1.1em;
  accent-color: var(--brand-accent);
}

/* ── Icon picker (admin services form) ─────────────────────────────────
 * A radio grid. The real <input> stays in the DOM and keyboard-reachable --
 * it is moved off-screen by .visually-hidden, not display:none, because a
 * display:none radio is unfocusable and drops out of the tab order. */
.icon-picker {
  display: grid;
  gap: var(--space-2);
  grid-template-columns: repeat(auto-fill, minmax(4.5rem, 1fr));
}

.icon-choice {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: var(--text-xs);
  text-align: center;
  color: var(--ink-3);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease),
    background var(--dur-fast) var(--ease);
}

.icon-choice .icon {
  font-size: 1.5rem;
  color: var(--ink-2);
}

.icon-choice:hover {
  background: var(--surface-2);
}

.icon-choice:has(input:checked) {
  border-color: var(--brand-accent);
  background: var(--accent-soft);
  color: var(--ink);
}

.icon-choice:has(input:focus-visible) {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* ══ Table ══════════════════════════════════════════════════════════════
 * The wrapper is what scrolls, not the page. Wide admin tables -- leads with
 * eight columns, spam with a message excerpt -- overflow inside their own box
 * instead of forcing a horizontal scrollbar on the document. */
.table-wrap {
  width: 100%;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}

.table {
  min-width: 40rem;
  font-size: var(--text-sm);
}

.table th,
.table td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  vertical-align: top;
  border-bottom: 1px solid var(--line);
}

.table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--surface-2);
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--ink-3);
  white-space: nowrap;
}

.table tbody tr:hover {
  background: var(--surface-2);
}

.table tbody tr:last-child td {
  border-bottom: 0;
}

.table-actions {
  white-space: nowrap;
  text-align: right;
}

.table-num {
  font-variant-numeric: tabular-nums;
}

/* The shared empty state. Four admin tables hand-rolled their own; this is
 * the one that replaces them. */
.empty-state {
  padding: var(--space-8) var(--space-5);
  text-align: center;
  color: var(--ink-3);
}

.empty-state .icon {
  font-size: 2.5rem;
  margin-inline: auto;
  margin-bottom: var(--space-3);
  color: var(--ink-4);
}

/* ══ Badge ══════════════════════════════════════════════════════════════ */
.badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.2em 0.7em;
  border-radius: var(--radius-pill);
  background: var(--surface-3);
  color: var(--ink-2);
  font-size: var(--text-xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.badge-ok { background: var(--ok-fill); color: var(--ok); }
.badge-warn { background: var(--warn-fill); color: var(--warn); }
.badge-danger { background: var(--danger-fill); color: var(--danger); }
.badge-info { background: var(--info-fill); color: var(--info); }
.badge-accent { background: var(--accent-soft); color: var(--accent-strong); }

/* ══ Alert / toast ══════════════════════════════════════════════════════
 * Same visual, two placements. .alert sits in the flow (a form error);
 * .toast is the session-flash message, pinned top-right on wide screens and
 * full-width on a phone. Both are plain markup with no JS requirement -- the
 * dismiss control is a link back to the current page, so a flash consumed
 * server-side simply does not render on the next load. */
.alert {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: var(--radius);
  background: var(--surface-2);
  font-size: var(--text-sm);
}

.alert .icon {
  font-size: 1.25rem;
  margin-top: 0.1em;
}

.alert-ok { border-left-color: var(--ok); background: var(--ok-fill); color: var(--ok); }
.alert-warn { border-left-color: var(--warn); background: var(--warn-fill); color: var(--warn); }
.alert-danger { border-left-color: var(--danger); background: var(--danger-fill); color: var(--danger); }
.alert-info { border-left-color: var(--info); background: var(--info-fill); color: var(--info); }


/* The public layout's flash slot. It exists only when there is a message, so
 * the padding here can be unconditional -- there is no empty element to leave
 * a gap above a page that has nothing to say. The admin layout needs no
 * equivalent because .admin-content is already padded. */
.flash-bar {
  padding-top: var(--space-5);
}
.toast-region {
  position: fixed;
  z-index: var(--z-toast);
  right: var(--space-4);
  bottom: var(--space-4);
  left: var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  pointer-events: none;
}

.toast-region > * {
  pointer-events: auto;
}

.toast {
  box-shadow: var(--shadow-lg);
  background: var(--surface);
  animation: toast-in var(--dur) var(--ease);
}

@keyframes toast-in {
  from { opacity: 0; transform: translateY(8px); }
  to { opacity: 1; transform: none; }
}

@media (min-width: 640px) {
  .toast-region {
    left: auto;
    top: calc(var(--header-height) + var(--space-4));
    bottom: auto;
    width: min(24rem, calc(100vw - var(--space-8)));
  }
}

/* ══ Pagination ═════════════════════════════════════════════════════════ */
.pagination {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  padding: 0;
  margin: 0;
  list-style: none;
}

.pagination li + li {
  margin-top: 0;
}

.pagination a,
.pagination span {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 2.5rem;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  font-weight: 600;
  text-decoration: none;
  color: var(--ink-2);
}

.pagination a:hover {
  background: var(--surface-2);
  border-color: var(--line-strong);
  color: var(--ink);
}

.pagination [aria-current='page'] {
  background: var(--brand-accent);
  border-color: var(--accent-strong);
  color: var(--accent-contrast);
}

.pagination .is-disabled {
  opacity: 0.45;
}

/* ══ Stat ═══════════════════════════════════════════════════════════════
 * Admin dashboard counters. tabular-nums so a column of figures does not
 * shimmer as the digits change width. */
.stat {
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--line);
  border-radius: var(--radius-lg);
  background: var(--surface);
}

.stat-value {
  display: block;
  font-size: var(--text-3xl);
  font-weight: 700;
  line-height: var(--leading-tight);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.stat-label {
  font-size: var(--text-sm);
  color: var(--ink-3);
}

/* ══ Site footer ════════════════════════════════════════════════════════ */
.site-footer {
  background: var(--surface-inverse);
  color: color-mix(in srgb, white 74%, var(--surface-inverse));
  padding-block: var(--space-7) var(--space-5);
}

.site-footer h2,
.site-footer h3,
.site-footer strong {
  color: #fff;
}

.site-footer a {
  color: #fff;
  text-decoration-color: var(--brand-accent);
}

/* The footer's right half is itself a grid of two short link lists, nested
 * inside one column of .grid-2. It inherits the 280px default --col, which
 * needs 584px to hold two tracks -- more than the ~530px half it actually
 * gets, so both lists dropped into a single column and left the row beside
 * them empty. These lists are a phone number and four nav links; 200px is
 * wide enough for either. */
.site-footer-columns { --col: 200px; }

.site-footer-bottom {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--surface-inverse-2);
  font-size: var(--text-sm);
}

/* ══ reCAPTCHA notice ═══════════════════════════════════════════════════
 * Google's terms require this disclosure wherever the badge is hidden. It is
 * small, quiet, and deliberately not display:none. */
.recaptcha-notice {
  font-size: var(--text-xs);
  color: var(--ink-4);
}

.recaptcha-notice a {
  color: inherit;
}

/* Google injects its own badge with position:fixed; the disclosure above
 * replaces it, per the same terms. */
.grecaptcha-badge {
  visibility: hidden;
}
