v0.12.0

- A working Settings page. It shows the current engine settings and lets BLACK and up start or
  pause the engine, manage competition placeholders, and export or import the competition's
  configuration. WHITE can view it but not change anything. Import and Reset only show for ADMIN
- Export to a ZIP. Staff pick any of checks, graders, injects, IR, users and settings, and
  `POST /api/settings/export` returns `hermes-settings.zip` with one TOML file per section. Inject
  and IR exports include their PDFs. Submissions are never exported. Requires BLACK
- Import from that ZIP. `POST /api/settings/bulk-import` now reads `graders.toml`, `inject.toml`
  and `ir.toml` along with checks, users and settings, and picks up the inject and IR PDFs from
  their `document_path` entries. Each section has its own destructive flag
  (`destructiveChecks`, `destructiveGraders`, `destructiveInjects`, `destructiveIR`,
  `destructiveUsers`, `destructiveSettings`) that replaces existing data instead of adding to it.
  Replacing graders fails with a 409 while reviews or submissions still reference them.
  Replacing injects deletes their submissions and reviews. Replacing IR deletes all IR reports
  and reviews so a rubric that has already been graded against can change
- The users export carries each account's bcrypt hash, and import restores it as is. An import
  rejects a user with a missing or invalid `hashed_password`
- Engine reset. `POST /api/settings/reset` deletes score adjustments, current team scores, team
  variable overrides, inject submissions, rounds, the round ledger and round scores, then sets
  the engine to PAUSED. It asks for confirmation on the page. Requires ADMIN
- List variables. A check variable can now hold a list of values instead of one. Each round it
  hands the check one item, walking the list in order, or a random item when `randomize` is set.
  Teams can override a list with their own items from the blue team check page. When a list
  variable is secret, every item in it is scrubbed from round output
- `variable_type`, `default_items` and `randomize` on the check variable endpoints, and `items`
  on a team's own variable writes. `POST /api/checks` takes a `variables` JSON array, so a check
  and its variables are created in one request and no round can run the check before its
  variables exist
- Multiple accepted outputs. HTTP, FTP, SMB, SSH and WinRM checks gain two match types,
  "equals one of" (`exactListMatch`) and "contains one of" (`substringListMatch`), which pass
  when the output matches any entry in the new `accepted_outputs` list. Telnet gains only
  "contains one of". SSH, Telnet and WinRM checks get a `match_type` field for this, defaulting
  to the comparison they used before
- A reusable list editor in the frontend for entering list items one at a time
- Bruno requests for settings export, list variables, randomized list variables, a team's list
  overrides, and a chain check

- Icons on several admin buttons
- Almost every frontend API hook now goes through `apiFetch`, replacing each hook's own fetch
  and error handling
- The checks export and import round-trip variables, including their type, list items and
  `randomize`. A `checks.toml` from before this release imports its variables as strings
- The round number comes from the latest round in the database instead of a counter the
  scheduler keeps in memory, so an engine reset starts back at round 1. An ABORTED round is
  retried under its own number

- Importing checks no longer fails with "conn busy", and chain checks import correctly
- A bulk import without a zip file returns a 400 instead of panicking on the missing upload
- The scoreboard fits every team on screen. Rows and columns share the available space down to a
  minimum size, and the panel scrolls past that, in the default, template and irsec-2026 themes