/* ==========================================================================
   Base layer: reset, typography, shared primitives.
   Loaded on every page, public and administrative.
   ========================================================================== */

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

:root {
  /* --------------------------------------------------------------------------
     Full height that survives a mobile browser.
     --------------------------------------------------------------------------
     100vh on a phone measures the viewport with the address bar hidden, which is
     taller than what is actually on screen. A full height panel therefore starts
     below the fold and the page jumps as the bar retracts. dvh measures what the
     user can see right now. vh stays as the fallback for older engines.
     -------------------------------------------------------------------------- */
  --app-min-h: 100vh;
}

@supports (min-height: 100dvh) {
  :root { --app-min-h: 100dvh; }
}

html {
  -webkit-text-size-adjust: 100%;
  /* One overflowing element should not make the whole document scroll
     sideways. clip rather than hidden, because hidden on the root element
     silently disables position: sticky everywhere. */
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--text);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.5;
  font-optical-sizing: auto;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4 {
  margin: 0;
  line-height: 1.15;
  font-weight: 600;
  letter-spacing: -0.015em;
  text-wrap: balance;
}

p { margin: 0; text-wrap: pretty; }
ul, ol, dl, dd { margin: 0; padding: 0; list-style: none; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; background: none; border: none; padding: 0; }
a { color: inherit; }
img, svg { max-width: 100%; }

:focus-visible {
  outline: 2px solid var(--pine);
  outline-offset: 3px;
  border-radius: 3px;
}

.mono { font-family: var(--font-mono); font-variant-ligatures: none; }
.tnum { font-variant-numeric: tabular-nums; }

.eyebrow {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--text-3);
}

.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --------------------------------------------------------------------------
   The hidden attribute must actually hide.
   --------------------------------------------------------------------------
   The browser hides [hidden] through its own user agent stylesheet, and any
   author rule that sets display beats it, because author styles always win over
   user agent styles regardless of specificity.

   So a component like .notice, which needs display:flex for its icon and text
   layout, silently defeats the hidden attribute on every element that carries
   both. The element renders empty, which reads as a broken UI rather than as a
   hidden one.

   This is the one place !important is correct: the hidden attribute is a
   statement about whether an element exists for the user, and no layout rule
   should be able to argue with it.
   -------------------------------------------------------------------------- */
[hidden] {
  display: none !important;
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 8px;
  z-index: var(--z-dialog);
  padding: 10px 16px;
  background: var(--ink-900);
  color: var(--paper-2);
  border-radius: var(--r-sm);
  font-size: 0.875rem;
  text-decoration: none;
}
.skip-link:focus { left: 8px; }

/* ---- buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  border-radius: var(--r-md);
  font-weight: 600;
  font-size: 0.9375rem;
  letter-spacing: -0.005em;
  padding: 14px 22px;
  min-height: 48px;
  text-decoration: none;
  transition: transform .12s var(--ease-out-quart), background .18s var(--ease-out-quart),
              box-shadow .18s var(--ease-out-quart), color .18s var(--ease-out-quart),
              border-color .18s var(--ease-out-quart);
}
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--ink-900); color: var(--paper-2); box-shadow: var(--sh-2); }
.btn--primary:hover { background: var(--pine-deep); }
.btn--primary:disabled { background: var(--paper-4); color: var(--text-3); box-shadow: none; cursor: not-allowed; transform: none; }
.btn--ghost {
  border: 1px solid var(--line-strong);
  background: var(--paper-2);
  color: var(--text);
  padding: 9px 15px;
  min-height: 38px;
  font-size: 0.8125rem;
}
.btn--ghost:hover { border-color: var(--text-3); background: var(--paper); }
.btn--block { width: 100%; }
.btn--sm { padding: 9px 18px; min-height: 38px; font-size: 0.8125rem; }

/* ---- pills ---- */
.pill {
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 5px;
  white-space: nowrap;
  display: inline-block;
}
.pill--ok   { background: var(--pine-soft);  color: var(--pine-deep); }
.pill--warn { background: var(--amber-soft); color: var(--amber); }
.pill--bad  { background: var(--red-soft);   color: var(--red); }
.pill--mute { background: var(--paper-4);    color: var(--text-2); }

/* ---- notices ---- */
.notice {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 14px 16px;
  border-radius: var(--r-md);
  font-size: 0.8125rem;
  line-height: 1.6;
}
.notice svg { flex: none; margin-top: 2px; }
.notice--warn  { border: 1px solid var(--amber-line); background: var(--amber-soft); color: oklch(38% 0.08 68); }
.notice--error { border: 1px solid var(--red-line);   background: var(--red-soft);   color: var(--red); }
.notice--info  { border: 1px solid var(--pine-line);  background: var(--pine-soft);  color: var(--pine-deep); }

/* ---- code and evidence ---- */
pre {
  margin: 0;
  padding: 14px 16px;
  border-radius: var(--r-sm);
  background: var(--ink-850);
  color: oklch(88% 0.02 150);
  border: 1px solid var(--ink-700);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.65;
  letter-spacing: -0.02em;
  overflow-x: auto;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ---- tables ----
   Defined here rather than in report.css because three admin panels use them
   too, and the console never loads the report stylesheet.

   Column widths are content driven. That is the browser's default behaviour
   with table-layout: auto, but it only works if every cell reports an honest
   minimum width, which is the subtlety below.
   -------------------------------------------------------------------------- */
.tbl {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.8125rem;
  /* Explicit rather than relied upon. The auto algorithm measures the content
     of every cell and distributes width in proportion, which is exactly the
     behaviour wanted here; fixed layout would size columns from the header row
     alone and ignore the values entirely. */
  table-layout: auto;
}
.tbl th {
  text-align: left;
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-3);
  padding: 0 14px 10px 0;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.tbl td { padding: 12px 14px 12px 0; border-bottom: 1px solid var(--line); vertical-align: top; }
.tbl tr:last-child td { border-bottom: none; }

/* Monospace cells hold URLs, rule ids and fingerprints.

   These previously carried word-break: break-all, which is what broke column
   sizing. break-all tells the browser the cell can wrap after any character, so
   its minimum content width becomes one character. The auto layout algorithm
   then treats the column as infinitely compressible and hands its space to
   whichever column refuses to shrink, regardless of how long the values are.

   overflow-wrap: anywhere still breaks a long unbroken string when it has to,
   but it reports a truthful minimum width, so the column is measured on its
   actual content. */
.tbl .mono {
  font-size: 0.75rem;
  letter-spacing: -0.02em;
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   Content-fitted columns
   --------------------------------------------------------------------------
   For tables where some columns hold short, uniform labels and one holds
   free text of unpredictable length. The short columns are sized to their
   widest value and stop there; the flexible column absorbs the remainder.

   Mark the flexible cells with .tbl__flex. Everything else fits its content.
   -------------------------------------------------------------------------- */
.tbl--fit td:not(.tbl__flex),
.tbl--fit th:not(.tbl__flex) {
  width: 1%;
  white-space: nowrap;
}

.tbl--fit .tbl__flex {
  /* No explicit width: whatever the fitted columns leave. */
  width: auto;
  white-space: normal;
  overflow-wrap: anywhere;
}

/* The unit is metadata about the number, not part of it. Separating it stops
   "1284 bytes" reading as one long token and lets the number stay scannable. */
.tbl__unit {
  margin-left: 5px;
  font-size: 0.6875rem;
  color: var(--text-3);
  letter-spacing: 0;
}

/* ---- empty state ---- */
.empty {
  text-align: center;
  padding: var(--space-8) var(--space-5);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
  color: var(--text-3);
}
.empty b { display: block; color: var(--text); font-size: 0.9375rem; margin-bottom: 6px; }
.empty p { font-size: 0.8125rem; max-width: 40ch; margin: 0 auto; }

/* ==========================================================================
   Touch and small screen baseline
   --------------------------------------------------------------------------
   Applies to phones and tablets. Three separate problems, all of which make
   the application feel broken rather than merely cramped.
   ========================================================================== */
@media (max-width: 1024px) {

  /* 1. iOS Safari zooms the entire page when a form control smaller than 16px
        receives focus, and it does not zoom back out afterwards. The layout is
        left scrolled sideways and the visitor has to pinch to recover. A 16px
        floor is the only reliable way to stop it, so the tighter desktop
        density is traded away here on purpose. */
  input,
  select,
  textarea,
  .inp,
  .url-input input {
    font-size: 1rem;
  }

  /* 2. Tables are the one component that genuinely cannot reflow: a five column
        report of URLs and byte counts has an irreducible width. Rather than
        crushing the columns or letting the whole page scroll sideways, the
        table alone becomes a horizontal scroller. -webkit-overflow-scrolling
        keeps the momentum feel on iOS. */
  .tbl {
    display: block;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* A faint edge shadow that only appears when there is more to the right,
       so the affordance is honest: no shadow means nothing is hidden. */
    background:
      linear-gradient(to right, var(--paper) 30%, transparent) left / 24px 100% no-repeat,
      linear-gradient(to left, var(--paper) 30%, transparent) right / 24px 100% no-repeat,
      radial-gradient(farthest-side at 0 50%, oklch(20% 0.02 158 / .10), transparent) left / 12px 100% no-repeat,
      radial-gradient(farthest-side at 100% 50%, oklch(20% 0.02 158 / .10), transparent) right / 12px 100% no-repeat;
    background-attachment: local, local, scroll, scroll;
  }
  .tbl thead,
  .tbl tbody {
    display: table;
    width: 100%;
    min-width: 520px;
  }

  /* A fitted table already sizes itself to its content, so forcing a 520px
     minimum would create a horizontal scrollbar on a table that fits. It keeps
     its fitted columns and simply wraps the flexible one harder. */
  .tbl--fit {
    display: table;
    overflow-x: visible;
    background: none;
  }
  .tbl--fit thead,
  .tbl--fit tbody {
    min-width: 0;
  }

  /* Below the narrowest sensible width the fitted columns stop fitting, so the
     nowrap is released rather than pushing the row off screen. */
  .tbl--fit td:not(.tbl__flex),
  .tbl--fit th:not(.tbl__flex) {
    white-space: normal;
  }

  /* 3. Touch targets. The visual size of these controls is correct for the
        design; the hit area is not. Both are expanded without changing how
        they look. */
  .chip {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    padding-inline: 14px;
  }

  .btn--ghost,
  .btn--sm {
    min-height: 44px;
  }

  /* A pseudo-element rather than padding, so the switch keeps its exact
     dimensions while the tappable region reaches the 44px minimum. */
  .sw::before {
    content: '';
    position: absolute;
    inset: -11px -8px;
  }

  /* Long unbroken strings are everywhere in this product: URLs, rule ids,
     fingerprints. Without this they are the single most common cause of a
     page that scrolls sideways. */
  .mono,
  pre,
  code {
    overflow-wrap: anywhere;
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}


/* ==========================================================================
   2.1.1 - Back to top
   --------------------------------------------------------------------------
   In base.css rather than report.css: the admin console, the legal pages and
   the report all run long, and a control that appears on one of them and not
   the others reads as an inconsistency rather than as a decision. scroll-top.js
   measures the document and declines to create the button on anything short, so
   this is only ever styling something that earned its place.

   Bottom right, above the reCAPTCHA badge when one is present. Deliberately not
   a filled brand-coloured circle: this is chrome, not a call to action, and a
   pine disc floating over a report would compete with the primary buttons
   inside the page.
   ========================================================================== */
.to-top {
  position: fixed;
  right: clamp(16px, 2.4vw, 28px);
  bottom: clamp(16px, 2.4vw, 28px);
  z-index: var(--z-chrome);

  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;

  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: var(--paper-2);
  color: var(--text-2);
  box-shadow: var(--sh-2);
  cursor: pointer;

  /* Hidden state. Not display:none, because that cannot be transitioned and
     because visibility keeps it out of the accessibility tree while it fades. */
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px) scale(0.94);

  transition:
    opacity .22s var(--ease-out-quart),
    transform .32s var(--ease-out-expo),
    visibility 0s linear .22s,
    color .16s var(--ease-out-quart),
    border-color .16s var(--ease-out-quart);
}

.to-top.is-on {
  opacity: 1;
  visibility: visible;
  transform: none;
  /* Visibility flips immediately on the way in, and only after the fade on the
     way out, which is what the delay in the base rule is doing. */
  transition-delay: 0s;
}

.to-top:hover {
  color: var(--text);
  border-color: var(--text-3);
}
.to-top:active { transform: translateY(1px); }
.to-top:focus-visible { outline: 2px solid var(--pine); outline-offset: 3px; }

/* The arrow is drawn rather than inlined, because this element is created by a
   script and the icon set is rendered server side. Two borders rotated 45
   degrees, with a stem, so it reads as an arrow and not as a chevron. */
.to-top__arrow {
  position: relative;
  width: 9px;
  height: 9px;
  margin-top: 3px;
  border-top: 1.8px solid currentColor;
  border-left: 1.8px solid currentColor;
  transform: rotate(45deg);
}
.to-top__arrow::after {
  content: '';
  position: absolute;
  top: -1px;
  left: -1px;
  width: 1.8px;
  height: 15px;
  background: currentColor;
  transform: rotate(-45deg);
  transform-origin: top left;
}

/* On a dark surface the paper disc would glare. The landing hero and the admin
   sidebar are both ink, so the control follows whichever it is sitting on. */
.page-landing .to-top {
  background: var(--ink-800);
  border-color: oklch(100% 0 0 / .16);
  color: var(--on-ink-2);
}
.page-landing .to-top:hover { color: var(--on-ink); border-color: oklch(100% 0 0 / .3); }
.page-landing .to-top:focus-visible { outline-color: var(--lime); }

/* Phones: slightly smaller, and clear of the home indicator. */
@media (max-width: 620px) {
  .to-top {
    width: 40px;
    height: 40px;
    bottom: max(16px, env(safe-area-inset-bottom, 0px));
  }
}

/* Chrome, not content. It has no place in a printed report. */
@media print {
  .to-top { display: none !important; }
}

@media (prefers-reduced-motion: reduce) {
  .to-top { transition: opacity .01ms, visibility .01ms; transform: none; }
  .to-top.is-on { transform: none; }
}
