/* public/css/utilities.css — NEX-335.
 *
 * Classes for style attributes that were repeated verbatim across pages. This
 * is a step toward NEX-306 (`style-src-attr 'none'`), which cannot be enforced
 * while any element carries a style attribute.
 *
 * Scope note, because the remaining count is not the remaining work:
 *
 * Of the 329 style attributes in public/, 104 are `display:none` on elements
 * that JS shows again with `el.style.display = ""`. Swapping those for a class
 * silently breaks them — clearing the inline style falls back to the class and
 * the element stays hidden — so each one needs its ~292 corresponding JS toggle
 * sites rewritten and checked in a browser. Every one of the 104 is referenced
 * in JS; none is a safe mechanical swap. That cluster is deliberately untouched.
 *
 * What is converted here is the presentational half: styles repeated verbatim
 * on static markup that no script reads. Elements whose id appears anywhere in
 * public/**.js were skipped even when their style looked inert.
 *
 * Not a general utility framework. Each class below replaces a specific
 * duplicated declaration, and the names describe the role rather than the
 * value, so a later change to (say) the wordmark colour happens in one place.
 */

/* ── brand header, repeated on every page's nav ──────────────────────────── */

.brand-link {
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 8px;
}

.brand-wordmark {
  font-family: Inter, system-ui, sans-serif;
  font-size: 20px;
  font-weight: 600;
  color: #e46b1f;
  letter-spacing: 0.06em;
}

.brand-logo {
  height: 36px;
  width: auto;
}

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

.cell-pad {
  padding: 10px 8px;
}

.table-th {
  text-align: left;
  color: var(--color-muted);
  border-bottom: 1px solid var(--color-border-hover);
  font-size: 13px;
  padding: 8px;
}

/* Column sizing on <col>/<th>. Percentages and fixed widths were both in use;
   both are kept rather than normalised, since changing a column's width is a
   layout decision and not this ticket's to make. */
.col-30 { width: 30%; }
.col-40 { width: 40%; }
.col-45 { width: 45%; }
.col-50 { width: 50%; }
.col-55 { width: 55%; }
.col-60 { width: 60%; }
.col-30px { width: 30px; }
.col-40px { width: 40px; }
.col-80px { width: 80px; }
.col-115px { width: 115px; }

/* ── separators and spacing ──────────────────────────────────────────────── */

.rule {
  border: none;
  border-top: 1px solid var(--color-border);
  margin: 24px 0;
}

.mb-4 { margin-bottom: 4px; }
.mb-8 { margin-bottom: 8px; }
.mt-12 { margin-top: 12px; }
.pt-24 { padding-top: 24px; }

/* ── u-hidden ────────────────────────────────────────────────────────────── */
/* NEX-400. Deliberately NOT a conversion of the 104 `display:none` attributes
 * this file's header describes — those are the ones JS reveals with
 * `el.style.display = ""`, which falls back to the class and leaves the element
 * hidden. That cluster still needs its toggle sites rewritten first.
 *
 * This exists for NEW markup, where both halves land together and the toggle is
 * `classList.toggle("u-hidden", ...)` from the start. Adding one more inline
 * `style="display:none"` would have pushed the NEX-306 ratchet the wrong way for
 * no reason other than matching the pattern beside it. */
.u-hidden { display: none; }

/* ── [hidden] ────────────────────────────────────────────────────────────── */
/* NEX-335 (for NEX-306). The `display:none` cluster is migrated to the HTML
 * `hidden` attribute, toggled from JS with `el.hidden = true/false`. The UA
 * stylesheet already maps [hidden] to display:none, but ANY author rule that
 * sets `display` on the same element (a `.row { display:flex }` class) wins
 * over the UA default and un-hides it. `!important` here restores the
 * intent: hidden means hidden, whatever the element's own display is — and
 * when it is shown again it takes the display its class gives it, which is
 * exactly what `el.style.display = ""` used to fall back to. Pages that do
 * not load this file carry the same one-line rule in their own <style>. */
[hidden] { display: none !important; }


/* .form-error-pinned — an error banner fixed to the viewport.
 *
 * NEX-500 follow-up. On the quote surfaces the failure message sits near the
 * top of a long form while the person who triggered it is at the bottom, next
 * to the button they just pressed. The message was there; it was simply not
 * where they were looking, so a rejected quote read as the page doing nothing.
 *
 * Deliberately NOT applied to .form-error itself. Sixteen pages use that class
 * and two of them (buyer-dashboard, buyer-rfq) carry their own rules for it —
 * pinning all of them would restyle error banners across the app to fix one
 * screen. This is opt-in, on the elements that need it.
 *
 * Empty means absent: the :empty rule keeps it out of the layout and out of
 * the accessibility tree until it has something to say, so role="alert"
 * announces on appearance rather than announcing a blank box on load. */
.form-error-pinned {
  position: fixed;
  top: 16px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 1000;
  max-width: min(640px, calc(100vw - 32px));
  box-sizing: border-box;
  padding: 12px 16px;
  border-radius: 6px;
  background: var(--color-red-error-bg, #3b1219);
  color: var(--color-red-error-text, #fecaca);
  border: 1px solid var(--color-red-error-border-40, #7f1d1d);
  font-size: 14px;
  line-height: 1.5;
  text-align: left;
  box-shadow: 0 4px 16px rgb(0 0 0 / 35%);
}

.form-error-pinned:empty { display: none; }

/* The Early Release banner is also fixed to the top (`.unav-alpha-banner`,
 * 32px tall, z-index 90) and the pinned error drew straight over it. The nav
 * already announces the banner with a body class and offsets its own main
 * content by it; this uses the same signal rather than guessing at a constant
 * that would be wrong on every page without the banner. */
body.has-alpha-banner .form-error-pinned { top: 48px; }

/* NEX-600: the same banner, against the phone layout.
 *
 * The offsets above assume the alpha banner is at top: 0, which it is on a
 * desktop. Below 768px NEX-593 put a fixed 52px top bar above it and moved the
 * banner to top: 52px, so `top: 48px` dropped the error straight onto the
 * banner — two runs of text overprinting each other, which is how it reached a
 * real phone (the validation error and the subscription error on
 * quote-builder were both unreadable).
 *
 * Measured from the same two constants the nav uses: the bar is 52px and the
 * banner 32px, so 52 + 8 clears the bar and 52 + 32 + 8 clears both. Stated
 * here rather than in _nav-unified.css because this is the error banner's own
 * rule and the pairing is easier to keep honest next to the values it beats. */
@media (max-width: 768px) {
  .form-error-pinned { top: 60px; }
  body.has-alpha-banner .form-error-pinned { top: 92px; }
}
