/* fonts.css is a separate <link> in index.html, not an @import here: @import
 * makes the browser wait for this file to arrive and parse before it can even
 * discover fonts.css, costing a full extra round trip. */

/* AP Dialog corporate identity.
 *
 * Brand values are binding and must not be altered: Primary Red #DC2455
 * (PANTONE 1935 UP), Secondary Blue #2D257D (PANTONE 2745 CP), with the red to
 * blue gradient as the signature element. Everything else here derives from
 * those two and the sanctioned tints.
 *
 * Every colour is declared once, in this block. No raw hex below it. */
:root {
  --brand-red: #DC2455;
  --brand-blue: #2D257D;
  --tint-pink: #F06181;    /* palette, unused - kept for a complete sanctioned set */
  --tint-dark-red: #A41D30;
  --tint-violet: #6051A2;
  --tint-navy: #17154D;
  --tint-black: #0F1027;   /* palette, unused - kept for a complete sanctioned set */

  --brand-gradient: linear-gradient(105deg, var(--brand-red) 0%, var(--brand-blue) 100%);

  /* Surfaces. Whitespace is part of the brand language, so the page is light
   * and the cards carry almost no decoration. */
  --page: #F4F4F7;
  --surface: #FFFFFF;
  --border: #E1E1EA;

  /* Text. Navy rather than black: 16.79:1 on --surface (15.29:1 on --page),
   * and it belongs to the palette. --ink-muted is 6.80:1 on --surface - both
   * measured against the actual rendered colour, not estimated. */
  --ink: var(--tint-navy);
  --ink-muted: #5A5875;
  --on-brand: #FFFFFF;

  /* Status. Deliberately NOT brand colours: a verdict must not be confusable
   * with decoration. Measured on --surface: good 6.09:1, fair 6.39:1,
   * poor 7.51:1 - all comfortably over the 4.5:1 floor.
   * --status-poor is the sanctioned dark red tint. */
  --status-good: #16704A;
  --status-fair: #8A5200;
  --status-poor: var(--tint-dark-red);

  --radius: 10px;
  --gap: 24px;
  --measure: 68ch;
}

* { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--page);
  color: var(--ink);
  font-family: "Libre Franklin", "Franklin Gothic Book", "Segoe UI", system-ui, sans-serif;
  font-size: 16px;
  line-height: 1.55;
}

/* ---- Brand header ------------------------------------------------------- */

/* The signature element: red to blue, overlaid with the fine white dot matrix.
 * The dots are drawn with a radial-gradient rather than an image so there is no
 * asset to lose and nothing extra to request. */
.masthead {
  background-image:
    radial-gradient(rgba(255, 255, 255, .30) 1px, transparent 1.1px),
    var(--brand-gradient);
  background-size: 14px 14px, auto;
  color: var(--on-brand);
  padding: 40px 24px 44px;
}

.masthead-inner {
  max-width: 960px;
  margin: 0 auto;
}

/* Wordmark set in type. The AP Dialog mark is never redrawn or approximated -
 * the official vector asset drops in here, see README.
 *
 * No opacity on any masthead text: at 375px .masthead-inner sits close to the
 * gradient's pure-red start, and opacity there was measured to fail 4.5:1
 * (wordmark 4.18:1, lead/#user-info 4.39:1). Full-opacity white is 4.74:1 -
 * the tightest ratio on the page, but it passes. Quieting the wordmark
 * relative to the H1 is done with letter-spacing/size instead. */
.wordmark {
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  font-size: .82rem;
  margin: 0 0 18px;
}

.masthead h1 {
  margin: 0 0 10px;
  font-size: clamp(1.6rem, 4vw, 2.4rem);
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -.01em;
}

.lead {
  margin: 0;
  max-width: var(--measure);
  font-size: 1.02rem;
}

#user-info {
  display: none;
  /* Top margin, not bottom: this is now the last child of .masthead-inner
   * (moved after .lead for DOM/reading order). A trailing bottom margin here
   * would add dead space below it instead of separating it from .lead above. */
  margin: 14px 0 0;
  font-size: .85rem;
  font-weight: 600;
  letter-spacing: .04em;
}

/* ---- Layout ------------------------------------------------------------- */

main {
  /* 960 content + 2x24 padding: the masthead pads outside its 960 inner block,
   * so a plain max-width of 960 here would inset the cards by 24px against the
   * headline above them. */
  max-width: 1008px;
  margin: 0 auto;
  padding: var(--gap) 24px 64px;
  display: flex;
  flex-direction: column;
  gap: var(--gap);
}

.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 24px;
}

.card > h2 {
  margin: 0 0 18px;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.hint {
  color: var(--ink-muted);
  font-size: .88rem;
  margin: 10px 0 0;
}

/* ---- Start card (issue #206) --------------------------------------------
 * Centred, because until a run finishes this is the only thing on the page:
 * two short lines, one button, and whatever is currently running. There is no
 * column of content to align a left edge against. The result cards below stay
 * left-aligned - they carry prose and numbers that read badly centred. */
.intro-card { text-align: center; }
.intro-card .controls { justify-content: center; }

.intro-lead {
  margin: 0 auto;
  max-width: 46ch;
  font-size: 1.08rem;
  font-weight: 600;
  line-height: 1.45;
}

.intro-note {
  margin: 10px auto 0;
  max-width: 46ch;
  font-size: .9rem;
  color: var(--ink-muted);
}

/* The progress bar is full-width elsewhere on the page; here it belongs to the
 * centred column, so it is held to the same measure and centred with it. */
/* No `display` here: #phase-progress always carries an inline one - the markup
 * ships display:none, runDownload() and waitWithCountdown() set it - and inline
 * wins, so the rule would be inert and would read as the thing that shows the
 * bar (CodeFred's approve on #207). Only the measure is this rule's business. */
.intro-card #phase-progress {
  margin: 14px auto 0;
  max-width: 46ch;
}

/* #phase-detail is the chatty per-chunk/per-packet text. On its own line under
 * the phase name rather than trailing it, so a long detail cannot push the
 * centred phase name off-centre as it grows and shrinks. */
.intro-card #phase-detail {
  display: block;
  margin-top: 4px;
  font-weight: 400;
  font-size: .88rem;
  color: var(--ink-muted);
}

/* ---- Controls ----------------------------------------------------------- */

.controls { display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }

button {
  font: inherit;
  font-weight: 600;
  border: 1px solid transparent;
  border-radius: 6px;
  padding: 12px 22px;
  cursor: pointer;
  background: var(--brand-red);
  color: var(--on-brand);
}

button.secondary {
  background: var(--surface);
  color: var(--brand-blue);
  border-color: var(--border);
}

button:hover:not(:disabled) { background: var(--tint-dark-red); }
button.secondary:hover:not(:disabled) { background: var(--page); }

button:disabled { opacity: .45; cursor: default; }

/* Visible focus, and not only on mouse-free navigation. */
button:focus-visible,
summary:focus-visible {
  outline: 3px solid var(--brand-blue);
  outline-offset: 2px;
}

progress {
  /* Without this, Chrome/Safari keep the native widget and ignore the
   * ::-webkit-progress-* rules below - the bar stays platform grey/blue and
   * the red fill never renders. */
  appearance: none;
  -webkit-appearance: none;
  width: 100%;
  height: 8px;
  margin-top: 14px;
  border: none;
  border-radius: 4px;
  background: var(--page);
}
progress::-webkit-progress-bar { background: var(--page); border-radius: 4px; }
progress::-webkit-progress-value { background: var(--brand-red); border-radius: 4px; }
progress::-moz-progress-bar { background: var(--brand-red); border-radius: 4px; }

#phase-line { margin: 14px 0 0; font-weight: 600; }

/* ---- Verdict ------------------------------------------------------------ */

.verdict-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--gap);
}

.verdict-block {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding-left: 16px;
  border-left: 4px solid var(--border);
}

.verdict-block.is-good { border-left-color: var(--status-good); }
.verdict-block.is-fair { border-left-color: var(--status-fair); }
.verdict-block.is-poor { border-left-color: var(--status-poor); }

.verdict-when {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.verdict-label { font-size: 1.85rem; font-weight: 700; line-height: 1.1; }

/* The band is named in words as well as coloured - the colour alone is not the
 * message, for greyscale printing and for colour-blind readers alike. */
.verdict-label.is-good { color: var(--status-good); }
.verdict-label.is-fair { color: var(--status-fair); }
.verdict-label.is-poor { color: var(--status-poor); }

/* Connection quality / network transit time rows (issue #21) - each states
 * one measured symptom, neither claims to be the other. */
.verdict-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: .88rem;
  margin-top: 4px;
}
.verdict-row-label { color: var(--ink-muted); }
.verdict-row-value { font-variant-numeric: tabular-nums; font-weight: 600; }
/* Same band colours as the headline label, at row scale - the eye should
 * catch a bad sub-score without reading the word first. */
.verdict-row-value.is-good { color: var(--status-good); }
.verdict-row-value.is-fair { color: var(--status-fair); }
.verdict-row-value.is-poor { color: var(--status-poor); }

.verdict-hint {
  font-size: .85rem;
  color: var(--ink-muted);
  margin: 8px 0 0;
  max-width: 42ch;
}
/* Empty when there's nothing to explain (a clean, nearby connection) -
 * :empty removes it from flow so it doesn't leave a dead gap. */
.verdict-hint:empty { display: none; }

/* Recommendation (issue #204) - the decision, set apart from the two verdict
 * blocks it is drawn from. Tinted rather than bordered like .verdict-block:
 * a fourth coloured left border beside three existing ones would read as a
 * third verdict, and this block deliberately carries no band of its own -
 * the decision is not a measurement. */
.recommendation {
  margin-top: var(--gap);
  padding: 20px;
  background: var(--page);
  border-radius: var(--radius);
}

.recommendation-headline {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
  line-height: 1.35;
  max-width: var(--measure);
}

/* Numbered because the steps are meant to be worked through in order: the
 * release decision first, then the operating rule it comes with, then what to
 * do when someone complains anyway. */
.recommendation-steps {
  margin: 16px 0 0;
  padding-left: 1.35em;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.recommendation-steps li { max-width: var(--measure); }
.recommendation-steps .step-title { display: block; font-size: .95rem; }
.recommendation-steps .step-text {
  display: block;
  margin-top: 2px;
  font-size: .88rem;
  color: var(--ink-muted);
}

.briefing-actions {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: var(--gap);
}

/* .copy-note carries margin-left: 6px for its inline use in the export card,
 * which would add to the flex gap above and render 18px here (CodeFred review
 * round 3 on #205). The gap is the one that should win in a flex row. */
.briefing-actions .copy-note { margin-left: 0; }

/* G.107 MOS/R - technical footnote below the primary split verdict, not a
 * second competing headline. */
.verdict-detail {
  /* Full shorthand, not just margin-top: this stylesheet has no p { margin: 0 }
   * reset, so the longhand alone left the UA default margin-block-end (1em) in
   * place under every verdict block - matches .verdict-hint two rules above. */
  margin: 10px 0 0;
  padding-top: 8px;
  border-top: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.verdict-detail .verdict-mos {
  font-variant-numeric: tabular-nums;
  font-size: .8rem;
  color: var(--ink-muted);
}
.verdict-detail .verdict-text { font-size: .8rem; color: var(--ink-muted); max-width: 42ch; }
/* Empty on a good verdict (renderVerdict() clears it there) - :empty removes
 * it from flow so it doesn't leave a dead gap, same as .verdict-hint above. */
.verdict-detail .verdict-text:empty { display: none; }

/* ---- Metrics ------------------------------------------------------------ */

.metrics {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 20px;
}

.metric { display: flex; flex-direction: column; gap: 2px; }

/* Issue #84: fmtMedia()'s directional breakdown (js/app.js) - headline plus
 * an Uplink/Downlink parenthetical, each with its own late-count/jitter and
 * optional burst clause - regularly clears 200 characters for a real
 * directional result. Every other tile's value is a handful of characters;
 * this is the one tile whose value is a paragraph. Left at the shared
 * minmax(210px, 1fr) track above, that paragraph wrapped into 8-9 narrow
 * lines (confirmed by rendering a worst-case value into the tile), rather
 * than clearing the row height its neighbours want. Spanning the full grid
 * row gives that paragraph the width the numbers need to read as prose
 * instead of a truncation problem - no figure or direction label is
 * shortened to make it fit. `1 / -1` (not a fixed column count) so it still
 * spans whatever width auto-fit is currently rendering, single column on
 * narrow viewports included, where it is already full width and this is a
 * no-op. */
.metric-wide { grid-column: 1 / -1; }

.metric-label {
  font-size: .74rem;
  font-weight: 700;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

.metric-value { font-size: 1.05rem; font-variant-numeric: tabular-nums; }

.metric-hint {
  font-size: .78rem;
  color: var(--ink-muted);
  line-height: 1.4;
}

/* Lighter inline sibling of details.card > summary below - same disclosure
 * pattern (visible, touch-friendly affordance, not a bare hover tooltip) but
 * scoped to one .metric tile instead of a full .card (issue #123). */
.metric-detail { margin-top: 2px; }
.metric-detail > summary {
  cursor: pointer;
  font-size: .78rem;
  font-weight: 600;
  color: var(--brand-blue);
}
.metric-detail p {
  margin: 4px 0 0;
  font-size: .78rem;
  color: var(--ink-muted);
  line-height: 1.4;
}

/* ---- Chart, export, log ------------------------------------------------- */

/* Chart.js writes the canvas dimensions as inline styles, which beat anything
 * declared here - a `width: 100%` rule on the canvas loses and the chart keeps
 * its old pixel width when the viewport shrinks, pushing the whole page
 * sideways. The wrapper owns the size instead and the library resizes into it. */
.chart-wrap { position: relative; height: 320px; }

canvas { display: block; }

pre {
  margin: 16px 0 0;
  padding: 18px;
  background: var(--page);
  border: 1px solid var(--border);
  border-radius: 6px;
  overflow-x: auto;
  font-size: .82rem;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
}

details.card > summary {
  cursor: pointer;
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--ink-muted);
}

/* Base has no colour: the outcome (success vs failure) decides it, via
 * .is-good / .is-poor set in js/app.js. A fixed --status-good here rendered
 * the clipboard failure message in the success colour. */
.copy-note { margin-left: 6px; font-weight: 600; }
.copy-note.is-good { color: var(--status-good); }
.copy-note.is-poor { color: var(--status-poor); }

footer {
  max-width: 1008px;
  margin: 0 auto;
  padding: 0 24px 48px;
  color: var(--ink-muted);
  font-size: .82rem;
}

/* ---- Small screens ------------------------------------------------------ */

@media (max-width: 520px) {
  :root { --gap: 16px; }
  .masthead { padding: 28px 18px 32px; }
  main { padding: 16px 18px 48px; }
  .card { padding: 18px; }
  button { width: 100%; }
  .chart-wrap { height: 260px; }
  /* main re-pads to 18px above; footer must match or its line sits 6px
   * further in than the card edges directly above it. */
  footer { padding: 0 18px 48px; }
}

/* Future-proofing guard, not a fix for anything present: this stylesheet has
 * no `transition` or `animation` declaration today (hover/focus states are
 * instant), and Chart.js's own animation is already off via
 * `animation: false` in the chart options in js/app.js, which a CSS rule
 * can't reach anyway. Kept so a later transition doesn't silently ignore the
 * preference. */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* The whole point of shape-coded chart points is that support engineers print
 * these - but background-image is white-on-white by default in print, and the
 * masthead's text is white. Without this the wordmark, H1 and "Angemeldet
 * als" line vanish on paper. */
@media print {
  .masthead { background: none; color: var(--ink); }
  details.card[open] > summary { list-style: none; }
}
