diff --git a/CHANGELOG.md b/CHANGELOG.md
index c35e6ee..e482cee 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,16 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); version
## [Unreleased]
+### Added
+
+- docs: HTML mockups for a full panel UI refresh
+ ([docs/assets/panel-ui/](docs/assets/panel-ui/index.html)) — current screens,
+ agreed and candidate roadmap surfaces (queue-retries, inbound-relay,
+ dmarc-reports, panel-docs), a hybrid width (ops pages use the window, forms
+ keep a reading measure), and a 390px emergency layout. The stamp and brick
+ palette stay; extra nav icons are proposals only. Design artefact; the
+ running panel is unchanged.
+
## [1.3.0] - 2026-08-14
Security and quality after 1.2.5: domain-admin send-log authorization,
diff --git a/docs/assets/panel-ui/app.html b/docs/assets/panel-ui/app.html
new file mode 100644
index 0000000..e32ffb9
--- /dev/null
+++ b/docs/assets/panel-ui/app.html
@@ -0,0 +1,907 @@
+
+
+
+
+
+SelfPost — panel mockups
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Status
+ Domains
+ example.com
+ Deliveries
+ Message
+ Mail queue
+ System log
+ Inbound
+ DMARC
+ Backup
+ Users
+ Help
+ Settings
+
+
?
+
+
+
+
+
+
Status warn
+
3 messages deferred — first retry in about 4 minutes. Everything else is up.
+
+
+
+
+
+
Mail queue warn
+ ?
+
+ 3 deferred, 0 bounce-hold. Oldest 18 minutes.
+ View queue
+
+
+
+
Inbound ok
+
+ Accepting on port 25 for 1 domain. Last forward 2 minutes ago.
+ Inbound domains
+
+
+
Inbound
+ Module off (INBOUND_RELAY_ENABLE=false). The outbound path is unchanged.
+
+
+
+
Machine ok
+ ?
+
+ CPU 12%
+ Memory 41%
+ Network ↓ 48 KiB/s · ↑ 12 KiB/s
+
+
+
Processes ok
+ postfix RUNNING
+ opendkim RUNNING
+ panel RUNNING
+
+
+
+
TLS certificate ok
+ ?
+
+ Expires
+ 2026-11-02 12:00 UTC
+ Let’s Encrypt, 78 days left. Mounted for port 465.
+
+
+
Milter sockets ok
+ opendkim ok
+ journal ok
+
+
+
+
Hostname / rDNS ok
+ ?
+
+ Server hostname
+ mail.example.org
+ Forward and reverse
+ mail.example.org → 203.0.113.10 → mail.example.org
+
+
+
+ SelfPost 1.2.3 · © Mixeme · License (AGPL-3.0)
+
+
+
+
+
+ Sign in
+
+
+
+ © Mixeme · License (AGPL-3.0)
+
+
+
+
+
+ Create administrator
+
+
This one-time link creates the single panel administrator. After you submit, the link stops working for good.
+
+
+
+
+
+
+
Domains
+
+
Add a sending domain
+
+
+
+
Domains
+
+
+
The DNS badge is the worst of DKIM, SPF and DMARC. Open a domain for details.
+
+
+
+
+
+ example.com
+ ← All domains
+
+
New application password
+
Shown once only and not stored. Copy it now.
+
Login
+
newsletter Copy
+
Password
+
xK.9fQ2m-pL7wR Copy
+
+
+
DNS status ok
+
+
+
DKIM ok
+
Host / name mail._domainkey.example.com
Type TXT
+
+
+
SPF ok
+
Host / name example.com
Type TXT
+
+
+
DMARC ok
+
Host / name _dmarc.example.com
Type TXT
+
p=none; rua points at SelfPost ingest.
+
+
+
Report authorization
+
Not required (rua= is on a domain SelfPost accepts).
+
+
+
+
+
+
+
DKIM and SPF records
+
DKIM
+
Host / name mail._domainkey.example.com Copy
Type TXT
+
Value
+
v=DKIM1; k=rsa; p=MIIBIjANBgkqh… Copy
+
SPF
+
v=spf1 ip4:203.0.113.10 -all Copy
+
+
+
DMARC record
+
Host / name _dmarc.example.com Copy
Type TXT
+
Value (from Settings)
+
v=DMARC1; p=none; rua=mailto:dmarc@mail.example.org Copy
+
Open DMARC reports for this domain.
+
+
+
+
+
Connection settings
+
Server
+
mail.example.org Copy
+
Port and encryption
+
465 — SSL/TLS (implicit)
+587 — STARTTLS (submission)
+
+
+
Add an application
+
+
+
+
+
Applications
+
+
+ newsletter
+ Any address of the domain — *@example.com
+
+
+
Edit limit
+
New password
+
Delete
+
+
+
+
Address mode
+
Any address of the domain Specific addresses (list)
+
+
+
Trusted-IP override active
+
+
+
+
+
+
+
+
+ billing
+ Fixed list — invoices@example.com
+
+ Edit
+ New password
+ Delete
+
+
+
+
+
+
Domain settings
+
+
+
DMARC reports
+
Aggregate reports (rua=)
+
Same as Settings (dmarc@mail.example.org) No aggregate reports Custom address
+
Save DMARC report settings
+
+
+
Level-2 rate limit active
+
+
Save limit
+
+
+
+
+
+
Export domain
+
Secret file — transfer securely, or encrypt as .spde.
+
+
Export domain
+
+
+
Danger zone
+
Deletes the DKIM key and every application on this domain.
+
Delete domain
+
+
+
+
+
+
+ Delete example.com
+ ← Back to example.com
+
+
Confirm deletion
+
You are about to delete example.com . This will:
+
+ permanently delete its DKIM signing key;
+ delete all 2 bound applications , including their SASL credentials;
+ reload OpenDKIM so the domain is no longer signed.
+
+
This cannot be undone.
+
Delete example.com
+
+
+
+
+
+ Deliveries
+
+
+
+ Time From To Subject Status
+
+
+ 2026-08-15 20:14:02
+ billing@example.com
+ ada@example.net
+ Invoice #4412
+ deferred
+ Details
+
+
+ 2026-08-15 20:11:40
+ news@example.com
+ list-bounces@example.net
+ August digest
+ delivered
+ Details
+
+
+ 2026-08-15 19:02:11
+ alerts@alerts.example.com
+ noreply@blocked.example
+ Disk 92% on web-3
+ bounced
+ Details
+
+
+ 2026-08-15 18:44:09
+ news@example.com
+ sam@example.org
+ August digest
+ delivered
+ Details
+
+
+
+
+
Page 1 of 4 · Older →
+
+
+
+
+
+ Invoice #4412
+
+ billing@example.com
+ →
+ ada@example.net
+ deferred
+
+ ← Back to deliveries
+
+
+
Message
+
+
Domain example.com
+
Application billing
+
Accepted 2026-08-15 20:14:02 UTC
+
Status reported 2026-08-15 20:14:08 UTC
+
Queue id 4C3A1E2F1A
+
Journal id 1842
+
+
+
+
History
+
+
+ 2026-08-15 20:14:02 UTC
+ accepted Received by the relay
+ SASL login billing, queued as 4C3A1E2F1A.
+
+
+ 2026-08-15 20:14:08 UTC
+ deferred Receiving MX asked to try later
+ Postfix retries: first after 5 minutes, then with increasing gaps up to 1 hour 7 minutes, for up to 5 days. There is no fixed attempt count — a deferred message stays in the queue until it is delivered or that lifetime runs out.
+
+
+ not yet
+ delivery Waiting on the next retry
+
+
+
+
+
+
Delivery log
+
+ Time Message
+
+ 20:14:02 postfix/smtpd[221]: 4C3A1E2F1A: client=203.0.113.40, sasl_username=billing
+ 20:14:02 postfix/cleanup[224]: 4C3A1E2F1A: message-id=<4412@example.com>
+ 20:14:08 postfix/smtp[230]: 4C3A1E2F1A: to=<ada@example.net>, relay=mx.example.net[198.51.100.20]:25, delay=6, status=deferred (450 4.2.1 mailbox busy)
+
+
+
+
20:14:08
+
status=deferred (450 4.2.1 mailbox busy)
+
20:14:02
+
client=203.0.113.40, sasl_username=billing
+
+
+
+
+
+
+ Mail queue
+
+
How delivery retries work
+
This Postfix’s policy, read once at panel start. There is no maximum attempt count — only time.
+
+
First retry 5 minutes
+
Later retries doubling, cap 1 h 7 min
+
Kept in queue 5 days
+
Then bounced
+
+
+
+
Pending messages
+
+ Queue id Age From To Size
+
+ 4C3A1E2F1A 18 min billing@example.com ada@example.net 12 KiB
+ 4C3A1E3010 11 min news@example.com pat@slow.example 48 KiB
+ 4C3A1E3102 4 min billing@example.com ada@example.net 9 KiB
+
+
+
+
postqueue -p
+
-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
+4C3A1E2F1A* 12288 Sat Aug 15 20:14:02 billing@example.com
+ ada@example.net
+4C3A1E3010 49152 Sat Aug 15 20:21:18 news@example.com
+ pat@slow.example
+-- 3 Kbytes in 3 Requests.
+
+
+
+
+
+ System log
+
+
Recent log entries
+ Aug 15 20:14:08 mail postfix/smtp[230]: 4C3A1E2F1A: to=<ada@example.net>, relay=mx.example.net[198.51.100.20]:25, delay=6, delays=0.2/0.1/0.4/5.3, dsn=4.2.1, status=deferred (450 4.2.1 mailbox busy)
+Aug 15 20:14:02 mail postfix/qmgr[119]: 4C3A1E2F1A: from=<billing@example.com>, size=12288, nrcpt=1 (queue active)
+Aug 15 20:14:02 mail postfix/smtpd[221]: 4C3A1E2F1A: client=203.0.113.40[203.0.113.40], sasl_method=PLAIN, sasl_username=billing
+Aug 15 20:11:40 mail postfix/smtp[228]: 4B19D0AA01: to=<list-bounces@example.net>, relay=mx.example.net[198.51.100.20]:25, delay=0.9, status=sent (250 2.0.0 Ok)
+Aug 15 20:02:11 mail postfix/smtp[226]: 4B19C0BB12: to=<noreply@blocked.example>, status=bounced (host mx.blocked.example[203.0.113.99] said: 550 5.7.1 rejected)
+
+
+
+
+
+ Backup & migration
+
+
Full backup
+
Download a full backup of all persistent state. Restore into a container of the same SelfPost version . TLS certificates and the mail queue are not included. The file is a secret.
+
+
Encrypt with a password
+
+
+
Download full backup
+
+
+
Import a domain
+
Plain .json or encrypted .spde.
+
Domain export file
+
+
Import domain
+
+
+
+
+
+ Users
+
+
Create user
+
+ Username Role Domains
+
+ admin Global All Edit
+ ops-alerts Domain admin alerts.example.com Edit
+
+
+
+
+
+
+
+ Edit user
+ ← Back to users
+
+
+
+
+
+ Delete ops-alerts
+ ← Back to ops-alerts
+
+
Confirm deletion
+
You are about to delete the panel user ops-alerts . A signed-in session for this user stops working immediately.
+
Delete ops-alerts
+
+
+
+
+
+ Settings
+
+
+
DMARC aggregate reports
+
Default rua= for every sending domain. When ingest is on, this can be an address SelfPost accepts.
+
Default report address
+
+
DMARC reports in the panel.
+
+ Save changes
+
+
Sending rate limits
+
Level 1 is set in Compose. Domain and application ceilings live on each domain’s page.
+
+
Level 1 / IP 100 / 60s
+
Level 2 domain optional, ≤ L1
+
App override trusted IPs only
+
+
+
+
+
+
+
+
Inbound 1.x
+
+ Backup-MX / forwarder. Accepts on port 25 only for listed domains; unknown recipients are rejected at RCPT. Off by default in Compose.
+
+
+
+
+
+
+ lists.example.com
+ ← All inbound domains
+
+
+
Upstream
+
Where accepted mail is handed off. Not a mailbox.
+
Host
+
Port
+
TLS to upstream
+
Opportunistic Required Off
+
Save upstream
+
+
+
DNS — MX
+
Unlike outbound, inbound needs an MX pointing at this server.
+
Host / name
+
lists.example.com Copy
+
Value
+
10 mail.example.org. Copy
+
+
+
+
Valid recipients
+
Unknown recipients are rejected at RCPT so this relay does not generate backscatter. One address per line; empty means reject all until listed.
+
+
Save recipients
+
+
+
Danger zone
+
Stops accepting mail for this domain. Does not touch outbound sending domains.
+
Remove inbound domain
+
+
+
+
+
+
+
DMARC reports candidate
+
+ Aggregate reports SelfPost accepted for rua=. Forensic (ruf=) is out of scope.
+
+
+ Ingest ok
+ Last report 6 hours ago. 14 kept, 0 parse failures this week.
+
+
+ example.com pass
+ 98% aligned last 7 days. Tightening p= looks reasonable.
+
+
+ alerts.example.com fail
+ A third-party sender is not in SPF/DKIM. See sources.
+
+
+
+
Sources · last 7 days
+
+ Domain Source Pass Fail Disposition
+
+ example.com 203.0.113.10 (this relay) 412 2 none
+ example.com google.com / 66.102.0.0/20 0 6 none
+ alerts.example.com 203.0.113.10 (this relay) 88 0 none
+ alerts.example.com unknown / 198.51.100.80 0 19 none
+
+
+
+
+
+
+
+
+
+
Help candidate
+
+ Short operator notes inside the panel — not a second copy of the full guide. Seeded from the Status blurbs that left the cards.
+
+
On this panel
+
+
The same texts open in the drawer from a card’s «?» — so a Status page can stay dense.
+
+
+
Machine
+
CPU and memory are the container’s own readings, not the host’s spare capacity. Network is a short window, not a daily total. High CPU with an empty queue usually means something else on the box — not SelfPost “being slow to send”.
+
+
+
TLS certificate
+
Port 465 presents the certificate the reverse proxy (or the image) mounted. The panel does not issue certificates. A warn here is “expires soon”; an error is “missing or unreadable”, and clients will refuse submission.
+
+
+
Hostname / reverse DNS
+
Forward-confirmed reverse DNS: the A/AAAA for SELFPOST_HOSTNAME must reverse to that same name. PTR is set at the hosting provider, not in this panel. Receiving networks use this pair as a cheap reputation check.
+
+
+
+
+
+
+
+
+ Close
+ Machine
+ CPU and memory are the container’s readings. Network is a short rate window. These numbers explain load; they do not replace the queue.
+ TLS certificate
+ Presented on port 465. Issued and renewed outside SelfPost. Warn = expires soon; error = missing, and submission will fail.
+ Hostname / reverse DNS
+ The hostname must forward to this IP and the PTR must come back to the same name. Set PTR at the provider.
+ Mail queue
+ Deferred mail is retried on a time schedule (first delay, doubling cap, queue lifetime). There is no “attempt 3 of N”.
+
+
+
+
+
diff --git a/docs/assets/panel-ui/index.html b/docs/assets/panel-ui/index.html
new file mode 100644
index 0000000..76d0f4c
--- /dev/null
+++ b/docs/assets/panel-ui/index.html
@@ -0,0 +1,173 @@
+
+
+
+
+
+SelfPost — макеты панели
+
+
+
+
+
+
+
+ макеты · не панель
+ SelfPost — полное обновление интерфейса
+ Click-through прототип всех экранов: текущая панель, согласованный roadmap и кандидаты. Знак и палитра из листа утверждения не меняются. Это HTML в docs/assets/panel-ui/, не вёрстка internal/web.
+
+
+
+
+ Ширина окна
+ Сейчас оболочка центрируется, колонка контента упирается в 64rem, формы ещё уже — 48rem. На широком мониторе поля пустые. Растянуть всё на 100% окна нельзя: поля Settings и Backup становятся нечитаемыми.
+ Гибрид
+ Навигация прижата влево. Ops-страницы (Status, Deliveries, очередь, лог, DMARC, inbound) занимают остаток до 90rem (~1440px) . Формы остаются 42rem и выровнены влево в колонке, а не висят по центру пустого поля. Login и setup — по-прежнему узкий центрированный блок.
+
+
+
сейчас, окно ~1600px
+
+ nav
+ 64rem
+ пусто
+
+
+
+
макет, ops-страница
+
+ nav
+ контент до 90rem
+
+
+
+
макет, форма
+
+ nav
+ 42rem
+ поле не растягивает инпуты
+
+
+
+ На телефоне это не «узкий десктоп». Таблицы из шести колонок складываются в список. Навигация — выезжающая колонка на CSS-checkbox, без обязательного JavaScript (как панели Edit у приложений уже сейчас).
+
+
+
+ Информационная архитектура
+
+
+
Global administrator
+
+ Status
+ Domains → domain → delete
+ Deliveries → message
+ Mail queue
+ System log
+ Inbound 1.x — скрыт, пока INBOUND_RELAY_ENABLE выключен
+ DMARC reports candidate
+ Backup, Users, Settings
+ Help candidate — пункт навбара и drawer с карточек Status
+
+
+
+
Domain-admin
+
+ Domains (только назначенные) → domain
+ Deliveries → message, включая пояснение retry
+ Settings — только учётные данные панели
+ Help drawer
+ Нет Status, очереди, system log, Backup, Users, inbound, глобального DMARC hub
+
+
+
+ В прототипе роль, inbound-флаг, ширина и тема переключаются в шапке. contributing и schema-squash экранов не имеют.
+
+
+
+ Знак и новые иконки
+ Утверждённый stamp не заменяется. Для узкой шапки телефона предлагается уже существующий SP-значок (фавикон), а не компактный wordmark: на 390px высота важнее, чем прочитать «SelfPost» второй раз.
+
+
+
+ эталон · login / setup
+
+
+
+ колонка навбара
+
+
+
+ телефон · шапка drawer
+
+
+ Иконки новых пунктов
+ Тот же штрих 1.5, что у Status / Domains. Не пиктограммы-эмодзи и не заливка.
+
+
+
+
+ Экраны
+ Вход
+
+ Текущая панель
+
+ Roadmap
+
+ Аварийный телефон
+
+
+
+
+ Как смотреть
+ Откройте app.html в браузере. Шапка прототипа — не часть панели: роль, Desktop/Phone, inbound, светлая/тёмная. Копирайт экранов английский, как в продукте. Drawer Help открывается с карточек Status (кнопка «?») или из навбара.
+ После утверждения макетов вёрстка panel.css и шаблонов — отдельная задача. CSP и progressive enhancement в этом HTML не воспроизводятся один в один: здесь допустимы вещи, которые в панели останутся в файле стилей.
+
+
+
+
+
diff --git a/docs/assets/panel-ui/mock.css b/docs/assets/panel-ui/mock.css
new file mode 100644
index 0000000..3ea51c3
--- /dev/null
+++ b/docs/assets/panel-ui/mock.css
@@ -0,0 +1,668 @@
+/* SelfPost panel UI mockups. Tokens match internal/web/view/static/panel.css.
+ This file is a design artifact, not the panel stylesheet. */
+
+@import url("https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@200;300;400;500;600;700&display=swap");
+
+:root {
+ color-scheme: light dark;
+ --font-sans: "IBM Plex Sans", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
+ --font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
+ --bg: #f4f2ed; --fg: #12161c;
+ --muted: #6b7684;
+ --card-bg: #fff;
+ --border: #dedcd7;
+ --control-border: #cbc8c1;
+ --input-bg: #fff;
+ --code-bg: #efede9;
+ --surface-bg: #eae7e0; --surface-bg-hover: #e0dcd3; --surface-open-bg: #d8d3c8;
+ --nav-active-bg: #ede4de;
+ --accent-fill: #7a3b2e; --accent-fill-hover: #632f25; --accent-text: #7a3b2e;
+ --on-accent: #fff;
+ --flash-bg: #edf6ef; --flash-border: #c2e0cc; --flash-fg: #2c6b43;
+ --credential-bg: #fcf6e4; --credential-border: #e0c874;
+ --danger-fill: #b42318; --danger-fill-hover: #912018;
+ --danger-bg: #fbedea; --danger-border: #efccc4; --danger-fg: #b42318; --danger-bg-hover: #f7dfda;
+ --st-ok-bg: #edf6ef; --st-ok-fg: #2c6b43; --st-ok-border: #c2e0cc;
+ --st-warn-bg: #fbf2e2; --st-warn-fg: #8a5510; --st-warn-border: #ebd5a6;
+ --st-error-bg: #fbedea; --st-error-fg: #b42318; --st-error-border: #efccc4;
+ --st-unknown-bg: #efede9; --st-unknown-fg: #6b7684; --st-unknown-border: #dedcd7;
+ --nav-w: 14rem;
+ --ops-max: 90rem;
+ --form-max: 42rem;
+ --auth-max: 24rem;
+ --gallery-h: 3.1rem;
+}
+
+.dark-tokens {
+ --bg: #16181b; --fg: #e9e6e0;
+ --muted: #9aa1a9;
+ --card-bg: #1d2024;
+ --border: #2c2f34;
+ --control-border: #3a3e44;
+ --input-bg: #14161a;
+ --code-bg: #14161a;
+ --surface-bg: #23262b; --surface-bg-hover: #2c3036; --surface-open-bg: #343941;
+ --nav-active-bg: #2a1f1b;
+ --accent-fill: #8e4535; --accent-fill-hover: #a0503e; --accent-text: #ce7b66;
+ --flash-bg: #132318; --flash-border: #22452f; --flash-fg: #7fcb9b;
+ --credential-bg: #26210d; --credential-border: #5e5013;
+ --danger-fill: #9b2c22; --danger-fill-hover: #b0342a;
+ --danger-bg: #2a1412; --danger-border: #5e2721; --danger-fg: #eb9b92; --danger-bg-hover: #381a17;
+ --st-ok-bg: #132318; --st-ok-fg: #7fcb9b; --st-ok-border: #22452f;
+ --st-warn-bg: #2a2109; --st-warn-fg: #e5be72; --st-warn-border: #5e4b12;
+ --st-error-bg: #2a1412; --st-error-fg: #eb9b92; --st-error-border: #5e2721;
+ --st-unknown-bg: #23262b; --st-unknown-fg: #9aa1a9; --st-unknown-border: #2c2f34;
+}
+
+@media (prefers-color-scheme: dark) {
+ :root { color-scheme: dark; }
+ html:not(.force-light) { }
+}
+
+* { box-sizing: border-box; }
+html { scrollbar-gutter: stable; }
+body {
+ font: 400 15px/1.5 var(--font-sans);
+ margin: 0; background: var(--bg); color: var(--fg);
+}
+
+.ctrl { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
+
+/* —— Gallery chrome (index + prototype toolbar) —— */
+.gallery {
+ position: sticky; top: 0; z-index: 40;
+ display: flex; flex-wrap: wrap; align-items: center; gap: 0.55rem 1rem;
+ min-height: var(--gallery-h);
+ padding: 0.45rem 1rem;
+ background: var(--surface-bg);
+ border-bottom: 1px solid var(--border);
+ font-size: 0.82rem;
+}
+.gallery a { color: var(--accent-text); }
+.gallery .brand-mini {
+ font-family: var(--font-mono); font-size: 0.7rem; font-weight: 600;
+ letter-spacing: 0.12em; text-transform: uppercase; color: var(--accent-text);
+ text-decoration: none;
+}
+.gallery .seg {
+ display: flex; flex-wrap: wrap; align-items: center; gap: 0.2rem 0.35rem;
+}
+.gallery .seg > span { color: var(--muted); margin-right: 0.15rem; }
+.gallery .seg label {
+ margin: 0; font-weight: 500; cursor: pointer;
+ padding: 0.2rem 0.55rem; border: 1px solid var(--control-border);
+ border-radius: 5px; background: var(--card-bg); color: var(--fg);
+}
+#role-global:checked ~ .gallery label[for="role-global"],
+#role-domain:checked ~ .gallery label[for="role-domain"],
+#vp-desktop:checked ~ .gallery label[for="vp-desktop"],
+#vp-phone:checked ~ .gallery label[for="vp-phone"],
+#theme-light:checked ~ .gallery label[for="theme-light"],
+#theme-dark:checked ~ .gallery label[for="theme-dark"] {
+ background: var(--nav-active-bg); color: var(--accent-text);
+ border-color: var(--accent-fill); font-weight: 600;
+}
+.gallery .check-lab {
+ display: flex; align-items: center; gap: 0.35rem; margin: 0; font-weight: 500; cursor: pointer;
+}
+.gallery .check-lab input { width: auto; margin: 0; }
+.tag {
+ display: inline-block; font-family: var(--font-mono); font-size: 0.68rem;
+ font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase;
+ padding: 0.12rem 0.4rem; border-radius: 4px;
+ background: var(--surface-bg); color: var(--muted); border: 1px solid var(--border);
+}
+.tag.future { color: var(--st-warn-fg); border-color: var(--st-warn-border); background: var(--st-warn-bg); }
+
+body:has(#theme-dark:checked) { background: #16181b; color: #e9e6e0; }
+
+#theme-dark:checked ~ .gallery,
+#theme-dark:checked ~ .app,
+#theme-dark:checked ~ .help-drawer {
+ --bg: #16181b; --fg: #e9e6e0;
+ --muted: #9aa1a9;
+ --card-bg: #1d2024;
+ --border: #2c2f34;
+ --control-border: #3a3e44;
+ --input-bg: #14161a;
+ --code-bg: #14161a;
+ --surface-bg: #23262b; --surface-bg-hover: #2c3036; --surface-open-bg: #343941;
+ --nav-active-bg: #2a1f1b;
+ --accent-fill: #8e4535; --accent-fill-hover: #a0503e; --accent-text: #ce7b66;
+ --flash-bg: #132318; --flash-border: #22452f; --flash-fg: #7fcb9b;
+ --credential-bg: #26210d; --credential-border: #5e5013;
+ --danger-fill: #9b2c22; --danger-fill-hover: #b0342a;
+ --danger-bg: #2a1412; --danger-border: #5e2721; --danger-fg: #eb9b92; --danger-bg-hover: #381a17;
+ --st-ok-bg: #132318; --st-ok-fg: #7fcb9b; --st-ok-border: #22452f;
+ --st-warn-bg: #2a2109; --st-warn-fg: #e5be72; --st-warn-border: #5e4b12;
+ --st-error-bg: #2a1412; --st-error-fg: #eb9b92; --st-error-border: #5e2721;
+ --st-unknown-bg: #23262b; --st-unknown-fg: #9aa1a9; --st-unknown-border: #2c2f34;
+ background: var(--bg); color: var(--fg);
+}
+
+/* —— Prototype shell: nav docked left, content uses remaining width —— */
+.app {
+ display: flex; align-items: flex-start; min-height: calc(100vh - var(--gallery-h));
+ background: var(--bg);
+}
+.stage { flex: 1 1 auto; min-width: 0; }
+.nav {
+ position: sticky; top: var(--gallery-h); align-self: stretch;
+ flex: none; width: var(--nav-w);
+ display: flex; flex-direction: column; gap: 0.75rem;
+ padding: 1.1rem 0.85rem 1.4rem;
+ border-right: 1px solid var(--border);
+ background: var(--bg);
+ max-height: calc(100vh - var(--gallery-h)); overflow-y: auto;
+}
+.nav .links, .nav .session { display: flex; flex-direction: column; gap: 0.1rem; }
+.nav .session { margin-top: auto; padding-top: 0.75rem; border-top: 1px solid var(--border); }
+.nav .session .muted { padding: 0 0.6rem; font-size: 0.85rem; overflow-wrap: anywhere; }
+.nav .session-user { display: flex; align-items: center; gap: 0.5rem; }
+.nav .brand { padding: 0; display: block; }
+.nav .brand img { display: block; width: 100%; height: auto; }
+.nav a, .nav [aria-current], .nav .current {
+ display: flex; align-items: center; gap: 0.5rem;
+ padding: 0.35rem 0.6rem; border-radius: 5px; text-decoration: none;
+ color: var(--fg);
+}
+.nav a:hover { background: var(--surface-bg); }
+.nav [aria-current], .nav .current {
+ font-weight: 600; color: var(--accent-text); background: var(--nav-active-bg);
+ box-shadow: inset 2px 0 0 var(--accent-fill);
+}
+html:not(:has(.screen:target)) .n-status,
+body:has(#status:target) .n-status,
+body:has(#domains:target) .n-domains,
+body:has(#domain:target) .n-domains,
+body:has(#domain-delete:target) .n-domains,
+body:has(#deliveries:target) .n-deliveries,
+body:has(#delivery:target) .n-deliveries,
+body:has(#mail-queue:target) .n-queue,
+body:has(#system-log:target) .n-log,
+body:has(#inbound:target) .n-inbound,
+body:has(#inbound-domain:target) .n-inbound,
+body:has(#dmarc:target) .n-dmarc,
+body:has(#backup:target) .n-backup,
+body:has(#users:target) .n-users,
+body:has(#user-form:target) .n-users,
+body:has(#user-delete:target) .n-users,
+body:has(#help:target) .n-help,
+body:has(#settings:target) .n-settings {
+ font-weight: 600; color: var(--accent-text); background: var(--nav-active-bg);
+ box-shadow: inset 2px 0 0 var(--accent-fill);
+}
+.pt { display: none; }
+html:not(:has(.screen:target)) .pt-status,
+body:has(#status:target) .pt-status,
+body:has(#login:target) .pt-login,
+body:has(#setup:target) .pt-setup,
+body:has(#domains:target) .pt-domains,
+body:has(#domain:target) .pt-domain,
+body:has(#domain-delete:target) .pt-domain,
+body:has(#deliveries:target) .pt-deliveries,
+body:has(#delivery:target) .pt-delivery,
+body:has(#mail-queue:target) .pt-queue,
+body:has(#system-log:target) .pt-log,
+body:has(#inbound:target) .pt-inbound,
+body:has(#inbound-domain:target) .pt-inbound,
+body:has(#dmarc:target) .pt-dmarc,
+body:has(#backup:target) .pt-backup,
+body:has(#users:target) .pt-users,
+body:has(#user-form:target) .pt-users,
+body:has(#user-delete:target) .pt-users,
+body:has(#help:target) .pt-help,
+body:has(#settings:target) .pt-settings { display: inline; }
+.nav .icon { width: 1rem; height: 1rem; flex: none; }
+.nav button, .nav .btn-ghost {
+ display: flex; align-items: center; gap: 0.5rem;
+ margin: 0; padding: 0.35rem 0.6rem; font: inherit; font-size: 0.95rem; font-weight: 400;
+ color: var(--danger-fg); background: var(--danger-bg); border: 1px solid var(--danger-border);
+ border-radius: 5px; cursor: pointer; text-decoration: none; width: 100%;
+}
+.nav button:hover, .nav .btn-ghost:hover { background: var(--danger-bg-hover); }
+
+.phone-bar { display: none; }
+.phone-bar .icon { width: 1.1rem; height: 1.1rem; }
+.mono { font-family: var(--font-mono); }
+.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }
+
+#feat-inbound:checked ~ .gallery label[for="feat-inbound"] {
+ background: var(--nav-active-bg); color: var(--accent-text);
+ border-color: var(--accent-fill);
+}
+.gallery label[for="feat-inbound"] {
+ margin: 0; font-weight: 500; cursor: pointer;
+ padding: 0.2rem 0.55rem; border: 1px solid var(--control-border);
+ border-radius: 5px; background: var(--card-bg); color: var(--fg);
+}
+#role-domain:checked ~ .gallery .g-only { display: none; }
+
+main {
+ flex: 1 1 auto; min-width: 0; width: 100%;
+ padding: 1.5rem 1.5rem 2.5rem;
+}
+main.ops { max-width: var(--ops-max); }
+main.form { max-width: calc(var(--form-max) + 3rem); }
+main.form .measure, main.form > :not(.phone-bar) { max-width: var(--form-max); }
+main.auth {
+ max-width: none; display: flex; flex-direction: column; align-items: center;
+ padding-top: 3rem;
+}
+main.auth > * { width: 100%; max-width: var(--auth-max); margin-left: auto; margin-right: auto; }
+
+.screen { display: none; }
+.screen:target { display: block; }
+main.auth.screen:target { display: flex; }
+html:not(:has(.screen:target)) #status { display: block; }
+body:has(#login:target) .nav,
+body:has(#setup:target) .nav,
+body:has(#login:target) .phone-bar,
+body:has(#setup:target) .phone-bar { display: none !important; }
+body:has(#login:target) .app,
+body:has(#setup:target) .app { display: block; }
+
+#role-domain:checked ~ .app .g-only { display: none !important; }
+#feat-inbound:not(:checked) ~ .app .in-only { display: none !important; }
+#feat-inbound:checked ~ .app .in-off { display: none !important; }
+
+h1 { font-size: 1.5rem; font-weight: 300; letter-spacing: -0.01em; margin: 0 0 1rem; }
+h1.subject { overflow-wrap: anywhere; }
+h2 { font-size: 1.05rem; font-weight: 600; margin: 0 0 0.4rem; }
+.mark { display: block; width: 100%; max-width: 24rem; height: auto; margin-bottom: 1.4rem; }
+.card {
+ background: var(--card-bg); border: 1px solid var(--border); border-radius: 6px;
+ padding: 1.25rem 1.4rem; margin: 0;
+}
+.card.narrow { max-width: var(--auth-max); }
+.card + .card { margin-top: 1rem; }
+.card.credential { border-color: var(--credential-border); background: var(--credential-bg); }
+.muted { color: var(--muted); }
+.error { color: var(--danger-fg); margin: 0.6rem 0 0; font-weight: 600; }
+a { color: var(--accent-text); }
+.back { display: block; margin: -0.4rem 0 1rem; }
+.flash {
+ background: var(--flash-bg); border: 1px solid var(--flash-border); color: var(--flash-fg);
+ padding: 0.7rem 1rem; border-radius: 6px; margin-bottom: 1rem;
+}
+.flash.error { background: var(--danger-bg); border-color: var(--danger-border); color: var(--danger-fg); }
+.version { margin-top: 1.6rem; text-align: right; font-size: 0.8rem; color: var(--muted); }
+.version a { color: inherit; }
+
+label { display: block; font-weight: 600; margin: 0.9rem 0 0.3rem; }
+label.check {
+ display: flex; align-items: center; gap: 0.5rem; margin: 0.45rem 0 0; font-weight: 600; cursor: pointer;
+}
+label.check input { width: auto; margin: 0; padding: 0; border: 0; background: none; }
+input, select, textarea {
+ width: 100%; padding: 0.55rem 0.7rem; font-family: inherit; font-size: 1rem;
+ border: 1px solid var(--control-border); border-radius: 5px; background: var(--input-bg); color: inherit;
+}
+textarea { resize: vertical; }
+fieldset {
+ margin: 0.9rem 0 0; padding: 0.55rem 0.85rem 0.85rem;
+ border: 1px solid var(--control-border); border-radius: 5px;
+}
+fieldset legend { padding: 0 0.25rem; font-weight: 600; }
+fieldset > .muted { margin: 0.15rem 0 0.35rem; font-size: 0.85rem; font-weight: 400; }
+
+button, a.btn, a.danger {
+ display: inline-block; margin-top: 1.1rem; padding: 0.6rem 1.1rem;
+ font: inherit; font-size: 1rem; font-weight: 600; text-decoration: none;
+ color: var(--on-accent); background: var(--accent-fill); border: 0; border-radius: 5px; cursor: pointer;
+}
+button:hover, a.btn:hover { background: var(--accent-fill-hover); }
+button.danger, a.danger { background: var(--danger-fill); color: var(--on-accent); }
+button.danger:hover, a.danger:hover { background: var(--danger-fill-hover); }
+form.inline { display: inline; margin: 0; }
+
+.st {
+ display: inline-block; padding: 0.14rem 0.45rem 0.28rem; border-radius: 4px;
+ font-family: var(--font-mono); font-size: 0.78rem; font-weight: 500; line-height: 1;
+ letter-spacing: 0.02em; vertical-align: middle; border: 1px solid transparent;
+}
+.st-ok { background: var(--st-ok-bg); color: var(--st-ok-fg); border-color: var(--st-ok-border); }
+.st-warn { background: var(--st-warn-bg); color: var(--st-warn-fg); border-color: var(--st-warn-border); }
+.st-error { background: var(--st-error-bg); color: var(--st-error-fg); border-color: var(--st-error-border); }
+.st-unknown { background: var(--st-unknown-bg); color: var(--st-unknown-fg); border-color: var(--st-unknown-border); }
+a.st, a.st:hover { color: inherit; text-decoration: none; }
+
+table { width: 100%; border-collapse: collapse; }
+th, td { text-align: left; padding: 0.5rem 0.45rem; border-bottom: 1px solid var(--border); overflow-wrap: anywhere; }
+th {
+ font-family: var(--font-mono); font-size: 0.75rem; font-weight: 500;
+ text-transform: uppercase; letter-spacing: 0.08em; color: var(--muted);
+}
+td.time, td.status { white-space: nowrap; }
+td.actions { text-align: right; white-space: nowrap; }
+td.subject span {
+ display: block; max-width: 22rem;
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
+}
+
+.code {
+ display: block; white-space: pre-wrap; word-break: break-all; font-family: var(--font-mono);
+ font-size: 0.85rem; background: var(--code-bg); border: 1px solid var(--border);
+ border-radius: 5px; padding: 0.7rem 0.8rem; margin: 0.3rem 0 0;
+}
+.code-row { display: flex; align-items: flex-start; gap: 0.5rem; }
+.code-row .code { flex: 1; min-width: 0; margin-top: 0; padding-top: 0.45rem; padding-bottom: 0.45rem; }
+.input-row { display: flex; align-items: stretch; gap: 0.5rem; }
+.input-row input { flex: 1; min-width: 0; }
+.input-row button { margin-top: 0; flex: none; white-space: nowrap; }
+
+.split {
+ display: grid; grid-template-columns: repeat(auto-fit, minmax(22rem, 1fr));
+ gap: 1rem; margin-top: 1rem;
+}
+.split > .card { min-width: 0; }
+.split > .card + .card { margin-top: 0; }
+.split + .card, .card + .split { margin-top: 1rem; }
+
+.check-cols {
+ display: grid; grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
+ gap: 1rem 1.2rem; margin-top: 1rem;
+}
+.check-col { min-width: 0; }
+.check-col-title { margin: 0.85rem 0 0.35rem; font-size: 1.05rem; font-weight: 600; }
+.field-pair {
+ display: grid; grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
+ gap: 0 1rem; margin-top: 0.45rem;
+}
+.field-pair.host-type { grid-template-columns: minmax(0, 1fr) auto; }
+.field-type { width: max-content; }
+.field-type .code { width: fit-content; min-width: 2.75rem; text-align: center; padding: 0.45rem; }
+
+.facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(9.5rem, 1fr)); gap: 0.5rem; margin-top: 1rem; }
+.fact { min-width: 0; padding: 0.5rem 0.7rem; border-radius: 6px; background: var(--surface-bg); }
+.fact-label {
+ display: block; font-family: var(--font-mono); font-size: 0.7rem; font-weight: 500;
+ text-transform: uppercase; letter-spacing: 0.1em; color: var(--muted);
+}
+.fact-value { display: block; margin-top: 0.1rem; overflow-wrap: anywhere; }
+.fact-value.mono { font-family: var(--font-mono); font-size: 0.85rem; }
+
+.timeline {
+ list-style: none; display: flex; flex-direction: column; gap: 1.1rem;
+ margin: 1rem 0 0; padding: 0.2rem 0 0.2rem 1.4rem; border-left: 2px solid var(--border);
+}
+.event { position: relative; min-width: 0; }
+.event::before {
+ content: ""; position: absolute; left: -1.85rem; top: 0.3rem;
+ width: 0.65rem; height: 0.65rem; border-radius: 50%;
+ background: var(--card-bg); border: 2px solid var(--control-border);
+}
+.event.lvl-ok::before { border-color: var(--st-ok-fg); background: var(--st-ok-bg); }
+.event.lvl-warn::before { border-color: var(--st-warn-fg); background: var(--st-warn-bg); }
+.event.lvl-error::before { border-color: var(--st-error-fg); background: var(--st-error-bg); }
+.event.pending { opacity: 0.7; }
+.event.pending::before { border-style: dashed; }
+.event-time { margin: 0; font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); }
+.event-title { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin: 0.15rem 0 0; font-weight: 600; }
+.event-detail { margin: 0.2rem 0 0; font-size: 0.9rem; }
+
+.route { display: flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; margin-top: -0.5rem; margin-bottom: 1rem; }
+.route .addr { font-family: var(--font-mono); font-size: 0.9rem; overflow-wrap: anywhere; }
+.route .arrow { color: var(--muted); }
+
+table.log { margin-top: 1rem; }
+table.log th:first-child, table.log td.time { width: 1%; }
+table.log td.log-text { font-family: var(--font-mono); font-size: 0.8rem; white-space: pre-wrap; }
+
+button.copy, .actions button, .actions > label.toggle, .actions a.danger {
+ margin: 0; padding: 0.45rem 0.7rem; font-size: 0.8rem; font-weight: 600;
+ border-radius: 5px; white-space: nowrap;
+ background: var(--surface-bg); color: var(--accent-text); border: 1px solid var(--control-border);
+}
+button.copy:hover, .actions button:hover, .actions > label.toggle:hover { background: var(--surface-bg-hover); }
+.actions button.danger, .actions a.danger { color: var(--danger-fg); background: var(--danger-bg); border-color: var(--danger-border); }
+.actions { display: flex; flex-wrap: wrap; gap: 0.4rem; align-items: center; }
+
+.apps { list-style: none; margin: 1rem 0 0; padding: 0; }
+.app-item { padding: 0.9rem 0; border-top: 1px solid var(--border); }
+.app-item:last-child { padding-bottom: 0; }
+.app-login { margin: 0; font-family: var(--font-mono); font-weight: 600; }
+.app-addr { margin: 0.15rem 0 0; word-break: break-all; }
+.app-item .actions { margin-top: 0.7rem; }
+.app-item .actions > .panel-toggle {
+ position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none;
+}
+.app-item .actions > .panel { display: none; flex: 1 0 100%; }
+.app-item .actions > .t-edit:checked ~ .panel-edit { display: block; }
+.app-item .actions > .t-edit:checked ~ .for-edit { background: var(--surface-open-bg); }
+.app-item .actions > .panel button { margin-top: 0.9rem; }
+
+.encrypt { margin-top: 1.2rem; }
+.encrypt-fields {
+ display: none; margin-top: 0.8rem; margin-left: 1.6rem; padding-left: 0.9rem;
+ border-left: 2px solid var(--border);
+}
+.encrypt:has(input[type="checkbox"]:checked) .encrypt-fields { display: block; }
+.encrypt-fields label { margin-top: 0.7rem; }
+
+.toolbar {
+ display: flex; flex-wrap: wrap; gap: 0.6rem 1rem; align-items: end;
+ padding: 0.85rem 1rem; margin-bottom: 1rem;
+ background: var(--card-bg); border: 1px solid var(--border); border-radius: 6px;
+}
+.toolbar .field { flex: 1 1 10rem; min-width: 8rem; }
+.toolbar label { margin-top: 0; font-size: 0.8rem; }
+.toolbar button { margin-top: 0; }
+
+/* Status: hero + dense grid */
+.status-hero {
+ display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.75rem 1.2rem;
+ padding: 1rem 1.25rem; margin-bottom: 1rem;
+ background: var(--card-bg); border: 1px solid var(--border); border-radius: 6px;
+}
+.status-hero.attn { border-color: var(--st-warn-border); background: var(--st-warn-bg); }
+.status-hero h1 { margin: 0; }
+.status-hero .lead { margin: 0; color: var(--fg); }
+.status-grid {
+ display: grid; grid-template-columns: repeat(auto-fit, minmax(17.5rem, 1fr));
+ gap: 1rem;
+}
+.status-grid .card { margin: 0; }
+.status-grid .card.attn { border-color: var(--st-warn-border); }
+.metric-row {
+ display: flex; justify-content: space-between; gap: 0.75rem; align-items: baseline;
+ padding: 0.35rem 0; border-bottom: 1px solid var(--border); font-size: 0.92rem;
+}
+.metric-row:last-child { border-bottom: 0; }
+.metric-row .k { font-weight: 600; }
+.metric-row .v { font-family: var(--font-mono); font-size: 0.85rem; }
+meter { width: 5rem; height: 0.7rem; vertical-align: middle; margin-right: 0.4rem; }
+.card-head {
+ display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; margin-bottom: 0.5rem;
+}
+.card-head h2 { margin: 0; }
+.help-link {
+ flex: none; width: 1.35rem; height: 1.35rem; display: inline-flex; align-items: center; justify-content: center;
+ border-radius: 50%; border: 1px solid var(--control-border); color: var(--muted);
+ text-decoration: none; font-family: var(--font-mono); font-size: 0.75rem; font-weight: 600;
+ background: var(--surface-bg);
+}
+.help-link:hover { color: var(--accent-text); border-color: var(--accent-fill); }
+
+.page-head {
+ display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between; gap: 0.5rem 1rem;
+ margin-bottom: 1rem;
+}
+.page-head h1 { margin: 0; }
+
+/* Compact retry policy */
+.retry-facts { display: grid; grid-template-columns: repeat(auto-fit, minmax(11rem, 1fr)); gap: 0.5rem; margin-top: 0.7rem; }
+
+.phone-list { display: none; list-style: none; margin: 0; padding: 0; }
+.phone-list li { border-bottom: 1px solid var(--border); }
+.phone-list a {
+ display: grid; grid-template-columns: 1fr auto; gap: 0.15rem 0.7rem;
+ padding: 0.75rem 0.1rem; text-decoration: none; color: var(--fg);
+}
+.phone-list .when { font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted); }
+.phone-list .pair { grid-column: 1 / -1; font-family: var(--font-mono); font-size: 0.82rem; overflow-wrap: anywhere; }
+.phone-list .subj { grid-column: 1 / -1; margin: 0; }
+
+/* Help drawer — CSS checkbox, no script required */
+.help-drawer {
+ display: none; position: fixed; top: var(--gallery-h); right: 0; bottom: 0;
+ width: min(26rem, 100vw); z-index: 30;
+ background: var(--card-bg); border-left: 1px solid var(--border);
+ padding: 1.25rem 1.35rem 2rem; overflow-y: auto;
+}
+#help-open:checked ~ .help-drawer { display: block; }
+#help-open:checked ~ .help-scrim { display: block; }
+.help-scrim {
+ display: none; position: fixed; inset: var(--gallery-h) 0 0 0; z-index: 25;
+ background: rgba(18, 22, 28, 0.28);
+}
+.help-drawer h2 { margin-top: 1.2rem; }
+.help-drawer h2:first-of-type { margin-top: 0; }
+.help-close {
+ position: absolute; top: 0.8rem; right: 0.8rem; margin: 0; padding: 0.35rem 0.6rem;
+ font-size: 0.8rem; font-weight: 600; background: var(--surface-bg); color: var(--fg);
+ border: 1px solid var(--control-border); border-radius: 5px; cursor: pointer;
+}
+
+/* Phone preview (gallery toggle) and real narrow windows */
+.nav-burger { display: none; }
+
+#vp-phone:checked ~ .app {
+ position: relative;
+ width: 390px; margin: 0.75rem auto 2rem; min-height: 760px;
+ border: 1px solid var(--border); border-radius: 6px; overflow: hidden;
+ max-height: none;
+}
+#vp-phone:checked ~ .app .nav {
+ position: absolute; top: 0; left: 0; bottom: 0;
+ height: auto; max-height: none;
+ width: min(16rem, 82%); z-index: 20;
+ transform: translateX(-110%);
+ border-right: 1px solid var(--border); background: var(--card-bg);
+}
+#nav-open:checked ~ .app .nav { transform: translateX(0); }
+#vp-phone:checked ~ .app .phone-bar {
+ display: flex; align-items: center; gap: 0.55rem;
+ margin: 0; padding: 0.65rem 0.85rem;
+ border-bottom: 1px solid var(--border); background: var(--card-bg);
+}
+#vp-phone:checked ~ .app .nav-burger {
+ display: inline-flex; align-items: center; justify-content: center;
+ width: 2.1rem; height: 2.1rem; margin: 0; padding: 0;
+ border: 1px solid var(--control-border); border-radius: 5px;
+ background: var(--surface-bg); cursor: pointer;
+}
+#vp-phone:checked ~ .app .phone-mark { width: 1.7rem; height: 1.7rem; }
+#vp-phone:checked ~ .app .phone-bar .grow { flex: 1; font-weight: 600; }
+#vp-phone:checked ~ .app main { padding: 1.5rem 0.9rem 2rem; }
+#vp-phone:checked ~ .app .desk-only { display: none !important; }
+#vp-phone:checked ~ .app .phone-only { display: block; }
+#vp-phone:checked ~ .app .phone-list { display: block; }
+#vp-phone:checked ~ .app .status-grid { display: flex; flex-direction: column; }
+#vp-phone:checked ~ .app .status-grid .attn { order: -1; }
+#vp-phone:checked ~ .app .split,
+#vp-phone:checked ~ .app .check-cols,
+#vp-phone:checked ~ .app .field-pair,
+#vp-phone:checked ~ .app .facts,
+#vp-phone:checked ~ .app .retry-facts {
+ grid-template-columns: 1fr;
+}
+#vp-phone:checked ~ .app td.subject span { max-width: none; }
+#vp-phone:checked ~ .help-drawer { width: 100vw; }
+
+.phone-only { display: none; }
+
+@media (max-width: 52rem) {
+ .app { min-height: calc(100vh - var(--gallery-h)); }
+ .nav {
+ position: fixed; top: var(--gallery-h); left: 0; bottom: 0;
+ z-index: 20; width: min(16rem, 84vw); max-height: none;
+ transform: translateX(-110%);
+ border-right: 1px solid var(--border); background: var(--card-bg);
+ }
+ #nav-open:checked ~ .app .nav { transform: translateX(0); }
+ .phone-bar {
+ display: flex; align-items: center; gap: 0.55rem;
+ margin: 0; padding: 0.65rem 0.85rem;
+ border-bottom: 1px solid var(--border); background: var(--card-bg);
+ }
+ .nav-burger {
+ display: inline-flex; align-items: center; justify-content: center;
+ width: 2.1rem; height: 2.1rem; margin: 0; padding: 0;
+ border: 1px solid var(--control-border); border-radius: 5px;
+ background: var(--surface-bg); cursor: pointer;
+ }
+ .phone-mark { width: 1.7rem; height: 1.7rem; }
+ .phone-bar .grow { flex: 1; font-weight: 600; }
+ main { padding: 1.5rem 0.9rem 2rem; }
+ .desk-only { display: none !important; }
+ .phone-only, .phone-list { display: block; }
+ .status-grid { display: flex; flex-direction: column; }
+ .status-grid .attn { order: -1; }
+ .split, .check-cols, .field-pair, .facts, .retry-facts { grid-template-columns: 1fr; }
+ .help-drawer { width: 100vw; }
+}
+
+/* Index page */
+.wrap { max-width: 58rem; margin: 0 auto; padding: 2.5rem 1.25rem 5rem; }
+.wrap > header { border-bottom: 2px solid var(--accent-fill); padding-bottom: 1.5rem; margin-bottom: 0.5rem; }
+.eyebrow {
+ font-family: var(--font-mono); font-size: 0.72rem; letter-spacing: 0.14em;
+ text-transform: uppercase; color: var(--accent-text); margin: 0 0 0.7rem;
+}
+.wrap h1 { font-size: clamp(1.7rem, 4vw, 2.4rem); font-weight: 200; }
+.wrap h1 b { font-weight: 600; }
+.wrap > header p { margin: 0; max-width: 62ch; color: var(--muted); }
+.wrap section { border-bottom: 1px solid var(--border); padding: 2.2rem 0; }
+.wrap h2 {
+ font-size: 0.8rem; font-weight: 500; margin: 0 0 1.1rem;
+ letter-spacing: 0.1em; text-transform: uppercase; font-family: var(--font-mono); color: var(--muted);
+}
+.wrap h3 { font-size: 1.05rem; font-weight: 600; margin: 1.2rem 0 0.4rem; }
+.note {
+ background: #f1eae4; border-left: 2px solid var(--accent-fill);
+ padding: 0.9rem 1rem; font-size: 0.92rem; line-height: 1.6; max-width: 64ch; margin-top: 1rem;
+}
+#theme-dark:checked ~ .doc .note { background: #2a1f1b; }
+.compare {
+ display: grid; grid-template-columns: 1fr; gap: 0.75rem; margin-top: 0.8rem;
+}
+.compare .row {
+ font-family: var(--font-mono); font-size: 0.75rem; color: var(--muted);
+}
+.bar {
+ display: flex; height: 2.1rem; border: 1px solid var(--border); border-radius: 4px; overflow: hidden; background: var(--card-bg);
+}
+.bar i { display: flex; align-items: center; justify-content: center; font-style: normal; font-size: 0.7rem; }
+.bar .navc { width: 14%; background: var(--surface-bg); color: var(--fg); border-right: 1px solid var(--border); }
+.bar .col { background: var(--nav-active-bg); color: var(--accent-text); }
+.bar .empty { flex: 1; background: var(--code-bg); color: var(--muted); }
+.bar .fill { flex: 1; background: var(--st-ok-bg); color: var(--st-ok-fg); }
+.ia { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
+@media (max-width: 40rem) { .ia { grid-template-columns: 1fr; } }
+.ia ul { margin: 0.3rem 0 0; padding-left: 1.1rem; }
+.ia li { margin: 0.2rem 0; }
+.screen-index { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 0.4rem 1.5rem; }
+.screen-index a { display: block; padding: 0.25rem 0; }
+.icons-row { display: flex; flex-wrap: wrap; gap: 1.2rem; align-items: end; margin-top: 0.8rem; }
+.icon-card {
+ background: var(--card-bg); border: 1px solid var(--border); border-radius: 6px;
+ padding: 1rem 1.1rem 0.85rem; text-align: center; min-width: 7.5rem;
+}
+.icon-card svg { width: 1.5rem; height: 1.5rem; display: block; margin: 0 auto 0.45rem; }
+.icon-card span { display: block; font-family: var(--font-mono); font-size: 0.7rem; color: var(--muted); }
+.mark-row { display: flex; flex-wrap: wrap; gap: 1.5rem; align-items: end; }
+.mark-row figure { margin: 0; }
+.mark-row figcaption { font-family: var(--font-mono); font-size: 0.7rem; color: var(--muted); margin-top: 0.4rem; }
+
+.cta {
+ display: inline-block; margin-top: 1rem; padding: 0.65rem 1.2rem;
+ background: var(--accent-fill); color: var(--on-accent); text-decoration: none;
+ font-weight: 600; border-radius: 5px;
+}
+.cta:hover { background: var(--accent-fill-hover); color: var(--on-accent); }
+.cta-row { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-top: 1rem; }
+.cta.quiet {
+ background: var(--surface-bg); color: var(--accent-text); border: 1px solid var(--control-border);
+}
+.cta.quiet:hover { background: var(--surface-bg-hover); color: var(--accent-text); }