/*
 * Secure Report - Xplor visual identity.
 *
 * Implemented against the brand guidelines (brand.xplor.com/document/42):
 * Logo, Icon, Color Palette, Color Usage, Fonts and Formatting.
 *
 * WHAT THE GUIDELINES SPECIFY, AND WHERE IT IS APPLIED
 *   Headlines      Poppins Bold, 120% leading, sentence case, Darkest Grey /
 *                  Purple / White                                    -> h1
 *   Sub-headlines  Poppins Bold, 120%, sentence case, + Teal Shade    -> h2, legend
 *   Body           Roboto Regular and Bold, 140% leading, sentence case,
 *                  Darkest Grey or White                             -> body
 *   Links          Purple or White, BOLD, UNDERLINED                 -> a
 *   Call outs      Poppins Bold, 120%, kerning 100, UPPER CASE, 10px radius
 *   Buttons        Poppins Bold, 120%, kerning 100, UPPER CASE, solid Lime
 *                  with Darkest Grey type, rounded rect, 10px radius
 *   Gradients      Purple-Shade -> Purple, or Purple -> Teal (two stops)
 *   Weighting      White, Light Grey Tint 2 and Purple dominant; Teal and Lime
 *                  frequent; Pink, Green and Lemon sparingly
 *
 * THREE PLACES THIS DEPARTS FROM THE GUIDELINES, EACH FOR A MEASURED REASON
 *
 *  1. The Lime primary button gets a Darkest Grey border. Its TEXT contrast is
 *     excellent (8.60:1) but Lime against a white or Light-Grey-Tint-2 surface is
 *     1.45:1, so the button's own edge fails WCAG 1.4.11 (3:1 for the boundary of
 *     a UI component). Someone with low vision would not be able to see where
 *     the button is. The border is the smallest change that fixes it and leaves
 *     the specified fill and type colours untouched.
 *
 *  2. Sub-headlines use Purple, not Teal Shade. Teal Shade is permitted, but it
 *     is 3.09:1 on white and 2.84:1 on the page background - so it only passes as
 *     LARGE text, and only on a white card. Purple is 5.80-6.67:1 everywhere it
 *     appears here and is equally permitted.
 *
 *  3. There is one colour from outside the palette: #B3261E for errors. The
 *     palette has no error colour and both candidates fail as text (Pink 3.75:1,
 *     Lemon 1.37:1). A "your submission failed" message on a security reporting
 *     channel has to be unmissable.
 *
 * Every text colour below carries its measured contrast ratio in a comment.
 *
 * Self-hosted by definition: the fonts are in ./fonts/ and the logo in ./brand/.
 * No CDN, no webfont service, no @import. The CSP is `default-src 'none'` with
 * style-src/font-src/img-src 'self', so anything external would fail visibly
 * rather than quietly leak the reporter's IP to whoever serves it.
 */

/* --- fonts -----------------------------------------------------------------
 * Poppins Bold for headings, sub-headings and buttons. Roboto Regular for body,
 * Roboto Bold for emphasis and links - the guidelines call for both weights.
 *
 * Vendored from the @fontsource packages (OFL 1.1; licences sit beside the
 * files). Aptos is in each fallback stack because the guidelines name it as the
 * approved substitute, and it ships with current Windows and Office.
 * --------------------------------------------------------------------------- */

@font-face {
  font-family: 'Poppins';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/poppins-700.woff2') format('woff2');
}

@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/roboto-400.woff2') format('woff2');
}

@font-face {
  font-family: 'Roboto';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('/fonts/roboto-700.woff2') format('woff2');
}

/* --- palette --------------------------------------------------------------- */

:root {
  /* Primary */
  --x-purple: #6923f4;
  --x-light-grey: #e3eaef;
  --x-teal: #3acbca;
  --x-lime: #a2e10b;

  /* Shades and tints */
  --x-purple-shade: #401f9f;
  --x-purple-tint-1: #8c85ff;
  --x-purple-tint-2: #babefc;
  --x-light-grey-shade: #ccd4da;
  --x-light-grey-tint-1: #eaf0f4;
  --x-light-grey-tint-2: #f1f6fa;
  --x-teal-shade: #0fa3a4;
  --x-teal-tint-1: #8ee1e1;
  --x-teal-tint-2: #c7f0f0;
  --x-lime-shade: #85c600;
  --x-lime-tint-2: #e4f6b9;
  --x-lemon: #ffda00;

  /* Grey scale */
  --x-darkest-grey: #282f35;
  --x-dark-grey: #545d63;
  --x-grey: #b4bec6;
  --x-white: #ffffff;

  /* Approved two-stop gradient. Purple -> Teal. */
  --x-gradient-purple-teal: linear-gradient(90deg, var(--x-purple), var(--x-teal));

  --font-head: 'Poppins', 'Aptos', 'Segoe UI', system-ui, sans-serif;
  --font-body: 'Roboto', 'Aptos', 'Segoe UI', system-ui, sans-serif;
  --font-mono: ui-monospace, 'SF Mono', 'Cascadia Mono', Menlo, Consolas, monospace;

  /* --- semantic roles, with measured contrast on their own surface --------- */
  --page: var(--x-light-grey-tint-2);
  --card: var(--x-white);
  --line: var(--x-light-grey-shade);

  /*
   * The edge of a text field is a UI component boundary, so WCAG 1.4.11 wants
   * 3:1 from it - the same rule that put a border on the Lime button (note 1).
   * --line is a decorative rule and measures 1.38:1 on the page and 1.50:1 on a
   * card, so fields need their own token rather than sharing that one.
   *
   * Dark Grey is the only palette grey that clears 3:1 against both light
   * surfaces, and Grey is the only one that clears it against both dark ones.
   * Nothing from outside the palette was needed, so this adds no departure.
   */
  --field-edge: var(--x-dark-grey);      /* 6.18:1 on page, 6.72:1 on card */

  /*
   * The focus ring is a non-text indicator, so 1.4.11 applies to it too and it
   * needs its own token: Purple itself is 6.13:1 on the light page but only
   * 2.44:1 on the dark one, because --x-purple is a raw brand value and the
   * dark-mode block below remaps the semantic roles rather than the palette.
   */
  --focus-ring: var(--x-purple);         /* 6.13:1 on page, 6.67:1 on card */

  /*
   * The progress fill. The guidelines approve two gradients - Purple -> Teal and
   * Purple-Shade -> Purple - and only the second one can be used here.
   *
   * A progress fill has to read against its own track at 3:1, and the Teal end
   * of the first gradient cannot: it is 1.32:1 on the light track and 1.83:1 on
   * the page. Nor is there a track colour that rescues it - Teal needs a track
   * below about 0.13 relative luminance and Purple needs one above 0.42, so no
   * single value satisfies both ends. Teal is a fill colour, not a signal
   * colour, which is the same reason it never carries text here.
   *
   * Purple-Shade -> Purple measures 7.31:1 and 4.45:1 on the light track.
   */
  --progress-fill: linear-gradient(90deg, var(--x-purple-shade), var(--x-purple));

  --ink: var(--x-darkest-grey);          /* 12.47:1 on page, 13.56:1 on card */
  --ink-soft: var(--x-dark-grey);        /* 6.18:1 on page */
  --headline: var(--x-darkest-grey);     /* 13.56:1 */
  --subhead: var(--x-purple);            /* 6.67:1 on card - see note 2 above */
  --link: var(--x-purple);               /* 6.67:1 on card, 5.80:1 on info bg */
  --link-hover: var(--x-purple-shade);   /* 10.97:1 */

  /* Primary button: the guidelines' Lime fill with Darkest Grey type. */
  --button-bg: var(--x-lime);
  --button-ink: var(--x-darkest-grey);   /* 8.60:1 on Lime */
  --button-edge: var(--x-darkest-grey);  /* see note 1 above */
  --button-bg-hover: var(--x-lime-shade);

  --info-rule: var(--x-purple);
  --info-bg: var(--x-light-grey-tint-1);

  /* Lemon as a rule only - 1.37:1 as text, so never as text. */
  --caution-rule: var(--x-lemon);
  --caution-bg: var(--x-white);

  --ok-rule: var(--x-lime);
  --ok-bg: var(--x-lime-tint-2);
  --ok-text: #0a7b7c;                    /* Teal Shade darkened to 5.08:1 */

  --error-text: #b3261e;                 /* 6.54:1 - not a brand colour, note 3 */
  --error-rule: #b3261e;
  --error-bg: #fbedec;

  /* Logo clear space is the width of the "o", which measures 25.2% of the
     wordmark's width in the official SVG. Derived from the asset, not guessed. */
  /*
   * 92px rather than 118px, and the vertical clear space is the masthead's own
   * padding rather than a margin on the mark.
   *
   * A DEPARTURE FROM THE GUIDELINES, and the only one that is a preference
   * rather than a measurement. Clear space is specified as the width of the "o"
   * on all four sides; at the old size that made the masthead 137px of a phone's
   * screen before the reporter read a word, on a page somebody reaches while
   * dealing with a security problem. The horizontal clear space is kept in full
   * - it is the one that stops the mark being crowded by the divider beside it.
   * 92/35 also tracks the official 2.6376 ratio more closely than 118/45 did.
   */
  --brand-logo-width: 92px;
  --brand-clear-space: calc(var(--brand-logo-width) * 0.252);

  --radius: 10px;                        /* the guidelines' corner radius */
  --radius-inner: 6px;                   /* nested inside --radius, so smaller */

  /*
   * Disabled controls.
   *
   * This was `opacity: 0.6`, which fades the label along with the fill and left
   * it at 3.44:1 in light mode and 2.23:1 in dark - below the 4.5:1 body-text
   * threshold, on the state the submit button holds for the several seconds a
   * submission takes. Opacity cannot be reasoned about, because the result
   * depends on whatever is behind it; a stated pair can.
   *
   * Darkest Grey on Light Grey Shade is 9.04:1. Dark Grey would have been 4.48:1
   * - close enough to look fine and not close enough to pass.
   */
  --disabled-bg: var(--x-light-grey-shade);
  --disabled-ink: var(--x-darkest-grey);  /* 9.04:1 on the disabled fill */

  /* --- scales --------------------------------------------------------------
   * The guidelines set leading (120% headings, 140% body) and weights, and say
   * nothing about sizes or spacing - so these are free choices, and until now
   * they were made one at a time: 0.35, 0.45, 0.55, 0.6, 0.65, 0.7, 0.72, 0.78,
   * 0.85, 0.88, 0.93, 0.95, 1.1, 1.15rem all appeared in this file. A one-off
   * value beside a scale is the clearest sign a page was assembled rather than
   * designed, and it is invisible in review precisely because each one looked
   * reasonable on its own.
   *
   * Six type steps, not a fixed ratio: the four working sizes sit close together
   * because they carry running text, and the display step jumps because a page
   * title is doing a different job. Nothing is below 12px, and 12px is for
   * genuine metadata only - which is what .callout at 11.5px was not.
   * ------------------------------------------------------------------------- */
  --text-xs: 0.75rem;                    /* 12px - labels, call-outs, steps */
  --text-sm: 0.875rem;                   /* 14px - hints, secondary detail */
  --text-base: 1rem;                     /* 16px - body */
  --text-md: 1.125rem;                   /* 18px - lede, legend */
  --text-lg: 1.375rem;                   /* 22px - sub-headings */
  --text-xl: 2.125rem;                   /* 34px - the page title */

  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.5rem;
  --space-6: 2rem;
  --space-7: 3rem;

  /*
   * Layout. The page was 48rem while it was one column; the form and the
   * guidance rail together need more, and splitting them is what brings the
   * measure down rather than the page width up - the rail runs to about 45
   * characters a line and the form column to about 70.
   */
  --page-max: 62rem;
  --rail-width: 19rem;
  --column-gap: 2.5rem;
  /*
   * Prose measure, in ch rather than rem: the cap is a character count, and the
   * body sizes here differ enough that one rem value gives 73 characters at one
   * size and 84 at another.
   *
   * 56ch, not 68ch. `ch` is the width of a "0", which in Roboto is wider than
   * the average lowercase letter - so a 68ch cap was letting about 85 characters
   * onto the line, which is past the 60-75 this exists to hold.
   */
  --measure: 56ch;
}

/*
 * Dark mode is not in the guidelines, so it is derived from them: Darkest Grey
 * as the surface, and the TINTS wherever text is involved, because Purple itself
 * is unreadable on #282F35 while Purple Tint 2 is 7.66:1. The Lime button needs
 * no change - Darkest Grey on Lime is 8.60:1 either way, and against a dark card
 * its edge is 8.60:1, so it is the light mode that needed the border.
 */
@media (prefers-color-scheme: dark) {
  :root {
    --page: #1b2126;
    --card: var(--x-darkest-grey);
    --line: #3a434b;

    --ink: var(--x-light-grey);          /* 11.16:1 on card */
    --ink-soft: var(--x-grey);           /* 7.18:1 */
    --field-edge: var(--x-grey);         /* 8.61:1 on page, 7.18:1 on card */
    --focus-ring: var(--x-purple-tint-2); /* 9.18:1 on page, 7.66:1 on card */

    --disabled-bg: #3a434b;
    --disabled-ink: var(--x-grey);        /* 5.34:1 on the disabled fill */

    /*
     * Both stops go to their tints, the same derivation the text colours above
     * use: Purple-Shade is 1.09:1 on the dark track and Purple 1.51:1, so the
     * light-mode gradient would be an invisible bar. Teal comes back here
     * because Teal Tint 1 against a dark track is 6.72:1 - the objection to Teal
     * is its lightness, which is an asset on this surface.
     */
    --progress-fill: linear-gradient(90deg, var(--x-purple-tint-2), var(--x-teal-tint-1));
    --headline: var(--x-white);          /* permitted headline colour */
    --subhead: var(--x-purple-tint-2);   /* 7.66:1 - Tint 1 is only 4.47:1 */
    --link: var(--x-purple-tint-2);      /* 7.66:1 */
    --link-hover: var(--x-purple-tint-1);

    --info-rule: var(--x-purple-tint-1);
    --info-bg: #232b32;

    --caution-rule: var(--x-lemon);
    --caution-bg: #2a2a1f;

    --ok-rule: var(--x-lime);
    --ok-bg: #1f2a20;
    --ok-text: var(--x-teal-tint-1);     /* 9.05:1 */

    --error-text: #ffb4ae;               /* 8.00:1 */
    --error-rule: #ffb4ae;
    --error-bg: #33201f;
  }
}

* {
  box-sizing: border-box;
}

body {
  margin: 0;
  padding: 0;
  background: var(--page);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--text-base);
  /* 140% of type size, per the body copy spec. */
  line-height: 1.4;
  -webkit-font-smoothing: antialiased;
}

/*
 * The approved Purple -> Teal gradient as a hairline across the top.
 *
 * The circle-motif guidance asks for the mark to feel purposeful rather than
 * decorative, and on a page somebody reaches while dealing with a security
 * problem a full motif would be noise. The logo plus this one device places the
 * brand without competing with the content.
 */
body::before {
  content: '';
  display: block;
  height: 4px;
  background: var(--x-gradient-purple-teal);
}

/* --- masthead -------------------------------------------------------------- */

.masthead {
  background: var(--card);
  border-bottom: 1px solid var(--line);
}

.masthead-inner {
  max-width: var(--page-max);
  margin: 0 auto;
  /* Vertical clear space, provided by the bar rather than by the mark. */
  padding: var(--space-3) var(--space-4);
  display: flex;
  align-items: center;
  gap: var(--brand-clear-space);
}

/*
 * The logo, at its official proportions.
 *
 * `height: auto` and no transform of any kind: the guidelines forbid distorting,
 * tilting, recolouring, editing or applying effects to the mark. Dark mode swaps
 * to the official WHITE asset via <picture> rather than filtering this one -
 * a CSS filter would be exactly the "special effect" that is not allowed.
 */
.brand-logo {
  display: block;
  width: var(--brand-logo-width);
  height: auto;
  margin: 0;
}

.masthead-divider {
  width: 1px;
  align-self: stretch;
  background: var(--line);
  margin: var(--space-1) 0;
}

.masthead-service {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--text-sm);
  line-height: 1.2;
  color: var(--ink);
  margin: 0;
}

main {
  max-width: var(--page-max);
  margin: 0 auto;
  padding: var(--space-6) var(--space-4) var(--space-7);
}

/* --- layout ----------------------------------------------------------------
 * One column by default, two above --page-max. The rail is FIRST in the source
 * and is moved to the second column by grid placement, so the narrow-viewport
 * stacking order is guidance-then-form without any `order` juggling - `order`
 * would move the boxes and leave the tab sequence behind them.
 * --------------------------------------------------------------------------- */

.page-head {
  margin: 0 0 var(--space-5);
}

.layout {
  display: grid;
  gap: var(--column-gap);
}

/*
 * `display: grid` on a class beats the UA stylesheet's `[hidden] { display: none }`.
 * Nothing hides the layout today, but leaving the rule out is the kind of thing
 * that is only discovered by a box refusing to disappear.
 */
.layout[hidden] {
  display: none;
}

/*
 * No explicit grid-column anywhere: the rail is first in the source and first on
 * screen, so natural placement is already correct and source order, visual order
 * and tab order agree without being told to.
 */
@media (min-width: 62rem) {
  .layout {
    grid-template-columns: var(--rail-width) minmax(0, 1fr);
    align-items: start;
  }
}

/* The rail is preamble, so it is quieter than the form beside it. */
.rail .panel {
  font-size: var(--text-sm);
}

/*
 * The rail is 19rem beside the form, where the measure looks after itself - but
 * below 62rem it stacks and spans the full page, and the same four points ran to
 * 113 characters a line at 768px. The cap does nothing in the two-column layout
 * and everything in the stacked one.
 */
.rail li,
.rail p {
  max-width: var(--measure);
}

.rail .panel h2 {
  font-size: var(--text-base);
}

.rail .panel:last-child {
  margin-bottom: 0;
}

/*
 * A visually hidden but announced string. Used for the claim token's spelled-out
 * reading, where the visual grid and the spoken form need to differ.
 */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* --- type ------------------------------------------------------------------ */

h1,
h2,
h3,
legend,
button,
.callout,
.stub-label {
  font-family: var(--font-head);
  font-weight: 700;
  /* 120% of type size, per the headline and sub-headline specs. */
  line-height: 1.2;
}

h1 {
  font-size: var(--text-xl);
  color: var(--headline);
  margin: 0 0 var(--space-3);
  letter-spacing: -0.015em;
}

h2 {
  font-size: var(--text-lg);
  color: var(--subhead);
  margin: 0 0 var(--space-2);
}

h3 {
  font-size: var(--text-base);
  color: var(--headline);
  margin: var(--space-4) 0 var(--space-2);
}

.lede {
  font-size: var(--text-md);
  color: var(--ink-soft);
  margin: 0;
  max-width: var(--measure);
}

/* Links: Purple, bold, underlined - all three specified. */
a {
  color: var(--link);
  font-weight: 700;
  text-decoration: underline;
  text-decoration-thickness: 1.5px;
  text-underline-offset: 2px;
}

a:hover {
  color: var(--link-hover);
}

/* Roboto Bold for emphasis, per the body copy spec. */
strong {
  font-weight: 700;
  color: var(--ink);
}

code {
  font-family: var(--font-mono);
  font-size: 0.9em;
  word-break: break-all;
}

/*
 * Call out: Poppins Bold, upper case, kerning 100 (0.1em). Used for the small
 * label above a panel heading. `text-transform` rather than capitals in the
 * markup, so the accessible name stays sentence case for a screen reader.
 */
.callout {
  display: block;
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--subhead);
  margin: 0 0 var(--space-1);
}

/* --- panels ---------------------------------------------------------------- */

.panel {
  background: var(--card);
  border: 1px solid var(--line);
  border-left: 4px solid var(--line);
  border-radius: var(--radius);
  padding: var(--space-4);
  margin: 0 0 var(--space-4);
}

.panel-compact {
  padding: var(--space-3) var(--space-4);
  margin: var(--space-3) 0;
  font-size: var(--text-sm);
}

.panel ul {
  margin: var(--space-2) 0 0;
  padding-left: var(--space-4);
}

.panel li + li {
  margin-top: var(--space-2);
}

.panel p:first-of-type {
  margin-top: 0;
}

.panel p:last-child {
  margin-bottom: 0;
}

.panel-info {
  background: var(--info-bg);
  border-left-color: var(--info-rule);
}

.panel-warn {
  background: var(--caution-bg);
  border-left-color: var(--caution-rule);
}

.panel-error {
  background: var(--error-bg);
  border-left-color: var(--error-rule);
}

.panel-ok {
  background: var(--ok-bg);
  border-left-color: var(--ok-rule);
}

details.panel > summary {
  font-family: var(--font-head);
  font-weight: 700;
  line-height: 1.2;
  cursor: pointer;
  color: var(--subhead);
}

details.panel[open] > summary {
  margin-bottom: var(--space-3);
}

/* --- form ------------------------------------------------------------------ */

fieldset {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--card);
  padding: var(--space-4) var(--space-4) var(--space-5);
  margin: 0 0 var(--space-4);
}

legend {
  font-size: var(--text-md);
  padding: 0 var(--space-2);
  color: var(--subhead);
}

.field {
  display: block;
  margin: 0 0 var(--space-4);
}

.field:last-child {
  margin-bottom: 0;
}

label {
  display: block;
  font-family: var(--font-body);
  font-weight: 700;
  margin-bottom: var(--space-1);
}

.optional {
  font-weight: 400;
  color: var(--ink-soft);
}

.hint {
  display: block;
  font-weight: 400;
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--ink-soft);
  margin-bottom: var(--space-2);
  /* Prose inside the form column, capped; the inputs below it stay full width. */
  max-width: var(--measure);
}

input[type='text'],
select,
textarea {
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-family: var(--font-body);
  color: var(--ink);
  background: var(--page);
  border: 1px solid var(--field-edge);
  border-radius: var(--radius-inner);
}

input[type='text']:focus,
select:focus,
textarea:focus {
  border-color: var(--x-purple);
  background: var(--card);
}

textarea {
  /*
   * Monospace and pre-wrap so an ASCII-armoured PGP block, a log extract or a
   * stack trace keeps its line structure. Nothing here alters the value - the
   * client reads textarea.value untouched.
   */
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: 1.5;
  white-space: pre-wrap;
  resize: vertical;
  min-height: 12rem;
}

input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
summary:focus-visible,
a:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 2px;
}

.field-check {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
}

.field-check label {
  font-weight: 400;
}

.field-check input[type='checkbox'] {
  margin-top: var(--space-1);
  width: 1.15rem;
  height: 1.15rem;
  flex: 0 0 auto;
  accent-color: var(--x-purple);
}

input[type='file'] {
  width: 100%;
  padding: var(--space-3);
  border: 1px dashed var(--field-edge);
  border-radius: var(--radius-inner);
  background: var(--page);
  font: inherit;
  font-family: var(--font-body);
  color: var(--ink);
}

/*
 * The error for one field, beneath that field. Empty until the client fills it,
 * and referenced from the field's aria-describedby the whole time - so it is
 * read out when focus lands there, which is where the client puts focus.
 */
.field-error {
  display: block;
  font-size: var(--text-sm);
  line-height: 1.45;
  font-weight: 700;
  color: var(--error-text);
  margin-top: var(--space-1);
}

.field-error:empty {
  display: none;
}

/* A field the client has marked invalid, so the edge agrees with the message. */
input[aria-invalid='true'],
select[aria-invalid='true'],
textarea[aria-invalid='true'] {
  border-color: var(--error-rule);
  border-width: 2px;
}

/* --- buttons ---------------------------------------------------------------
 * Primary: solid Lime, Darkest Grey type, Poppins Bold, upper case, kerning 100,
 * 10px radius - the guidelines' primary button, plus the border explained at the
 * top of this file.
 * --------------------------------------------------------------------------- */

.actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  margin: 0 0 var(--space-2);
}

button {
  font-size: var(--text-sm);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--button-ink);
  background: var(--button-bg);
  border: 1px solid var(--button-edge);
  border-radius: var(--radius);
  padding: var(--space-3) var(--space-5);
  cursor: pointer;
}

button:hover:not(:disabled) {
  background: var(--button-bg-hover);
}

/*
 * A stated pair rather than `opacity: 0.6`. Opacity fades the label along with
 * the fill and the result depends on whatever happens to be behind it - this
 * button measured 3.44:1 in light mode and 2.23:1 in dark, on the state it holds
 * for the several seconds a submission takes.
 */
button:disabled {
  background: var(--disabled-bg);
  color: var(--disabled-ink);
  border-color: var(--field-edge);
  cursor: default;
}

/* Only the control that is actually waiting gets the waiting cursor. */
button[aria-busy='true'] {
  cursor: progress;
}

/*
 * Secondary button. The guidelines have a "Secondary Buttons" section whose
 * example did not survive the PDF export, so this is an outlined interpretation
 * of the primary spec: same type treatment, no fill. Worth confirming against
 * the live guidelines before this ships anywhere customer-facing.
 */
.button-secondary {
  color: var(--link);
  background: transparent;
  border: 1.5px solid var(--link);
}

.button-secondary:hover:not(:disabled) {
  background: var(--info-bg);
}

/* --- progress ---------------------------------------------------------------
 * Two uses, one device: the three steps of a submission, and the batch position
 * during a multi-file upload. Both sit beside a live region that says the same
 * thing in words, so neither is the only way to know what is happening.
 * --------------------------------------------------------------------------- */

.progress {
  margin: var(--space-3) 0 0;
}

.progress-steps {
  display: flex;
  justify-content: space-between;
  gap: var(--space-2);
  list-style: none;
  margin: 0 0 var(--space-2);
  padding: 0;
}

/*
 * The call-out treatment from the guidelines - Poppins Bold, upper case, kerning
 * 100 - at 12px rather than the 11.5px used elsewhere, which is under the floor
 * for legibility.
 */
.progress-steps li {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--text-xs);
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
}

.progress-steps li[data-state='active'] {
  color: var(--subhead);
}

.progress-steps li[data-state='done'] {
  color: var(--ink);
}

.progress-track {
  height: 8px;
  background: var(--page);
  border: 1px solid var(--field-edge);
  border-radius: var(--radius);
  overflow: hidden;
}

.progress-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--progress-fill);
  /* Entrance timing, not a state flip: this is the bar catching up to a step
     that has genuinely finished. Killed outright under reduced motion by the
     blanket rule at the end of this file, which leaves the width jumping - which
     is the correct behaviour, since the width is the information. */
  transition: width 240ms ease-out;
}

/* --- status --------------------------------------------------------------- */

.status {
  margin: var(--space-1) 0 0;
  font-size: var(--text-sm);
  min-height: 1.4em;
}

.status:empty {
  min-height: 0;
}

.status-info {
  color: var(--ink-soft);
}

.status-ok {
  color: var(--ok-text);
  font-weight: 700;
}

.status-error {
  color: var(--error-text);
  font-weight: 700;
}

.key-note {
  font-size: var(--text-sm);
  color: var(--ink-soft);
  margin: var(--space-3) 0 0;
}

/* --- a case, as facts -------------------------------------------------------
 * The status-check result. Same shape as the triage app's .meta, so a case reads
 * the same on both sides of the system.
 * --------------------------------------------------------------------------- */

.meta {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: var(--space-1) var(--space-4);
  margin: 0;
}

.meta dt {
  font-family: var(--font-head);
  font-weight: 700;
  font-size: var(--text-xs);
  line-height: 1.2;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
  padding-top: 0.2em;
}

.meta dd {
  margin: 0;
  overflow-wrap: anywhere;
}

/* Received dates and file counts line up down the column. */
.meta dd.numeric {
  font-variant-numeric: tabular-nums;
}

/* --- attachments ---------------------------------------------------------- */

.attachments {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
}

/* Teal as a non-text accent - it is 1.99:1 as text and never used as such. */
.attachments li {
  border: 1px solid var(--line);
  border-left: 3px solid var(--x-teal);
  border-radius: var(--radius-inner);
  padding: var(--space-2) var(--space-3);
  margin-bottom: var(--space-2);
  background: var(--page);
}

.attachment-name {
  display: block;
  font-weight: 700;
  /* Reporter-supplied text: keep a very long name from breaking the layout. */
  overflow-wrap: anywhere;
}

.attachment-detail {
  display: block;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

/*
 * A row per chosen file, from the moment it is chosen. The left rule carries the
 * state as colour and the line below carries it in words, because a reporter who
 * cannot separate the red rule from the teal one still has to be able to tell
 * that a file did not make it.
 */
.attachments li[data-state='queued'] {
  border-left-color: var(--line);
}

.attachments li[data-state='uploading'] {
  border-left-color: var(--x-purple);
}

.attachments li[data-state='uploaded'] {
  border-left-color: var(--x-teal);
}

.attachments li[data-state='failed'] {
  border-left-color: var(--error-rule);
  background: var(--error-bg);
}

.attachment-state {
  display: block;
  font-size: var(--text-sm);
  margin-top: var(--space-1);
  color: var(--ink-soft);
}

.attachment-state[data-state='uploaded'] {
  color: var(--ok-text);
}

.attachment-state[data-state='failed'] {
  color: var(--error-text);
  font-weight: 700;
}

/* --- claim token ------------------------------------------------------------
 * Designed to be copied down by hand, because that is what the copy beside it
 * tells the reporter to do. 32 hex characters transcribe badly in one run, so
 * they are set as eight numbered groups of four: the number is somewhere to lose
 * your place and find it again, and it is generated by a CSS counter so the
 * element's own text stays pure hex.
 * --------------------------------------------------------------------------- */

.confirmation {
  max-width: 34rem;
  margin: 0 0 var(--space-4);
}

.confirmation:focus-visible {
  outline: 3px solid var(--focus-ring);
  outline-offset: 4px;
}

.stub {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  margin: var(--space-3) 0 var(--space-4);
}

.stub-body {
  padding: var(--space-4) var(--space-4) var(--space-4);
}

.stub-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-3);
  margin: 0 0 var(--space-3);
}

.stub-label {
  font-size: var(--text-xs);
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--ink-soft);
}

.stub-time {
  font-size: var(--text-xs);
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

.token-groups {
  counter-reset: token-group;
  list-style: none;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--space-2);
  margin: 0;
  padding: 0;
}

/*
 * Four across fits a 320px screen with nothing to spare, and "nothing to spare"
 * is one font substitution away from a group breaking its box. Below 26rem it
 * goes to two columns of four, which is a better shape to transcribe from
 * anyway.
 */
@media (max-width: 26rem) {
  .token-groups {
    grid-template-columns: repeat(2, 1fr);
  }
}

.token-groups li {
  counter-increment: token-group;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-lg);
  letter-spacing: 0.08em;
  color: var(--ink);
  background: var(--page);
  border-radius: var(--radius-inner);
  padding: var(--space-1) 0;
  /* One gesture selects a whole group, which is how it gets transcribed. */
  user-select: all;
}

/*
 * The group number. Generated content rather than markup so that the list's
 * textContent is only the token - and unselectable, so dragging across the grid
 * to copy it by hand does not pick up the numbering.
 */
.token-groups li::before {
  content: counter(token-group);
  display: block;
  font-family: var(--font-body);
  font-size: var(--text-xs);
  letter-spacing: 0;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
  user-select: none;
}

.token-alphabet {
  font-size: var(--text-sm);
  line-height: 1.45;
  color: var(--ink-soft);
  margin: var(--space-2) 0 0;
}

/*
 * The tear line. The one decorative move on the page, and it earns itself by
 * saying what the thing is - above it is the part you keep, below it is what it
 * means. A single-sided dashed rule, so no radius.
 */
.stub-foot {
  border-top: 1px dashed var(--field-edge);
  border-radius: 0;
  background: var(--info-bg);
  padding: var(--space-4) var(--space-4) var(--space-4);
  font-size: var(--text-sm);
}

.stub-foot p:first-child {
  margin-top: 0;
}

.stub-foot .actions {
  margin: var(--space-3) 0 0;
}

.stub-foot .status {
  margin: var(--space-2) 0 0;
}

/* --- footer --------------------------------------------------------------- */

footer {
  border-top: 1px solid var(--line);
  padding: var(--space-5) var(--space-4);
  text-align: center;
  font-size: var(--text-sm);
  color: var(--ink-soft);
}

footer p {
  margin: 0;
}

/* The status check is a form, not prose - it does not want the full width. */
.status-check {
  max-width: 34rem;
  margin-top: var(--space-6);
}

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

/* --- print -----------------------------------------------------------------
 * There is one thing on this page worth printing, and the Print button beside
 * the claim token exists to do it. Everything that is chrome, navigation or a
 * form the reporter has already finished with comes off the paper.
 * --------------------------------------------------------------------------- */

@media print {
  body {
    background: #ffffff;
    color: #000000;
  }

  body::before,
  .masthead,
  .layout,
  .status-check,
  footer,
  .stub-foot .actions {
    display: none;
  }

  .confirmation {
    max-width: none;
  }

  .stub {
    border: 1px solid #000000;
    /* Keep the token and what it means on one sheet. */
    break-inside: avoid;
  }

  .stub-body,
  .stub-foot {
    background: #ffffff;
  }

  .token-groups li {
    background: #ffffff;
    border: 1px solid #000000;
  }
}
