Add an optional site password for visitors

Settings → Privacy can set a password for the whole public site. Until a
visitor enters it, every public page shows a prompt over a blurred
made-up page, and images, preview clips and exports return 401; nothing
real is sent. Unlocking sets a 90-day cookie derived from the password
hash, so changing the password asks everyone again. The owner is never
asked while signed in. Wrong guesses are rate limited, and a locked site
sends noindex.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
cdnutterandClaude Opus 5.5 committed 2026-09-30 15:47:17 -07:00
1 parent 568162202b
commit 06097d3bca
11 files changed
+302 -9

No files matched your search

+25
View File
@@ -571,6 +571,31 @@ input[type="checkbox"] { accent-color: var(--accent); width: 18px; height: 18px;
.auth-back { display: inline-flex; align-items: center; gap: 6px; justify-content: center; color: var(--faint); font-size: 13px; text-decoration: none; margin-top: 4px; }
.auth-back:hover { color: var(--text); }
/* ---------- site password gate ---------- */
.page-gate main { position: relative; display: grid; place-items: center; min-height: 100dvh; padding: 32px 16px; overflow: hidden; }
.gate-bg {
position: fixed; inset: -40px; z-index: 0; pointer-events: none; overflow: hidden;
filter: blur(18px) saturate(1.2); opacity: 0.85;
}
.gate-bg .container { padding-top: 96px; }
.gate-sk-profile { display: flex; align-items: center; gap: 28px; padding-bottom: 40px; }
.gate-sk-avatar { width: 112px; height: 112px; border-radius: 50%; background: var(--accent); flex: none; }
.gate-sk-lines { display: flex; flex-direction: column; gap: 14px; flex: 1; }
.gate-sk-lines i { display: block; height: 34px; width: min(420px, 70%); border-radius: 10px; background: var(--surface-2); }
.gate-sk-lines i + i { height: 16px; width: min(280px, 50%); }
.gate-sk-shelf { padding: 24px 0 36px; }
.gate-sk-heading { display: block; width: 220px; height: 22px; border-radius: 8px; background: var(--surface-2); margin-bottom: 20px; }
.gate-sk-row { display: grid; grid-template-columns: repeat(auto-fill, minmax(132px, 1fr)); gap: 18px; }
.gate-sk-row span { aspect-ratio: 2 / 3; border-radius: var(--radius-art); background: linear-gradient(160deg, color-mix(in srgb, var(--accent) 55%, var(--surface-2)), var(--surface-2)); }
.gate-sk-row span:nth-child(3n+2) { background: linear-gradient(200deg, color-mix(in srgb, var(--accent) 25%, var(--surface)), color-mix(in srgb, var(--text) 18%, var(--surface-2))); }
.gate-sk-row span:nth-child(3n) { background: linear-gradient(140deg, color-mix(in srgb, var(--text) 30%, var(--surface-2)), color-mix(in srgb, var(--accent) 40%, var(--bg))); }
.gate-card {
position: relative; z-index: 1;
background: color-mix(in srgb, var(--surface) 86%, transparent);
backdrop-filter: blur(12px) saturate(1.3); -webkit-backdrop-filter: blur(12px) saturate(1.3);
}
.gate-card h1 { overflow-wrap: anywhere; }
/* ---------- toasts ---------- */
.toasts { position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); z-index: 1000; display: flex; flex-direction: column; gap: 8px; align-items: center; pointer-events: none; width: max-content; max-width: calc(100vw - 32px); }
.toast {