From 4a832964a3f31ef36ca2bd321b12abb2b7aeabe3 Mon Sep 17 00:00:00 2001 From: m17hr1l Date: Sat, 23 May 2026 00:38:47 +0200 Subject: [PATCH] stage-26 polish: restyle the /admin gate as a secure console Replaced the plain login box with a proper restricted-zone screen: ambient breathing radial backdrop, frosted glass card with a glowing top accent + animated HUD corner brackets, an SVG shield-lock emblem in a pulsing glow ring, a SECURE CHANNEL status line with a blinking dot, the QR in a white frame with a sweeping cyan scanline, a large OTP-style monospace code field, and a gradient UNLOCK button. Honors prefers-reduced-motion. Co-Authored-By: Claude Opus 4.7 --- src/psyc/cockpit/static/cockpit.css | 104 ++++++++++++++++----- src/psyc/cockpit/templates/admin_gate.html | 39 ++++++-- 2 files changed, 111 insertions(+), 32 deletions(-) diff --git a/src/psyc/cockpit/static/cockpit.css b/src/psyc/cockpit/static/cockpit.css index bf3c271..a4fa42a 100644 --- a/src/psyc/cockpit/static/cockpit.css +++ b/src/psyc/cockpit/static/cockpit.css @@ -376,32 +376,92 @@ tr.sev-low .sev-badge { color: var(--muted); } .disco-strobe, .disco-bolt, .ioc-fly { animation: none; } } -/* ── admin gate (TOTP) ──────────────────────────────────────── */ -.gate-wrap { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; } +/* ── admin gate (TOTP) — secure console ─────────────────────── */ +.gate-body { overflow: hidden; } +.gate-bg { + position: fixed; inset: 0; z-index: 0; pointer-events: none; + background: + radial-gradient(900px 600px at 50% -10%, rgba(30,200,255,0.16), transparent 60%), + radial-gradient(700px 500px at 80% 110%, rgba(167,139,250,0.12), transparent 60%), + radial-gradient(600px 500px at 12% 90%, rgba(30,200,255,0.08), transparent 60%); + animation: gate-breathe 7s ease-in-out infinite; +} +@keyframes gate-breathe { 0%,100% { opacity: 0.75; } 50% { opacity: 1; } } +.gate-wrap { position: relative; z-index: 1; min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 24px; } .gate-card { - width: 360px; max-width: 100%; text-align: center; padding: 32px 28px; - background: var(--panel); border: 1px solid var(--border); border-radius: 12px; - box-shadow: 0 0 40px rgba(0,0,0,0.5); position: relative; + width: 380px; max-width: 100%; text-align: center; padding: 38px 32px 28px; + background: linear-gradient(180deg, rgba(28,34,48,0.82), rgba(18,22,30,0.92)); + backdrop-filter: blur(14px) saturate(120%); + border: 1px solid rgba(120,140,170,0.18); border-radius: 16px; + box-shadow: 0 24px 70px rgba(0,0,0,0.55), 0 0 0 1px rgba(30,200,255,0.06), inset 0 1px 0 rgba(255,255,255,0.04); + position: relative; animation: gate-rise 0.5s cubic-bezier(.2,.9,.3,1); } -.gate-card::before, .gate-card::after { content: ""; position: absolute; width: 16px; height: 16px; border: 2px solid var(--accent); opacity: 0.6; } -.gate-card::before { top: 8px; left: 8px; border-right: none; border-bottom: none; } -.gate-card::after { bottom: 8px; right: 8px; border-left: none; border-top: none; } -.gate-logo { width: 56px; height: 56px; filter: drop-shadow(0 0 10px var(--accent-glow)); } -.gate-title { font-family: var(--font-display); font-size: 22px; margin: 12px 0 4px; letter-spacing: 0.08em; text-shadow: 0 0 16px var(--accent-glow); } -.gate-sub { color: var(--muted); font-size: 13px; margin: 0 0 18px; } -.gate-setup { margin: 16px 0; padding: 14px; background: var(--bg); border: 1px solid var(--border); border-radius: 8px; } -.gate-step { font-size: 12px; color: var(--text); margin: 0 0 12px; } -.gate-qr { width: 168px; height: 168px; border-radius: 6px; background: #fff; padding: 6px; } -.gate-error { color: var(--red); font-size: 13px; margin: 12px 0; } -.gate-form { display: flex; gap: 8px; margin-top: 14px; } +.gate-card::before { /* glowing top accent line */ + content: ""; position: absolute; top: 0; left: 22%; right: 22%; height: 2px; border-radius: 2px; + background: linear-gradient(90deg, transparent, var(--accent), transparent); + box-shadow: 0 0 14px var(--accent); opacity: 0.85; +} +@keyframes gate-rise { from { opacity: 0; transform: translateY(14px); } to { opacity: 1; transform: none; } } +.corner { position: absolute; width: 18px; height: 18px; border: 2px solid var(--accent); opacity: 0.5; } +.corner.tl { top: 10px; left: 10px; border-right: none; border-bottom: none; border-radius: 4px 0 0 0; } +.corner.tr { top: 10px; right: 10px; border-left: none; border-bottom: none; border-radius: 0 4px 0 0; } +.corner.bl { bottom: 10px; left: 10px; border-right: none; border-top: none; border-radius: 0 0 0 4px; } +.corner.br { bottom: 10px; right: 10px; border-left: none; border-top: none; border-radius: 0 0 4px 0; } + +.gate-emblem { position: relative; display: inline-grid; place-items: center; color: var(--accent); margin-bottom: 6px; } +.gate-emblem svg { filter: drop-shadow(0 0 10px var(--accent-glow)); position: relative; z-index: 1; } +.emblem-ring { + position: absolute; width: 76px; height: 76px; border-radius: 50%; + border: 1px solid rgba(30,200,255,0.35); box-shadow: 0 0 24px rgba(30,200,255,0.25) inset; + animation: ring-pulse 2.6s ease-in-out infinite; +} +@keyframes ring-pulse { 0%,100% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.12); opacity: 0.9; } } + +.gate-status { + display: inline-flex; align-items: center; gap: 7px; margin: 8px 0 2px; + font-size: 10.5px; letter-spacing: 0.22em; color: var(--muted); text-transform: uppercase; +} +.pulse-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 8px var(--green); animation: dot-blink 1.4s steps(2) infinite; } +@keyframes dot-blink { 0%,60% { opacity: 1; } 80%,100% { opacity: 0.25; } } +.gate-title { font-family: var(--font-display); font-size: 25px; font-weight: 700; margin: 6px 0 2px; letter-spacing: 0.16em; color: #eaf6ff; text-shadow: 0 0 22px rgba(30,200,255,0.45); } +.gate-sub { color: var(--muted); font-size: 12.5px; margin: 0 0 20px; letter-spacing: 0.04em; } + +.gate-setup { margin: 4px 0 18px; } +.gate-qr-frame { + position: relative; display: inline-block; padding: 10px; border-radius: 12px; + background: #fff; box-shadow: 0 0 0 1px rgba(30,200,255,0.4), 0 0 26px rgba(30,200,255,0.22); overflow: hidden; +} +.gate-qr { display: block; width: 158px; height: 158px; border-radius: 4px; } +.scanline { + position: absolute; left: 10px; right: 10px; height: 22px; top: 10px; border-radius: 4px; + background: linear-gradient(180deg, rgba(30,200,255,0.45), transparent); + box-shadow: 0 0 14px var(--accent); animation: scan 2.4s ease-in-out infinite; +} +@keyframes scan { 0% { transform: translateY(0); } 50% { transform: translateY(136px); } 100% { transform: translateY(0); } } +.gate-step { font-size: 12px; color: var(--text); margin: 12px 0 0; opacity: 0.85; } + +.gate-error { color: var(--red); font-size: 12.5px; margin: 0 0 12px; padding: 8px; border: 1px solid rgba(248,113,113,0.4); border-radius: 8px; background: rgba(248,113,113,0.08); } +.gate-form { display: flex; flex-direction: column; gap: 10px; margin-top: 6px; } .gate-input { - flex: 1; background: var(--bg); color: var(--text); border: 1px solid var(--border); - border-radius: 6px; padding: 10px; font: inherit; font-size: 22px; letter-spacing: 0.4em; text-align: center; + background: rgba(10,13,18,0.85); color: #eaf6ff; border: 1px solid rgba(120,140,170,0.25); + border-radius: 10px; padding: 14px; font-family: ui-monospace, Menlo, monospace; + font-size: 30px; letter-spacing: 0.5em; text-align: center; text-indent: 0.5em; transition: all 0.15s; } -.gate-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); } -.gate-btn { padding: 10px 16px; } -.gate-hint { color: var(--muted); font-size: 11px; margin-top: 12px; } +.gate-input::placeholder { color: rgba(125,133,151,0.5); letter-spacing: 0.4em; } +.gate-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow), 0 0 22px rgba(30,200,255,0.25); } +.gate-btn { + padding: 13px; border: none; border-radius: 10px; cursor: pointer; + font-family: var(--font-display); font-weight: 700; font-size: 14px; letter-spacing: 0.18em; + color: #04121a; background: linear-gradient(180deg, #5ad8ff, var(--accent)); + box-shadow: 0 6px 20px rgba(30,200,255,0.3); transition: all 0.15s; +} +.gate-btn:hover { box-shadow: 0 8px 30px rgba(30,200,255,0.5); transform: translateY(-1px); } +.gate-btn:active { transform: translateY(0); } +.gate-hint { color: var(--muted); font-size: 10.5px; margin-top: 14px; letter-spacing: 0.06em; } +@media (prefers-reduced-motion: reduce) { .gate-bg, .emblem-ring, .pulse-dot, .scanline, .gate-card { animation: none; } } + +/* ── admin dashboard ────────────────────────────────────────── */ .admin-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 14px; margin-top: 18px; } -.admin-tile { padding: 16px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; } +.admin-tile { padding: 18px; background: linear-gradient(180deg, var(--panel-2), rgba(18,22,30,0.6)); border: 1px solid var(--border); border-radius: 10px; } .admin-tile h2 { font-size: 15px; margin: 0 0 6px; } .admin-tile p { font-size: 13px; color: var(--muted); margin: 0; } diff --git a/src/psyc/cockpit/templates/admin_gate.html b/src/psyc/cockpit/templates/admin_gate.html index a6125d6..1321257 100644 --- a/src/psyc/cockpit/templates/admin_gate.html +++ b/src/psyc/cockpit/templates/admin_gate.html @@ -2,35 +2,54 @@ + psyc · restricted + - + +
- -

Restricted Zone

-

Admin control center — authenticator required.

+ + + +
+ + +
+ +
SECURE CHANNEL · TOTP
+

RESTRICTED ZONE

+

psyc · admin control center

{% if not provisioned %}
-

First time? Scan this with Google Authenticator / Authy, then enter the code below.

- TOTP QR code +
+ TOTP enrollment QR + +
+

Scan with Google Authenticator or Authy to enroll

{% endif %} - {% if error %}
✗ Invalid or expired code — try the current one.
{% endif %} + {% if error %}
✗ Invalid or expired code
{% endif %}
- + maxlength="6" placeholder="••••••" class="gate-input" autofocus> +
-

6-digit code, rotates every 30s.

+

6-digit code · rotates every 30 s