Move the Users table and add a new rule-text editor into a dedicated
admin.html/admin.js page with its own collapsible sidebar (Users,
Rules categories), reachable only for system admins via a button next
to the brand. The Competitions page no longer renders admin-only
controls inline.
Competitions page: cards are now fully clickable to open the
competition (drop the separate "Open" button), the admin-only delete
action moves into a "..." menu (new generic openInlineMenu/
openAnchoredPopover helpers in common.js), and add an open/closed
filter.
Topbar: language switch and logout move out of the topbar into a
small popup under the profile button, which itself opens the full
settings modal for username/display name/language/password.
Strip a leading UTF-8 BOM before parsing rule CSVs (it was causing the
header row to be misread as a rule) and skip rows that clearly failed
CSV parsing (e.g. a whole line landing in the rule_number field after
a spreadsheet app double-encoded a quoted cell) instead of silently
loading garbage.
Add PUT /api/rules/{lang}/{number} and GET /api/rules/numbers so rule
text, suggested penalty and escalation mode can be edited per language
from the admin UI, always writing the CSV back out with correct
quoting via encoding/csv. Also fixes two bugs found while wiring this
up: rulesDir() ignored the configured rules_dir (falling back to a
RULES_DIR env var that's never set), and the CORS middleware didn't
allow PUT.